stacklok / stacklok/toolhive

Transparent proxy cannot serve stateless streamable-http backends (no client-facing Mcp-Session-Id when the backend issues none)

Open
#5,796 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
2.2k
Forks
300
Avg merge
1d 15h
Merged PRs (30d)
184

Description

Description

The transparent proxy (used by both MCPServer and MCPRemoteProxy with transport: streamable-http) cannot serve a stateless streamable-http backend — one that, as the spec allows, never issues an Mcp-Session-Id. The proxy runs stateful session semantics unconditionally, but its client-facing session id comes only from capturing the backend's initialize response header. When the backend issues none:

  • the proxy's initialize response to the client carries no Mcp-Session-Id header (body passes through fine);
  • a subsequent tools/list without a session id returns HTTP 200 with an empty body;
  • a client-generated Mcp-Session-Id is rejected with {"error":{"code":-32001,"message":"Session not found"},"id":null,"jsonrpc":"2.0"};
  • GET /mcp returns 405 with Allow: DELETE, POST, confirming the proxy is in stateful mode with the session lifecycle active.

So a spec-compliant stateless backend (per the streamable-http transport spec, a server MAY assign a session ID at initialization time — session management is optional) is unusable behind the proxy: clients can initialize but never reach tools/list/tools/call.

Session pass-through itself works correctly: with a session-issuing backend (googleapis/mcp-toolbox 1.6.0, which issues Mcp-Session-Id when the client initializes with protocolVersion: 2025-03-26) the proxy forwards the backend's session id to the client and the full flow (initializenotifications/initializedtools/listtools/call) succeeds end to end. The gap is only the stateless-backend case.

Steps to reproduce

  1. Deploy the operator (v0.34.0) and an MCPRemoteProxy (or MCPServer) with transport: streamable-http pointing at any stateless streamable-http MCP server. Two easy backends that reproduce it:
    • a FastMCP (Python) server with stateless_http=True;
    • googleapis/mcp-toolbox when the client initializes with protocolVersion: 2025-06-18 (it issues session ids only for 2025-03-26).
  2. POST /mcp initialize through the proxy → 200, valid body, no Mcp-Session-Id response header (verified with curl -v directly against the proxy pod via port-forward, so no ingress is involved).
  3. POST /mcp tools/list → HTTP 200, Content-Type per backend, empty body (0 bytes). Directly against the backend the same request returns the full tool list.
  4. POST /mcp tools/list with a client-generated Mcp-Session-Id-32001 Session not found.

Expected behavior

Either the proxy synthesizes and manages a client-facing session id when the backend issues none (bridging stateful clients to stateless backends), or an explicit stateless mode is exposed in the operator CRDs. The RunConfig stateless flag exists internally (thv run --stateless) but is not exposed on MCPRemoteProxy/MCPServer (kubectl explain mcpremoteproxy.spec.stateless → field does not exist, v0.34.0), and it appears to gate only HTTP methods and health checks rather than session handling.

Actual behavior

Stateless backends are unreachable through the proxy beyond initialize (details above).

Environment

  • ToolHive operator v0.34.0 (operator mode, Kubernetes v1.26.6); code paths look unchanged as of v0.36.0
  • Backends verified: FastMCP with stateless_http=True; googleapis/mcp-toolbox 1.6.0 at protocolVersion: 2025-06-18

Additional context

Not all MCP servers need sessions; for a server that uses no stateful features they are pure protocol overhead, which is presumably why the upcoming 2026-07-28 revision removes them — I see #5743 / #5755 already design for that. This report is about the current stable revisions, where stateless servers are already legal and already break behind the proxy today; an interim fix (or folding "current-spec stateless backends" explicitly into that workstream) would unblock gateways fronting such servers now. Related art in another MCP gateway: agentgateway/agentgateway#221.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing streamable-http handling for MCPRemoteProxy and MCPServer, including the RunConfig stateless flag and the thv run --stateless entry point. Reproduce the initialize and tools/list requests against a backend that omits Mcp-Session-Id, then inspect the operator CRD fields. Done means a compliant stateless backend can complete tools/list and tools/call through the proxy, or an explicit supported stateless mode is exposed.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, kubernetes
Domain
backend-api-design, cloud
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.