From 2feec9cb09c0d5a092d7b8b82f711c6ffcb86b2b Mon Sep 17 00:00:00 2001 From: hdu_zhou <94913492+zuizhuai@users.noreply.github.com> Date: Thu, 5 Nov 2026 21:40:32 +0800 Subject: [PATCH] fix(mcp): degrade a blob without mimeType instead of failing the tool call (#7800) * fix(mcp): degrade a blob without mimeType instead of failing the tool call A resource link or embedded resource without mimeType made the blob guard raise, so the whole tool call failed instead of degrading the attachment. Keep the guard for genuinely unusable payloads and let a missing media type fall through to the caller's default. * Modify test for text blocks and mime type validation Update test to check for text blocks and their mime types. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * test(mcp): remove MIME assertion from text content block --------- Co-authored-by: Willem Jiang Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../packages/harness/deerflow/mcp/tools.py | 12 ++++- .../tests/test_mcp_blob_missing_mime_type.py | 51 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 backend/tests/test_mcp_blob_missing_mime_type.py diff --git a/backend/packages/harness/deerflow/mcp/tools.py b/backend/packages/harness/deerflow/mcp/tools.py index 19f5ffcec5..b77ef85129 100644 --- a/backend/packages/harness/deerflow/mcp/tools.py +++ b/backend/packages/harness/deerflow/mcp/tools.py @@ -604,7 +604,17 @@ def _convert_call_tool_result( if mime and mime.startswith("image/"): lc_content.append(create_image_block(base64=res.blob, mime_type=mime)) else: - lc_content.append(create_file_block(base64=res.blob, mime_type=mime)) + # ``BlobResourceContents.mimeType`` is optional in the MCP spec, but + # langchain-core refuses a base64 block without one. Degrade to a + # generic binary type instead of raising, so the rest of the result — + # including the text blocks the server returned — survives. The + # sibling ``ResourceLink`` branch already degrades the same way. + lc_content.append( + create_file_block( + base64=res.blob, + mime_type=mime or "application/octet-stream", + ) + ) else: lc_content.append(create_text_block(text=str(res))) else: diff --git a/backend/tests/test_mcp_blob_missing_mime_type.py b/backend/tests/test_mcp_blob_missing_mime_type.py new file mode 100644 index 0000000000..b1c0aa5d88 --- /dev/null +++ b/backend/tests/test_mcp_blob_missing_mime_type.py @@ -0,0 +1,51 @@ +"""An MCP blob without a mimeType must degrade, not fail the whole tool call.""" + +import base64 + +from mcp.types import BlobResourceContents, CallToolResult, EmbeddedResource, TextContent + +from deerflow.mcp.tools import _convert_call_tool_result + +BLOB = base64.b64encode(b"binary payload").decode("ascii") + + +def _result(mime_type: str | None, *, with_text: bool = True) -> CallToolResult: + content = [TextContent(type="text", text="here is your report")] if with_text else [] + content.append( + EmbeddedResource( + type="resource", + resource=BlobResourceContents(uri="file:///tmp/report.bin", blob=BLOB, mimeType=mime_type), + ) + ) + return CallToolResult(content=content, isError=False) + + +def test_blob_without_mime_type_degrades_to_octet_stream(): + lc_content, _artifact = _convert_call_tool_result(_result(None)) + + assert [block["type"] for block in lc_content] == ["text", "file"] + assert lc_content[0]["text"] == "here is your report" + assert lc_content[1]["mime_type"] == "application/octet-stream" + assert lc_content[1]["base64"] == BLOB + + +def test_text_blocks_survive_the_fallback(): + lc_content, _artifact = _convert_call_tool_result(_result(None)) + + assert [block["type"] for block in lc_content] == ["text", "file"] + assert lc_content[0]["text"] == "here is your report" + assert lc_content[1]["mime_type"] == "application/octet-stream" + + +def test_declared_non_image_mime_type_is_preserved(): + lc_content, _artifact = _convert_call_tool_result(_result("application/pdf")) + + assert lc_content[1]["type"] == "file" + assert lc_content[1]["mime_type"] == "application/pdf" + + +def test_declared_image_mime_type_still_becomes_an_image_block(): + lc_content, _artifact = _convert_call_tool_result(_result("image/png")) + + assert [block["type"] for block in lc_content] == ["text", "image"] + assert lc_content[1]["mime_type"] == "image/png"