Skip to content

MCPServerEntry.headerForward.addPlaintextHeaders is silently ignored — never reaches upstream #5289

@lorr1

Description

@lorr1

Summary

MCPServerEntry.spec.headerForward.addPlaintextHeaders is silently ignored. Headers configured on a remote-URL MCPServerEntry (proxied through a VirtualMCPServer) never reach the upstream MCP server. The field is defined in the CRD and accepted by the operator, but is dropped during the conversion from CR to runtime vmcp.Backend and has no representation anywhere in the vMCP outgoing HTTP client.

Verified in v0.27.1.

Reproduction

MCPServerEntry configured against GitHub's Copilot MCP at api.githubcopilot.com/mcp/, with the X-MCP-Toolsets header configured to select a specific set of toolsets:

apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPServerEntry
metadata:
  name: github-copilot-projects
spec:
  remoteUrl: "https://api.githubcopilot.com/mcp/"
  transport: streamable-http
  externalAuthConfigRef:
    name: github-oauth-upstream-inject
  headerForward:
    addPlaintextHeaders:
      X-MCP-Toolsets: "projects,issues,pull_requests,users,repos"

Direct call to https://api.githubcopilot.com/mcp/ with the same token + same X-MCP-Toolsets header returns 39 tools strictly filtered to the 5 requested toolsets, including projects_get, projects_list, projects_write.

Through the vMCP, the upstream returns 43 tools matching GitHub's default toolset for the Copilot endpoint — which includes copilot, orgs, secret_protection tools (NOT requested) and is missing projects_* (which IS requested). The fingerprint matches "header not sent → GitHub returns defaults."

Root cause

Source trace in v0.27.1:

  • CRD field exists: cmd/thv-operator/api/v1beta1/mcpserverentry_types.go:38-41 defines HeaderForward *HeaderForwardConfig; cmd/thv-operator/api/v1beta1/mcpremoteproxy_types.go:12-16 defines HeaderForwardConfig.AddPlaintextHeaders.

  • Reconciler doesn't read it: pkg/vmcp/workloads/k8s.go:503-586 (mcpServerEntryToBackend) reads RemoteURL, Transport, CABundleRef, ExternalAuthConfigRef — but never reads entry.Spec.HeaderForward.

  • No field to carry it: pkg/vmcp/types.go:290-333 vmcp.Backend has no field for static headers.

  • No middleware to apply it: pkg/vmcp/client/client.go:265-311 shows the vMCP outgoing HTTP client's transport chain is size limiter → trace propagation → identity propagation → auth → HTTP. There is no static-header layer.

  • MCPRemoteProxy works via a separate code path: cmd/thv-operator/controllers/mcpremoteproxy_runconfig.go:270-297 calls runner.WithHeaderForward(proxy.Spec.HeaderForward.AddPlaintextHeaders), which flows through pkg/runner/middleware.go:301-310 into pkg/transport/middleware/header_forward.go:100-137. MCPServerEntry bypasses RunConfig entirely and goes straight to the vMCP Backend struct, so it never picks up this middleware.

Suggested fix

  1. Add a HeaderForward map[string]string field (or equivalent) to vmcp.Backend in pkg/vmcp/types.go.
  2. Populate it from entry.Spec.HeaderForward.AddPlaintextHeaders in mcpServerEntryToBackend (pkg/vmcp/workloads/k8s.go).
  3. Add a static-header middleware layer in the vMCP HTTP client transport chain (pkg/vmcp/client/client.go) that reads the per-backend headers and injects them into outgoing requests.
  4. Consider doing the same for addHeadersFromSecret so secret-sourced headers are also forwarded.

Impact

Any vMCP backend that depends on header-based configuration of the upstream (e.g. GitHub's X-MCP-Toolsets, API keys passed via custom headers, correlation IDs) silently does nothing. The field accepting valid input without applying it is the worst form — there is no validation error, no warning in logs, nothing in kubectl describe to indicate the headers aren't being sent.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingkubernetesItems related to Kubernetesoperator

    Type

    No type
    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