-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add Tasks support #1013
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
cliffhall
wants to merge
2
commits into
modelcontextprotocol:main
Choose a base branch
from
cliffhall:add-task-support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Add Tasks support #1013
+627
−94
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- Imports:
- Added: `Task` and `GetTaskResultSchema` to the same import block.
- UI Icons: Added `ListTodo` from `lucide-react` to the icon imports.
- Components: Added `TasksTab` import beside `ToolsTab`.
- Config utils: Added `getMCPTaskTtl` to the config utils import block.
- State additions:
- `const [tasks, setTasks] = useState<Task[]>([]);`
- Extended `errors` state to include a `tasks` key: `tasks: null,`
- `const [selectedTask, setSelectedTask] = useState<Task | null>(null);`
- `const [isPollingTask, setIsPollingTask] = useState(false);`
- `const [nextTaskCursor, setNextTaskCursor] = useState<string | undefined>();`
- Hook: `useConnection({...})` return value usage extended
- Added destructured functions: `cancelTask: cancelMcpTask` and `listTasks: listMcpTasks` from the custom hook.
- Notification handling:
- In `onNotification`, added:
- If `method === "notifications/tasks/list_changed"`, call `listTasks()` (voided).
- If `method === "notifications/tasks/status"`, treat `notification.params` as a `Task` and update `tasks` state: replace if exists by `taskId`, otherwise prepend. Also update `selectedTask` if it’s the same `taskId`.
- Tab routing:
- When computing valid tabs, added `tasks` when server declares capability: `...(serverCapabilities?.tasks ? ["tasks"] : []),`
- When choosing a default tab, added a branch to fall back to `"tasks"` if neither resources/prompts/tools are present but tasks are.
- Effect for Tasks tab:
- When `mcpClient` is connected and `activeTab === "tasks"`, invoke `listTasks()`.
- Tools → task-augmented calls integration in `callTool`:
- Parameter signature supports `runAsTask?: boolean` (already present in this file), but now:
- If `runAsTask` is true, augment the `tools/call` request’s `params` with a `task` object: `{ task: { ttl: getMCPTaskTtl(config) } }`.
- Use a permissive result schema for tool call: `sendMCPRequest(request, z.any(), "tools")` to avoid version-mismatch schema issues.
- Task reference detection introduced:
- `isTaskResult` helper checks for a nested `task` object with `taskId` (i.e., `response.task.taskId`).
- When task is detected:
- Set `isPollingTask(true)`.
- Immediately set a temporary `toolResult` that includes text content “Task created: … Polling for status…” and `_meta` with `"io.modelcontextprotocol/related-task": { taskId }`.
- Start a polling loop:
- Delay 1s between polls.
- Call `tasks/get` with `GetTaskResultSchema` for status.
- If status is `completed`: call `tasks/result` with `z.any()` to retrieve the final result and set it as `toolResult`; call `listTasks()`.
- If status is `failed` or `cancelled`: set an error `toolResult` content that includes the status + `statusMessage`; call `listTasks()`.
- Else (still running): update `toolResult` content with current `status`/`statusMessage` and preserve `_meta` related-task.
- After loop, set `isPollingTask(false)`.
- When not a task response, set `toolResult` directly from response (cast to `CompatibilityCallToolResult`).
- Tasks list + cancel helpers in App:
- `listTasks`: uses `listMcpTasks(nextTaskCursor)` from the hook, updates `tasks`, `nextTaskCursor`, and clears `errors.tasks`.
- `cancelTask`: calls `cancelMcpTask(taskId)`, updates `tasks` array by `taskId`, updates `selectedTask` if it matches, and clears `errors.tasks`.
- UI integration:
- Added a `TabsTrigger` for “Tasks” with `<ListTodo />` icon, disabled unless server supports tasks.
- Added `<TasksTab />` to the main `TabsContent` block, passing: `tasks`, `listTasks`, `clearTasks`, `cancelTask`, `selectedTask`, `setSelectedTask`, `error={errors.tasks}`, `nextCursor={nextTaskCursor}`.
- Passed `isPollingTask={isPollingTask}` and `toolResult` into `ToolsTab` so the Tools tab can show the live “Polling Task…” state and block reruns while polling.
Note: The raw diff is long; the key hunks align with the above bullet points (imports, state, notifications, tab wiring, request augmentation, polling loop, UI additions).
---
### client/src/components/ToolsTab.tsx
- Props shape changed:
- Added `isPollingTask?: boolean` prop in the destructured props and in the prop types.
- The `callTool` callback signature is now `(name, params, metadata?, runAsTask?) => Promise<void>` (runAsTask added earlier; test updates elsewhere reflect this).
- Local state additions:
- `const [runAsTask, setRunAsTask] = useState(false);`
- Reset behavior:
- When switching tools (`useEffect` on `selectedTool`), reset `runAsTask(false)`.
- When clearing the list in `ListPane.clearItems`, also call `setRunAsTask(false)`.
- UI additions:
- New checkbox control block to toggle “Run as task”:
- Checkbox `id="run-as-task"`, bound to `runAsTask`, with `onCheckedChange` → `setRunAsTask(checked)`.
- Label “Run as task”.
- Run button disabling conditions expanded to include `isPollingTask`.
- Run button text shows spinner with conditional label:
- If `isToolRunning || isPollingTask` → show spinner and text `isPollingTask ? "Polling Task..." : "Running..."`.
- Call invocation change:
- When clicking “Run Tool”, the `callTool` is invoked with `(selectedTool.name, params, metadata?, runAsTask)`.
- ToolResults relay:
- Passes `isPollingTask` to `<ToolResults />`.
---
### client/src/components/ToolResults.tsx
- Props shape changed:
- Added optional prop: `isPollingTask?: boolean`.
- Task-running banner logic:
- Extracts related task from the tool result’s `_meta["io.modelcontextprotocol/related-task"]` if present.
- Computes `isTaskRunning` as `isPollingTask ||` a text-heuristic against `structuredResult.content` entries that contain text like “Polling” or “Task status”.
- Header “Tool Result:” now conditionally shows:
- `Error` (red) if `isError` is true, else
- `Task Running` (yellow) if `isTaskRunning`, else
- `Success` (green).
No other changes to validation or rendering of content blocks.
---
### client/src/components/TasksTab.tsx (new file)
- A brand new tab to list and inspect tasks.
- Key elements:
- Imports `Task` type and multiple status icons.
- `TaskStatusIcon` component maps task `status` to an icon and color.
- Main `TasksTab` props: `tasks`, `listTasks`, `clearTasks`, `cancelTask`, `selectedTask`, `setSelectedTask`, `error`, `nextCursor`.
- Left column (`ListPane`): lists tasks, shows status icon, `taskId`, `status`, and last update time; button text changes to “List More Tasks” if `nextCursor` present; disables button if no cursor and list non-empty.
- Right column:
- Shows error `Alert` if `error` prop provided.
- If a task is selected: header with `Task Details`, a Cancel button when `status === "working"` (shows a spinner while cancelling), and a grid of task fields: Status (with colored label and icon), Last Updated, Created At, TTL (shows “Infinite” if `ttl === null`, otherwise shows numeric with `s` suffix), optional Status Message, and full task JSON via `JsonView`.
- If no task is selected: centered empty state with a “Refresh Tasks” button.
---
### client/src/lib/hooks/useConnection.ts
- Imports added from `@modelcontextprotocol/sdk/types.js`:
- `ListTasksResultSchema`, `CancelTaskResultSchema`, `TaskStatusNotificationSchema`.
- Client capabilities on `connect`:
- Added `tasks: { list: {}, cancel: {} }` into the `clientCapabilities` passed to `new Client(...)`.
- Notification handling setup:
- The hook’s notification schema registration now includes the `TaskStatusNotificationSchema` in the `setNotificationHandler` list so the app receives `notifications/tasks/status`.
- New hook functions:
- `cancelTask(taskId: string)` sends `tasks/cancel` with `CancelTaskResultSchema`.
- `listTasks(cursor?: string)` sends `tasks/list` with `ListTasksResultSchema`.
- Exports:
- Returned object now includes `cancelTask` and `listTasks`.
---
### client/src/utils/configUtils.ts
- Added a new getter:
- `export const getMCPTaskTtl = (config: InspectorConfig): number => { return config.MCP_TASK_TTL.value as number; };`
---
### client/src/lib/configurationTypes.ts
- `InspectorConfig` type extended with a new item:
- `MCP_TASK_TTL: ConfigItem;`
- Includes descriptive JSDoc about default TTL in milliseconds for newly created tasks.
---
### client/src/lib/constants.ts
- `DEFAULT_INSPECTOR_CONFIG` extended with a default for task TTL:
- Key: `MCP_TASK_TTL`
- Label: `"Task TTL"`
- Description: `"Default Time-to-Live (TTL) in milliseconds for newly created tasks"`
- Default `value: 60000`
- `is_session_item: false`
---
### client/src/components/__tests__/ToolsTab.test.tsx
- Expectations updated due to new `callTool` signature (4th arg `runAsTask`). Everywhere the test asserts a `callTool` invocation, an additional trailing `false` argument was added to reflect the default state when the box isn’t checked.
- Examples of added trailing `false` at various assertion points (line offsets from diff): after calls around prior lines 132, 157, 193, 236, 257, 279, 297, 818, 1082 (now passing 4 arguments: name, params, metadata-or-undefined, false).
---
### Additional notes
- The Tasks feature is wired end-to-end:
- Client capability declaration (list/cancel)
- Notification handler for `notifications/tasks/status`
- Tools call augmentation with `task` `{ ttl }`
- Polling loop using `tasks/get` and `tasks/result`
- UI feedback in Tools and dedicated Tasks tab
- Configurable TTL via new config item and getter
…completes, leaving the cancel task button available.
|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
Added client support for tasks.
notifications/tasks/statustask{ ttl }tasks/getandtasks/resultType of Change
Changes Made
client/src/App.tsx
Imports:
TaskandGetTaskResultSchemato the same import block.ListTodofromlucide-reactto the icon imports.TasksTabimport besideToolsTab.getMCPTaskTtlto the config utils import block.State additions:
const [tasks, setTasks] = useState<Task[]>([]);errorsstate to include ataskskey:tasks: null,const [selectedTask, setSelectedTask] = useState<Task | null>(null);const [isPollingTask, setIsPollingTask] = useState(false);const [nextTaskCursor, setNextTaskCursor] = useState<string | undefined>();Hook:
useConnection({...})return value usage extendedcancelTask: cancelMcpTaskandlistTasks: listMcpTasksfrom the custom hook.Notification handling:
onNotification, added:method === "notifications/tasks/list_changed", calllistTasks()(voided).method === "notifications/tasks/status", treatnotification.paramsas aTaskand updatetasksstate: replace if exists bytaskId, otherwise prepend. Also updateselectedTaskif it’s the sametaskId.Tab routing:
taskswhen server declares capability:...(serverCapabilities?.tasks ? ["tasks"] : []),"tasks"if neither resources/prompts/tools are present but tasks are.Effect for Tasks tab:
mcpClientis connected andactiveTab === "tasks", invokelistTasks().Tools → task-augmented calls integration in
callTool:runAsTask?: boolean(already present in this file), but now:runAsTaskis true, augment thetools/callrequest’sparamswith ataskobject:{ task: { ttl: getMCPTaskTtl(config) } }.sendMCPRequest(request, z.any(), "tools")to avoid version-mismatch schema issues.isTaskResulthelper checks for a nestedtaskobject withtaskId(i.e.,response.task.taskId).isPollingTask(true).toolResultthat includes text content “Task created: … Polling for status…” and_metawith"io.modelcontextprotocol/related-task": { taskId }.tasks/getwithGetTaskResultSchemafor status. - If status iscompleted: calltasks/resultwithz.any()to retrieve the final result and set it astoolResult; calllistTasks(). - If status isfailedorcancelled: set an errortoolResultcontent that includes the status +statusMessage; calllistTasks(). - Else (still running): updatetoolResultcontent with currentstatus/statusMessageand preserve_metarelated-task.isPollingTask(false).toolResultdirectly from response (cast toCompatibilityCallToolResult).Tasks list + cancel helpers in App:
listTasks: useslistMcpTasks(nextTaskCursor)from the hook, updatestasks,nextTaskCursor, and clearserrors.tasks.cancelTask: callscancelMcpTask(taskId), updatestasksarray bytaskId, updatesselectedTaskif it matches, and clearserrors.tasks.UI integration:
TabsTriggerfor “Tasks” with<ListTodo />icon, disabled unless server supports tasks.<TasksTab />to the mainTabsContentblock, passing:tasks,listTasks,clearTasks,cancelTask,selectedTask,setSelectedTask,error={errors.tasks},nextCursor={nextTaskCursor}.isPollingTask={isPollingTask}andtoolResultintoToolsTabso the Tools tab can show the live “Polling Task…” state and block reruns while polling.Note: The raw diff is long; the key hunks align with the above bullet points (imports, state, notifications, tab wiring, request augmentation, polling loop, UI additions).
client/src/components/ToolsTab.tsx
Props shape changed:
isPollingTask?: booleanprop in the destructured props and in the prop types.callToolcallback signature is now(name, params, metadata?, runAsTask?) => Promise<void>(runAsTask added earlier; test updates elsewhere reflect this).Local state additions:
const [runAsTask, setRunAsTask] = useState(false);Reset behavior:
useEffectonselectedTool), resetrunAsTask(false).ListPane.clearItems, also callsetRunAsTask(false).UI additions:
id="run-as-task", bound torunAsTask, withonCheckedChange→setRunAsTask(checked).isPollingTask.isToolRunning || isPollingTask→ show spinner and textisPollingTask ? "Polling Task..." : "Running...".Call invocation change:
callToolis invoked with(selectedTool.name, params, metadata?, runAsTask).ToolResults relay:
isPollingTaskto<ToolResults />.client/src/components/ToolResults.tsx
Props shape changed:
isPollingTask?: boolean.Task-running banner logic:
_meta["io.modelcontextprotocol/related-task"]if present.isTaskRunningasisPollingTask ||a text-heuristic againststructuredResult.contententries that contain text like “Polling” or “Task status”.Error(red) ifisErroris true, elseTask Running(yellow) ifisTaskRunning, elseSuccess(green).No other changes to validation or rendering of content blocks.
client/src/components/TasksTab.tsx (new file)
Tasktype and multiple status icons.TaskStatusIconcomponent maps taskstatusto an icon and color.TasksTabprops:tasks,listTasks,clearTasks,cancelTask,selectedTask,setSelectedTask,error,nextCursor.ListPane): lists tasks, shows status icon,taskId,status, and last update time; button text changes to “List More Tasks” ifnextCursorpresent; disables button if no cursor and list non-empty.Alertiferrorprop provided.Task Details, a Cancel button whenstatus === "working"(shows a spinner while cancelling), and a grid of task fields: Status (with colored label and icon), Last Updated, Created At, TTL (shows “Infinite” ifttl === null, otherwise shows numeric withssuffix), optional Status Message, and full task JSON viaJsonView.client/src/lib/hooks/useConnection.ts
Imports added from
@modelcontextprotocol/sdk/types.js:ListTasksResultSchema,CancelTaskResultSchema,TaskStatusNotificationSchema.Client capabilities on
connect:tasks: { list: {}, cancel: {} }into theclientCapabilitiespassed tonew Client(...).Notification handling setup:
TaskStatusNotificationSchemain thesetNotificationHandlerlist so the app receivesnotifications/tasks/status.New hook functions:
cancelTask(taskId: string)sendstasks/cancelwithCancelTaskResultSchema.listTasks(cursor?: string)sendstasks/listwithListTasksResultSchema.Exports:
cancelTaskandlistTasks.client/src/utils/configUtils.ts
export const getMCPTaskTtl = (config: InspectorConfig): number => { return config.MCP_TASK_TTL.value as number; };client/src/lib/configurationTypes.ts
InspectorConfigtype extended with a new item:MCP_TASK_TTL: ConfigItem;client/src/lib/constants.ts
DEFAULT_INSPECTOR_CONFIGextended with a default for task TTL:MCP_TASK_TTL"Task TTL""Default Time-to-Live (TTL) in milliseconds for newly created tasks"value: 60000is_session_item: falseclient/src/components/tests/ToolsTab.test.tsx
callToolsignature (4th argrunAsTask). Everywhere the test asserts acallToolinvocation, an additional trailingfalseargument was added to reflect the default state when the box isn’t checked.falseat various assertion points (line offsets from diff): after calls around prior lines 132, 157, 193, 236, 257, 279, 297, 818, 1082 (now passing 4 arguments: name, params, metadata-or-undefined, false).Related Issues
Fixes #931
Testing
Test Results and/or Instructions
Testing against the draft PR for adding Tasks to Everything server. Specifically using the
simulate-research-querytool.Running Tool as a Task
run-tool-as-task.mov
Tasks Tab - Completed Task
Tasks Tab - Running Task
Tasks Tab - Input Required
Tasks Tab - Cancelled Task
Tools Tab - Cancelled Task
Tools Tab - Running Task
Tools Tab - Task Complete
Checklist
npm run prettier-fix)Breaking Changes
Nope.
Additional Context