modelcontextprotocol / modelcontextprotocol/typescript-sdk
tools/call has no path to a protocol-level "Server errors" response, even for unhandled exceptions
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.4k
- Forks
- 2.2k
- Avg merge
- 3d 15h
- Merged PRs (30d)
- 4
Description
The Tools spec defines two error mechanisms: Protocol Errors (unknown tool, malformed requests, server errors) returned as a JSON-RPC error, and Tool Execution Errors (API failures, input validation, business logic) returned as a normal result with isError: true. "Server errors" is explicitly listed under Protocol Errors.
In McpServer's CallToolRequestSchema handler (server/mcp.js), the entire tool invocation is wrapped in a single try/catch:
catch (error) {
if (error instanceof McpError) {
if (error.code === ErrorCode.UrlElicitationRequired) {
throw error; // escapes as a real protocol error
}
}
return this.createToolError(error instanceof Error ? error.message : String(error));
}
createToolError always returns { content: [...], isError: true }. Every thrown error — regardless of McpError code, regardless of whether it represents a business-level failure or a genuine unhandled/internal exception — is funneled into a Tool Execution Error. The only value that ever escapes this catch and becomes a real JSON-RPC error is McpError with code UrlElicitationRequired, and that's for an unrelated reason (the multi-round-trip input flow).
To Reproduce
server.registerTool("example", { description: "..." }, async () => {
throw new Error("something actually crashed, unrelated to tool args or business logic");
});
Calling this tool always returns isError: true over a normal (e.g. HTTP 200 on Streamable HTTP) response — never a top-level JSON-RPC error, no matter what's thrown or what code an McpError carries.
Expected behavior
Some way for an error to legitimately escape as a Protocol Error / "Server errors" response, since the spec defines that case explicitly. Right now there's no way to distinguish, at the protocol level, "the tool intentionally reported a failure" from "the handler crashed" — everything becomes isError: true.
Related: #1429 (same catch-all is why raw internal error messages can leak to the client) and #2162 / #1956 (same catch-all also suppresses a protocol-error path for invalid-argument errors specifically). This issue is about the general case — there's no way to reach the spec's "server errors" protocol-error case at all, for any kind of unhandled exception.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in server/mcp.js at McpServer's CallToolRequestSchema handler and reproduce the registered-tool failure described in the issue. Compare the handler's catch behavior with the Tools spec's Protocol Errors and Tool Execution Errors sections. Done should include a defined path for genuine unhandled server failures to produce a top-level JSON-RPC error while preserving intentional tool execution errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100