Skip to content

McpError raised in tool handlers is swallowed as isError:true instead of propagating as JSON-RPC error #2770

@mengyunxie

Description

@mengyunxie

Bug

When a tool handler raises McpError, the call_tool handler in src/mcp/server/lowlevel/server.py catches it via except Exception as e
and wraps it as CallToolResult(isError=True). This loses the structured error code on the wire.

The existing UrlElicitationRequiredError (a subclass of McpError) already has a raise bypass for exactly this reason, but the parent
McpError class does not.

Note: The v2 high-level API (src/mcp/server/mcpserver/server.py on main) already handles this correctly with except MCPError: raise.
This issue is specific to the v1.x decorator-based low-level server.

Expected behavior

McpError raised in a tool handler should propagate to the transport layer's _handle_request, which converts it to a JSON-RPC error
response with the structured error code preserved — the same behavior as UrlElicitationRequiredError.

Reproduction

  from mcp.shared.exceptions import McpError
  
  @server.call_tool()
  async def handle_call_tool(name, arguments):
      raise McpError(code=-32000, message="server fault")
      # Actual: client sees CallToolResult(isError=True, content="server fault")
      # Expected: client sees JSON-RPC error with code=-32000

Proposed fix

In src/mcp/server/lowlevel/server.py, replace the tool call handler's exception chain:

  # Before:
  except UrlElicitationRequiredError:
      raise
  except Exception as e:
      return self._make_error_result(str(e))
  # After:
  except McpError:
      raise
  except Exception as e:
      return self._make_error_result(str(e))

This subsumes the existing except UrlElicitationRequiredError: raise since it's a subclass of McpError.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions