Skip to content

Strip reserved io.modelcontextprotocol/* keys from backend response _meta (shared Legacy+Modern helper) #5986

Description

@jhrozek

Part of #5756. Source: a follow-up flagged in-code at pkg/vmcp/server/modern_envelope.go (newModernResultMeta, ~line 72-76) and its Legacy sibling conversion.ToMCPMeta (pkg/vmcp/conversion/meta.go, used from serve_handlers.go / pkg/vmcp/client/client.go).

Problem

On the response path (backend → client), both revisions forward all backend _meta keys unfiltered — including reserved io.modelcontextprotocol/* keys:

  • Modern (newModernResultMeta) clones backend meta and overwrites only io.modelcontextprotocol/serverInfo, forwarding every other reserved key untouched.
  • Legacy (conversion.ToMCPMeta) does the same (the Modern code comment explicitly notes it matches this sibling).

So a backend can set other reserved keys (protocolVersion, clientCapabilities, or any future io.modelcontextprotocol/* key) on a tools/call/tools/list/resources/read result and have them reach the vMCP client as if vMCP set them. vMCP — not the backend — is the client's MCP peer, so vMCP must own the reserved namespace on responses. The merged code already recognized this for serverInfo ("vMCP's own serverInfo must win") but deliberately punted the general case.

Changes

  • Strip/normalize reserved io.modelcontextprotocol/* keys from backend response _meta before forwarding to the client.
  • Do it in one helper shared by the Legacy (conversion.ToMCPMeta/serve_handlers) and Modern (newModernResultMeta) paths — not in one only, or "Legacy and Modern would drift" (the explicit warning in the modern_envelope.go comment).
  • Preserve non-reserved backend meta (progress tokens, trace ids, …); keep vMCP's own serverInfo authoritative.

Note — the request-path mirror already exists

The client edge (request path, client → backend) already strips reserved keys in #5980's ModernRequestMeta ("single source of truth for the client side of the reserved _meta"). This issue is the response-path counterpart; it should share a home with (or at least mirror) that logic rather than add a third independent copy.

Verification

  • A backend that sets io.modelcontextprotocol/protocolVersion (or any reserved key) on a result does not leak it to the client — on both the Legacy and Modern paths.
  • vMCP's own serverInfo remains authoritative.
  • Non-reserved backend meta is preserved unchanged.
  • One shared helper covers both revisions (a regression test pinning that both paths call it).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions