Skip to content

fix(protocol): send the original McpError message, not the prefixed one - #2805

Open
fszcd wants to merge 3 commits into
modelcontextprotocol:v1.xfrom
fszcd:fix/v1-mcperror-double-prefix
Open

fix(protocol): send the original McpError message, not the prefixed one#2805
fszcd wants to merge 3 commits into
modelcontextprotocol:v1.xfrom
fszcd:fix/v1-mcperror-double-prefix

Conversation

@fszcd

@fszcd fszcd commented Sep 13, 2026

Copy link
Copy Markdown

Root cause

McpError's constructor builds .message as MCP error <code>: <message>. When a v1 request handler throws, the protocol layer serialized error.message — prefix included — onto the wire. A v1 peer then reconstructs with new McpError(code, wireMessage), adding the prefix a second time:

client received: MCP error -32601: MCP error -32601: Unknown tool: nope

Per @pj-workspace's analysis on the issue, the wire convention is an unprefixed message: v2 servers already emit it (v2 removed the constructor prefix in #1727), and both v1 and v2 clients add the prefix when reconstructing. Only the v1 server leaks its local prefixed form. Since changing McpError.message in v1 would be breaking (callers may match on it), this PR takes the additive route.

Fix

  • McpError retains the constructor argument as a new readonly originalMessage (.message is unchanged — no breaking change).
  • The protocol's error serialization sends originalMessage when the thrown error is an McpError, so the wire carries exactly what the handler author wrote. Other error types keep the existing error.message ?? 'Internal error' behavior.

Effect on @pj-workspace's matrix: the v1→v1 row goes to 0 wire prefixes / 1 client prefix, matching v2→v2; v1→v2 stays clean.

Tests

New test/issues/test_2786_mcp_error_wire_message.test.ts runs a real ClientServer pair over InMemoryTransport, captures the raw JSON-RPC error at the transport boundary, and asserts:

  • wire error.message is Unknown tool: nope (no prefix)
  • the client-visible error is MCP error -32601: Unknown tool: nope (exactly one prefix) with the right code and originalMessage

Fails on unpatched v1.x (wire message has the prefix), passes with the fix. Full suite: 53 files / 1648 tests pass; the 2 test/client/stdio.test.ts pipe errors reproduce identically on an unmodified checkout (Windows-local, unrelated). tsgo --noEmit and eslint clean. (prettier --check . flags 227 untouched files on this Windows checkout due to autocrlf CRLF; the three changed files pass individually and are committed as LF.)

Fixes #2786

A handler that throws McpError produced a wire error whose message
already carried the local `MCP error <code>:` prefix, so a v1 peer
reconstructing it via `new McpError(code, message)` displayed the prefix
twice ("MCP error -32601: MCP error -32601: ...").

Retain the constructor argument as `McpError.originalMessage` and send
that on the wire; peers add the prefix when reconstructing, matching the
v2 behavior where the constructor no longer prefixes.

Fixes modelcontextprotocol#2786
@fszcd
fszcd requested a review from a team as a code owner September 13, 2026 06:49
@changeset-bot

changeset-bot Bot commented Sep 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 95a404d

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Sep 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@modelcontextprotocol/sdk@2805

commit: 95a404d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant