Skip to content

Commit 4c8d27c

Browse files
committed
Python: Decode base64 image, audio and blob content from MCP servers
1 parent d41312f commit 4c8d27c

2 files changed

Lines changed: 44 additions & 2 deletions

File tree

‎python/semantic_kernel/connectors/mcp.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,13 @@ def _mcp_content_types_to_kernel_content(
111111
if isinstance(mcp_type, types.TextContent):
112112
return [TextContent(text=mcp_type.text, inner_content=mcp_type)]
113113
if isinstance(mcp_type, types.ImageContent):
114-
return [ImageContent(data=mcp_type.data, mime_type=mcp_type.mimeType, inner_content=mcp_type)]
114+
return [
115+
ImageContent(data=mcp_type.data, data_format="base64", mime_type=mcp_type.mimeType, inner_content=mcp_type)
116+
]
115117
if isinstance(mcp_type, types.AudioContent):
116-
return [AudioContent(data=mcp_type.data, mime_type=mcp_type.mimeType, inner_content=mcp_type)]
118+
return [
119+
AudioContent(data=mcp_type.data, data_format="base64", mime_type=mcp_type.mimeType, inner_content=mcp_type)
120+
]
117121
if isinstance(mcp_type, types.ResourceLink):
118122
return [
119123
BinaryContent(
@@ -147,6 +151,8 @@ def _mcp_content_types_to_kernel_content(
147151
return [
148152
BinaryContent(
149153
data=mcp_type.resource.blob,
154+
data_format="base64",
155+
mime_type=mcp_type.resource.mimeType,
150156
inner_content=mcp_type,
151157
metadata=mcp_type.annotations.model_dump() if mcp_type.annotations else {},
152158
)

‎python/tests/unit/connectors/mcp/test_mcp.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,3 +1004,39 @@ async def test_mcp_prompt_optional_arguments_are_not_required():
10041004
result = await kernel.invoke(function, code="x = 1")
10051005
assert str(result) == "review this"
10061006
plugin.session.get_prompt.assert_awaited_once_with("review", arguments={"code": "x = 1"})
1007+
1008+
1009+
PNG_BYTES = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"
1010+
PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUg=="
1011+
1012+
1013+
@pytest.mark.parametrize(
1014+
"mcp_content,mime_type",
1015+
[
1016+
(types.ImageContent(type="image", data=PNG_BASE64, mimeType="image/png"), "image/png"),
1017+
(types.AudioContent(type="audio", data=PNG_BASE64, mimeType="audio/wav"), "audio/wav"),
1018+
(
1019+
types.EmbeddedResource(
1020+
type="resource",
1021+
resource=types.BlobResourceContents(uri="file:///image.png", blob=PNG_BASE64, mimeType="image/png"),
1022+
),
1023+
"image/png",
1024+
),
1025+
],
1026+
ids=["image", "audio", "blob_resource"],
1027+
)
1028+
async def test_mcp_base64_content_is_decoded(mcp_content, mime_type):
1029+
plugin = MCPStdioPlugin(name="test", command="echo")
1030+
plugin.session = AsyncMock(spec=ClientSession)
1031+
plugin.session.call_tool.return_value = types.CallToolResult(content=[mcp_content])
1032+
plugin.session.get_prompt.return_value = types.GetPromptResult(
1033+
messages=[types.PromptMessage(role="user", content=mcp_content)]
1034+
)
1035+
1036+
[tool_content] = await plugin.call_tool("tool")
1037+
[prompt_message] = await plugin.get_prompt("prompt")
1038+
1039+
for content in (tool_content, prompt_message.items[0]):
1040+
assert content.data == PNG_BYTES
1041+
assert content.mime_type == mime_type
1042+
assert content.data_uri == f"data:{mime_type};base64,{PNG_BASE64}"

0 commit comments

Comments
 (0)