diff --git a/src/examples/server/mcpServerOutputSchema.ts b/src/examples/server/mcpServerOutputSchema.ts index 7ef9f6227..be121d894 100644 --- a/src/examples/server/mcpServerOutputSchema.ts +++ b/src/examples/server/mcpServerOutputSchema.ts @@ -41,7 +41,12 @@ server.registerTool( void country; // Simulate weather API call const temp_c = Math.round((Math.random() * 35 - 5) * 10) / 10; - const conditions = ['sunny', 'cloudy', 'rainy', 'stormy', 'snowy'][Math.floor(Math.random() * 5)]; + const conditions = ['sunny', 'cloudy', 'rainy', 'stormy', 'snowy'][Math.floor(Math.random() * 5)] as + | 'sunny' + | 'cloudy' + | 'rainy' + | 'stormy' + | 'snowy'; const structuredContent = { temperature: { diff --git a/src/server/mcp.test.ts b/src/server/mcp.test.ts index 776d0a129..fa1090825 100644 --- a/src/server/mcp.test.ts +++ b/src/server/mcp.test.ts @@ -1283,25 +1283,27 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => { timestamp: z.string() } }, - async ({ input }) => ({ - content: [ - { - type: 'text', - text: JSON.stringify({ - processedInput: input, - resultType: 'structured', - // Missing required 'timestamp' field - someExtraField: 'unexpected' // Extra field not in schema - }) + async ({ input }) => + // @ts-expect-error - Intentionally returning invalid data to test runtime validation + ({ + content: [ + { + type: 'text', + text: JSON.stringify({ + processedInput: input, + resultType: 'structured', + // Missing required 'timestamp' field + someExtraField: 'unexpected' // Extra field not in schema + }) + } + ], + structuredContent: { + processedInput: input, + resultType: 'structured', + // Missing required 'timestamp' field + someExtraField: 'unexpected' // Extra field not in schema } - ], - structuredContent: { - processedInput: input, - resultType: 'structured', - // Missing required 'timestamp' field - someExtraField: 'unexpected' // Extra field not in schema - } - }) + }) ); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); diff --git a/src/server/mcp.ts b/src/server/mcp.ts index b9b6d5596..433f134cb 100644 --- a/src/server/mcp.ts +++ b/src/server/mcp.ts @@ -19,7 +19,7 @@ import { Implementation, Tool, ListToolsResult, - CallToolResult, + CallToolResult as BaseCallToolResult, McpError, ErrorCode, CompleteResult, @@ -56,6 +56,19 @@ import { RequestHandlerExtra } from '../shared/protocol.js'; import { Transport } from '../shared/transport.js'; import { validateAndWarnToolName } from '../shared/toolNameValidation.js'; +/** + * A result of a tool call, with structured content typed according to the tool's output schema. + */ +export type CallToolResult = T extends ZodRawShapeCompat + ? Omit & { + structuredContent?: ShapeOutput; + } + : T extends AnySchema + ? Omit & { + structuredContent?: SchemaOutput; + } + : BaseCallToolResult; + /** * High-level MCP server that provides a simpler API for working with resources, tools, and prompts. * For advanced usage (like sending notifications or setting custom request handlers), use the underlying @@ -148,10 +161,10 @@ export class McpServer { }) ); - this.server.setRequestHandler(CallToolRequestSchema, async (request, extra): Promise => { + this.server.setRequestHandler(CallToolRequestSchema, async (request, extra): Promise => { const tool = this._registeredTools[request.params.name]; - let result: CallToolResult; + let result: BaseCallToolResult; try { if (!tool) { @@ -178,10 +191,10 @@ export class McpServer { const args = parseResult.data; - result = await Promise.resolve(cb(args, extra)); + result = (await Promise.resolve(cb(args, extra))) as BaseCallToolResult; } else { const cb = tool.callback as ToolCallback; - result = await Promise.resolve(cb(extra)); + result = (await Promise.resolve(cb(extra))) as BaseCallToolResult; } if (tool.outputSchema && !result.isError) { @@ -223,7 +236,7 @@ export class McpServer { * @param errorMessage - The error message. * @returns The tool error result. */ - private createToolError(errorMessage: string): CallToolResult { + private createToolError(errorMessage: string): BaseCallToolResult { return { content: [ { @@ -698,7 +711,7 @@ export class McpServer { outputSchema: ZodRawShapeCompat | AnySchema | undefined, annotations: ToolAnnotations | undefined, _meta: Record | undefined, - callback: ToolCallback + callback: ToolCallback ): RegisteredTool { // Validate tool name according to SEP specification validateAndWarnToolName(name); @@ -867,7 +880,7 @@ export class McpServer { annotations?: ToolAnnotations; _meta?: Record; }, - cb: ToolCallback + cb: ToolCallback ): RegisteredTool { if (this._registeredTools[name]) { throw new Error(`Tool ${name} is already registered`); @@ -883,7 +896,7 @@ export class McpServer { outputSchema, annotations, _meta, - cb as ToolCallback + cb as ToolCallback ); } @@ -1086,14 +1099,20 @@ export class ResourceTemplate { * - `content` if the tool does not have an outputSchema * - Both fields are optional but typically one should be provided */ -export type ToolCallback = Args extends ZodRawShapeCompat - ? (args: ShapeOutput, extra: RequestHandlerExtra) => CallToolResult | Promise - : Args extends AnySchema +export type ToolCallback< + InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined, + OutputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined +> = InputArgs extends ZodRawShapeCompat + ? ( + args: ShapeOutput, + extra: RequestHandlerExtra + ) => CallToolResult | Promise> + : InputArgs extends AnySchema ? ( - args: SchemaOutput, + args: SchemaOutput, extra: RequestHandlerExtra - ) => CallToolResult | Promise - : (extra: RequestHandlerExtra) => CallToolResult | Promise; + ) => CallToolResult | Promise> + : (extra: RequestHandlerExtra) => CallToolResult | Promise>; export type RegisteredTool = { title?: string; @@ -1102,11 +1121,11 @@ export type RegisteredTool = { outputSchema?: AnySchema; annotations?: ToolAnnotations; _meta?: Record; - callback: ToolCallback; + callback: ToolCallback; enabled: boolean; enable(): void; disable(): void; - update(updates: { + update(updates: { name?: string | null; title?: string; description?: string; @@ -1114,7 +1133,7 @@ export type RegisteredTool = { outputSchema?: OutputArgs; annotations?: ToolAnnotations; _meta?: Record; - callback?: ToolCallback; + callback?: ToolCallback; enabled?: boolean; }): void; remove(): void;