X Tutup
Skip to content

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

Open
BabyChrist666 wants to merge 1 commit intomodelcontextprotocol:mainfrom
BabyChrist666:feat/raise-based-error-handling-2153
Open

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

Conversation

@BabyChrist666
Copy link
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()
def my_tool(x: int) -> str:
    raise ToolError("x must be positive")     # → CallToolResult(is_error=True, text="x must be positive")
    raise MCPError(code=..., message="...")    # → JSON-RPC error response
    raise RuntimeError("secret db password")  # → sanitized: "Error executing tool my_tool"

@server.resource("resource://data")
def my_resource() -> str:
    raise ResourceError("access denied")      # → MCPError(INVALID_PARAMS, "access denied")

@server.prompt()
def my_prompt(name: str) -> str:
    raise PromptError("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

Closes modelcontextprotocol#2153

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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

X Tutup