Skip to content

feat(capability): send screenshots as MCP image blocks instead of base64 text - #533

Merged
gaurav-singh-9227 merged 2 commits into
mainfrom
feat/screenshot-image-block
Oct 30, 2026
Merged

gaurav-singh-9227 merged 2 commits into
mainfrom
feat/screenshot-image-block

Conversation

@sarve-shreyas

Copy link
Copy Markdown
Collaborator

Why

take_screenshot (web and mobile) returns {"value": "<base64 png>"}. The registry parsed that JSON whole and put it in the tool result's text block, so a phone screenshot cost hundreds of KB of base64 as text and the model still could not see the picture. Raised as an eval finding against Device Control.

What

  • New optional capability field media: {mime, json_path}. On a 2xx, invokeCapability lifts the string at that JSON pointer out of the body, compresses it with the same maybeCompressBase64 the legacy screenshot tools use, and returns it as a second image content block. The JSON block keeps a note in its place ("<image/png, 35 KB, attached to this result as an image>") and an image: {mime, bytes} field. Error bodies and bodies with nothing at the path are returned unchanged.
  • Both take_screenshot records declare media and their intent/guidance no longer promise base64 text.
  • Load-time check: mime must be one a client renders (png/jpeg/gif/webp) and json_path must be a JSON pointer.
  • maybeCompressBase64 moves to src/lib/image.ts (re-exported from utils.ts) so the registry can import it without pulling in the server entry module.

Remote

Nothing new for remote-mcp-server: takeAppScreenshot already ships an image block through the hosted server, the compressor already has its REMOTE_MCP branch (libvips cache off, single thread), and the response size ceiling is the same 1 MB.

Verified

  • 1754/1754 tests, including new capabilityRegistryMedia.test.ts.
  • Live on a peer Claude session against this build: Chrome session on BrowserStack, take_screenshot came back as text + image, the model described the page content, value carried the note and image.bytes was 35651.

🤖 Generated with Claude Code

… text

A WebDriver screenshot is `{"value": "<base64 png>"}` and a phone screen runs to
hundreds of kilobytes. Returned inside invokeCapability's JSON text block it was
paid for as text and still invisible to the model. A capability may now declare
`media: {mime, json_path}`; on a 2xx the string at that pointer is detached,
compressed the way the legacy screenshot tools compress theirs, and sent as a
second `image` content block, with a short note left in the body where it sat.
Error bodies and bodies with nothing at the path go back untouched.

Both infra products' take_screenshot carry it. The index is refused at load for a
mime no client renders or a path that is not a JSON pointer. maybeCompressBase64
moves to src/lib/image.ts so the registry can import it without pulling in the
server entry module; utils.ts re-exports it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Central YAML (base), Organization UI (inherited), Workspace UI (inherited); Enforced overrides: Organization
  • Review profile: ASSERTIVE
  • Plan: Enterprise
  • Run ID: cdb3bb5f-38db-4f49-9071-88a09835a68b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

- media.mime may only be image/png: the compressor re-encodes anything over
  1 MB as PNG, so a JPEG declared would be attached as PNG under a JPEG label
- the note, `image.bytes` and the telemetry report the size of the block as
  attached, after compression, not as received (received is kept as its own
  telemetry field)
- after a fallback chain the step that answered decides whether there is an
  image to detach, since the 2xx body is that step's
- checkMedia lives beside checkFallbacks in the loader and throws IndexError
  itself, so the try/catch rethrow goes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gaurav-singh-9227
gaurav-singh-9227 merged commit fc04f34 into main Oct 30, 2026
6 checks passed
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