Skip to content

Commit 2f85e25

Browse files
committed
feat(server): support json schema to validate define tool parameters
1 parent 7e18c70 commit 2f85e25

File tree

6 files changed

+378
-78
lines changed

6 files changed

+378
-78
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,5 @@ out
130130

131131
.DS_Store
132132
dist/
133+
134+
.vscode

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,25 @@ server.tool(
163163
};
164164
}
165165
);
166+
167+
// Tool defined using JSON Schema
168+
server.tool({
169+
name: "count-fingers",
170+
paramsSchema: {
171+
type: "object",
172+
properties: {
173+
hands: { type: "number" }
174+
},
175+
additionalProperties: false
176+
},
177+
cb: async (args) => {
178+
return {
179+
content: [
180+
{ type: "text", text: String(args.hands * 5) }
181+
]
182+
};
183+
}
184+
})
166185
```
167186

168187
### Prompts

package-lock.json

Lines changed: 91 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"client": "tsx src/cli.ts client"
4747
},
4848
"dependencies": {
49+
"ajv": "^8.17.1",
4950
"content-type": "^1.0.5",
5051
"cors": "^2.8.5",
5152
"cross-spawn": "^7.0.3",

src/server/mcp.test.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,140 @@ describe("tool()", () => {
622622
});
623623
});
624624

625+
describe('with options argument', () => {
626+
test('should register tool with options', async () => {
627+
const mcpServer = new McpServer({
628+
name: "test server",
629+
version: "1.0",
630+
});
631+
const client = new Client({
632+
name: "test client",
633+
version: "1.0",
634+
});
635+
636+
mcpServer.tool({
637+
name: "test",
638+
description: "A tool with everything",
639+
paramsSchema: {
640+
type: "object",
641+
properties: { name: { type: "string" } },
642+
additionalProperties: false
643+
},
644+
annotations: { title: "Complete Test Tool", readOnlyHint: true, openWorldHint: false },
645+
cb: async (args) => ({
646+
// @ts-expect-error - no type inference when using a JSON Schema object
647+
content: [{ type: "text", text: `Hello, ${args.name}!` }]
648+
})
649+
});
650+
651+
const [clientTransport, serverTransport] =
652+
InMemoryTransport.createLinkedPair();
653+
654+
await Promise.all([
655+
client.connect(clientTransport),
656+
mcpServer.server.connect(serverTransport),
657+
]);
658+
659+
const result = await client.request(
660+
{ method: "tools/list" },
661+
ListToolsResultSchema,
662+
);
663+
664+
expect(result.tools).toHaveLength(1);
665+
expect(result.tools[0].name).toBe("test");
666+
expect(result.tools[0].description).toBe("A tool with everything");
667+
expect(result.tools[0].inputSchema).toMatchObject({
668+
type: "object",
669+
properties: { name: { type: "string" } }
670+
});
671+
expect(result.tools[0].annotations).toEqual({
672+
title: "Complete Test Tool",
673+
readOnlyHint: true,
674+
openWorldHint: false
675+
});
676+
});
677+
678+
test("should validate tool args", async () => {
679+
const mcpServer = new McpServer({
680+
name: "test server",
681+
version: "1.0",
682+
});
683+
684+
const client = new Client(
685+
{
686+
name: "test client",
687+
version: "1.0",
688+
},
689+
{
690+
capabilities: {
691+
tools: {},
692+
},
693+
},
694+
);
695+
696+
mcpServer.tool({
697+
name: "test",
698+
paramsSchema: {
699+
type: "object",
700+
properties: {
701+
name: { type: "string" },
702+
value: { type: "number" }
703+
},
704+
additionalProperties: false
705+
},
706+
cb: async (args) => ({
707+
content: [
708+
{
709+
type: "text",
710+
// @ts-expect-error - no type inference when using a JSON Schema object
711+
text: `${args.name}: ${args.value}`,
712+
},
713+
],
714+
}),
715+
});
716+
717+
const [clientTransport, serverTransport] =
718+
InMemoryTransport.createLinkedPair();
719+
720+
await Promise.all([
721+
client.connect(clientTransport),
722+
mcpServer.server.connect(serverTransport),
723+
]);
724+
725+
await expect(
726+
client.request(
727+
{
728+
method: "tools/call",
729+
params: {
730+
name: "test",
731+
arguments: {
732+
name: "test",
733+
value: 123,
734+
},
735+
},
736+
},
737+
CallToolResultSchema,
738+
),
739+
).resolves.toBeDefined();
740+
741+
await expect(
742+
client.request(
743+
{
744+
method: "tools/call",
745+
params: {
746+
name: "test",
747+
arguments: {
748+
name: "test",
749+
value: "not a number",
750+
},
751+
},
752+
},
753+
CallToolResultSchema,
754+
),
755+
).rejects.toThrow(/Invalid arguments/);
756+
});
757+
})
758+
625759
test("should validate tool args", async () => {
626760
const mcpServer = new McpServer({
627761
name: "test server",

0 commit comments

Comments
 (0)