Skip to content

Commit e301f4c

Browse files
bhosmer-antclaude
andcommitted
Refactor McpServer tool registration API
- Remove tool() overload that takes a config object with callback - Add new registerTool() method that separates config from callback - Extract common tool registration logic into _createRegisteredTool() helper - Update all tests to use registerTool() for tools with output schemas - Update examples to use registerTool() and high-level Client API - Add comment noting that output schema validation only works with high-level API This change improves API clarity by using a distinct method for tools with output schemas while maintaining backward compatibility. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 9e82d20 commit e301f4c

File tree

4 files changed

+199
-179
lines changed

4 files changed

+199
-179
lines changed

src/examples/server/mcpServerOutputSchema.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,28 @@ const server = new McpServer(
1616
);
1717

1818
// Define a tool with structured output - Weather data
19-
server.tool("get_weather", {
20-
description: "Get weather information for a city",
21-
inputSchema:{
22-
city: z.string().describe("City name"),
23-
country: z.string().describe("Country code (e.g., US, UK)")
24-
},
25-
outputSchema: {
26-
temperature: z.object({
27-
celsius: z.number(),
28-
fahrenheit: z.number()
29-
}),
30-
conditions: z.enum(["sunny", "cloudy", "rainy", "stormy", "snowy"]),
31-
humidity: z.number().min(0).max(100),
32-
wind: z.object({
33-
speed_kmh: z.number(),
34-
direction: z.string()
35-
})
19+
server.registerTool(
20+
"get_weather",
21+
{
22+
description: "Get weather information for a city",
23+
inputSchema:{
24+
city: z.string().describe("City name"),
25+
country: z.string().describe("Country code (e.g., US, UK)")
26+
},
27+
outputSchema: {
28+
temperature: z.object({
29+
celsius: z.number(),
30+
fahrenheit: z.number()
31+
}),
32+
conditions: z.enum(["sunny", "cloudy", "rainy", "stormy", "snowy"]),
33+
humidity: z.number().min(0).max(100),
34+
wind: z.object({
35+
speed_kmh: z.number(),
36+
direction: z.string()
37+
})
38+
},
3639
},
37-
callback: async ({ city, country }) => {
40+
async ({ city, country }) => {
3841
// Parameters are available but not used in this example
3942
void city;
4043
void country;
@@ -57,7 +60,7 @@ server.tool("get_weather", {
5760
}
5861
};
5962
}
60-
});
63+
);
6164

6265
async function main() {
6366
const transport = new StdioServerTransport();

src/examples/server/testOutputSchemaServers.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import { Client } from "../../client/index.js";
99
import { StdioClientTransport } from "../../client/stdio.js";
10-
import { ListToolsResultSchema, CallToolResultSchema } from "../../types.js";
1110

1211
async function main() {
1312
const serverPath = process.argv[2];
@@ -39,9 +38,7 @@ async function main() {
3938

4039
// List available tools
4140
console.log("Listing available tools...");
42-
const toolsResult = await client.request({
43-
method: "tools/list"
44-
}, ListToolsResultSchema);
41+
const toolsResult = await client.listTools();
4542

4643
console.log("Available tools:");
4744
for (const tool of toolsResult.tools) {
@@ -56,16 +53,15 @@ async function main() {
5653

5754
// Call the weather tool
5855
console.log("\nCalling get_weather tool...");
59-
const weatherResult = await client.request({
60-
method: "tools/call",
61-
params: {
62-
name: "get_weather",
63-
arguments: {
64-
city: "London",
65-
country: "UK"
66-
}
56+
// Note: Output schema validation only works when using the high-level Client API
57+
// methods like callTool(). Low-level protocol requests do not validate the response.
58+
const weatherResult = await client.callTool({
59+
name: "get_weather",
60+
arguments: {
61+
city: "London",
62+
country: "UK"
6763
}
68-
}, CallToolResultSchema);
64+
});
6965

7066
console.log("\nWeather tool result:");
7167
if (weatherResult.structuredContent) {

src/server/mcp.test.ts

Lines changed: 75 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -463,14 +463,14 @@ describe("tool()", () => {
463463
);
464464

465465
// new api
466-
mcpServer.tool(
466+
mcpServer.registerTool(
467467
"test (new api)",
468-
{
469-
inputSchema: { name: z.string(), value: z.number() },
470-
callback: async ({ name, value }) => ({
471-
content: [{ type: "text", text: `${name}: ${value}` }],
472-
}),
473-
}
468+
{
469+
inputSchema: { name: z.string(), value: z.number() },
470+
},
471+
async ({ name, value }) => ({
472+
content: [{ type: "text", text: `${name}: ${value}` }],
473+
})
474474
);
475475

476476
const [clientTransport, serverTransport] =
@@ -525,17 +525,20 @@ describe("tool()", () => {
525525
}));
526526

527527
// new api
528-
mcpServer.tool("test (new api)", {
529-
description: "Test description",
530-
callback: async () => ({
528+
mcpServer.registerTool(
529+
"test (new api)",
530+
{
531+
description: "Test description",
532+
},
533+
async () => ({
531534
content: [
532535
{
533536
type: "text" as const,
534537
text: "Test response",
535538
},
536539
],
537540
})
538-
});
541+
);
539542

540543

541544
const [clientTransport, serverTransport] =
@@ -582,17 +585,20 @@ describe("tool()", () => {
582585
],
583586
}));
584587

585-
mcpServer.tool("test (new api)", {
586-
annotations: { title: "Test Tool", readOnlyHint: true },
587-
callback: async () => ({
588+
mcpServer.registerTool(
589+
"test (new api)",
590+
{
591+
annotations: { title: "Test Tool", readOnlyHint: true },
592+
},
593+
async () => ({
588594
content: [
589595
{
590596
type: "text" as const,
591597
text: "Test response",
592598
},
593599
],
594-
}),
595-
});
600+
})
601+
);
596602

597603
const [clientTransport, serverTransport] =
598604
InMemoryTransport.createLinkedPair();
@@ -638,13 +644,16 @@ describe("tool()", () => {
638644
})
639645
);
640646

641-
mcpServer.tool("test (new api)", {
642-
inputSchema: { name: z.string() },
643-
annotations: { title: "Test Tool", readOnlyHint: true },
644-
callback: async ({ name }) => ({
647+
mcpServer.registerTool(
648+
"test (new api)",
649+
{
650+
inputSchema: { name: z.string() },
651+
annotations: { title: "Test Tool", readOnlyHint: true },
652+
},
653+
async ({ name }) => ({
645654
content: [{ type: "text", text: `Hello, ${name}!` }]
646655
})
647-
});
656+
);
648657

649658
const [clientTransport, serverTransport] =
650659
InMemoryTransport.createLinkedPair();
@@ -694,14 +703,17 @@ describe("tool()", () => {
694703
})
695704
);
696705

697-
mcpServer.tool("test (new api)", {
698-
description: "A tool with everything",
699-
inputSchema: { name: z.string() },
700-
annotations: { title: "Complete Test Tool", readOnlyHint: true, openWorldHint: false },
701-
callback: async ({ name }) => ({
706+
mcpServer.registerTool(
707+
"test (new api)",
708+
{
709+
description: "A tool with everything",
710+
inputSchema: { name: z.string() },
711+
annotations: { title: "Complete Test Tool", readOnlyHint: true, openWorldHint: false },
712+
},
713+
async ({ name }) => ({
702714
content: [{ type: "text", text: `Hello, ${name}!` }]
703715
})
704-
});
716+
);
705717

706718
const [clientTransport, serverTransport] =
707719
InMemoryTransport.createLinkedPair();
@@ -757,14 +769,17 @@ describe("tool()", () => {
757769
})
758770
);
759771

760-
mcpServer.tool("test (new api)", {
761-
description: "A tool with everything but empty params",
762-
inputSchema: {},
763-
annotations: { title: "Complete Test Tool with empty params", readOnlyHint: true, openWorldHint: false },
764-
callback: async () => ({
772+
mcpServer.registerTool(
773+
"test (new api)",
774+
{
775+
description: "A tool with everything but empty params",
776+
inputSchema: {},
777+
annotations: { title: "Complete Test Tool with empty params", readOnlyHint: true, openWorldHint: false },
778+
},
779+
async () => ({
765780
content: [{ type: "text" as const, text: "Test response" }]
766781
})
767-
});
782+
);
768783

769784
const [clientTransport, serverTransport] =
770785
InMemoryTransport.createLinkedPair();
@@ -834,20 +849,23 @@ describe("tool()", () => {
834849
}),
835850
);
836851

837-
mcpServer.tool("test (new api)", {
838-
inputSchema: {
839-
name: z.string(),
840-
value: z.number(),
852+
mcpServer.registerTool(
853+
"test (new api)",
854+
{
855+
inputSchema: {
856+
name: z.string(),
857+
value: z.number(),
858+
},
841859
},
842-
callback: async ({ name, value }) => ({
860+
async ({ name, value }) => ({
843861
content: [
844862
{
845863
type: "text",
846864
text: `${name}: ${value}`,
847865
},
848866
],
849-
}),
850-
});
867+
})
868+
);
851869

852870
const [clientTransport, serverTransport] =
853871
InMemoryTransport.createLinkedPair();
@@ -958,7 +976,7 @@ describe("tool()", () => {
958976
);
959977

960978
// Register a tool with outputSchema
961-
mcpServer.tool(
979+
mcpServer.registerTool(
962980
"test",
963981
{
964982
description: "Test tool with structured output",
@@ -970,14 +988,14 @@ describe("tool()", () => {
970988
resultType: z.string(),
971989
timestamp: z.string()
972990
},
973-
callback: async ({ input }) => ({
974-
structuredContent: {
975-
processedInput: input,
976-
resultType: "structured",
977-
timestamp: "2023-01-01T00:00:00Z"
978-
},
979-
}),
980991
},
992+
async ({ input }) => ({
993+
structuredContent: {
994+
processedInput: input,
995+
resultType: "structured",
996+
timestamp: "2023-01-01T00:00:00Z"
997+
},
998+
})
981999
);
9821000

9831001
const [clientTransport, serverTransport] =
@@ -1061,7 +1079,7 @@ describe("tool()", () => {
10611079
);
10621080

10631081
// Register a tool with outputSchema that returns invalid data
1064-
mcpServer.tool(
1082+
mcpServer.registerTool(
10651083
"test",
10661084
{
10671085
description: "Test tool with invalid structured output",
@@ -1073,15 +1091,15 @@ describe("tool()", () => {
10731091
resultType: z.string(),
10741092
timestamp: z.string()
10751093
},
1076-
callback: async ({ input }) => ({
1077-
structuredContent: {
1078-
processedInput: input,
1079-
resultType: "structured",
1080-
// Missing required 'timestamp' field
1081-
someExtraField: "unexpected" // Extra field not in schema
1082-
},
1083-
}),
10841094
},
1095+
async ({ input }) => ({
1096+
structuredContent: {
1097+
processedInput: input,
1098+
resultType: "structured",
1099+
// Missing required 'timestamp' field
1100+
someExtraField: "unexpected" // Extra field not in schema
1101+
},
1102+
})
10851103
);
10861104

10871105
const [clientTransport, serverTransport] =

0 commit comments

Comments
 (0)