Skip to content

Preserve resource-read _meta end to end (mcpcompat handler shape) #6027

Description

@jhrozek

Problem

Backend _meta on a resources/read result never reaches a vMCP client on the
Legacy (2025-11-25) path. Trace ids, progress tokens, and any custom backend
metadata are silently dropped.

pkg/vmcp/server/serve_handlers.go's coreResourceHandler returns a bare
[]mcp.ResourceContents and discards result.Meta, as does the transport
bridge (pkg/transport/bridge.go:226,241). The vMCP client side already
extracts the meta correctly (ResourceReadResult.Meta is populated on both the
Legacy and Modern paths) — it is thrown away at the server edge.

This is not a go-sdk limitation

pkg/vmcp/client/meta_integration_test.go:217 documents this as a "KNOWN
LIMITATION: Due to MCP SDK constraints, resource handlers return
[]ResourceContents directly". That framing is stale. go-sdk supports the
field:

// go-sdk mcp/resource.go:39
type ResourceHandler func(context.Context, *ReadResourceRequest) (*ReadResourceResult, error)
// ReadResourceResult embeds Meta

The loss is introduced one layer up, in toolhive-core's mcpcompat bridge:

// mcpcompat/server/server.go:1039
jsonConvert(mcp.ReadResourceResult{Contents: contents}, out)  // Meta left nil

mcpcompat's own mcp.ReadResourceResult embeds Result and can carry
Meta. The bridge has nothing to populate it from, because
ResourceHandlerFunc returns bare []ResourceContents — the mark3labs/mcp-go
shape, inherited for source compatibility during #5729. So the constraint is a
compat shape we chose, sitting above an SDK that handles this fine.

Why now

#5986 (PR #6024) made vMCP strip reserved io.modelcontextprotocol/* keys from
backend response _meta on both revisions. Modern preserves-and-strips resource
meta because its hand-rolled envelope bypasses the SDK handler entirely; Legacy
still drops it wholesale. That is a client-visible fork between revisions for
the same backend and the same request.

This was in scope for #2640 ("Virtual MCP Server Not Preserving _meta Field in
Responses", item 2: Resource Reads), which shipped the tools/call and
prompts/get halves and closed with the resource half unfixed on the
SDK-limitation rationale above.

Shape of the fix

Additive, no break to the shim's mcp-go source-compatibility contract (mcp-go
never had a result-returning resource handler to be compatible with):

  1. toolhive-core — add a result-returning resource handler variant so a
    handler can supply _meta.
  2. ToolHive — consume it at the two sites that currently drop meta, and
    correct the stale test comment.

Ordering: core lands and releases first; the ToolHive change is downstream of a
version bump.

Not urgent

No correctness impact — metadata only. Not a blocker for #6024.

Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssue needs initial triage by a maintainer

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions