Skip to content

refactor: consistent raise-based error handling in MCPServer handlers - #2252

Closed
BabyChrist666 wants to merge 1 commit into
modelcontextprotocol:mainfrom
BabyChrist666:feat/raise-based-error-handling-2153
Closed

refactor: consistent raise-based error handling in MCPServer handlers#2252
BabyChrist666 wants to merge 1 commit into
modelcontextprotocol:mainfrom
BabyChrist666:feat/raise-based-error-handling-2153

Conversation

@BabyChrist666

Copy link
Copy Markdown
Contributor

Summary

Implements the pattern described in #2153: MCPServer users should raise exceptions to signal errors, and the framework converts them to appropriate protocol responses — without leaking internal details to clients.

Changes

Error propagation — let user-raised exceptions pass through:

  • Tool.run(): ToolError and MCPError now pass through without re-wrapping (previously all exceptions were blindly wrapped as ToolError)
  • FunctionResource.read(): ResourceError and MCPError pass through
  • Prompt.render(): PromptError (new) and MCPError pass through

Handler-level error conversion — framework catches and converts:

  • _handle_call_tool(): Explicitly catches ToolErrorCallToolResult(is_error=True); unexpected exceptions get a sanitized message
  • _handle_read_resource(): Catches ResourceErrorMCPError(INVALID_PARAMS); unexpected exceptions → MCPError(INTERNAL_ERROR) with sanitized message
  • _handle_get_prompt(): Catches PromptErrorMCPError(INVALID_PARAMS); unexpected exceptions → MCPError(INTERNAL_ERROR) with sanitized message

New PromptError class for symmetry with ToolError and ResourceError.

Security improvement (#698): Unexpected exceptions no longer leak internal details (e.g., database connection strings, file paths) to the client. Only user-raised ToolError/ResourceError/PromptError messages reach the client.

User-facing behavior

@server.tool()defmy_tool(x: int) ->str:
raiseToolError("x must be positive") # → CallToolResult(is_error=True, text="x must be positive")raiseMCPError(code=..., message="...") # → JSON-RPC error responseraiseRuntimeError("secret db password") # → sanitized: "Error executing tool my_tool"@server.resource("resource://data")defmy_resource() ->str:
raiseResourceError("access denied") # → MCPError(INVALID_PARAMS, "access denied")@server.prompt()defmy_prompt(name: str) ->str:
raisePromptError("invalid context") # → MCPError(INVALID_PARAMS, "invalid context")

Test plan

  • 9 new tests covering all error paths (tool/resource/prompt × user-raised/unexpected/MCPError)
  • Updated existing tests to match sanitized error messages
  • All 315 existing mcpserver tests pass
  • Verified ToolError, ResourceError, PromptError messages reach client correctly
  • Verified unexpected exceptions do NOT leak internal details

🤖 Generated with Claude Code

Implements the pattern described in modelcontextprotocol#2153: MCPServer users should raise
exceptions and the framework converts them to appropriate protocol
responses, without leaking internal details.
Changes:
- ToolError raised by user → CallToolResult(is_error=True) with user's message
- ResourceError raised by user → MCPError (JSON-RPC error) with user's message
- PromptError (new) raised by user → MCPError (JSON-RPC error) with user's message
- MCPError → re-raised as-is for protocol-level errors
- Unexpected exceptions → sanitized message (no internal detail leakage)
Files modified:
- exceptions.py: Add PromptError class
- tools/base.py: Let ToolError/MCPError pass through Tool.run() without wrapping
- resources/types.py: Let ResourceError/MCPError pass through FunctionResource.read()
- prompts/base.py: Let PromptError/MCPError pass through Prompt.render()
- prompts/manager.py: Raise PromptError instead of ValueError for unknown prompts
- server.py: Add handler-level error handling to _handle_read_resource() and
_handle_get_prompt(), explicit ToolError catch in _handle_call_tool()
- __init__.py: Export ToolError, ResourceError, PromptError from package
Closesmodelcontextprotocol#2153
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@maxisbey

Copy link
Copy Markdown
Contributor

Thanks for the PR. Tracking this in #2198 instead. Closing as part of a general backlog cleanup following the v2 release. If this is still relevant against v2, feel free to reopen.

AI Disclaimer

Sign up for freeto 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.

2 participants

@BabyChrist666@maxisbey