Skip to content

fix: prevent tool exceptions from leaking internal details to client#2198

Open
Varun6578 wants to merge 2 commits intomodelcontextprotocol:mainfrom
Varun6578:fix/tool-exception-leak
Open

fix: prevent tool exceptions from leaking internal details to client#2198
Varun6578 wants to merge 2 commits intomodelcontextprotocol:mainfrom
Varun6578:fix/tool-exception-leak

Conversation

@Varun6578
Copy link
Contributor

Summary

Fixes #698

Tool.run() and _handle_call_tool() previously exposed internal exception details (str(e)) to MCP clients. This could leak sensitive information like connection strings, file paths, or internal state.

Changes

src/mcp/server/mcpserver/tools/base.py:

  • Added except ToolError: raise to pass through intentional user-facing errors
  • Changed generic exception handler to return An unexpected error occurred executing tool {name} instead of str(e)
  • Added server-side logging of the full traceback for debugging

src/mcp/server/mcpserver/server.py:

  • Added separate except ToolError handler in _handle_call_tool() to pass through ToolError messages
  • Changed generic exception handler to return a generic message instead of f"Error calling tool {name}: {e}"

Tests updated:

  • test_server.py: Verified exception details are NOT leaked in error responses
  • test_tool_manager.py: Updated regex match for new generic message
  • test_url_elicitation_error_throw.py: Verified generic message and no detail leak
  • test_sampling_callback.py, test_list_roots_callback.py: Updated expected error messages

Security

This is a security hardening change. Before this fix, any Python exception raised inside a tool function would have its full str() representation sent to the client, potentially exposing:

  • Database connection strings
  • File system paths
  • Internal API keys or tokens
  • Stack trace information

Varun Sharma and others added 2 commits March 2, 2026 16:45
Tool.run() and _handle_call_tool() now return a generic error message
for unexpected exceptions instead of str(e), which could expose
sensitive internal details like connection strings, file paths, or
stack traces to MCP clients.

- ToolError is still passed through unchanged (intentional user-facing errors)
- UrlElicitationRequiredError and MCPError are re-raised at the server level
- All other exceptions log the full traceback server-side and return
  a generic 'An unexpected error occurred' message to the client

Fixes modelcontextprotocol#698

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add test for ToolError passthrough (covers tools/base.py except ToolError)
- Mark unreachable defensive except in _handle_call_tool as no cover

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
except ToolError as e:
return CallToolResult(content=[TextContent(type="text", text=str(e))], is_error=True)
except Exception: # pragma: no cover
logger.exception(f"Error calling tool {params.name}")
Copy link
Contributor

Choose a reason for hiding this comment

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

if we have logging here, then do we need logging in base.py? won't that cause duplicate logs?

except ToolError as e:
return CallToolResult(content=[TextContent(type="text", text=str(e))], is_error=True)
except Exception: # pragma: no cover
logger.exception(f"Error calling tool {params.name}")
Copy link
Contributor

Choose a reason for hiding this comment

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

please remove the pragma and add a unit test

@maxisbey maxisbey added bug Something isn't working P1 Significant bug affecting many users, highly requested feature labels Mar 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working P1 Significant bug affecting many users, highly requested feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tool.run should not reveal exception value to the client

2 participants