tools/call handler
# grade this stage (from your course directory)
npx theinference test --stage tools-call-handler --watchinitialize and tools/list are conversation. tools/call is where work actually happens — the client names a tool, hands you arguments, and expects you to run it and report back. This is the method your Chapter 9 client spent a whole step learning to send; now you answer it.
The subtlety is that there are two different kinds of failure, and MCP draws a sharp line between them. A tool that runs but fails — bad input, a missing file, an unknown tool name — is a successful JSON-RPC response carrying isError: true inside its result. The model is meant to see that text and recover. A request you can't even route — an unknown method — is a protocol failure, and that gets a JSON-RPC error frame instead. Same wire, two layers; conflating them is the classic MCP-server bug.
tools/call, unknown TOOL → result: { content:[{type:"text", text:"unknown tool: nope"}], isError:true }
(a normal response — the model reads it and recovers)
unknown METHOD → error: { code:-32601, message:"Method not found" }
(a JSON-RPC error frame — the request couldn't be routed)
What to build
Handle tools/call and unknown methods
Extend handle(request) in src/mcp.ts to cover tools/call and to reject unknown methods.
tools/call→ readparams.nameandparams.arguments. If the named tool isn't one you declared, return aresultwithisError: trueandcontent: [{ type: 'text', text: 'unknown tool: ' + name }]— a successful response carrying a tool-level error. If the tool exists, execute it and returncontent: [{ type: 'text', text: <output> }]withisError: false. For theechotool the output is`${name}(${JSON.stringify(args)})`— e.g.echo({"x":1}).- Unknown method (anything that isn't
initialize,tools/list, ortools/call) → return a JSON-RPC error frame:{ jsonrpc: '2.0', id: req.id, error: { code: -32601, message: 'Method not found' } }. Noteerror, notresult.
Validate before you execute: a missing tool is a tool error, not a thrown exception. Graded by program.ts mcp-server.
// inside handle(req), after the initialize / tools/list cases:
if (req.method === 'tools/call') {
const { name, arguments: args } = req.params as { name: string; arguments: Record<string, unknown> }
const tool = this.tools.find(t => t.name === name)
if (!tool) {
// result: { content: [{ type:'text', text:`unknown tool: ${name}` }], isError: true }
}
// const text = `${name}(${JSON.stringify(args)})`
// result: { content: [{ type:'text', text }], isError: false }
}
// fell through every known method -> JSON-RPC error frame:
// { jsonrpc:'2.0', id: req.id, error: { code:-32601, message:'Method not found' } }
An unknown tool is not a broken request — the client asked a well-formed tools/call, you just couldn't satisfy it. So it's a normal JSON-RPC response (result, matching id) that happens to set isError: true. That's deliberate: the host threads the content text straight back to the model as a tool_result, and the model reads unknown tool: nope and tries something else. Demote it to a protocol error frame and the model never sees the message — it just sees the call vanish.
-32601 Method not found is JSON-RPC's code for I don't know this method. Use it only for the method you can't route — never for a tool that ran and failed. The grader sends an unknown tool (nope) expecting an isError: true result, and the spec reserves the error frame for things like an unknown method. Returning an error frame for the unknown tool — or a result for a genuinely unknown method — is the mismatch this step is built to catch.
Process mode. program.ts mcp-server builds the server from serverTools (one echo tool) and runs case.json's two tools/call requests: a valid echo({ x: 1 }) and a call to a nonexistent nope. It prints { responses: [...] }, compared to fixtures/expected.json exactly: id 1 → result: { content:[{type:'text', text:'echo({"x":1})'}], isError:false }, and id 2 → result: { content:[{type:'text', text:'unknown tool: nope'}], isError:true }. Both are result responses here — the error-frame path is exercised by the unknown-method case behind the scenes.