MCP Framework
Transports

HTTP Stream Transport

Modern HTTP-based transport with streaming, session management, and authentication

HTTP Stream Transport

The HTTP Stream Transport is the recommended transport mechanism for web-based MCP applications, implementing the Streamable HTTP transport protocol from the MCP specification version 2025-03-26.

Overview

The HTTP Stream Transport provides a modern, flexible transport layer that supports both batch responses and streaming via Server-Sent Events (SSE). It offers advanced features like session management, resumable streams, and comprehensive authentication options.

Key Features

  • Single Endpoint: Uses a single HTTP endpoint for all MCP communication
  • Multiple Response Modes: Support for both batch (JSON) and streaming (SSE) responses
  • Session Management: Built-in session tracking and management
  • Resumability: Support for resuming broken SSE connections
  • Authentication: Comprehensive authentication support
  • CORS: Flexible CORS configuration for web applications

Configuration

The HTTP Stream Transport supports extensive configuration options:

import { MCPServer } from "mcp-framework";

const server = new MCPServer({
  transport: {
    type: "http-stream",
    options: {
      port: 8080,                // Port to listen on (default: 8080)
      host: "127.0.0.1",         // Host to bind to (default: "127.0.0.1")
      endpoint: "/mcp",          // HTTP endpoint path (default: "/mcp")
      responseMode: "batch",     // Response mode: "batch" or "stream" (default: "batch")
      maxMessageSize: "4mb",     // Maximum message size (default: "4mb")
      batchTimeout: 30000,       // Timeout for batch responses in ms (default: 30000)
      headers: {                 // Custom headers for responses
        "X-Custom-Header": "value"
      },
      cors: {                    // CORS configuration
        allowOrigin: "*",
        allowMethods: "GET, POST, DELETE, OPTIONS",
        allowHeaders: "Content-Type, Accept, Authorization, x-api-key, Mcp-Session-Id, Last-Event-ID",
        exposeHeaders: "Content-Type, Authorization, x-api-key, Mcp-Session-Id",
        maxAge: "86400"
      },
      auth: {                    // Authentication configuration
        provider: authProvider
      },
      session: {                 // Session configuration
        enabled: true,           // Enable session management (default: true)
        headerName: "Mcp-Session-Id", // Session header name (default: "Mcp-Session-Id")
        allowClientTermination: true  // Allow clients to terminate sessions (default: true)
      },
      resumability: {            // Stream resumability configuration
        enabled: false,          // Enable stream resumability (default: false)
        historyDuration: 300000  // How long to keep message history in ms (default: 300000 - 5 minutes)
      }
    }
  }
});

await server.start();

Quick Start Configuration

For a simple setup with recommended defaults, you can use:

import { MCPServer } from "mcp-framework";

const server = new MCPServer({
  transport: {
    type: "http-stream",
    options: {
      port: 8080,
      cors: {
        allowOrigin: "*"
      }
    }
  }
});

await server.start();

Using CLI to Create a Project with HTTP Transport

You can use the MCP Framework CLI to create a new project with HTTP transport enabled:

mcp create my-mcp-server --http --port 1337 --cors

This will create a new project with HTTP transport configured on port 1337 with CORS enabled.

Configuration Options

Port, Host, and Endpoint

  • port: The HTTP port to listen on (default: 8080)
  • host: The host address to bind to (default: "127.0.0.1")
  • endpoint: The endpoint path for all MCP communication (default: "/mcp")

The server binds to 127.0.0.1 by default for security. Set host: '0.0.0.0' to accept connections from other machines (required for Docker, cloud platforms).

transport: {
  type: "http-stream",
  options: {
    port: 8080,
    host: "0.0.0.0", // Accept connections from any network interface
  }
}

Response Mode

The responseMode option controls how the server responds to client requests:

  • batch: Collects all responses for a request batch and sends them as a single JSON response (default)
  • stream: Opens an SSE stream for each request, allowing streaming responses
transport: {
  type: "http-stream",
  options: {
    responseMode: "batch" // or "stream"
  }
}

Batch mode is more efficient for simple operations, while stream mode is better for long-running operations that may benefit from progressive responses.

Batch Timeout

When using batch mode, the batchTimeout option controls how long the server will wait for all responses to be collected before sending the batch:

batchTimeout: 30000 // 30 seconds (default)

Message Size Limit

The maxMessageSize option controls the maximum allowed size for incoming messages:

maxMessageSize: "4mb" // default

CORS Configuration

The HTTP Stream Transport provides comprehensive CORS support:

cors: {
  allowOrigin: "*",                // Access-Control-Allow-Origin
  allowMethods: "GET, POST, DELETE, OPTIONS", // Access-Control-Allow-Methods
  allowHeaders: "Content-Type, Accept, Authorization, x-api-key, Mcp-Session-Id, Last-Event-ID", // Access-Control-Allow-Headers
  exposeHeaders: "Content-Type, Authorization, x-api-key, Mcp-Session-Id", // Access-Control-Expose-Headers
  maxAge: "86400"                 // Access-Control-Max-Age
}

Origin Validation (DNS Rebinding Protection)

When allowedOrigins is configured within the cors block, the server validates the Origin header on every incoming request to protect against DNS rebinding attacks:

cors: {
  allowedOrigins: ["http://localhost:3000", "https://myapp.example.com"],
  allowOrigin: "*",  // CORS Access-Control-Allow-Origin (separate from origin validation)
  allowMethods: "GET, POST, DELETE, OPTIONS",
  allowHeaders: "Content-Type, Accept, Authorization, x-api-key, Mcp-Session-Id, Last-Event-ID",
  exposeHeaders: "Content-Type, Authorization, x-api-key, Mcp-Session-Id",
  maxAge: "86400"
}

How origin validation works:

  • Requests with an Origin header that does not match any entry in allowedOrigins receive an HTTP 403 Forbidden response.
  • Requests without an Origin header (non-browser clients like curl, SDKs, and other server-side callers) are allowed through, since they are not subject to DNS rebinding.
  • Origin: null is always rejected when allowedOrigins is set. This is a security best practice, as null origins can be crafted by malicious pages.
  • When allowedOrigins is not configured, all origins are allowed. This preserves backwards compatibility with existing deployments.

For production deployments, always configure allowedOrigins to prevent DNS rebinding attacks.

Session Management

The HTTP Stream Transport provides built-in session management capabilities:

session: {
  enabled: true,                  // Enable session management (default: true)
  headerName: "Mcp-Session-Id",   // Session header name (default: "Mcp-Session-Id")
  allowClientTermination: true,   // Allow clients to terminate sessions (default: true)
  reinitializationMode: 'smart',  // How to handle repeated init requests (default: 'smart')
  debounceTime: 100              // Debounce time for rapid init requests in ms (default: 100)
}

When sessions are enabled:

  • A unique session ID is generated during initialization
  • The session ID is included in the Mcp-Session-Id header of the server's response
  • Clients must include this session ID in subsequent requests
  • Sessions can be explicitly terminated by clients via a DELETE request (if allowed)

Handling Rapid Initialization Requests

The reinitializationMode option controls how the transport handles repeated initialization requests:

  • 'recreate': Always recreate the transport (legacy behavior)
  • 'reuse': Reuse the existing session without recreation
  • 'smart': Intelligently handle rapid initialization requests with debouncing (default)

The default 'smart' mode prevents issues caused by clients that send multiple initialization requests in rapid succession. This mode:

  • Debounces rapid requests (if multiple init requests come within 100ms, they're grouped)
  • Only recreates the transport once after the debounce period
  • Prevents race conditions and 500 errors that can occur with rapid session recreation
// Example: Handling rapid initialization gracefully
const server = new MCPServer({
  transport: {
    type: "http-stream",
    options: {
      port: 8080,
      cors: "*",
      // Smart session management is enabled by default
      // No need to configure unless you want to change the behavior
    }
  }
});

If you need to customize the session handling:

session: {
  reinitializationMode: 'reuse',  // Never recreate, always reuse existing session
  // or
  reinitializationMode: 'recreate', // Always recreate (legacy behavior)
  // or  
  reinitializationMode: 'smart',    // Intelligent debouncing (default)
  debounceTime: 200                 // Increase debounce window to 200ms
}

Stream Resumability

The HTTP Stream Transport can maintain message history to support resuming broken SSE connections:

resumability: {
  enabled: false,               // Enable stream resumability (default: false)
  historyDuration: 300000       // How long to keep message history in ms (default: 300000 - 5 minutes)
}

When enabled:

  • Each SSE event is assigned a unique ID
  • Clients can reconnect and provide the last received event ID using the Last-Event-ID header
  • The server will replay missed messages since that event ID

HTTP Methods

The HTTP Stream Transport uses the following HTTP methods:

  • POST: For sending client requests, notifications, and responses
  • GET: For establishing SSE streams for receiving server messages
  • DELETE: For terminating sessions (when session.allowClientTermination is enabled)
  • OPTIONS: For CORS preflight requests

Client Implementation

Here's an example of how to implement a client for the HTTP Stream Transport:

/**
 * Basic client for the HTTP Stream Transport
 */
class HttpStreamClient {
  private baseUrl: string;
  private sessionId: string | null = null;
  private eventSource: EventSource | null = null;
  
  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }
  
  async initialize() {
    // Create initialization request
    const initRequest = {
      jsonrpc: "2.0",
      id: "init-" + Date.now(),
      method: "initialize",
      params: { /* initialization parameters */ }
    };
    
    // Send initialize request
    const response = await fetch(this.baseUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json, text/event-stream'
      },
      body: JSON.stringify(initRequest)
    });
    
    // Get session ID from response headers
    this.sessionId = response.headers.get('Mcp-Session-Id');
    console.log(`Session established: ${this.sessionId}`);
    
    // Process the response
    if (response.headers.get('Content-Type')?.includes('text/event-stream')) {
      // Handle streaming response
      this.processStream(response);
    } else {
      // Handle JSON response
      const result = await response.json();
      console.log('Initialization result:', result);
    }
    
    // Open SSE stream for server-to-client messages
    this.openEventStream();
  }
  
  private openEventStream() {
    const url = new URL(this.baseUrl);
    if (this.sessionId) {
      url.searchParams.append('session', this.sessionId);
    }
    
    this.eventSource = new EventSource(url.toString());
    
    this.eventSource.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data);
        console.log('Received SSE message:', message);
        // Process message...
      } catch (e) {
        console.error('Error parsing SSE message:', e);
      }
    };
    
    this.eventSource.onerror = (error) => {
      console.error('SSE connection error:', error);
      this.reconnectEventStream();
    };
    
    console.log('SSE stream opened');
  }
  
  private reconnectEventStream() {
    if (this.eventSource) {
      this.eventSource.close();
      this.eventSource = null;
    }
    
    setTimeout(() => this.openEventStream(), 1000);
  }
  
  private async processStream(response: Response) {
    const reader = response.body?.getReader();
    if (!reader) return;
    
    const decoder = new TextDecoder();
    let buffer = "";
    
    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        
        buffer += decoder.decode(value, { stream: true });
        
        // Process SSE events in buffer
        const events = buffer.split("\n\n");
        buffer = events.pop() || "";
        
        for (const event of events) {
          const lines = event.split("\n");
          const data = lines.find(line => line.startsWith("data:"))?.slice(5);
          
          if (data) {
            try {
              const message = JSON.parse(data);
              console.log('Received stream message:', message);
              // Process message...
            } catch (e) {
              console.error('Error parsing stream message:', e);
            }
          }
        }
      }
    } catch (e) {
      console.error('Error reading stream:', e);
    }
  }
  
  async sendRequest(method: string, params: any = {}) {
    if (!this.sessionId) {
      throw new Error('Session not initialized');
    }
    
    const request = {
      jsonrpc: "2.0",
      id: method + "-" + Date.now(),
      method,
      params
    };
    
    const response = await fetch(this.baseUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json, text/event-stream',
        'Mcp-Session-Id': this.sessionId
      },
      body: JSON.stringify(request)
    });
    
    if (response.headers.get('Content-Type')?.includes('text/event-stream')) {
      // Handle streaming response
      this.processStream(response);
      return null; // Response will be processed asynchronously
    } else {
      // Handle JSON response
      return await response.json();
    }
  }
  
  async terminate() {
    if (!this.sessionId) return;
    
    if (this.eventSource) {
      this.eventSource.close();
      this.eventSource = null;
    }
    
    try {
      await fetch(this.baseUrl, {
        method: 'DELETE',
        headers: {
          'Mcp-Session-Id': this.sessionId
        }
      });
      console.log('Session terminated');
    } catch (e) {
      console.error('Error terminating session:', e);
    }
    
    this.sessionId = null;
  }
}

Security Considerations

  1. HTTPS: Always use HTTPS in production environments
  2. Authentication: Enable authentication for all endpoints
  3. CORS: Configure appropriate CORS settings for your environment
  4. Origin Validation: Configure allowedOrigins to prevent DNS rebinding attacks
  5. Host Binding: Keep the default 127.0.0.1 binding unless external access is required
  6. Message Size: Set appropriate message size limits
  7. Session Timeout: Implement session timeout logic for production use
  8. Rate Limiting: Implement rate limiting for production use

Backward Compatibility

The HTTP Stream Transport is designed to replace the deprecated SSE Transport while maintaining compatibility with the MCP protocol. If you're migrating from the SSE Transport:

  1. Update your server configuration to use type: "http-stream" instead of type: "sse"
  2. Update your client to use the single endpoint pattern instead of separate endpoints for SSE and messages
  3. Implement session management using the Mcp-Session-Id header

Error Handling

The transport includes comprehensive error handling, with appropriate HTTP status codes and JSON-RPC error responses:

  • 400 Bad Request: Invalid JSON, invalid message format
  • 401 Unauthorized: Authentication failure
  • 404 Not Found: Invalid session ID
  • 405 Method Not Allowed: Unsupported HTTP method
  • 406 Not Acceptable: Missing required Accept header
  • 413 Payload Too Large: Message size exceeds limit
  • 429 Too Many Requests: Rate limit exceeded
  • 500 Internal Server Error: Server-side errors

JSON-RPC error responses follow the standard format with detailed information:

{
  "jsonrpc": "2.0",
  "id": "request-id",
  "error": {
    "code": -32000,
    "message": "Error message",
    "data": {
      // Additional error information
    }
  }
}

HTTP QUICKSTART [EXPERIMENTAL]

Ready to build your first HTTP-based MCP server? Follow our HTTP Quickstart Guide to create and run a project using the HTTP Stream Transport in just a few minutes.