Skip to content

Commit efd901a

Browse files
committed
Non-existing tool, disabled tool and tool inputSchema validation return CallToolResult instead of a protocol-level error
1 parent 7098bff commit efd901a

File tree

2 files changed

+101
-77
lines changed

2 files changed

+101
-77
lines changed

src/server/mcp.test.ts

Lines changed: 60 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -897,37 +897,53 @@ describe('tool()', () => {
897897

898898
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
899899

900-
await expect(
901-
client.request(
902-
{
903-
method: 'tools/call',
904-
params: {
900+
const result = await client.request(
901+
{
902+
method: 'tools/call',
903+
params: {
904+
name: 'test',
905+
arguments: {
905906
name: 'test',
906-
arguments: {
907-
name: 'test',
908-
value: 'not a number'
909-
}
907+
value: 'not a number'
910908
}
911-
},
912-
CallToolResultSchema
913-
)
914-
).rejects.toThrow(/Invalid arguments/);
909+
}
910+
},
911+
CallToolResultSchema
912+
);
915913

916-
await expect(
917-
client.request(
914+
expect(result.isError).toBe(true);
915+
expect(result.content).toEqual(
916+
expect.arrayContaining([
918917
{
919-
method: 'tools/call',
920-
params: {
921-
name: 'test (new api)',
922-
arguments: {
923-
name: 'test',
924-
value: 'not a number'
925-
}
918+
type: 'text',
919+
text: expect.stringContaining('Invalid arguments')
920+
}
921+
])
922+
);
923+
924+
const result2 = await client.request(
925+
{
926+
method: 'tools/call',
927+
params: {
928+
name: 'test (new api)',
929+
arguments: {
930+
name: 'test',
931+
value: 'not a number'
926932
}
927-
},
928-
CallToolResultSchema
929-
)
930-
).rejects.toThrow(/Invalid arguments/);
933+
}
934+
},
935+
CallToolResultSchema
936+
);
937+
938+
expect(result2.isError).toBe(true);
939+
expect(result2.content).toEqual(
940+
expect.arrayContaining([
941+
{
942+
type: 'text',
943+
text: expect.stringContaining('Invalid arguments')
944+
}
945+
])
946+
);
931947
});
932948

933949
/***
@@ -1518,17 +1534,25 @@ describe('tool()', () => {
15181534

15191535
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
15201536

1521-
await expect(
1522-
client.request(
1537+
const result = await client.request(
1538+
{
1539+
method: 'tools/call',
1540+
params: {
1541+
name: 'nonexistent-tool'
1542+
}
1543+
},
1544+
CallToolResultSchema
1545+
);
1546+
1547+
expect(result.isError).toBe(true);
1548+
expect(result.content).toEqual(
1549+
expect.arrayContaining([
15231550
{
1524-
method: 'tools/call',
1525-
params: {
1526-
name: 'nonexistent-tool'
1527-
}
1528-
},
1529-
CallToolResultSchema
1530-
)
1531-
).rejects.toThrow(/Tool nonexistent-tool not found/);
1551+
type: 'text',
1552+
text: expect.stringContaining('Tool nonexistent-tool not found')
1553+
}
1554+
])
1555+
);
15321556
});
15331557

15341558
/***

src/server/mcp.ts

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -126,55 +126,37 @@ export class McpServer {
126126

127127
this.server.setRequestHandler(CallToolRequestSchema, async (request, extra): Promise<CallToolResult> => {
128128
const tool = this._registeredTools[request.params.name];
129-
if (!tool) {
130-
throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
131-
}
132-
133-
if (!tool.enabled) {
134-
throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} disabled`);
135-
}
136129

137130
let result: CallToolResult;
138131

139-
if (tool.inputSchema) {
140-
const parseResult = await tool.inputSchema.safeParseAsync(request.params.arguments);
141-
if (!parseResult.success) {
142-
throw new McpError(
143-
ErrorCode.InvalidParams,
144-
`Invalid arguments for tool ${request.params.name}: ${parseResult.error.message}`
145-
);
132+
try {
133+
if (!tool) {
134+
throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
146135
}
147136

148-
const args = parseResult.data;
149-
const cb = tool.callback as ToolCallback<ZodRawShape>;
150-
try {
151-
result = await Promise.resolve(cb(args, extra));
152-
} catch (error) {
153-
result = {
154-
content: [
155-
{
156-
type: 'text',
157-
text: error instanceof Error ? error.message : String(error)
158-
}
159-
],
160-
isError: true
161-
};
137+
if (!tool.enabled) {
138+
throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} disabled`);
162139
}
163-
} else {
164-
const cb = tool.callback as ToolCallback<undefined>;
165-
try {
140+
141+
if (tool.inputSchema) {
142+
const cb = tool.callback as ToolCallback<ZodRawShape>;
143+
const parseResult = await tool.inputSchema.safeParseAsync(request.params.arguments);
144+
if (!parseResult.success) {
145+
throw new McpError(
146+
ErrorCode.InvalidParams,
147+
`Invalid arguments for tool ${request.params.name}: ${parseResult.error.message}`
148+
);
149+
}
150+
151+
const args = parseResult.data;
152+
153+
result = await Promise.resolve(cb(args, extra));
154+
} else {
155+
const cb = tool.callback as ToolCallback<undefined>;
166156
result = await Promise.resolve(cb(extra));
167-
} catch (error) {
168-
result = {
169-
content: [
170-
{
171-
type: 'text',
172-
text: error instanceof Error ? error.message : String(error)
173-
}
174-
],
175-
isError: true
176-
};
177157
}
158+
} catch (error) {
159+
return this.createToolError(error instanceof Error ? error.message : String(error));
178160
}
179161

180162
if (tool.outputSchema && !result.isError) {
@@ -201,6 +183,24 @@ export class McpServer {
201183
this._toolHandlersInitialized = true;
202184
}
203185

186+
/**
187+
* Creates a tool error result.
188+
*
189+
* @param errorMessage - The error message.
190+
* @returns The tool error result.
191+
*/
192+
private createToolError(errorMessage: string): CallToolResult {
193+
return {
194+
content: [
195+
{
196+
type: 'text',
197+
text: errorMessage
198+
}
199+
],
200+
isError: true
201+
};
202+
}
203+
204204
private _completionHandlerInitialized = false;
205205

206206
private setCompletionRequestHandler() {

0 commit comments

Comments
 (0)