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 <willem.jiang@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
hdu_zhou 2026-11-05 21:40:32 +08:00 committed by GitHub
parent 4a53a36e32
commit 2feec9cb09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 62 additions and 1 deletions

View File

@ -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:

View File

@ -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"