feat: add _meta field support to tool definitions (#922)

Co-authored-by: David Soria Parra <167242713+dsp-ant@users.noreply.github.com>
This commit is contained in:
Khanh Nguyen 2025-09-09 09:39:52 -04:00 • committed by GitHub
commit 5dd7a2b700
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 103 additions and 2 deletions

View file

@ -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()", () => {

View file

@ -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<string, unknown> | undefined,
callback: ToolCallback<ZodRawShape | undefined>
): 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<ZodRawShape | undefined>;
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<string, unknown>;
},
cb: ToolCallback<InputArgs>
): 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<ZodRawShape | undefined>
);
}
@ -1173,6 +1179,7 @@ export type RegisteredTool = {
inputSchema?: AnyZodObject;
outputSchema?: AnyZodObject;
annotations?: ToolAnnotations;
_meta?: Record<string, unknown>;
callback: ToolCallback<undefined | ZodRawShape>;
enabled: boolean;
enable(): void;
@ -1185,6 +1192,7 @@ export type RegisteredTool = {
paramsSchema?: InputArgs,
outputSchema?: OutputArgs,
annotations?: ToolAnnotations,
_meta?: Record<string, unknown>,
callback?: ToolCallback<InputArgs>,
enabled?: boolean
}): void