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).
Part of #5756. Source: a follow-up flagged in-code at
pkg/vmcp/server/modern_envelope.go(newModernResultMeta, ~line 72-76) and its Legacy siblingconversion.ToMCPMeta(pkg/vmcp/conversion/meta.go, used fromserve_handlers.go/pkg/vmcp/client/client.go).Problem
On the response path (backend → client), both revisions forward all backend
_metakeys unfiltered — including reservedio.modelcontextprotocol/*keys:newModernResultMeta) clones backend meta and overwrites onlyio.modelcontextprotocol/serverInfo, forwarding every other reserved key untouched.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 futureio.modelcontextprotocol/*key) on atools/call/tools/list/resources/readresult 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 forserverInfo("vMCP's own serverInfo must win") but deliberately punted the general case.Changes
io.modelcontextprotocol/*keys from backend response_metabefore forwarding to the client.conversion.ToMCPMeta/serve_handlers) and Modern (newModernResultMeta) paths — not in one only, or "Legacy and Modern would drift" (the explicit warning in themodern_envelope.gocomment).serverInfoauthoritative.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
io.modelcontextprotocol/protocolVersion(or any reserved key) on a result does not leak it to the client — on both the Legacy and Modern paths.serverInforemains authoritative.