Background
Upstream resolution of modelcontextprotocol/go-sdk#1116 (closed 2026-07-28, maintainer comment, no fix PR) confirmed that logging/setLevel is removed from the MCP 2026-07-28 ("Modern") method surface, not merely deprecated. Verified against modelcontextprotocol/modelcontextprotocol@main schema/draft/schema.ts:
ClientRequest is a closed union with no SetLevelRequest — the method does not exist on Modern.
RequestMetaObject carries "io.modelcontextprotocol/logLevel"?: LoggingLevel, documented as: "If absent, the server MUST NOT send any notifications/message notifications for this request. The client opts in to log messages by explicitly setting a level. Replaces the former logging/setLevel RPC." (itself @deprecated per SEP-2577, twelve-month window).
- Logging survives on Modern only as the deprecated
notifications/message notification plus the deprecated logging server capability.
go-sdk's ClientSession.SetLoggingLevel therefore stays broken-by-design on Modern sessions (it sends a Modern protocol header with no _meta.protocolVersion, and the method it targets no longer exists). Callers are expected to stop using it there and set the per-request logLevel _meta key instead.
Impact on ToolHive
The pinned Modern contract is validated — no change needed to the rejection behavior:
pkg/vmcp/server/modern_realbackend_integration_test.go:398-466 — well-formed Modern logging/setLevel → 404/-32601 (matches go-sdk's own server); go-sdk's malformed variant → 400/-32020 HeaderMismatch. Both are the spec-correct answers.
pkg/mcp/revision.go:59-133 already models io.modelcontextprotocol/logLevel correctly: in the strip set (per-hop key, terminated at the vMCP boundary) but excluded from modernSignalMetaKeys (presence alone does not claim Modern).
- The streamable proxy's Legacy
logging/setLevel interception + max-verbosity reconciliation (pkg/transport/proxy/streamable/streamable_proxy.go:1187-1204, :1429-1453) is unaffected — it only runs on session-bearing Legacy requests and hand-rolls the upstream call.
The real gap: with logging, a downstream client of vMCP gets no notifications/message from a Modern backend at all:
enableBackendLogging (pkg/vmcp/client/forwarding.go:51-63) calls c.SetLoggingLevel(ctx, mcp.LoggingLevelDebug) — but only from legacyCallTool (pkg/vmcp/client/client.go:1701), so it never runs against a Modern-classified backend (and would fail there anyway, per above).
- The Modern path —
modernCallTool (pkg/vmcp/client/client.go:1638-1672) via modernCall (pkg/vmcp/client/modern.go:143-178) — mints the per-request _meta but never sets io.modelcontextprotocol/logLevel, so per the draft schema a conformant Modern backend MUST NOT emit notifications/message for the request.
So the mid-call logging forwarding that Legacy sessions get (backend emits at debug → notification forwarder relays downstream) has no Modern equivalent, even though the schema's opt-in semantics are exactly what the forwarder wants: setting logLevel on the request is what permits the backend to send notifications/message for that request.
Proposed fix
On the Modern backend path, inject io.modelcontextprotocol/logLevel: "debug" into the request _meta vMCP mints in modernCall (mirroring the Legacy enableBackendLogging intent: ask for debug so the backend emits, relay everything downstream — per the documented limitation in docs/arch/10-virtual-mcp-architecture.md:807, downstream level filtering is already out of scope).
Scope is small and self-contained:
pkg/vmcp/client/modern.go / client.go:1638-1672 — overlay the logLevel key into the minted _meta on tools/call (and any other Modern request where backend log relay is wanted), gated on the same "forwarders bound" condition the Legacy path uses.
pkg/vmcp/client/forwarding.go:46-63 — doc comment: note the RPC is Legacy-only and the Modern equivalent is the _meta key.
Follow-up hygiene (same PR or separate)
- Reframe the two pin comments that attribute the -32020 rejection to an "upstream bug, filed separately" —
pkg/vmcp/server/forwarding_realbackend_integration_test.go:476-485 and pkg/vmcp/server/modern_realbackend_integration_test.go:405-410. With upstream's wont-fix-by-design resolution, the malformed wire shape is a permanent fixture of go-sdk v1.7.x; the -32020 arm is the standing contract for any caller still using the removed RPC on a Modern session, not a temporary bug guard.
docs/arch/10-virtual-mcp-architecture.md:807 — the logging limitation gains a second dimension: on Modern, downstream clients express their level per-request via logLevel _meta, which vMCP currently strips without honoring.
Priority
Low. SEP-2577 deprecates the entire logging feature (the logLevel key itself is deprecated with a twelve-month window), and the failure mode today is silent absence of a best-effort notification stream, not an error. Worth doing while the deprecated feature is still in the window so Modern backends get parity with Legacy for the remaining deprecation period.
Refs: go-sdk#1116 (resolution), go-sdk#1112 and go-sdk#1113 (siblings, still open), SEP-2575, SEP-2577, MCP schema/draft/schema.ts (RequestMetaObject, ClientRequest union).
Background
Upstream resolution of modelcontextprotocol/go-sdk#1116 (closed 2026-07-28, maintainer comment, no fix PR) confirmed that
logging/setLevelis removed from the MCP 2026-07-28 ("Modern") method surface, not merely deprecated. Verified againstmodelcontextprotocol/modelcontextprotocol@mainschema/draft/schema.ts:ClientRequestis a closed union with noSetLevelRequest— the method does not exist on Modern.RequestMetaObjectcarries"io.modelcontextprotocol/logLevel"?: LoggingLevel, documented as: "If absent, the server MUST NOT send anynotifications/messagenotifications for this request. The client opts in to log messages by explicitly setting a level. Replaces the formerlogging/setLevelRPC." (itself@deprecatedper SEP-2577, twelve-month window).notifications/messagenotification plus the deprecatedloggingserver capability.go-sdk's
ClientSession.SetLoggingLeveltherefore stays broken-by-design on Modern sessions (it sends a Modern protocol header with no_meta.protocolVersion, and the method it targets no longer exists). Callers are expected to stop using it there and set the per-requestlogLevel_metakey instead.Impact on ToolHive
The pinned Modern contract is validated — no change needed to the rejection behavior:
pkg/vmcp/server/modern_realbackend_integration_test.go:398-466— well-formed Modernlogging/setLevel→ 404/-32601 (matches go-sdk's own server); go-sdk's malformed variant → 400/-32020HeaderMismatch. Both are the spec-correct answers.pkg/mcp/revision.go:59-133already modelsio.modelcontextprotocol/logLevelcorrectly: in the strip set (per-hop key, terminated at the vMCP boundary) but excluded frommodernSignalMetaKeys(presence alone does not claim Modern).logging/setLevelinterception + max-verbosity reconciliation (pkg/transport/proxy/streamable/streamable_proxy.go:1187-1204,:1429-1453) is unaffected — it only runs on session-bearing Legacy requests and hand-rolls the upstream call.The real gap: with logging, a downstream client of vMCP gets no
notifications/messagefrom a Modern backend at all:enableBackendLogging(pkg/vmcp/client/forwarding.go:51-63) callsc.SetLoggingLevel(ctx, mcp.LoggingLevelDebug)— but only fromlegacyCallTool(pkg/vmcp/client/client.go:1701), so it never runs against a Modern-classified backend (and would fail there anyway, per above).modernCallTool(pkg/vmcp/client/client.go:1638-1672) viamodernCall(pkg/vmcp/client/modern.go:143-178) — mints the per-request_metabut never setsio.modelcontextprotocol/logLevel, so per the draft schema a conformant Modern backend MUST NOT emitnotifications/messagefor the request.So the mid-call logging forwarding that Legacy sessions get (backend emits at debug → notification forwarder relays downstream) has no Modern equivalent, even though the schema's opt-in semantics are exactly what the forwarder wants: setting
logLevelon the request is what permits the backend to sendnotifications/messagefor that request.Proposed fix
On the Modern backend path, inject
io.modelcontextprotocol/logLevel: "debug"into the request_metavMCP mints inmodernCall(mirroring the LegacyenableBackendLoggingintent: ask for debug so the backend emits, relay everything downstream — per the documented limitation indocs/arch/10-virtual-mcp-architecture.md:807, downstream level filtering is already out of scope).Scope is small and self-contained:
pkg/vmcp/client/modern.go/client.go:1638-1672— overlay thelogLevelkey into the minted_metaontools/call(and any other Modern request where backend log relay is wanted), gated on the same "forwarders bound" condition the Legacy path uses.pkg/vmcp/client/forwarding.go:46-63— doc comment: note the RPC is Legacy-only and the Modern equivalent is the_metakey.Follow-up hygiene (same PR or separate)
pkg/vmcp/server/forwarding_realbackend_integration_test.go:476-485andpkg/vmcp/server/modern_realbackend_integration_test.go:405-410. With upstream's wont-fix-by-design resolution, the malformed wire shape is a permanent fixture of go-sdk v1.7.x; the -32020 arm is the standing contract for any caller still using the removed RPC on a Modern session, not a temporary bug guard.docs/arch/10-virtual-mcp-architecture.md:807— the logging limitation gains a second dimension: on Modern, downstream clients express their level per-request vialogLevel_meta, which vMCP currently strips without honoring.Priority
Low. SEP-2577 deprecates the entire logging feature (the
logLevelkey itself is deprecated with a twelve-month window), and the failure mode today is silent absence of a best-effort notification stream, not an error. Worth doing while the deprecated feature is still in the window so Modern backends get parity with Legacy for the remaining deprecation period.Refs: go-sdk#1116 (resolution), go-sdk#1112 and go-sdk#1113 (siblings, still open), SEP-2575, SEP-2577, MCP
schema/draft/schema.ts(RequestMetaObject,ClientRequestunion).