Skip to content

Latest commit

 

History

History
183 lines (138 loc) · 6.49 KB

File metadata and controls

183 lines (138 loc) · 6.49 KB

MCP Session ID Handling for HTTP Backends

Overview

This document explains how the MCP Gateway handles session IDs for HTTP backend servers, particularly the fix for the "Missing Mcp-Session-Id header" error.

Background

The Model Context Protocol (MCP) allows backend servers to be accessed over HTTP. Some HTTP MCP servers require the Mcp-Session-Id header to be present in all requests, including initialization requests like tools/list.

Problem Statement

When the MCP Gateway initializes, it calls tools/list on each backend server to discover available tools. For HTTP backends, these initialization calls were being made without a session ID in the context, which meant no Mcp-Session-Id header was being sent.

This caused HTTP backends that require the header to reject the request with:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32600,
    "message": "Invalid Request: Missing Mcp-Session-Id header"
  },
  "id": 1
}

Solution

The gateway now creates a temporary session ID for HTTP backend initialization calls. The session ID patterns are:

  • During initialization: awmg-init-{requestID} (temporary, replaced by server-issued session ID)
  • During client requests: The session ID from the client's Authorization header

Implementation Details

Code Changes

  1. File: internal/mcp/http_transport.go
    • Function: initializeHTTPSession
    • Change: Creates temporary session ID using awmg-init-{requestID} pattern during initialization
// Generate a temporary session ID for the initialize request
tempSessionID := fmt.Sprintf("awmg-init-%d", requestID)

// Execute HTTP request, setting the temporary session ID directly on the request header
result, err := c.executeHTTPRequest(ctx, "initialize", initParams, requestID, func(httpReq *http.Request) {
    httpReq.Header.Set("Mcp-Session-Id", tempSessionID)
})

// Capture the session ID issued by the server in the response header
sessionID := result.Header.Get("Mcp-Session-Id")
if sessionID == "" {
    // Fall back to temporary session ID if server doesn't return one
    sessionID = tempSessionID
}
// sessionID is then stored as c.httpSessionID for reuse in subsequent requests
  1. File: internal/mcp/http_transport.go
    • Function: sendHTTPRequest
    • Behavior: Adds Mcp-Session-Id header using the context session ID if present, otherwise falling back to the stored httpSessionID captured during initialization
// Add Mcp-Session-Id header with priority:
// 1) Context session ID (if explicitly provided for this request)
// 2) Stored httpSessionID from initialization
var sessionID string
if ctxSessionID, ok := ctx.Value(SessionIDContextKey).(string); ok && ctxSessionID != "" {
    sessionID = ctxSessionID
} else if c.httpSessionID != "" {
    sessionID = c.httpSessionID
}
if sessionID != "" {
    httpReq.Header.Set("Mcp-Session-Id", sessionID)
}

Session ID Flow

Initialization Flow

  1. Gateway starts up
  2. NewUnified creates the unified server
  3. registerAllTools is called
  4. For each HTTP backend, connection initialization occurs:
    • initializeHTTPSession creates temporary session ID: awmg-init-{requestID}
    • Initialize request sent with temporary session ID in Mcp-Session-Id header
    • Server responds with actual session ID in response header
    • Session ID is stored in connection for reuse
  5. For tool discovery, registerToolsFromBackend is called
  6. SendRequestWithServerID(ctx, "tools/list", ...) uses stored session ID
  7. HTTP backend receives request with established session ID

Client Request Flow (Routed Mode)

  1. Client sends request to /mcp/{serverID} with Authorization header
  2. Gateway extracts session ID from Authorization header
  3. Session ID is stored in request context
  4. Tool handler is called with context containing session ID
  5. SendRequestWithServerID(ctx, "tools/call", ...) is called
  6. For HTTP backends, sendHTTPRequest adds Mcp-Session-Id header with client's session ID
  7. HTTP backend receives request with client's session ID

Client Request Flow (Unified Mode)

Similar to routed mode, but all backends share the same endpoint /mcp.

Testing

Three comprehensive tests were added to verify the fix:

  1. TestHTTPBackendInitialization

    • Verifies that session ID is sent during initialization
    • Checks that session ID follows awmg-init-{requestID} pattern for temporary IDs
  2. TestHTTPBackendInitializationWithSessionIDRequirement

    • Simulates an HTTP backend that strictly requires the header
    • Fails with "Missing Mcp-Session-Id header" error if header is not present
    • Verifies tools are successfully registered with the fix
  3. TestHTTPBackend_SessionIDPropagation

    • Tests that different session IDs are used for initialization vs client calls
    • Verifies client session IDs are properly propagated to backend

Configuration

No configuration changes are required. The fix works automatically for all HTTP backends defined in the configuration:

[servers.my-http-backend]
type = "http"
url = "https://example.com/mcp"

Or in JSON format (stdin):

{
  "mcpServers": {
    "my-http-backend": {
      "type": "http",
      "url": "https://example.com/mcp"
    }
  }
}

Backward Compatibility

This change is fully backward compatible:

  • For HTTP backends that don't require the header: They will simply ignore it
  • For HTTP backends that require the header: They now work correctly
  • For stdio backends: No change in behavior (they don't use HTTP headers)

Debugging

To debug session ID handling, enable debug logging:

DEBUG=mcp:* ./awmg --config config.toml

This will show log messages like:

[mcp:connection] Sending initialize with temporary session ID: awmg-init-12345
[mcp:connection] Captured Mcp-Session-Id from response: session-abc123
[mcp:connection] Using stored session ID from initialization: session-abc123

Related Files

  • internal/server/unified.go - Main fix location
  • internal/mcp/http_transport.go - HTTP session initialization and request handling
  • internal/server/unified_http_backend_test.go - Comprehensive tests
  • internal/mcp/http_connection_test.go - Unit tests for HTTP session handling
  • internal/server/routed.go - Session ID extraction in routed mode
  • internal/server/transport.go - Session ID extraction in unified mode

References