Skip to content

Commit cad3ff2

Browse files
committed
added type hint
1 parent 620b72f commit cad3ff2

File tree

2 files changed

+87
-5
lines changed

2 files changed

+87
-5
lines changed

src/strands/tools/mcp/mcp_client.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from mcp.types import GetPromptResult, ListPromptsResult
2424
from mcp.types import ImageContent as MCPImageContent
2525
from mcp.types import TextContent as MCPTextContent
26-
from mcp.types import EmbeddedResource as MCPEmbeddedResource # <-- minimal add
26+
from mcp.types import EmbeddedResource as MCPEmbeddedResource
2727

2828
from ...types import PaginatedList
2929
from ...types.exceptions import MCPClientInitializationError
@@ -429,7 +429,7 @@ def _background_task(self) -> None:
429429

430430
def _map_mcp_content_to_tool_result_content(
431431
self,
432-
content: MCPTextContent | MCPImageContent | Any,
432+
content: MCPTextContent | MCPImageContent | MCPEmbeddedResource | Any,
433433
) -> Union[ToolResultContent, None]:
434434
"""Maps MCP content types to tool result content types.
435435
@@ -453,7 +453,6 @@ def _map_mcp_content_to_tool_result_content(
453453
"source": {"bytes": base64.b64decode(content.data)},
454454
}
455455
}
456-
# If EmbeddedResource
457456
elif isinstance(content, MCPEmbeddedResource):
458457
self._log_debug_with_thread("mapping MCP embedded resource content")
459458
res = getattr(content, "resource", None)
@@ -530,7 +529,12 @@ def _is_textual(mt: Optional[str]) -> bool:
530529
# Handle URI-only resources
531530
uri = _get("uri")
532531
if uri:
533-
return {"text": f"[embedded resource] {uri} ({mime_type or 'unknown mime'})"}
532+
return {
533+
"json": {
534+
"uri": uri,
535+
"mime_type": mime_type
536+
}
537+
}
534538

535539
# Make sure we return in all paths
536540
self._log_debug_with_thread("embedded resource had no usable text/blob/uri; dropping")

tests/strands/tools/mcp/test_mcp_client.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,4 +590,82 @@ def test_call_tool_sync_embedded_nested_base64_textual_mime(mock_transport, mock
590590
mock_session.call_tool.assert_called_once_with("get_file_contents", {}, None)
591591
assert result["status"] == "success"
592592
assert len(result["content"]) == 1
593-
assert result["content"][0]["text"] == '{"k":"v"}'
593+
assert result["content"][0]["text"] == '{"k":"v"}'
594+
595+
596+
def test_call_tool_sync_embedded_image_blob(mock_transport, mock_session):
597+
"""EmbeddedResource.resource (blob with image MIME) should map to image content."""
598+
import base64
599+
# Create a simple 1x1 PNG image
600+
png_data = base64.b64decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==")
601+
payload = base64.b64encode(png_data).decode()
602+
603+
er = {
604+
"type": "resource",
605+
"resource": {
606+
"uri": "mcp://resource/embedded-image",
607+
"blob": payload,
608+
"mimeType": "image/png",
609+
},
610+
}
611+
mock_session.call_tool.return_value = MCPCallToolResult(isError=False, content=[er])
612+
613+
from strands.tools.mcp import MCPClient
614+
with MCPClient(mock_transport["transport_callable"]) as client:
615+
result = client.call_tool_sync(tool_use_id="er-image", name="get_file_contents", arguments={})
616+
617+
mock_session.call_tool.assert_called_once_with("get_file_contents", {}, None)
618+
assert result["status"] == "success"
619+
assert len(result["content"]) == 1
620+
assert "image" in result["content"][0]
621+
assert result["content"][0]["image"]["format"] == "png"
622+
assert "bytes" in result["content"][0]["image"]["source"]
623+
624+
625+
def test_call_tool_sync_embedded_non_textual_blob_dropped(mock_transport, mock_session):
626+
"""EmbeddedResource.resource (blob with non-textual/unknown MIME) should be dropped."""
627+
payload = base64.b64encode(b'\x00\x01\x02\x03').decode()
628+
629+
er = {
630+
"type": "resource",
631+
"resource": {
632+
"uri": "mcp://resource/embedded-binary",
633+
"blob": payload,
634+
"mimeType": "application/octet-stream",
635+
},
636+
}
637+
mock_session.call_tool.return_value = MCPCallToolResult(isError=False, content=[er])
638+
639+
from strands.tools.mcp import MCPClient
640+
with MCPClient(mock_transport["transport_callable"]) as client:
641+
result = client.call_tool_sync(tool_use_id="er-binary", name="get_file_contents", arguments={})
642+
643+
mock_session.call_tool.assert_called_once_with("get_file_contents", {}, None)
644+
assert result["status"] == "success"
645+
assert len(result["content"]) == 0 # Content should be dropped
646+
647+
648+
def test_call_tool_sync_embedded_multiple_textual_mimes(mock_transport, mock_session):
649+
"""EmbeddedResource with different textual MIME types should decode to text."""
650+
import base64
651+
652+
# Test YAML content
653+
yaml_content = base64.b64encode(b'key: value\nlist:\n - item1\n - item2').decode()
654+
er = {
655+
"type": "resource",
656+
"resource": {
657+
"uri": "mcp://resource/embedded-yaml",
658+
"blob": yaml_content,
659+
"mimeType": "application/yaml",
660+
},
661+
}
662+
mock_session.call_tool.return_value = MCPCallToolResult(isError=False, content=[er])
663+
664+
from strands.tools.mcp import MCPClient
665+
with MCPClient(mock_transport["transport_callable"]) as client:
666+
result = client.call_tool_sync(tool_use_id="er-yaml", name="get_file_contents", arguments={})
667+
668+
mock_session.call_tool.assert_called_once_with("get_file_contents", {}, None)
669+
assert result["status"] == "success"
670+
assert len(result["content"]) == 1
671+
assert "key: value" in result["content"][0]["text"]

0 commit comments

Comments
 (0)