From debc6619a9d533f615a5cc0c59de88d2e42a27d7 Mon Sep 17 00:00:00 2001 From: Khanh Nguyen Date: Thu, 4 Sep 2025 15:34:40 -0400 Subject: [PATCH] feat: add _meta field support to tool definitions Enables tools to include arbitrary metadata that gets returned in tools/list responses. Includes comprehensive tests and maintains backward compatibility. --- src/server/mcp.test.ts | 93 ++++++++++++++++++++++++++++++++++++++++++ src/server/mcp.ts | 12 +++++- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/src/server/mcp.test.ts b/src/server/mcp.test.ts index 10e550df4..d9142702f 100644 --- a/src/server/mcp.test.ts +++ b/src/server/mcp.test.ts @@ -1633,6 +1633,99 @@ describe("tool()", () => { ), ).rejects.toThrow(/Tool nonexistent-tool not found/); }); + + /*** + * Test: Tool Registration with _meta field + */ + test("should register tool with _meta field and include it in list response", async () => { + const mcpServer = new McpServer({ + name: "test server", + version: "1.0", + }); + const client = new Client({ + name: "test client", + version: "1.0", + }); + + const metaData = { + author: "test-author", + version: "1.2.3", + category: "utility", + tags: ["test", "example"] + }; + + mcpServer.registerTool( + "test-with-meta", + { + description: "A tool with _meta field", + inputSchema: { name: z.string() }, + _meta: metaData, + }, + async ({ name }) => ({ + content: [{ type: "text", text: `Hello, ${name}!` }] + }) + ); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + mcpServer.server.connect(serverTransport), + ]); + + const result = await client.request( + { method: "tools/list" }, + ListToolsResultSchema, + ); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe("test-with-meta"); + expect(result.tools[0].description).toBe("A tool with _meta field"); + expect(result.tools[0]._meta).toEqual(metaData); + }); + + /*** + * Test: Tool Registration without _meta field should have undefined _meta + */ + test("should register tool without _meta field and have undefined _meta in response", async () => { + const mcpServer = new McpServer({ + name: "test server", + version: "1.0", + }); + const client = new Client({ + name: "test client", + version: "1.0", + }); + + mcpServer.registerTool( + "test-without-meta", + { + description: "A tool without _meta field", + inputSchema: { name: z.string() }, + }, + async ({ name }) => ({ + content: [{ type: "text", text: `Hello, ${name}!` }] + }) + ); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + mcpServer.server.connect(serverTransport), + ]); + + const result = await client.request( + { method: "tools/list" }, + ListToolsResultSchema, + ); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe("test-without-meta"); + expect(result.tools[0]._meta).toBeUndefined(); + }); }); describe("resource()", () => { diff --git a/src/server/mcp.ts b/src/server/mcp.ts index fb797a8b4..ac4880c99 100644 --- a/src/server/mcp.ts +++ b/src/server/mcp.ts @@ -123,6 +123,7 @@ export class McpServer { }) as Tool["inputSchema"]) : EMPTY_OBJECT_JSON_SCHEMA, annotations: tool.annotations, + _meta: tool._meta, }; if (tool.outputSchema) { @@ -773,6 +774,7 @@ export class McpServer { inputSchema: ZodRawShape | undefined, outputSchema: ZodRawShape | undefined, annotations: ToolAnnotations | undefined, + _meta: Record | undefined, callback: ToolCallback ): RegisteredTool { const registeredTool: RegisteredTool = { @@ -783,6 +785,7 @@ export class McpServer { outputSchema: outputSchema === undefined ? undefined : z.object(outputSchema), annotations, + _meta, callback, enabled: true, disable: () => registeredTool.update({ enabled: false }), @@ -798,6 +801,7 @@ export class McpServer { if (typeof updates.paramsSchema !== "undefined") registeredTool.inputSchema = z.object(updates.paramsSchema) if (typeof updates.callback !== "undefined") registeredTool.callback = updates.callback if (typeof updates.annotations !== "undefined") registeredTool.annotations = updates.annotations + if (typeof updates._meta !== "undefined") registeredTool._meta = updates._meta if (typeof updates.enabled !== "undefined") registeredTool.enabled = updates.enabled this.sendToolListChanged() }, @@ -915,7 +919,7 @@ export class McpServer { } const callback = rest[0] as ToolCallback; - return this._createRegisteredTool(name, undefined, description, inputSchema, outputSchema, annotations, callback) + return this._createRegisteredTool(name, undefined, description, inputSchema, outputSchema, annotations, undefined, callback) } /** @@ -929,6 +933,7 @@ export class McpServer { inputSchema?: InputArgs; outputSchema?: OutputArgs; annotations?: ToolAnnotations; + _meta?: Record; }, cb: ToolCallback ): RegisteredTool { @@ -936,7 +941,7 @@ export class McpServer { throw new Error(`Tool ${name} is already registered`); } - const { title, description, inputSchema, outputSchema, annotations } = config; + const { title, description, inputSchema, outputSchema, annotations, _meta } = config; return this._createRegisteredTool( name, @@ -945,6 +950,7 @@ export class McpServer { inputSchema, outputSchema, annotations, + _meta, cb as ToolCallback ); } @@ -1173,6 +1179,7 @@ export type RegisteredTool = { inputSchema?: AnyZodObject; outputSchema?: AnyZodObject; annotations?: ToolAnnotations; + _meta?: Record; callback: ToolCallback; enabled: boolean; enable(): void; @@ -1185,6 +1192,7 @@ export type RegisteredTool = { paramsSchema?: InputArgs, outputSchema?: OutputArgs, annotations?: ToolAnnotations, + _meta?: Record, callback?: ToolCallback, enabled?: boolean }): void