feat(mcp): MediaSanitizer decode/validate/attach + vision-tier routing for MCP image passthrough#6331
Merged
Merged
Conversation
bug-ops
enabled auto-merge (squash)
July 16, 2026 18:40
bug-ops
force-pushed
the
feat/issue-6240/mcp-media-sanitizer
branch
from
July 16, 2026 18:40
c1ba842 to
bdac0f9
Compare
…uting for MCP image passthrough Adds the P2 phase of spec-072 (multimodal MCP passthrough): an MCP server can now return an image in a tool result and have it attached as a native MessagePart::Image sibling, visible to a vision-capable provider, instead of the previous text-only placeholder. - zeph-sanitizer::MediaSanitizer: magic-byte sniff vs declared MIME, format allowlist, byte-size cap, header-only dimension pre-check before a spawn_blocking full decode, with dimension/pixel caps re-enforced on the decoded image (decompression-bomb defense-in-depth). - New per-server media_passthrough opt-in (default false, hard-blocked for Sandboxed trust regardless of the flag) and [mcp.media] global caps. - McpToolExecutor gates media decode on the opt-in flag and trust level, audit-logs every accept/reject decision. - ToolResultClassification.media populated only on tool success; process_one_tool_result emits sibling Image parts respecting a max_images_per_turn cap, never producing a runtime 400/422. - TriageRouter and RouterProvider (Cascade/Bandit/Ema/Thompson) both escalate to a vision-capable tier when a pending request carries media, and strip Image parts before dispatch when no tier can be guaranteed vision-capable. - Static system-prompt caveat marks tool-sourced images as untrusted data whenever any server has media_passthrough enabled. Closes #6240
New McpMediaConfig/media_passthrough fields grew Config's size enough to push handle_skill_command's future past clippy's large_futures threshold (16432 bytes) under the CI feature set. Box the three test call sites, mirroring the existing install_test_skill boxing in the same module.
bug-ops
force-pushed
the
feat/issue-6240/mcp-media-sanitizer
branch
from
July 16, 2026 19:00
2e10e23 to
d4755d5
Compare
13 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the P2 phase of spec-072 (multimodal MCP passthrough, parent #6229). An MCP server can now return an image in a tool result and have it attached as a native
MessagePart::Imagesibling part, visible to a vision-capable provider, instead of the previous text-only[image: mime, N bytes]placeholder.zeph-sanitizer::MediaSanitizer: magic-byte sniff vs. declared MIME, format allowlist, byte-size cap, a header-only dimension pre-check (image::ImageReader::into_dimensions) before aspawn_blockingfull decode, with the same dimension/pixel caps re-enforced on the decoded image — the header check rejects an oversized image without paying for the full decode's memory allocation (decompression-bomb defense-in-depth).[[mcp.servers]].media_passthroughper-server opt-in flag (defaultfalse), always hard-blocked whentrust_level = "sandboxed"regardless of the flag. New[mcp.media]global caps (5 MiB / 8192px / ~64MP / 4-per-result / 8-per-turn / jpeg,png,gif,webp).zeph-mcp::McpToolExecutorgates media decode on the opt-in flag and trust level, logs every accept/reject decision via the existing tool audit path.ToolResultClassification.mediapopulated only on tool success;process_one_tool_resultemits siblingMessagePart::Imageparts respecting amax_images_per_turnrunning cap, never producing a runtime 400/422.TriageRouterandRouterProvider(Cascade/Bandit/Ema/Thompson strategies) aggregate vision capability across their configured tiers and stripImageparts before dispatch when no tier can be guaranteed vision-capable — closes a gap found during review where the non-triage router strategies silently dropped images without a safety net.media_passthroughenabled, added once per session before the cache-stability marker.Review chain
Reduced
new-featurechain (architect/pre-critique skipped — issue carried exact file-level scope fromspecs/072-multimodal-mcp-passthrough/plan.md): developer → tester/perf/security/impl-critic (parallel) → code reviewer, with one fix-review round for two significant impl-critic findings (RouterProvider vision-blindness risk of a real 400/422 under Cascade/Bandit/Ema/Thompson; a Gemini debug-dump redaction gap, resolved upstream) and one further trivial comment-fix round. Final reviewer verdict: approved.Test plan
cargo +nightly fmt --checkcargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warningscargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins— 13844 passed, 0 failedRUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"cargo test --doc --workspace --features "desktop,ide,server,chat,pdf,scheduler"gitleaks protect --staged --no-banner --redact— no leaks.local/testing/playbooks/mcp-media-passthrough.md, 5 new coverage-status rows (all currentlyUntestedpending a live session)curl, matches known team precedent for 2026-07), local Ollama hit a suspected unrelated pre-existingcached_prompt_tokensbug plus a hardware/idle-unload timeout on the 7B model. The exact routing logic this gate exists to validate is covered by 9+ non-tautological mocked-provider unit tests intriage.rs/router/tests.rs. Recommend re-attempting once cloud credits are replenished; will file thecached_prompt_tokensanomaly as a separate follow-up issue.Closes #6240