You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Restore session-stable passthrough-header forwarding on the vMCP Serve path.
When server.New is routed through core.New + Serve (#5445 / PR #5556), tool/resource calls
flow through the shared backend client (pkg/vmcp/client) via core.CallTool/ReadResource
rather than through a per-session backend connection. The legacy server.New path opened a per-session backend connection at session-creation time, baking the captured passthrough
headers (headerforward.CaptureMiddleware → ForwardedHeadersFromContext) into that connection's
round-tripper. As a result, the forwarded header value was session-stable: it was fixed at
session creation and a later (mid-session) change to the incoming header did NOT change what the
backend received.
On the Serve path there is no per-session connection carrying that state (the factory's per-session
connections are unused — see #5442's deferral of per-session backend affinity), so the shared client
reads the forwarded headers per request. That means:
Passthrough headers are forwarded (good), but
They are no longer session-stable: a mid-session change to the incoming header value reaches
the backend, which is a behavioral and security-relevant regression vs. the legacy path.
This is the passthrough-header slice of the per-session backend affinity mechanism deferred in #5442. It was surfaced by TestVMCPServer_PassthroughHeaders while completing #5445; that test is skipped for now (it references this issue) and must be re-enabled by the fix.
The per-session connector that captured session-stable headers: pkg/vmcp/session/internal/backend/mcp_session.go (it merged headerforward.ForwardedHeadersFromContext(ctx) with the backend's static HeaderForward).
The shared client that now handles Serve-path calls: pkg/vmcp/client/client.go (builds the header-forward tripper from target.HeaderForward only).
Security constraint: captured passthrough headers can be credentials (e.g. an API key), so the
session-stable value MUST be held in node-local state only — it must not be persisted to the
Redis session metadata store (see .claude/rules/security.md: don't store credentials in shared
state).
On the Serve path, a passthrough header is captured once at session creation and the
backend receives that captured value for the lifetime of the session.
A mid-session change to the incoming passthrough header value does NOT reach the backend
(parity with the legacy per-session-connection behavior; the value is session-stable).
The session-stable header value is stored node-local only (never written to Redis session
metadata); credentials must not leak into shared state.
Cross-pod behavior is explicitly documented (a session restored on another pod has no captured
headers — matching the legacy path's imperfect cross-pod behavior — or is otherwise specified).
Static per-backend headerForward config continues to apply, and the collision/restricted-header
rules from the legacy merge (mergeForwardedHeaders) are preserved.
TestVMCPServer_PassthroughHeaders (test/integration/vmcp) is re-enabled (skip removed)
and passes, including the session-stability assertions (first call sees the captured value; a
changed value on a later call does not reach the backend).
All tests pass (task test); lint clean (task lint-fix).
Re-introducing a per-session backend connection on the Serve path (the core deliberately routes
through the shared client); the fix should capture/re-inject the header state, not reopen the
legacy connection model.
Description
Restore session-stable passthrough-header forwarding on the vMCP
Servepath.When
server.Newis routed throughcore.New+Serve(#5445 / PR #5556), tool/resource callsflow through the shared backend client (
pkg/vmcp/client) viacore.CallTool/ReadResourcerather than through a per-session backend connection. The legacy
server.Newpath opened aper-session backend connection at session-creation time, baking the captured passthrough
headers (
headerforward.CaptureMiddleware→ForwardedHeadersFromContext) into that connection'sround-tripper. As a result, the forwarded header value was session-stable: it was fixed at
session creation and a later (mid-session) change to the incoming header did NOT change what the
backend received.
On the Serve path there is no per-session connection carrying that state (the factory's per-session
connections are unused — see #5442's deferral of per-session backend affinity), so the shared client
reads the forwarded headers per request. That means:
the backend, which is a behavioral and security-relevant regression vs. the legacy path.
This is the passthrough-header slice of the per-session backend affinity mechanism deferred in
#5442. It was surfaced by
TestVMCPServer_PassthroughHeaderswhile completing #5445; that test isskipped for now (it references this issue) and must be re-enabled by the fix.
Context
server.Newto the wrapper").pkg/vmcp/session/internal/backend/mcp_session.go(it mergedheaderforward.ForwardedHeadersFromContext(ctx)with the backend's staticHeaderForward).pkg/vmcp/client/client.go(builds the header-forward tripper fromtarget.HeaderForwardonly).session-stable value MUST be held in node-local state only — it must not be persisted to the
Redis session metadata store (see
.claude/rules/security.md: don't store credentials in sharedstate).
Parent story: #5432 (Phase 3)
Relates to: #5442 (per-session backend affinity deferral), #5445 / PR #5556
Acceptance Criteria
Servepath, a passthrough header is captured once at session creation and thebackend receives that captured value for the lifetime of the session.
(parity with the legacy per-session-connection behavior; the value is session-stable).
metadata); credentials must not leak into shared state.
headers — matching the legacy path's imperfect cross-pod behavior — or is otherwise specified).
headerForwardconfig continues to apply, and the collision/restricted-headerrules from the legacy merge (
mergeForwardedHeaders) are preserved.TestVMCPServer_PassthroughHeaders(test/integration/vmcp) is re-enabled (skip removed)and passes, including the session-stability assertions (first call sees the captured value; a
changed value on a later call does not reach the backend).
task test); lint clean (task lint-fix).Out of Scope
P2.4 Replace discovery-into-context with direct VMCP calls #5442 — this issue is scoped to passthrough headers only.
through the shared client); the fix should capture/re-inject the header state, not reopen the
legacy connection model.
References
pkg/vmcp/headerforward(CaptureMiddleware,ForwardedHeadersFromContext,BuildHeaderForwardTripper)🤖 Generated with Claude Code