Skip to content

Restore session-stable passthrough-header forwarding on the Serve path #5560

Description

@tgrunnagle

Description

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.CaptureMiddlewareForwardedHeadersFromContext) 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:

  1. Passthrough headers are forwarded (good), but
  2. 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.

Context

  • Discovered while implementing P3.2 Reduce server.New body to the wrapper #5445 (P3.2, "Reduce server.New to the wrapper").
  • 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).

Parent story: #5432 (Phase 3)
Relates to: #5442 (per-session backend affinity deferral), #5445 / PR #5556

Acceptance Criteria

  • 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).
  • Code reviewed and approved.

Out of Scope

  • The broader per-session backend MCP-session affinity (sticky backend session IDs) deferred in
    P2.4 Replace discovery-into-context with direct VMCP calls #5442 — this issue is scoped to passthrough headers only.
  • 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.

References

  • RFC THV-0076: vMCP Core Interface
  • pkg/vmcp/headerforward (CaptureMiddleware, ForwardedHeadersFromContext, BuildHeaderForwardTripper)

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssue needs initial triage by a maintainerrefactorvmcpVirtual MCP Server related issues

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions