diff --git a/src/examples/client/parallelToolCallsClient.ts b/src/examples/client/parallelToolCallsClient.ts index 83c101b7..3783992d 100644 --- a/src/examples/client/parallelToolCallsClient.ts +++ b/src/examples/client/parallelToolCallsClient.ts @@ -61,7 +61,7 @@ async function main(): Promise { // Log the results from each tool call for (const [caller, result] of Object.entries(toolResults)) { console.log(`\n=== Tool result for ${caller} ===`); - result.content?.forEach((item: { type: string; text?: string; }) => { + result.content.forEach((item: { type: string; text?: string; }) => { if (item.type === 'text') { console.log(` ${item.text}`); } else { diff --git a/src/examples/client/simpleStreamableHttp.ts b/src/examples/client/simpleStreamableHttp.ts index 9a20f03a..0328f0d2 100644 --- a/src/examples/client/simpleStreamableHttp.ts +++ b/src/examples/client/simpleStreamableHttp.ts @@ -264,12 +264,12 @@ async function terminateSession(): Promise { console.log('Terminating session with ID:', transport.sessionId); await transport.terminateSession(); console.log('Session terminated successfully'); - + // Check if sessionId was cleared after termination if (!transport.sessionId) { console.log('Session ID has been cleared'); sessionId = undefined; - + // Also close the transport and clear client objects await transport.close(); console.log('Transport closed after session termination'); @@ -341,7 +341,7 @@ async function callTool(name: string, args: Record): Promise { + result.content.forEach(item => { if (item.type === 'text') { console.log(` ${item.text}`); } else { @@ -456,7 +456,7 @@ async function cleanup(): Promise { console.error('Error terminating session:', error); } } - + // Then close the transport await transport.close(); } catch (error) { diff --git a/src/examples/client/streamableHttpWithSseFallbackClient.ts b/src/examples/client/streamableHttpWithSseFallbackClient.ts index aaefc680..7646f0f7 100644 --- a/src/examples/client/streamableHttpWithSseFallbackClient.ts +++ b/src/examples/client/streamableHttpWithSseFallbackClient.ts @@ -173,7 +173,7 @@ async function startNotificationTool(client: Client): Promise { const result = await client.request(request, CallToolResultSchema); console.log('Tool result:'); - result.content?.forEach(item => { + result.content.forEach(item => { if (item.type === 'text') { console.log(` ${item.text}`); } else { diff --git a/src/examples/server/mcpServerOutputSchema.ts b/src/examples/server/mcpServerOutputSchema.ts index 65230fc7..73042d64 100644 --- a/src/examples/server/mcpServerOutputSchema.ts +++ b/src/examples/server/mcpServerOutputSchema.ts @@ -20,7 +20,7 @@ server.registerTool( "get_weather", { description: "Get weather information for a city", - inputSchema:{ + inputSchema: { city: z.string().describe("City name"), country: z.string().describe("Country code (e.g., US, UK)") }, @@ -46,10 +46,11 @@ server.registerTool( const conditions = ["sunny", "cloudy", "rainy", "stormy", "snowy"][Math.floor(Math.random() * 5)]; return { + content: [], structuredContent: { temperature: { celsius: temp_c, - fahrenheit: Math.round((temp_c * 9/5 + 32) * 10) / 10 + fahrenheit: Math.round((temp_c * 9 / 5 + 32) * 10) / 10 }, conditions, humidity: Math.round(Math.random() * 100), diff --git a/src/server/mcp.test.ts b/src/server/mcp.test.ts index 1b2f4d4b..620a875f 100644 --- a/src/server/mcp.test.ts +++ b/src/server/mcp.test.ts @@ -398,7 +398,7 @@ describe("tool()", () => { name: z.string(), value: z.number(), }, - callback: async ({name, value}) => ({ + callback: async ({ name, value }) => ({ content: [ { type: "text", @@ -729,7 +729,7 @@ describe("tool()", () => { }); mcpServer.tool( - "test", + "test", { name: z.string() }, { title: "Test Tool", readOnlyHint: true }, async ({ name }) => ({ @@ -787,7 +787,7 @@ describe("tool()", () => { }); mcpServer.tool( - "test", + "test", "A tool with everything", { name: z.string() }, { title: "Complete Test Tool", readOnlyHint: true, openWorldHint: false }, @@ -828,8 +828,8 @@ describe("tool()", () => { type: "object", properties: { name: { type: "string" } } }); - expect(result.tools[0].annotations).toEqual({ - title: "Complete Test Tool", + expect(result.tools[0].annotations).toEqual({ + title: "Complete Test Tool", readOnlyHint: true, openWorldHint: false }); @@ -853,7 +853,7 @@ describe("tool()", () => { }); mcpServer.tool( - "test", + "test", "A tool with everything but empty params", {}, { title: "Complete Test Tool with empty params", readOnlyHint: true, openWorldHint: false }, @@ -894,8 +894,8 @@ describe("tool()", () => { type: "object", properties: {} }); - expect(result.tools[0].annotations).toEqual({ - title: "Complete Test Tool with empty params", + expect(result.tools[0].annotations).toEqual({ + title: "Complete Test Tool with empty params", readOnlyHint: true, openWorldHint: false }); @@ -1077,9 +1077,9 @@ describe("tool()", () => { input: z.string(), }, outputSchema: { - processedInput: z.string(), - resultType: z.string(), - timestamp: z.string() + processedInput: z.string(), + resultType: z.string(), + timestamp: z.string() }, }, async ({ input }) => ({ @@ -1088,6 +1088,16 @@ describe("tool()", () => { resultType: "structured", timestamp: "2023-01-01T00:00:00Z" }, + content: [ + { + type: "text", + text: JSON.stringify({ + processedInput: input, + resultType: "structured", + timestamp: "2023-01-01T00:00:00Z" + }), + }, + ] }) ); @@ -1186,6 +1196,17 @@ describe("tool()", () => { }, }, async ({ input }) => ({ + 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", @@ -2664,7 +2685,7 @@ describe("prompt()", () => { name: z.string(), value: z.string(), }, - callback: async ({name, value}) => ({ + callback: async ({ name, value }) => ({ messages: [ { role: "assistant", diff --git a/src/types.ts b/src/types.ts index bd299c8f..98a51321 100644 --- a/src/types.ts +++ b/src/types.ts @@ -843,13 +843,13 @@ export const ToolSchema = z * If not set, a CallToolResult for this Tool MUST NOT contain a structuredContent field and MUST contain a content field. */ outputSchema: z.optional( - z.object({ - type: z.literal("object"), - properties: z.optional(z.object({}).passthrough()), - required: z.optional(z.array(z.string())), - }) + z.object({ + type: z.literal("object"), + properties: z.optional(z.object({}).passthrough()), + required: z.optional(z.array(z.string())), + }) .passthrough() - ), + ), /** * Optional additional tool information. */ @@ -892,46 +892,21 @@ export const ContentListSchema = z.array( ]), ); -export const CallToolUnstructuredResultSchema = ResultSchema.extend({ +export const CallToolResultSchema = ResultSchema.extend({ /** * A list of content objects that represent the result of the tool call. * * If the Tool does not define an outputSchema, this field MUST be present in the result. + * For backwards compatibility, this field is always present, but it may be empty. */ - content: ContentListSchema, + content: ContentListSchema.default([]), - /** - * Structured output must not be provided in an unstructured tool result. - */ - structuredContent: z.never().optional(), - - /** - * Whether the tool call ended in an error. - * - * If not set, this is assumed to be false (the call was successful). - */ - isError: z.optional(z.boolean()), -}); - -export const CallToolStructuredResultSchema = ResultSchema.extend({ /** * An object containing structured tool output. * * If the Tool defines an outputSchema, this field MUST be present in the result, and contain a JSON object that matches the schema. */ - structuredContent: z.object({}).passthrough(), - - /** - * A list of content objects that represent the result of the tool call. - * - * If the Tool defines an outputSchema, this field MAY be present in the result. - * - * Tools may use this field to provide compatibility with older clients that - * do not support structured content. - * - * Clients that support structured content should ignore this field. - */ - content: z.optional(ContentListSchema), + structuredContent: z.object({}).passthrough().optional(), /** * Whether the tool call ended in an error. @@ -941,11 +916,6 @@ export const CallToolStructuredResultSchema = ResultSchema.extend({ isError: z.optional(z.boolean()), }); -export const CallToolResultSchema = z.union([ - CallToolUnstructuredResultSchema, - CallToolStructuredResultSchema, -]); - /** * CallToolResultSchema extended with backwards compatibility to protocol version 2024-10-07. */ @@ -1397,8 +1367,6 @@ export type Tool = Infer; export type ListToolsRequest = Infer; export type ListToolsResult = Infer; export type ContentList = Infer; -export type CallToolUnstructuredResult = Infer; -export type CallToolStructuredResult = Infer; export type CallToolResult = Infer; export type CompatibilityCallToolResult = Infer; export type CallToolRequest = Infer;