Skip to content

fix: propagate McpError as JSON-RPC error response in lowlevel server#3096

Open
nankingjing wants to merge 1 commit into
modelcontextprotocol:v1.xfrom
nankingjing:fix/mcperror-jsonrpc-propagation
Open

fix: propagate McpError as JSON-RPC error response in lowlevel server#3096
nankingjing wants to merge 1 commit into
modelcontextprotocol:v1.xfrom
nankingjing:fix/mcperror-jsonrpc-propagation

Conversation

@nankingjing

Copy link
Copy Markdown

Fixes #2770

Motivation and Context

When a tool handler registered via the lowlevel server's @server.call_tool() decorator raises McpError, the handler catches it via the generic except Exception block and wraps it as CallToolResult(isError=True). This drops the structured error code on the wire, so clients cannot distinguish error types without parsing the message string.

The UrlElicitationRequiredError subclass already has a re-raise bypass for exactly this reason, but the parent McpError class does not. (The v2 high-level API on main already handles this correctly.)

How Has This Been Tested?

  • Added a regression test asserting that McpError raised in a lowlevel tool handler reaches the client as a JSON-RPC error with the original code, message, and data preserved (it fails without this change), plus a companion test asserting non-McpError exceptions still return CallToolResult(isError=True).
  • Ran the lowlevel server and elicitation test suites locally: all passing.

Breaking Changes

Tool handlers that deliberately raise McpError now produce a JSON-RPC error response (the documented behavior for the error, matching UrlElicitationRequiredError and the v2 API) instead of an isError: true result. Handlers that raise other exceptions are unaffected.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

This scopes the fix to the lowlevel server per the issue's proposed fix. #2772 covers related ground but also changes FastMCP Tool.run() behavior; this PR is the minimal lowlevel-only change with regression tests at the lowlevel layer, in case the maintainers prefer to take that separately.

When a tool handler raises McpError, the call_tool decorator handler
caught it via the generic `except Exception` block and wrapped it as
CallToolResult(isError=True), dropping the structured error code on
the wire. The UrlElicitationRequiredError subclass already had a
re-raise bypass for exactly this reason, but the parent McpError class
did not.

Broaden the re-raise guard from UrlElicitationRequiredError to McpError
so any McpError propagates to _handle_request, which converts it to a
JSON-RPC error response preserving the original error code, message,
and data. This subsumes the existing UrlElicitationRequiredError bypass
since it is a subclass of McpError.

Github-Issue:modelcontextprotocol#2770
Reported-by:mengyunxie

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Re-trigger cubic

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