MCP Session ID Handling for HTTP Backends
March 12, 2026 ยท View on GitHub
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
- File:
internal/mcp/http_transport.go- Function:
initializeHTTPSession - Change: Creates temporary session ID using
awmg-init-{requestID}pattern during initialization
- Function:
// 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
- File:
internal/mcp/http_transport.go- Function:
sendHTTPRequest - Behavior: Adds
Mcp-Session-Idheader using the context session ID if present, otherwise falling back to the storedhttpSessionIDcaptured during initialization
- Function:
// 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
- Gateway starts up
NewUnifiedcreates the unified serverregisterAllToolsis called- For each HTTP backend, connection initialization occurs:
initializeHTTPSessioncreates temporary session ID:awmg-init-{requestID}- Initialize request sent with temporary session ID in
Mcp-Session-Idheader - Server responds with actual session ID in response header
- Session ID is stored in connection for reuse
- For tool discovery,
registerToolsFromBackendis called SendRequestWithServerID(ctx, "tools/list", ...)uses stored session ID- HTTP backend receives request with established session ID
Client Request Flow (Routed Mode)
- Client sends request to
/mcp/{serverID}with Authorization header - Gateway extracts session ID from Authorization header
- Session ID is stored in request context
- Tool handler is called with context containing session ID
SendRequestWithServerID(ctx, "tools/call", ...)is called- For HTTP backends,
sendHTTPRequestaddsMcp-Session-Idheader with client's session ID - 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:
-
TestHTTPBackendInitialization
- Verifies that session ID is sent during initialization
- Checks that session ID follows
awmg-init-{requestID}pattern for temporary IDs
-
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
-
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 locationinternal/mcp/http_transport.go- HTTP session initialization and request handlinginternal/server/unified_http_backend_test.go- Comprehensive testsinternal/mcp/http_connection_test.go- Unit tests for HTTP session handlinginternal/server/routed.go- Session ID extraction in routed modeinternal/server/transport.go- Session ID extraction in unified mode