Skip to content

[BUG]: mcpgateway.wrapper lacks MCP session management — all post-initialize requests fail with HTTP 400 #4419

@jonpspri

Description

@jonpspri

Bug Summary

The mcpgateway.wrapper stdio bridge sends every JSON-RPC message as an independent HTTP POST to the gateway without tracking or forwarding the mcp-session-id header. After the MCP Streamable HTTP transport hardening (#3344), the gateway now strictly requires mcp-session-id for post-initialize requests. This means any wrapper session with more than one request failstools/list, tools/call, resources/read, etc. all return HTTP 400.

Affected Component

  • mcpgateway - API
  • Federation or Transports

Root Cause

mcpgateway/wrapper.py forward_once() (line 413-418) builds headers fresh for every request:

headers = {
    "Content-Type": "application/json; charset=utf-8",
    "Accept": "application/json, application/x-ndjson, text/event-stream",
}
if settings.auth_header:
    headers["Authorization"] = settings.auth_header
# No mcp-session-id. No mcp-protocol-version. No session tracking.

The wrapper's main_async() (line 640-662) creates independent async tasks for each stdin message — no session state is shared between them.

Grep for session_id, mcp-session, or any session tracking in the wrapper returns zero matches.

Steps to Reproduce

export MCP_SERVER_URL='http://localhost:8080'
export MCP_AUTH='Bearer <valid-token>'
python -m mcpgateway.wrapper

Then send via stdin:

{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2025-03-26", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0.0"}}}
{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}

Result:

  • id=1: Initialize succeeds, returns capabilities
  • id=2: Never arrives — instead get {"jsonrpc": "2.0", "id": "bridge", "error": {"code": 400, "message": "HTTP 400"}}

Expected Behavior

The wrapper should:

  1. Extract mcp-session-id from the initialize response headers
  2. Include it in all subsequent HTTP POST requests
  3. Optionally send mcp-protocol-version header per the Streamable HTTP spec

Impact

  • All tests/e2e/test_mcp_rbac_transport.py tests fail (14+ test cases) — they use wrapper-based helpers
  • Any production usage of the wrapper against a Streamable HTTP endpoint with multiple requests is broken
  • The test_mcp_protocol_e2e.py tests are unaffected (they use fastmcp.client.Client which handles sessions properly)

Code References

Location Description
mcpgateway/wrapper.py:392-458 forward_once() — stateless HTTP POST, no session headers
mcpgateway/wrapper.py:640-662 main_async() — independent tasks per message, no shared state
mcpgateway/wrapper.py:156-183 convert_url() — normalizes to /mcp/ (Streamable HTTP)
mcpgateway/wrapper.py:231-242 make_error() — generates id: 'bridge' sentinel for transport errors

Mitigation

The test_mcp_rbac_transport.py e2e tests are being migrated to use fastmcp.client.Client (same pattern as test_mcp_protocol_e2e.py) to unblock CI. The wrapper itself still needs a fix for production use.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmcp-protocolAlignment with MCP protocol or specificationtriageIssues / Features awaiting triage

    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