From 6ba64de9be18fb55579fbf110d3805dd8884dc84 Mon Sep 17 00:00:00 2001 From: Genmin Date: Fri, 1 May 2026 11:32:43 -0700 Subject: [PATCH 1/2] fix(server): support two-arg no-schema task handlers --- .changeset/fair-tasks-share.md | 5 ++ .../src/experimental/tasks/interfaces.ts | 19 ++-- packages/server/src/server/mcp.ts | 7 +- packages/server/test/server/mcp.tasks.test.ts | 88 +++++++++++++++++++ 4 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 .changeset/fair-tasks-share.md create mode 100644 packages/server/test/server/mcp.tasks.test.ts diff --git a/.changeset/fair-tasks-share.md b/.changeset/fair-tasks-share.md new file mode 100644 index 0000000000..aca7b185ca --- /dev/null +++ b/.changeset/fair-tasks-share.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/server': patch +--- + +fix(server): pass task context as the second argument for no-schema task handlers diff --git a/packages/server/src/experimental/tasks/interfaces.ts b/packages/server/src/experimental/tasks/interfaces.ts index 2aef91a8c0..b2c8289906 100644 --- a/packages/server/src/experimental/tasks/interfaces.ts +++ b/packages/server/src/experimental/tasks/interfaces.ts @@ -19,6 +19,10 @@ import type { BaseToolCallback } from '../../server/mcp.js'; // Task Handler Types (for registerToolTask) // ============================================================================ +type TaskCallbackWithoutArgs = + | ((ctx: Ctx) => SendResultT | Promise) + | ((_args: undefined, ctx: Ctx) => SendResultT | Promise); + /** * Handler for creating a task. * @experimental @@ -26,17 +30,20 @@ import type { BaseToolCallback } from '../../server/mcp.js'; export type CreateTaskRequestHandler< SendResultT extends Result, Args extends StandardSchemaWithJSON | undefined = undefined -> = BaseToolCallback; +> = Args extends StandardSchemaWithJSON + ? BaseToolCallback + : TaskCallbackWithoutArgs; /** * Handler for task operations (`get`, `getResult`). * @experimental */ -export type TaskRequestHandler = BaseToolCallback< - SendResultT, - TaskServerContext, - Args ->; +export type TaskRequestHandler< + SendResultT extends Result, + Args extends StandardSchemaWithJSON | undefined = undefined +> = Args extends StandardSchemaWithJSON + ? BaseToolCallback + : TaskCallbackWithoutArgs; /** * Interface for task-based tool handlers. diff --git a/packages/server/src/server/mcp.ts b/packages/server/src/server/mcp.ts index fb45fd5db6..814b4c5d22 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -1206,7 +1206,12 @@ function createToolExecutor( if (inputSchema) { return taskHandler.createTask(args, taskCtx); } - // When no inputSchema, call with just ctx (the handler expects (ctx) signature) + + if (taskHandler.createTask.length >= 2) { + return taskHandler.createTask(undefined, taskCtx); + } + + // When no inputSchema, preserve the existing context-only handler signature. return (taskHandler.createTask as (ctx: CreateTaskServerContext) => CreateTaskResult | Promise)(taskCtx); }; } diff --git a/packages/server/test/server/mcp.tasks.test.ts b/packages/server/test/server/mcp.tasks.test.ts new file mode 100644 index 0000000000..423e4b6395 --- /dev/null +++ b/packages/server/test/server/mcp.tasks.test.ts @@ -0,0 +1,88 @@ +import type { CreateTaskServerContext, JSONRPCMessage, TaskServerContext } from '@modelcontextprotocol/core'; +import { InMemoryTaskStore, InMemoryTransport, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core'; +import { describe, expect, it, vi } from 'vitest'; +import { McpServer } from '../../src/index.js'; +import type { ToolTaskHandler } from '../../src/index.js'; + +describe('registerToolTask', () => { + it('passes task context as the second argument for no-schema task handlers', async () => { + const server = new McpServer( + { name: 'task-test', version: '1.0.0' }, + { + capabilities: { + tasks: { + requests: { tools: { call: {} } }, + taskStore: new InMemoryTaskStore() + } + } + } + ); + + let receivedArgs: unknown = 'not-called'; + let receivedCtx: CreateTaskServerContext | undefined; + + const handler = { + createTask: async (_args: undefined, ctx: CreateTaskServerContext) => { + receivedArgs = _args; + receivedCtx = ctx; + const task = await ctx.task.store.createTask({ ttl: ctx.task.requestedTtl }); + return { task }; + }, + getTask: async (_args: undefined, ctx: TaskServerContext) => ({ + taskId: ctx.task.id ?? 'unused', + status: 'working' as const, + ttl: null, + createdAt: new Date(0).toISOString(), + lastUpdatedAt: new Date(0).toISOString() + }), + getTaskResult: async () => ({ + content: [{ type: 'text' as const, text: 'done' }] + }) + } satisfies ToolTaskHandler; + + server.experimental.tasks.registerToolTask('no-schema-task', { description: 'Create a task without input arguments' }, handler); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await server.connect(serverTransport); + await clientTransport.start(); + + const responses: JSONRPCMessage[] = []; + clientTransport.onmessage = message => responses.push(message); + + await clientTransport.send({ + jsonrpc: '2.0', + id: 1, + method: 'initialize', + params: { + protocolVersion: LATEST_PROTOCOL_VERSION, + capabilities: {}, + clientInfo: { name: 'task-client', version: '1.0.0' } + } + } as JSONRPCMessage); + await clientTransport.send({ jsonrpc: '2.0', method: 'notifications/initialized' } as JSONRPCMessage); + await clientTransport.send({ + jsonrpc: '2.0', + id: 2, + method: 'tools/call', + params: { + name: 'no-schema-task', + task: { ttl: 600 } + } + } as JSONRPCMessage); + + await vi.waitFor(() => expect(responses.some(response => 'id' in response && response.id === 2)).toBe(true)); + + expect(receivedArgs).toBeUndefined(); + expect(receivedCtx?.task.store).toBeDefined(); + expect(receivedCtx?.task.requestedTtl).toBe(600); + + const response = responses.find(message => 'id' in message && message.id === 2) as { + error?: unknown; + result?: { task: { ttl?: number | null } }; + }; + expect(response.error).toBeUndefined(); + expect(response.result?.task.ttl).toBe(600); + + await server.close(); + }); +}); From 4411b7e9592cf8a778c284135732f929ccfc045a Mon Sep 17 00:00:00 2001 From: Genmin Date: Fri, 1 May 2026 12:19:28 -0700 Subject: [PATCH 2/2] fix(server): avoid private task callback docs warning --- packages/server/src/experimental/tasks/interfaces.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/server/src/experimental/tasks/interfaces.ts b/packages/server/src/experimental/tasks/interfaces.ts index b2c8289906..6447349ad2 100644 --- a/packages/server/src/experimental/tasks/interfaces.ts +++ b/packages/server/src/experimental/tasks/interfaces.ts @@ -19,10 +19,6 @@ import type { BaseToolCallback } from '../../server/mcp.js'; // Task Handler Types (for registerToolTask) // ============================================================================ -type TaskCallbackWithoutArgs = - | ((ctx: Ctx) => SendResultT | Promise) - | ((_args: undefined, ctx: Ctx) => SendResultT | Promise); - /** * Handler for creating a task. * @experimental @@ -32,7 +28,9 @@ export type CreateTaskRequestHandler< Args extends StandardSchemaWithJSON | undefined = undefined > = Args extends StandardSchemaWithJSON ? BaseToolCallback - : TaskCallbackWithoutArgs; + : + | ((ctx: CreateTaskServerContext) => SendResultT | Promise) + | ((_args: undefined, ctx: CreateTaskServerContext) => SendResultT | Promise); /** * Handler for task operations (`get`, `getResult`). @@ -43,7 +41,9 @@ export type TaskRequestHandler< Args extends StandardSchemaWithJSON | undefined = undefined > = Args extends StandardSchemaWithJSON ? BaseToolCallback - : TaskCallbackWithoutArgs; + : + | ((ctx: TaskServerContext) => SendResultT | Promise) + | ((_args: undefined, ctx: TaskServerContext) => SendResultT | Promise); /** * Interface for task-based tool handlers.