Skip to content

Commit b106062

Browse files
refactor(server)!: remove getTask/getTaskResult from ToolTaskHandler
These handlers were defined on the ToolTaskHandler interface but never invoked by the SDK. Three code paths bypassed them and called TaskStore directly: - TaskManager.handleGetTask - TaskManager.handleGetTaskPayload - McpServer.handleAutomaticTaskPolling Every test and example implementation was pure boilerplate delegation to ctx.task.store. Rather than wire them up (which would require in-memory taskId→tool mapping that doesn't survive restarts or multi-instance deployments), this removes them. The TaskStore interface remains the pluggable extension point for custom task retrieval. Also adds an isToolTaskHandler type guard and uses it in place of inline 'createTask' in checks. BREAKING CHANGE: ToolTaskHandler.getTask and ToolTaskHandler.getTaskResult have been removed. Delete these methods from your registerToolTask handlers. If you need custom task retrieval, implement a custom TaskStore. Closes #1332 Co-authored-by: Luca Chang <lucalc@amazon.com>
1 parent 40174d2 commit b106062

File tree

12 files changed

+75
-312
lines changed

12 files changed

+75
-312
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@modelcontextprotocol/server": minor
3+
---
4+
5+
Remove `getTask` and `getTaskResult` from `ToolTaskHandler`
6+
7+
**Breaking changes (experimental API):**
8+
9+
- `ToolTaskHandler` now only defines `createTask`. The `getTask` and `getTaskResult` handlers have been removed.
10+
- `TaskRequestHandler` type has been removed.
11+
12+
These handlers were never invoked by the SDK — `tasks/get` and `tasks/result` requests were always served directly from the configured `TaskStore`, bypassing the user's handlers. Rather than wire them up (which would require in-memory taskId→tool mapping that doesn't survive restarts), we've removed them.
13+
14+
**Migration:** Delete your `getTask` and `getTaskResult` implementations. If you need to customize task retrieval, implement a custom `TaskStore` instead — it's the pluggable interface that `tasks/get` and `tasks/result` consult.

docs/migration-SKILL.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ Notes:
9696
| `ErrorCode.RequestTimeout` | `SdkErrorCode.RequestTimeout` |
9797
| `ErrorCode.ConnectionClosed` | `SdkErrorCode.ConnectionClosed` |
9898
| `StreamableHTTPError` | REMOVED (use `SdkError` with `SdkErrorCode.ClientHttp*`) |
99+
| `TaskRequestHandler` | REMOVED (no replacement) |
100+
| `ToolTaskHandler.getTask` | REMOVED (use custom `TaskStore` instead) |
101+
| `ToolTaskHandler.getTaskResult` | REMOVED (use custom `TaskStore` instead) |
99102

100103
All other symbols from `@modelcontextprotocol/sdk/types.js` retain their original names (e.g., `CallToolResultSchema`, `ListToolsResultSchema`, etc.).
101104

docs/migration.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,38 @@ import { JSONRPCError, ResourceReference, isJSONRPCError } from '@modelcontextpr
478478
import { JSONRPCErrorResponse, ResourceTemplateReference, isJSONRPCErrorResponse } from '@modelcontextprotocol/server';
479479
```
480480

481+
### `ToolTaskHandler.getTask` and `getTaskResult` removed (experimental)
482+
483+
The `getTask` and `getTaskResult` methods have been removed from the experimental `ToolTaskHandler` interface. These handlers were never invoked by the SDK — `tasks/get` and `tasks/result` requests are served directly from the configured `TaskStore`.
484+
485+
**Before:**
486+
487+
```typescript
488+
server.experimental.tasks.registerToolTask('long-task', config, {
489+
createTask: async (args, ctx) => {
490+
const task = await ctx.task.store.createTask({ ttl: 60_000 });
491+
startBackgroundWork(task.taskId, args);
492+
return { task };
493+
},
494+
getTask: async (ctx) => ctx.task.store.getTask(ctx.task.id),
495+
getTaskResult: async (ctx) => ctx.task.store.getTaskResult(ctx.task.id)
496+
});
497+
```
498+
499+
**After:**
500+
501+
```typescript
502+
server.experimental.tasks.registerToolTask('long-task', config, {
503+
createTask: async (args, ctx) => {
504+
const task = await ctx.task.store.createTask({ ttl: 60_000 });
505+
startBackgroundWork(task.taskId, args);
506+
return { task };
507+
}
508+
});
509+
```
510+
511+
If you need to customize task retrieval, implement a custom `TaskStore`.
512+
481513
### Request handler context types
482514

483515
The `RequestHandlerExtra` type has been replaced with a structured context type hierarchy using nested groups:

examples/server/src/simpleStreamableHttp.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -480,13 +480,6 @@ const getServer = () => {
480480
return {
481481
task
482482
};
483-
},
484-
async getTask(_args, ctx) {
485-
return await ctx.task.store.getTask(ctx.task.id);
486-
},
487-
async getTaskResult(_args, ctx) {
488-
const result = await ctx.task.store.getTaskResult(ctx.task.id);
489-
return result as CallToolResult;
490483
}
491484
}
492485
);
@@ -588,13 +581,6 @@ const getServer = () => {
588581
})();
589582

590583
return { task };
591-
},
592-
async getTask(_args, ctx) {
593-
return await ctx.task.store.getTask(ctx.task.id);
594-
},
595-
async getTaskResult(_args, ctx) {
596-
const result = await ctx.task.store.getTaskResult(ctx.task.id);
597-
return result as CallToolResult;
598584
}
599585
}
600586
);

packages/server/src/experimental/tasks/interfaces.ts

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,9 @@
33
* WARNING: These APIs are experimental and may change without notice.
44
*/
55

6-
import type {
7-
CallToolResult,
8-
CreateTaskResult,
9-
CreateTaskServerContext,
10-
GetTaskResult,
11-
Result,
12-
StandardSchemaWithJSON,
13-
TaskServerContext
14-
} from '@modelcontextprotocol/core';
6+
import type { CreateTaskResult, CreateTaskServerContext, Result, StandardSchemaWithJSON } from '@modelcontextprotocol/core';
157

16-
import type { BaseToolCallback } from '../../server/mcp.js';
8+
import type { AnyToolHandler, BaseToolCallback } from '../../server/mcp.js';
179

1810
// ============================================================================
1911
// Task Handler Types (for registerToolTask)
@@ -28,21 +20,12 @@ export type CreateTaskRequestHandler<
2820
Args extends StandardSchemaWithJSON | undefined = undefined
2921
> = BaseToolCallback<SendResultT, CreateTaskServerContext, Args>;
3022

31-
/**
32-
* Handler for task operations (`get`, `getResult`).
33-
* @experimental
34-
*/
35-
export type TaskRequestHandler<SendResultT extends Result, Args extends StandardSchemaWithJSON | undefined = undefined> = BaseToolCallback<
36-
SendResultT,
37-
TaskServerContext,
38-
Args
39-
>;
40-
4123
/**
4224
* Interface for task-based tool handlers.
4325
*
44-
* Task-based tools split a long-running operation into three phases:
45-
* `createTask`, `getTask`, and `getTaskResult`.
26+
* Task-based tools create a task on `tools/call` and let the SDK's {@linkcode TaskStore}
27+
* handle subsequent `tasks/get` and `tasks/result` requests. To customize task retrieval
28+
* behavior, implement a custom {@linkcode TaskStore}.
4629
*
4730
* @see {@linkcode @modelcontextprotocol/server!experimental/tasks/mcpServer.ExperimentalMcpServerTasks#registerToolTask | registerToolTask} for registration.
4831
* @experimental
@@ -55,12 +38,14 @@ export interface ToolTaskHandler<Args extends StandardSchemaWithJSON | undefined
5538
* background work, and returns the task object.
5639
*/
5740
createTask: CreateTaskRequestHandler<CreateTaskResult, Args>;
58-
/**
59-
* Handler for `tasks/get` requests.
60-
*/
61-
getTask: TaskRequestHandler<GetTaskResult, Args>;
62-
/**
63-
* Handler for `tasks/result` requests.
64-
*/
65-
getTaskResult: TaskRequestHandler<CallToolResult, Args>;
41+
}
42+
43+
/**
44+
* Type guard for {@linkcode ToolTaskHandler}.
45+
* @experimental
46+
*/
47+
export function isToolTaskHandler(
48+
handler: AnyToolHandler<StandardSchemaWithJSON | undefined>
49+
): handler is ToolTaskHandler<StandardSchemaWithJSON | undefined> {
50+
return 'createTask' in handler;
6651
}

packages/server/src/experimental/tasks/mcpServer.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ export class ExperimentalMcpServerTasks {
4545
* Registers a task-based tool with a config object and handler.
4646
*
4747
* Task-based tools support long-running operations that can be polled for status
48-
* and results. The handler must implement {@linkcode ToolTaskHandler.createTask | createTask}, {@linkcode ToolTaskHandler.getTask | getTask}, and {@linkcode ToolTaskHandler.getTaskResult | getTaskResult}
49-
* methods.
48+
* and results. The handler implements {@linkcode ToolTaskHandler.createTask | createTask}
49+
* to start the task; subsequent `tasks/get` and `tasks/result` requests are served
50+
* from the configured {@linkcode TaskStore}.
5051
*
5152
* @example
5253
* ```typescript
@@ -59,19 +60,13 @@ export class ExperimentalMcpServerTasks {
5960
* const task = await ctx.task.store.createTask({ ttl: 300000 });
6061
* startBackgroundWork(task.taskId, args);
6162
* return { task };
62-
* },
63-
* getTask: async (args, ctx) => {
64-
* return ctx.task.store.getTask(ctx.task.id);
65-
* },
66-
* getTaskResult: async (args, ctx) => {
67-
* return ctx.task.store.getTaskResult(ctx.task.id);
6863
* }
6964
* });
7065
* ```
7166
*
7267
* @param name - The tool name
7368
* @param config - Tool configuration (description, schemas, etc.)
74-
* @param handler - Task handler with {@linkcode ToolTaskHandler.createTask | createTask}, {@linkcode ToolTaskHandler.getTask | getTask}, {@linkcode ToolTaskHandler.getTaskResult | getTaskResult} methods
69+
* @param handler - Task handler with {@linkcode ToolTaskHandler.createTask | createTask}
7570
* @returns {@linkcode server/mcp.RegisteredTool | RegisteredTool} for managing the tool's lifecycle
7671
*
7772
* @experimental

packages/server/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export type {
3939
export { WebStandardStreamableHTTPServerTransport } from './server/streamableHttp.js';
4040

4141
// experimental exports
42-
export type { CreateTaskRequestHandler, TaskRequestHandler, ToolTaskHandler } from './experimental/tasks/interfaces.js';
42+
export type { CreateTaskRequestHandler, ToolTaskHandler } from './experimental/tasks/interfaces.js';
43+
export { isToolTaskHandler } from './experimental/tasks/interfaces.js';
4344
export { ExperimentalMcpServerTasks } from './experimental/tasks/mcpServer.js';
4445
export { ExperimentalServerTasks } from './experimental/tasks/server.js';
4546

packages/server/src/server/mcp.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
} from '@modelcontextprotocol/core';
4141

4242
import type { ToolTaskHandler } from '../experimental/tasks/interfaces.js';
43+
import { isToolTaskHandler } from '../experimental/tasks/interfaces.js';
4344
import { ExperimentalMcpServerTasks } from '../experimental/tasks/mcpServer.js';
4445
import { getCompleter, isCompletable } from './completable.js';
4546
import type { ServerOptions } from './server.js';
@@ -170,7 +171,7 @@ export class McpServer {
170171
try {
171172
const isTaskRequest = !!request.params.task;
172173
const taskSupport = tool.execution?.taskSupport;
173-
const isTaskHandler = 'createTask' in (tool.handler as AnyToolHandler<StandardSchemaWithJSON>);
174+
const isTaskHandler = isToolTaskHandler(tool.handler);
174175

175176
// Validate task hint configuration
176177
if ((taskSupport === 'required' || taskSupport === 'optional') && !isTaskHandler) {
@@ -325,14 +326,9 @@ export class McpServer {
325326

326327
while (task.status !== 'completed' && task.status !== 'failed' && task.status !== 'cancelled') {
327328
await new Promise(resolve => setTimeout(resolve, pollInterval));
328-
const updatedTask = await ctx.task.store.getTask(taskId);
329-
if (!updatedTask) {
330-
throw new ProtocolError(ProtocolErrorCode.InternalError, `Task ${taskId} not found during polling`);
331-
}
332-
task = updatedTask;
329+
task = await ctx.task.store.getTask(taskId);
333330
}
334331

335-
// Return the final result
336332
return (await ctx.task.store.getTaskResult(taskId)) as CallToolResult;
337333
}
338334

@@ -1120,9 +1116,7 @@ function createToolExecutor(
11201116
inputSchema: StandardSchemaWithJSON | undefined,
11211117
handler: AnyToolHandler<StandardSchemaWithJSON | undefined>
11221118
): ToolExecutor {
1123-
const isTaskHandler = 'createTask' in handler;
1124-
1125-
if (isTaskHandler) {
1119+
if (isToolTaskHandler(handler)) {
11261120
const taskHandler = handler as TaskHandlerInternal;
11271121
return async (args, ctx) => {
11281122
if (!ctx.task?.store) {

test/integration/test/client/client.test.ts

Lines changed: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2331,17 +2331,6 @@ describe('Task-based execution', () => {
23312331
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
23322332

23332333
return { task };
2334-
},
2335-
async getTask(_args, ctx) {
2336-
const task = await ctx.task.store.getTask(ctx.task.id);
2337-
if (!task) {
2338-
throw new Error(`Task ${ctx.task.id} not found`);
2339-
}
2340-
return task;
2341-
},
2342-
async getTaskResult(_args, ctx) {
2343-
const result = await ctx.task.store.getTaskResult(ctx.task.id);
2344-
return result as { content: Array<{ type: 'text'; text: string }> };
23452334
}
23462335
}
23472336
);
@@ -2414,17 +2403,6 @@ describe('Task-based execution', () => {
24142403
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
24152404

24162405
return { task };
2417-
},
2418-
async getTask(_args, ctx) {
2419-
const task = await ctx.task.store.getTask(ctx.task.id);
2420-
if (!task) {
2421-
throw new Error(`Task ${ctx.task.id} not found`);
2422-
}
2423-
return task;
2424-
},
2425-
async getTaskResult(_args, ctx) {
2426-
const result = await ctx.task.store.getTaskResult(ctx.task.id);
2427-
return result as { content: Array<{ type: 'text'; text: string }> };
24282406
}
24292407
}
24302408
);
@@ -2498,17 +2476,6 @@ describe('Task-based execution', () => {
24982476
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
24992477

25002478
return { task };
2501-
},
2502-
async getTask(_args, ctx) {
2503-
const task = await ctx.task.store.getTask(ctx.task.id);
2504-
if (!task) {
2505-
throw new Error(`Task ${ctx.task.id} not found`);
2506-
}
2507-
return task;
2508-
},
2509-
async getTaskResult(_args, ctx) {
2510-
const result = await ctx.task.store.getTaskResult(ctx.task.id);
2511-
return result as { content: Array<{ type: 'text'; text: string }> };
25122479
}
25132480
}
25142481
);
@@ -2586,17 +2553,6 @@ describe('Task-based execution', () => {
25862553
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
25872554

25882555
return { task };
2589-
},
2590-
async getTask(_args, ctx) {
2591-
const task = await ctx.task.store.getTask(ctx.task.id);
2592-
if (!task) {
2593-
throw new Error(`Task ${ctx.task.id} not found`);
2594-
}
2595-
return task;
2596-
},
2597-
async getTaskResult(_args, ctx) {
2598-
const result = await ctx.task.store.getTaskResult(ctx.task.id);
2599-
return result as { content: Array<{ type: 'text'; text: string }> };
26002556
}
26012557
}
26022558
);
@@ -3078,17 +3034,6 @@ describe('Task-based execution', () => {
30783034
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
30793035

30803036
return { task };
3081-
},
3082-
async getTask(_args, ctx) {
3083-
const task = await ctx.task.store.getTask(ctx.task.id);
3084-
if (!task) {
3085-
throw new Error(`Task ${ctx.task.id} not found`);
3086-
}
3087-
return task;
3088-
},
3089-
async getTaskResult(_args, ctx) {
3090-
const result = await ctx.task.store.getTaskResult(ctx.task.id);
3091-
return result as { content: Array<{ type: 'text'; text: string }> };
30923037
}
30933038
}
30943039
);
@@ -3353,17 +3298,6 @@ test('should respect server task capabilities', async () => {
33533298
await ctx.task.store.storeTaskResult(task.taskId, 'completed', result);
33543299

33553300
return { task };
3356-
},
3357-
async getTask(_args, ctx) {
3358-
const task = await ctx.task.store.getTask(ctx.task.id);
3359-
if (!task) {
3360-
throw new Error(`Task ${ctx.task.id} not found`);
3361-
}
3362-
return task;
3363-
},
3364-
async getTaskResult(_args, ctx) {
3365-
const result = await ctx.task.store.getTaskResult(ctx.task.id);
3366-
return result as { content: Array<{ type: 'text'; text: string }> };
33673301
}
33683302
}
33693303
);

0 commit comments

Comments
 (0)