diff --git a/src/mcp/server/mcpserver/tools/base.py b/src/mcp/server/mcpserver/tools/base.py index 754313eb8a..4851eefb3e 100644 --- a/src/mcp/server/mcpserver/tools/base.py +++ b/src/mcp/server/mcpserver/tools/base.py @@ -10,7 +10,7 @@ from mcp.server.mcpserver.utilities.context_injection import find_context_parameter from mcp.server.mcpserver.utilities.func_metadata import FuncMetadata, func_metadata from mcp.shared._callable_inspection import is_async_callable -from mcp.shared.exceptions import UrlElicitationRequiredError +from mcp.shared.exceptions import MCPError from mcp.shared.tool_name_validation import validate_and_warn_tool_name from mcp.types import Icon, ToolAnnotations @@ -111,9 +111,7 @@ async def run( result = self.fn_metadata.convert_result(result) return result - except UrlElicitationRequiredError: - # Re-raise UrlElicitationRequiredError so it can be properly handled - # as an MCP error response with code -32042 + except MCPError: raise except Exception as e: raise ToolError(f"Error executing tool {self.name}: {e}") from e diff --git a/tests/server/mcpserver/test_url_elicitation_error_throw.py b/tests/server/mcpserver/test_url_elicitation_error_throw.py index 1f45fd60f0..9d42350257 100644 --- a/tests/server/mcpserver/test_url_elicitation_error_throw.py +++ b/tests/server/mcpserver/test_url_elicitation_error_throw.py @@ -90,6 +90,23 @@ async def multi_auth(ctx: Context) -> str: assert url_error.elicitations[1].elicitation_id == "gdrive-auth" +@pytest.mark.anyio +async def test_mcp_error_propagates_as_json_rpc_error(): + """Test that MCPError raised from a tool propagates as a JSON-RPC error, not isError result.""" + mcp = MCPServer(name="McpErrorServer") + + @mcp.tool(description="A tool that raises a plain MCPError") + async def failing_tool(ctx: Context) -> str: + raise MCPError(-32000, "custom MCP error") + + async with Client(mcp) as client: + with pytest.raises(MCPError) as exc_info: + await client.call_tool("failing_tool", {}) + + assert exc_info.value.error.code == -32000 + assert exc_info.value.error.message == "custom MCP error" + + @pytest.mark.anyio async def test_normal_exceptions_still_return_error_result(): """Test that normal exceptions still return CallToolResult with is_error=True."""