Skip to content

fix(mcp): handle unsupported image and blob mime types in tool results#2218

Open
gautamsirdeshmukh wants to merge 1 commit into
strands-agents:mainfrom
gautamsirdeshmukh:feat/mcp-multimodal-fallbacks
Open

fix(mcp): handle unsupported image and blob mime types in tool results#2218
gautamsirdeshmukh wants to merge 1 commit into
strands-agents:mainfrom
gautamsirdeshmukh:feat/mcp-multimodal-fallbacks

Conversation

@gautamsirdeshmukh
Copy link
Copy Markdown
Contributor

Description

When an MCP server returns an image with an unsupported mime type (e.g. image/bmp), the SDK crashes with a KeyError. This PR falls back to returning the content as JSON with a warning instead — matching the TypeScript SDK's behavior. Also applies the same JSON fallback to blob resources with unsupported mime types, which were previously silently dropped.

Related Issues

Achieves parity with strands-agents/sdk-typescript#865

Type of Change

Bug fix

Testing

How have you tested the change? Verify that the changes do not break functionality or introduce warnings in consuming repositories: agents-docs, agents-tools, agents-cli

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread src/strands/tools/mcp/mcp_client.py Outdated
Comment thread tests/strands/tools/mcp/test_mcp_client.py
@github-actions
Copy link
Copy Markdown
Contributor

Assessment: Comment

Solid bug fix — the change from crashing/silently dropping content to a graceful JSON fallback with warnings is the right approach, and TypeScript SDK parity is a good motivator. Two minor items flagged inline.

Review Details
  • Logging consistency: One of the three new warning log lines is missing structured context fields per the project style guide (field=<value> | message). The other two are formatted correctly.
  • Test thoroughness: The blob decode failure test is less thorough than its sibling tests — it only checks for "json" key presence without verifying the payload shape is preserved in the fallback.

@github-actions
Copy link
Copy Markdown
Contributor

Assessment: Approve

Both items from the previous review have been addressed. The logging now consistently follows the structured format (uri=<%s> | ...), and the decode failure test asserts the full JSON payload shape. The code is clean, well-tested, and the fallback behavior is a clear improvement over crashing/silently dropping content.

Comment thread src/strands/tools/mcp/mcp_client.py
@gautamsirdeshmukh gautamsirdeshmukh enabled auto-merge (squash) April 28, 2026 18:54
logger.warning(
"mime_type=<%s> | unsupported mcp image mime type, falling back to json", content.mimeType
)
return {"json": content.model_dump(exclude_none=True)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we test this end to end? are we okay dumping bytes to model context? will that even help?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is definitely an antipattern in agent development imho. I cannot count the number of agents/mcps/tools that do this, and I have complained about 😅

@yonib05 yonib05 added area-mcp MCP related area-tool Tool behavior/api labels May 27, 2026
@bannff
Copy link
Copy Markdown

bannff commented May 29, 2026

Thanks for tackling the crashes here — the json fallback for unsupported image MIME and the previously-silently-dropped blobs is a real improvement. Wanted to flag a related gap this PR doesn't currently cover, in case you're open to extending it (or want a sibling PR after this lands).

Gap: text-path branches still drop uri + mimeType

_map_mcp_content_to_tool_result_content has two text-emitting branches in the EmbeddedResource path that #2218 doesn't touch:

# 1. TextResourceContents branch
if isinstance(resource, TextResourceContents):
    return {"text": resource.text}  # uri+mimeType dropped

# 2. BlobResourceContents text-like branch
if resource.mimeType and (resource.mimeType.startswith("text/") or ... or resource.mimeType.endswith(("+json", "+xml"))):
    try:
        return {"text": raw_bytes.decode("utf-8", errors="replace")}  # uri+mimeType dropped
    except Exception:
        pass

After #2218 lands, the image and unknown-MIME branches preserve full envelope context via content.model_dump(exclude_none=True) (which round-trips resource.uri and resource.mimeType), but the text-path branches still flatten to a bare {"text": ...} with no way for downstream consumers to recover the URI or original MIME.

Concrete consumer: mcp-ui SDK UIResource envelopes

mcp-ui's inline_html mode emits UIResource envelopes with text/html;profile=mcp-app MIME (text-like, falls into branch 2 above) and ui:// URIs. The MIME + URI together are the only signal that lets a wildcard frontend renderer distinguish a UIResource from any other tool emitting HTML text. Once the URI is stripped, the envelope is unrecoverable downstream — which is the same conceptual ask as #2251 (public extension-point promotion), just from the data-preservation angle.

Minimal extension

             if isinstance(resource, TextResourceContents):
-                return {"text": resource.text}
+                return {
+                    "text": resource.text,
+                    "uri": str(resource.uri),
+                    "mimeType": resource.mimeType or "text/plain",
+                }
             elif isinstance(resource, BlobResourceContents):
                 ...
                 if resource.mimeType and (
                     resource.mimeType.startswith("text/")
                     or resource.mimeType in ("application/json", ...)
                     or resource.mimeType.endswith(("+json", "+xml"))
                 ):
                     try:
-                        return {"text": raw_bytes.decode("utf-8", errors="replace")}
+                        return {
+                            "text": raw_bytes.decode("utf-8", errors="replace"),
+                            "uri": str(resource.uri),
+                            "mimeType": resource.mimeType,
+                        }
                     except Exception:
                         pass

This is symmetric with the spirit of #2218 (the json fallback there also preserves uri+mimeType via model_dump) — just brings the text-path to parity.

Wire-shape note

ToolResultContent is TypedDict(total=False) with document/image/json/text known keys. The added uri/mimeType keys are extras. Bedrock's _format_request_message_content (strands/models/bedrock.py) dispatches on known-key presence and ignores unknown keys, so the LLM-bound payload is unchanged. Carrier consumers read the extras off agent.messages via AfterToolCallEvent hooks.

Offer

Happy to either:

  1. Send a follow-up commit on this branch if you'd prefer a single PR covering all branches, or
  2. File a sibling PR scoped purely to the text-path after this one lands — no conflict with fix(mcp): handle unsupported image and blob mime types in tool results #2218 (different branches of the same method).

Either works on our side. Let me know which you'd prefer.

@yonib05 yonib05 added the python Pull requests that update python code label May 29, 2026
@yonib05 yonib05 added the bug Something isn't working label May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-mcp MCP related area-tool Tool behavior/api bug Something isn't working python Pull requests that update python code size/s

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants