Skip to content

Commit f61d689

Browse files
DX-122122: propagate remote tool errors as ToolError (isError=True)
- Import ToolError from mcp.server.fastmcp.exceptions in mcp.py - call_tool raises ToolError when remote tools are disabled or when InvokeToolResponse.error is set, so the MCP SDK sets isError=True on the CallToolResult instead of silently reporting success - Fix E2E assertion in stremable_http_cli: check not result.isError and "error" not in structuredContent (was always-true before) - Update test_remote_tools: error paths now expect pytest.raises(ToolError) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9fe6b80 commit f61d689

3 files changed

Lines changed: 15 additions & 14 deletions

File tree

src/dremioai/servers/mcp.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend
4444
from mcp.server.auth.provider import AccessToken, TokenVerifier
4545
from mcp.server.fastmcp import FastMCP
46+
from mcp.server.fastmcp.exceptions import ToolError
4647
from mcp.server.fastmcp.prompts import Prompt
4748
from mcp.server.fastmcp.resources import FunctionResource
4849
from mcp.server.lowlevel.server import request_ctx
@@ -469,10 +470,12 @@ async def call_tool(
469470
if name in static_names:
470471
return await super().call_tool(name, arguments)
471472

472-
if not settings.instance().dremio.get("enable_remote_tools"):
473-
return {"error": f"Tool '{name}' not found (remote tools disabled)"}
473+
if not self.expose_remote_tools():
474+
raise ToolError(f"Tool '{name}' not found (remote tools not enabled)")
474475

475476
result = await self._invoke_remote_tool(name, arguments)
477+
if result.error:
478+
raise ToolError(result.error)
476479
return result.model_dump(exclude_none=True)
477480

478481
def streamable_http_app(self):

tests/servers/test_remote_tools.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from dremioai.config import settings
2424
from dremioai.config.tools import ToolType
2525
from dremioai.servers.mcp import FastMCPServerWithAuthToken
26+
from mcp.server.fastmcp.exceptions import ToolError
2627

2728

2829
def _make_server() -> FastMCPServerWithAuthToken:
@@ -161,16 +162,14 @@ async def colliding_tool() -> str:
161162

162163
@pytest.mark.asyncio
163164
async def test_call_unknown_tool_returns_error():
164-
"""Unknown remote tool → invoke_tool returns error dict."""
165+
"""Unknown remote tool → invoke_tool error propagates as ToolError (isError=True)."""
165166
server = _make_server()
166167

167168
invoke_response = InvokeToolResponse(error="tool 'nonexistent_tool' not found")
168169

169170
with patch.object(server, "_invoke_remote_tool", new=AsyncMock(return_value=invoke_response)):
170-
result = await server.call_tool("nonexistent_tool", {})
171-
172-
assert isinstance(result, dict)
173-
assert "error" in result
171+
with pytest.raises(ToolError, match="nonexistent_tool"):
172+
await server.call_tool("nonexistent_tool", {})
174173

175174

176175
@pytest.mark.asyncio
@@ -188,12 +187,10 @@ async def test_remote_tool_api_error_handled():
188187

189188

190189
@pytest.mark.asyncio
191-
async def test_remote_tools_disabled_call_returns_error_dict():
192-
"""call_tool for non-static tool when enable_remote_tools=False returns an error dict."""
190+
async def test_remote_tools_disabled_call_raises_tool_error():
191+
"""call_tool for non-static tool when remote tools disabled raises ToolError (isError=True)."""
193192
settings.set_base_settings(_make_settings(enable_remote_tools=False))
194193
server = _make_server()
195194

196-
result = await server.call_tool("any_remote_tool", {})
197-
198-
assert isinstance(result, dict)
199-
assert "error" in result
195+
with pytest.raises(ToolError):
196+
await server.call_tool("any_remote_tool", {})

tests/stremable_http_cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1219,7 +1219,8 @@ async def _run_smoketests(
12191219
pp(f"Calling remote tool '{first_remote.name}'..", end=" ")
12201220
result = await session.call_tool(first_remote.name, {})
12211221
_assert(
1222-
result.structuredContent is not None or not result.isError,
1222+
not result.isError
1223+
and "error" not in (result.structuredContent or {}),
12231224
f"Remote tool call failed: {result.content}",
12241225
)
12251226
pp("[green]OK[/green]")

0 commit comments

Comments
 (0)