diff --git a/packages/server/src/server/mcp.ts b/packages/server/src/server/mcp.ts index fb45fd5db6..f18b38cbab 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -1216,6 +1216,14 @@ function createToolExecutor( return async (args, ctx) => callback(args, ctx); } + if (handler.length > 1) { + throw new Error( + `Tool handler accepts ${handler.length} parameters but no inputSchema was provided. ` + + 'Add inputSchema to the tool definition to enable the (args, ctx) two-parameter signature, ' + + 'or change the handler to accept only one parameter (ctx).' + ); + } + // When no inputSchema, call with just ctx (the handler expects (ctx) signature) const callback = handler as (ctx: ServerContext) => CallToolResult | Promise; return async (_args, ctx) => callback(ctx); diff --git a/packages/server/test/server/mcp.compat.test.ts b/packages/server/test/server/mcp.compat.test.ts index 322b615353..a3eccda754 100644 --- a/packages/server/test/server/mcp.compat.test.ts +++ b/packages/server/test/server/mcp.compat.test.ts @@ -119,6 +119,17 @@ describe('registerTool/registerPrompt accept raw Zod shape (auto-wrapped)', () = await server.close(); }); + + it('rejects a two-argument handler when inputSchema is omitted', () => { + const server = new McpServer({ name: 't', version: '1.0.0' }); + const handler = ((_args: unknown, _ctx: unknown) => ({ + content: [{ type: 'text' as const, text: 'ok' }] + })) as never; + + expect(() => server.registerTool('no-schema', {}, handler)).toThrow( + /no inputSchema.*two-parameter signature|inputSchema.*two-parameter signature/ + ); + }); }); describe('InferRawShape', () => {