Skip to content

fix(mcp): name the non-text blocks a tool result drops - #874

Open
Vasanthdev2004 wants to merge 2 commits into
mainfrom
fix/823-mcp-non-text-blocks
Open

fix(mcp): name the non-text blocks a tool result drops#874
Vasanthdev2004 wants to merge 2 commits into
mainfrom
fix/823-mcp-non-text-blocks

Conversation

@Vasanthdev2004

@Vasanthdev2004 Vasanthdev2004 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Part 1 of #823. It stops the silent drop. It does not carry the payload yet.

Why this half is worth landing alone

TextContent keeps only blocks whose type is text, so a server returning an image, audio, or an embedded resource produced (empty MCP tool result). A screenshot or browser server completes the call, hands back a perfectly good image, and the model is told nothing came back. It then usually retries, spending another call to reach the same empty answer, and the user never learns an image existed.

Naming the dropped blocks ends that loop without moving a single byte of payload.

The change

  • Content decodes mimeType. Additive and omitempty, so servers that never send it decode exactly as before.
  • DroppedContentSummary describes what TextContent discarded: 1 image/png block, 2 resource blocks, 1 audio/wav block. Grouped by mime type, ordered by first appearance, so the same result always reads the same way. A server that omits mimeType falls back to the block type rather than printing an empty label.
  • registryTool.Run appends that as a note, and only when something was actually dropped, so a text-only result is byte-for-byte unchanged.

The note also says retrying will return the same thing. Telling the model what happened without telling it not to retry only fixes half the loop.

What is still open on #823

Carrying image blocks through. That got easier today: #843 landed zeroruntime.ImageBlock, Message.Images, CloneImageBlocks and SeedMessagesWithImages, which is the plumbing the issue says to reuse. The MCP side now only has to fill it. I have left #823 open for that.

Tests

non_text_content_test.go drives registryTool.Run, not the helper alone, because the property has to be true of the delivered result. Covers image-only, text-alongside-image, text-only unchanged, and the summary's own cases including a missing mime type and repeated blocks.

Mutation-checked: disabling the note block fails TestAnImageOnlyResultSaysWhatItReturned and TestTextAlongsideAnImageStillReportsTheImage, while TestATextOnlyResultIsUnchanged correctly stays green.

go build ./..., GOOS=windows go build ./..., go vet, gofmt, and the full internal/mcp and internal/tools suites are clean.

Summary by CodeRabbit

  • New Features

    • MCP results now identify non-text content, such as images, that cannot be displayed.
    • Mixed results preserve available text while indicating which additional content was omitted.
    • Content summaries group repeated types and provide clearer returned-content details.
  • Bug Fixes

    • Image-only results no longer appear as empty responses.
    • Text-only results remain unchanged.

TextContent keeps only blocks whose type is "text", so a server that
returns an image, audio, or an embedded resource produced "(empty MCP
tool result)". A screenshot or browser server would complete a call
successfully, hand back a perfectly good image, and the model would be
told nothing came back. It then usually retries, which spends another
call to reach the same empty answer, and the user never learns an image
existed at all.

Carrying the payload is the larger half of #823 and is not this change.
Dropping silently is the part that causes the retry loop, and it can be
fixed on its own.

Decode mimeType on Content, and add DroppedContentSummary, which
describes what TextContent discarded ("1 image/png block", "2 resource
blocks, 1 audio/wav block"), grouping by mime type and ordering by first
appearance so the same result always reads the same way. Servers that
omit mimeType fall back to the block type rather than printing an empty
label.

registryTool.Run appends that as a note, and only when something was
actually dropped, so a text-only result is byte-for-byte unchanged. The
note also says retrying will return the same thing, since telling the
model what happened without telling it not to retry only fixes half the
loop.

Refs #823.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8674484d-c8d8-41d4-8ca7-c4d1155157f3

📥 Commits

Reviewing files that changed from the base of the PR and between af45b1a and 55d7cc3.

📒 Files selected for processing (2)
  • internal/mcp/non_text_content_test.go
  • internal/mcp/registry.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/mcp/registry.go
  • internal/mcp/non_text_content_test.go

Walkthrough

The MCP client now preserves optional MIME metadata and summarizes dropped non-text content. registryTool.Run appends this summary when unsupported content is present. Tests cover MIME grouping, ordering, counts, and text/non-text response combinations.

Changes

MCP dropped content reporting

Layer / File(s) Summary
Content metadata and dropped-content summary
internal/mcp/client.go, internal/mcp/non_text_content_test.go
Content stores optional MimeType metadata. DroppedContentSummary groups non-text blocks, applies MIME fallbacks, preserves order, and formats block counts.
Registry result reporting
internal/mcp/registry.go, internal/mcp/non_text_content_test.go
registryTool.Run appends a note for dropped content. Tests cover image-only, mixed, text-only, empty, MIME-less, repeated, and mixed content.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPClient
  participant registryToolRun
  participant Result
  MCPClient->>registryToolRun: return MCP content blocks
  registryToolRun->>registryToolRun: summarize unsupported non-text blocks
  registryToolRun->>Result: append dropped-content note when applicable
Loading

Possibly related issues

  • Gitlawb/zero#823 — Adds MIME-aware handling and user-facing summaries for dropped non-text MCP blocks.

Suggested reviewers: anandh8x

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: naming non-text content blocks dropped from MCP tool results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/823-mcp-non-text-blocks

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/mcp/non_text_content_test.go (1)

30-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the retry guidance.

The test verifies that the output is non-empty and names the MIME type. It does not verify the retry guidance. Add an assertion for the corrected message so this behavior cannot regress.

Proposed test
 	if !strings.Contains(result.Output, "image/png") {
 		t.Errorf("the output does not name what the server returned:\n%s", result.Output)
 	}
+	if !strings.Contains(result.Output, "Retrying cannot recover this payload.") {
+		t.Errorf("the output does not contain retry guidance:\n%s", result.Output)
+	}

As per coding guidelines, add a regression test for behavior changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/mcp/non_text_content_test.go` around lines 30 - 35, Extend the
assertions in the non-text content test to verify that result.Output contains
the corrected retry guidance message, alongside the existing non-empty and
MIME-type checks. Use the established expected wording from the implementation
rather than adding a new message.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/mcp/registry.go`:
- Around line 333-338: The retry guidance assembled in DroppedContentSummary
handling within registryTool.Run must not claim retries return the same result;
replace that wording with “Retrying cannot recover this payload.” Update
internal/mcp/registry.go lines 333-338 and adjust the image-only result
assertion in internal/mcp/non_text_content_test.go lines 30-35 to verify the
corrected message.

---

Nitpick comments:
In `@internal/mcp/non_text_content_test.go`:
- Around line 30-35: Extend the assertions in the non-text content test to
verify that result.Output contains the corrected retry guidance message,
alongside the existing non-empty and MIME-type checks. Use the established
expected wording from the implementation rather than adding a new message.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 97f552dc-1eef-46ae-b884-152993cde36f

📥 Commits

Reviewing files that changed from the base of the PR and between cd0eb19 and af45b1a.

📒 Files selected for processing (3)
  • internal/mcp/client.go
  • internal/mcp/non_text_content_test.go
  • internal/mcp/registry.go

Comment thread internal/mcp/registry.go
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Tests: go test ./...
  • [pass] Build: go run ./cmd/zero-release build
  • [pass] Smoke build: go run ./cmd/zero-release smoke

Scope

Head: 55d7cc37ef7c
Changed files (3): internal/mcp/client.go, internal/mcp/non_text_content_test.go, internal/mcp/registry.go

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

jatmn
jatmn previously approved these changes Aug 6, 2026

@jatmn jatmn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

gnanam1990
gnanam1990 previously approved these changes Aug 7, 2026

@gnanam1990 gnanam1990 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: Approve

Checked out and verified rather than read. The defect is real and the fix is the smallest thing that ends it.

The claim holds. TextContent keeps only text blocks, so an image-only result reached the model as (empty MCP tool result) — which reads as "the tool produced nothing", and a model that believes that retries. Naming the payload ends the loop even though the bytes still cannot be forwarded, and it says so ("Retrying will return the same thing"), which is the part that actually stops the retry.

Gut-the-fix: forcing DroppedContentSummary to return "" fails three tests — TestAnImageOnlyResultSaysWhatItReturned, TestTextAlongsideAnImageStillReportsTheImage, TestDroppedContentSummaryNamesTheBlocks. Load-bearing, and the wiring in registry.go is covered too, not just the helper.

Additivity: I verified a text-only result produces "", so the note is appended only when something was genuinely dropped and an all-text call is byte-for-byte what it was. nil content likewise, so (empty MCP tool result) still appears for a genuinely empty result rather than being replaced by a confusing note.

Edge cases I probed directly, all correct:

  • a block with neither mimeType nor type yields 1 unknown block, not an empty label
  • grouping and pluralisation: 2 image/png blocks, 1 resource block
  • ordering is by first appearance, so the same result always produces the same sentence — worth having, since a map-ordered version would produce a different string each run and make the output non-deterministic for no reason

Two small things, neither blocking:

  1. MimeType is decoded but never forwarded. That is stated in the comment, so it is deliberate — worth a follow-up issue reference so it does not become a field nobody remembers is unused.
  2. The message says "Zero cannot forward yet". Now that #843 has landed an image channel on tool results, that sentence has a shelf life. Not this PR's job, but the two will need reconciling.

go test ./internal/mcp/ green on the branch.

The dropped-block note said "Retrying will return the same thing".
registryTool.Run makes a fresh remote call on every retry, so the server
may well answer differently; that is a promise this code is in no
position to make.

What cannot change is that Zero still has nowhere to put a non-text
block, so the note now says retrying cannot RECOVER the payload. Same
guidance, a claim that is actually true.

The test pins both directions: the new wording must be present, and the
absolute claim must not come back.

Raised by CodeRabbit on #874.
@Vasanthdev2004
Vasanthdev2004 dismissed stale reviews from gnanam1990 and jatmn via 55d7cc3 August 7, 2026 15:34
@Vasanthdev2004

Copy link
Copy Markdown
Collaborator Author

Fixed in 55d7cc3, and the point was correct. registryTool.Run makes a fresh call on every retry, so promising an identical response was a claim this code cannot make. The note now says retrying cannot recover the payload, which stays true whatever the server does next, because Zero still has nowhere to put a non-text block.

TestAnImageOnlyResultSaysWhatItReturned pins both directions: the new wording must be present and the absolute claim must not come back. Mutation checked, reverting the string fails the test.

@Vasanthdev2004
Vasanthdev2004 requested a review from jatmn August 7, 2026 15:37

@gnanam1990 gnanam1990 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-approving after 55d7cc37. My earlier approval was auto-dismissed by the push; the new commit only improves things.

The wording change is the right correction, and a subtle one. Retrying will return the same thing was a claim this code is in no position to make — each retry is a fresh call and the server may well answer differently. Retrying cannot recover this payload is true regardless of what the server does, because the thing that cannot change is that Zero has nowhere to put a non-text block. Same steer to the model, without the false promise.

Verified at 55d7cc37:

  • go test ./internal/mcp/ green.
  • Mutation-checked the new assertions: restoring the old will return the same thing wording in registry.go:340,342 fails TestAnImageOnlyResultSaysWhatItReturned on both lines — the missing cannot recover guidance and the reintroduced identical-response promise. The test pins the wording in both directions, so neither half can rot silently.

Nothing further from me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants