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

Closed
NA
2mo ago
+55-5

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.