diff --git a/src/client/index.test.ts b/src/client/index.test.ts index abd0c34e4..86ebe6e63 100644 --- a/src/client/index.test.ts +++ b/src/client/index.test.ts @@ -16,6 +16,8 @@ import { CreateMessageRequestSchema, ElicitRequestSchema, ListRootsRequestSchema, + ListPromptsRequestSchema, + ListResourceTemplatesRequestSchema, ErrorCode, } from "../types.js"; import { Transport } from "../shared/transport.js"; @@ -1301,3 +1303,137 @@ describe('outputSchema validation', () => { }); + +test("should return empty arrays for all list methods when server has capabilities but no handlers", async () => { + const server = new Server( + { + name: "test server", + version: "1.0", + }, + { + capabilities: { + prompts: {}, + resources: {}, + tools: {}, + }, + }, + ); + + server.setRequestHandler(InitializeRequestSchema, (_request) => ({ + protocolVersion: LATEST_PROTOCOL_VERSION, + capabilities: { + prompts: {}, + resources: {}, + tools: {}, + }, + serverInfo: { + name: "test", + version: "1.0", + }, + })); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + const client = new Client( + { + name: "test client", + version: "1.0", + }, + { + capabilities: { + sampling: {}, + }, + }, + ); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const promptsResult = await client.listPrompts(); + const resourcesResult = await client.listResources(); + const resourceTemplatesResult = await client.listResourceTemplates(); + const toolsResult = await client.listTools(); + + expect(promptsResult.prompts).toEqual([]); + expect(resourcesResult.resources).toEqual([]); + expect(resourceTemplatesResult.resourceTemplates).toEqual([]); + expect(toolsResult.tools).toEqual([]); +}); + +test("should handle empty collections consistently across all capabilities", async () => { + const server = new Server( + { + name: "test server", + version: "1.0", + }, + { + capabilities: { + prompts: {}, + resources: {}, + tools: {}, + }, + }, + ); + + server.setRequestHandler(InitializeRequestSchema, (_request) => ({ + protocolVersion: LATEST_PROTOCOL_VERSION, + capabilities: { + prompts: {}, + resources: {}, + tools: {}, + }, + serverInfo: { + name: "test", + version: "1.0", + }, + })); + + server.setRequestHandler(ListPromptsRequestSchema, () => ({ + prompts: [], + })); + + server.setRequestHandler(ListResourcesRequestSchema, () => ({ + resources: [], + })); + + server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({ + resourceTemplates: [], + })); + + server.setRequestHandler(ListToolsRequestSchema, () => ({ + tools: [], + })); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + const client = new Client( + { + name: "test client", + version: "1.0", + }, + { + capabilities: { + sampling: {}, + }, + }, + ); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + const promptsResult = await client.listPrompts(); + const resourcesResult = await client.listResources(); + const resourceTemplatesResult = await client.listResourceTemplates(); + const toolsResult = await client.listTools(); + + expect(promptsResult.prompts).toEqual([]); + expect(resourcesResult.resources).toEqual([]); + expect(resourceTemplatesResult.resourceTemplates).toEqual([]); + expect(toolsResult.tools).toEqual([]); +}); \ No newline at end of file diff --git a/src/shared/protocol.ts b/src/shared/protocol.ts index 7df190ba1..5ebc56e66 100644 --- a/src/shared/protocol.ts +++ b/src/shared/protocol.ts @@ -366,6 +366,52 @@ export abstract class Protocol< ); } + /** + * Default handler for requests when no specific handler is registered. + * Automatically returns empty arrays for standard MCP list methods. + */ + private _defaultFallbackRequestHandler( request: JSONRPCRequest, transport: Transport ): SendResultT | null { + const method = request.method; + const listMethodResponses: Record = { + "prompts/list": "prompts", + "resources/list": "resources", + "resources/templates/list": "resourceTemplates", + "tools/list": "tools", + }; + const responseKey = listMethodResponses[method]; + const result = responseKey ? { [responseKey]: [] } as SendResultT : null; + if (result !== null) { + transport + .send({ + result: result, + jsonrpc: "2.0", + id: request.id, + }) + .catch((error) => + this._onerror( + new Error(`Failed to send default list response: ${error}`), + ), + ); + return result; + } + + transport + .send({ + jsonrpc: "2.0", + id: request.id, + error: { + code: ErrorCode.MethodNotFound, + message: "Method not found", + }, + }) + .catch((error) => + this._onerror( + new Error(`Failed to send an error response: ${error}`), + ), + ); + return null; + } + private _onrequest(request: JSONRPCRequest, extra?: MessageExtraInfo): void { const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler; @@ -373,22 +419,12 @@ export abstract class Protocol< // Capture the current transport at request time to ensure responses go to the correct client const capturedTransport = this._transport; + if(capturedTransport === undefined) { + throw new Error("Error: transport not found.") + } if (handler === undefined) { - capturedTransport - ?.send({ - jsonrpc: "2.0", - id: request.id, - error: { - code: ErrorCode.MethodNotFound, - message: "Method not found", - }, - }) - .catch((error) => - this._onerror( - new Error(`Failed to send an error response: ${error}`), - ), - ); - return; + this._defaultFallbackRequestHandler(request, capturedTransport); + return; } const abortController = new AbortController();