From 252fdbdb96bbb4909e45718643faf31cdd80fd73 Mon Sep 17 00:00:00 2001 From: Basil Hosmer Date: Tue, 20 May 2025 18:39:17 -0400 Subject: [PATCH] fill in compatible field on client side --- src/client/index.test.ts | 74 ++++++++++++++++++++++++++++++++++++++++ src/client/index.ts | 14 ++++++-- src/types.ts | 6 ++++ 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/client/index.test.ts b/src/client/index.test.ts index bbfa80faf..979c6ded3 100644 --- a/src/client/index.test.ts +++ b/src/client/index.test.ts @@ -1261,5 +1261,79 @@ describe('outputSchema validation', () => { ); }); + /*** + * Test: client should automatically provide compatible content field + */ + test('should automatically provide compatible content field', async () => { + const server = new Server({ + name: 'test-server', + version: '1.0.0', + }, { + capabilities: { + tools: {}, + }, + }); + + // Set up server handlers + server.setRequestHandler(InitializeRequestSchema, async (request) => ({ + protocolVersion: request.params.protocolVersion, + capabilities: {}, + serverInfo: { + name: 'test-server', + version: '1.0.0', + } + })); + + server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [ + { + name: 'test-tool', + description: 'A test tool', + inputSchema: { + type: 'object', + properties: {}, + }, + outputSchema: { + type: 'object', + properties: { + result: { type: 'string' }, + count: { type: 'number' }, + }, + required: ['result', 'count'], + additionalProperties: false, + }, + }, + ], + })); + + server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (request.params.name === 'test-tool') { + return { + structuredContent: { result: 'success', count: 42 } + }; + } + throw new Error('Unknown tool'); + }); + + const client = new Client({ + name: 'test-client', + version: '1.0.0', + }); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + // List tools to cache the schemas + await client.listTools(); + + // Call the tool - client should automatically provide compatible content field + const result = await client.callTool({ name: 'test-tool' }); + expect(result.structuredContent).toEqual({ result: 'success', count: 42 }); + expect(result.content).toEqual([{ type: 'text', text: JSON.stringify(result.structuredContent, null, 2) }]); + }); }); diff --git a/src/client/index.ts b/src/client/index.ts index 98618a171..e51031e38 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -42,6 +42,7 @@ import { Tool, ErrorCode, McpError, + CallToolResultWithContent, } from "../types.js"; import Ajv from "ajv"; import type { ValidateFunction } from "ajv"; @@ -420,7 +421,7 @@ export class Client< | typeof CallToolResultSchema | typeof CompatibilityCallToolResultSchema = CallToolResultSchema, options?: RequestOptions, - ) { + ): Promise { const result = await this.request( { method: "tools/call", params }, resultSchema, @@ -459,10 +460,19 @@ export class Client< `Failed to validate structured content: ${error instanceof Error ? error.message : String(error)}` ); } + // for compatibility, serialize structuredContent to content if none was provided + if (!result.content) { + result.content = [ + { + type: "text", + text: JSON.stringify(result.structuredContent, null, 2), + }, + ]; + } } } - return result; + return result as CallToolResultWithContent; } private cacheToolOutputSchemas(tools: Tool[]) { diff --git a/src/types.ts b/src/types.ts index bd299c8f7..5c8dc9619 100644 --- a/src/types.ts +++ b/src/types.ts @@ -946,6 +946,11 @@ export const CallToolResultSchema = z.union([ CallToolStructuredResultSchema, ]); +export const CallToolResultWithContentSchema = z.intersection( + CallToolResultSchema, + z.object({ content: ContentListSchema }) +) + /** * CallToolResultSchema extended with backwards compatibility to protocol version 2024-10-07. */ @@ -1400,6 +1405,7 @@ export type ContentList = Infer; export type CallToolUnstructuredResult = Infer; export type CallToolStructuredResult = Infer; export type CallToolResult = Infer; +export type CallToolResultWithContent = Infer; export type CompatibilityCallToolResult = Infer; export type CallToolRequest = Infer; export type ToolListChangedNotification = Infer;