Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/strands/tools/mcp/mcp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,8 @@ def _handle_tool_result(self, tool_use_id: str, call_tool_result: MCPCallToolRes
content=mapped_contents,
)

if call_tool_result.isError:
result["isError"] = True
if call_tool_result.structuredContent:
result["structuredContent"] = call_tool_result.structuredContent
if call_tool_result.meta:
Expand Down
5 changes: 5 additions & 0 deletions src/strands/tools/mcp/mcp_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ class MCPToolResult(ToolResult):
metadata: Optional arbitrary metadata returned by the MCP tool. This field allows
MCP servers to attach custom metadata to tool results (e.g., token usage,
performance metrics, or business-specific tracking information).
isError: Whether the MCP tool itself reported an application-level error.
True means the tool executed but returned a failure result (application error).
Absent means the call succeeded or that an exception was raised before the
tool could return a result (protocol or transport error).
"""

structuredContent: NotRequired[dict[str, Any]]
metadata: NotRequired[dict[str, Any]]
isError: NotRequired[bool]
15 changes: 15 additions & 0 deletions tests/strands/tools/mcp/test_mcp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,21 @@ def test_call_tool_sync_with_structured_content(mock_transport, mock_session):
assert result["structuredContent"]["status"] == "completed"


@pytest.mark.parametrize("is_error,expect_is_error_field", [(True, True), (False, False)])
def test_call_tool_sync_preserves_is_error(mock_transport, mock_session, is_error, expect_is_error_field):
"""Test that call_tool_sync exposes CallToolResult.isError in MCPToolResult."""
mock_content = MCPTextContent(type="text", text="Tool output")
mock_session.call_tool.return_value = MCPCallToolResult(isError=is_error, content=[mock_content])

with MCPClient(mock_transport["transport_callable"]) as client:
result = client.call_tool_sync(tool_use_id="test-123", name="test_tool", arguments={})

if expect_is_error_field:
assert result.get("isError") is True
else:
assert "isError" not in result


def test_call_tool_sync_exception(mock_transport, mock_session):
"""Test that call_tool_sync correctly handles exceptions."""
mock_session.call_tool.side_effect = Exception("Test exception")
Expand Down