Skip to content

Commit a475afc

Browse files
author
Mengyun Xie
committed
fix: propagate McpError from tool handlers as JSON-RPC error
1 parent 6213787 commit a475afc

3 files changed

Lines changed: 25 additions & 8 deletions

File tree

src/mcp/server/fastmcp/tools/base.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from mcp.server.fastmcp.exceptions import ToolError
1212
from mcp.server.fastmcp.utilities.context_injection import find_context_parameter
1313
from mcp.server.fastmcp.utilities.func_metadata import FuncMetadata, func_metadata
14-
from mcp.shared.exceptions import UrlElicitationRequiredError
14+
from mcp.shared.exceptions import McpError
1515
from mcp.shared.tool_name_validation import validate_and_warn_tool_name
1616
from mcp.types import Icon, ToolAnnotations
1717

@@ -109,9 +109,7 @@ async def run(
109109
result = self.fn_metadata.convert_result(result)
110110

111111
return result
112-
except UrlElicitationRequiredError:
113-
# Re-raise UrlElicitationRequiredError so it can be properly handled
114-
# as an MCP error response with code -32042
112+
except McpError:
115113
raise
116114
except Exception as e:
117115
raise ToolError(f"Error executing tool {self.name}: {e}") from e

src/mcp/server/lowlevel/server.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ async def main():
9090
from mcp.server.models import InitializationOptions
9191
from mcp.server.session import ServerSession
9292
from mcp.shared.context import RequestContext
93-
from mcp.shared.exceptions import McpError, UrlElicitationRequiredError
93+
from mcp.shared.exceptions import McpError
9494
from mcp.shared.message import ServerMessageMetadata, SessionMessage
9595
from mcp.shared.session import RequestResponder
9696
from mcp.shared.tool_name_validation import validate_and_warn_tool_name
@@ -576,9 +576,7 @@ async def handler(req: types.CallToolRequest):
576576
isError=False,
577577
)
578578
)
579-
except UrlElicitationRequiredError:
580-
# Re-raise UrlElicitationRequiredError so it can be properly handled
581-
# by _handle_request, which converts it to an error response with code -32042
579+
except McpError:
582580
raise
583581
except Exception as e:
584582
return self._make_error_result(str(e))

tests/server/fastmcp/test_url_elicitation_error_throw.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,24 @@ async def failing_tool(ctx: Context[ServerSession, None]) -> str:
111111
assert len(result.content) == 1
112112
assert isinstance(result.content[0], types.TextContent)
113113
assert "Something went wrong" in result.content[0].text
114+
115+
116+
@pytest.mark.anyio
117+
async def test_mcp_error_in_tool_handler_propagates_as_jsonrpc_error():
118+
"""Test that McpError raised from a tool is received as a JSON-RPC error, not isError:true."""
119+
mcp = FastMCP(name="McpErrorServer")
120+
121+
@mcp.tool(description="A tool that raises McpError")
122+
async def server_fault_tool(ctx: Context[ServerSession, None]) -> str:
123+
raise McpError(types.ErrorData(code=-32000, message="server fault"))
124+
125+
async with create_connected_server_and_client_session(mcp._mcp_server) as client_session:
126+
await client_session.initialize()
127+
128+
# McpError should propagate as a JSON-RPC error, not CallToolResult(isError=True)
129+
with pytest.raises(McpError) as exc_info:
130+
await client_session.call_tool("server_fault_tool", {})
131+
132+
error = exc_info.value.error
133+
assert error.code == -32000
134+
assert error.message == "server fault"

0 commit comments

Comments
 (0)