Skip to content

HTTP Transport

Chris & Mike edited this page Mar 12, 2026 · 159 revisions

HTTP Transport

This page explains how to run mysql-mcp as an HTTP server supporting two MCP transport protocols simultaneously — Streamable HTTP for modern clients and legacy SSE for backward compatibility.


When to Use HTTP Mode

Use HTTP mode when:

  • Deploying the server to a remote location (cloud, Docker container)
  • Multiple AI clients need to connect to the same database
  • You need OAuth 2.1 authentication for enterprise security
  • Running the server as a standalone network service
  • Serverless/stateless deployments (--stateless)

Use stdio mode (default) when:

  • Running locally with Claude Desktop or Cursor IDE
  • Single-user development environment
  • Simplest setup with no network configuration needed

💡 Tip: Most users should use stdio mode. HTTP mode is for advanced deployments.


Quick Start

Local Installation

# Build the project first
npm run build

# Start HTTP server on port 3000
node dist/cli.js --transport http --port 3000 --server-host 0.0.0.0 --mysql mysql://user:password@localhost:3306/database

Docker

# Run with port mapping
docker run -p 3000:3000 writenotenow/mysql-mcp \
  --transport http \
  --port 3000 \
  --server-host 0.0.0.0 \
  --mysql mysql://user:password@host.docker.internal:3306/database

With Simple Bearer Auth

node dist/cli.js --transport http --port 3000 --auth-token my-secret --mysql mysql://user:password@localhost:3306/database

Stateless Mode

node dist/cli.js --transport http --port 3000 --stateless --mysql mysql://user:password@localhost:3306/database

Transport Protocols

The HTTP transport supports two MCP protocol versions simultaneously, allowing both modern and legacy clients to connect to the same server.

Streamable HTTP (Recommended)

Modern protocol (MCP 2025-03-26) — single endpoint, session-based:

Method Endpoint Purpose
POST /mcp JSON-RPC requests (initialize, tools/list, etc.)
GET /mcp SSE stream for server notifications
DELETE /mcp Session termination

In stateless mode (--stateless): GET /mcp returns 405, DELETE /mcp returns 204, /sse and /messages return 404. Each POST /mcp creates a fresh transport with no session persistence.

Sessions are managed via the Mcp-Session-Id header. The server returns a session ID in the response to initialize, and clients must include it in subsequent requests.

Example — Initialize a session:

curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"my-client","version":"1.0.0"}}}'

Example — List tools (with session):

curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Mcp-Session-Id: <session-id-from-initialize>" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

Important: Streamable HTTP requests must include Accept: application/json, text/event-stream — the server may respond with either format.

Legacy SSE (Backward Compatibility)

Legacy protocol (MCP 2024-11-05) — for older MCP clients:

Method Endpoint Purpose
GET /sse Opens SSE stream, returns /messages?sessionId=<id> endpoint
POST /messages?sessionId=<id> Send JSON-RPC messages to the session

Example:

# Establish SSE connection (will stream events)
curl -N http://localhost:3000/sse

# Send a message (in another terminal, using the sessionId from the SSE stream)
curl -X POST "http://localhost:3000/messages?sessionId=<id>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"my-client","version":"1.0.0"}}}'

Cross-Protocol Guard

Sessions are bound to their transport protocol. An SSE session ID cannot be used on the /mcp endpoint and vice versa — the server returns 400 Bad Request with a descriptive error.

Utility Endpoints

Method Endpoint Purpose
GET /health Health check (returns {"status":"healthy"})
GET /.well-known/oauth-protected-resource OAuth 2.1 metadata (when OAuth is enabled)

Security Features

Security Headers

All HTTP responses include the following headers (6 base, 7 with HSTS):

Header Value
X-Content-Type-Options nosniff
X-Frame-Options DENY
Cache-Control no-store, no-cache, must-revalidate
Content-Security-Policy default-src 'none'; frame-ancestors 'none'
Permissions-Policy camera=(), microphone=(), geolocation=()
Referrer-Policy no-referrer
Strict-Transport-Security Opt-in via enableHSTS (for HTTPS)

Server Timeouts (Slowloris Protection)

The HTTP server applies three timeout layers to prevent slow-connection DoS attacks:

Timeout Value Purpose
requestTimeout 120,000 ms Maximum time for the entire request lifecycle
keepAliveTimeout 65,000 ms Idle time before closing keep-alive connections
headersTimeout 66,000 ms Maximum time to receive complete headers

CORS

Configure allowed origins with --cors-origins:

# Allow specific origins
node dist/cli.js --transport http --cors-origins "https://example.com,https://app.example.com" --mysql ...

# Wildcard subdomain pattern
node dist/cli.js --transport http --cors-origins "*.example.com" --mysql ...

# Allow all origins (default)
node dist/cli.js --transport http --mysql ...

Wildcard subdomain patterns (e.g., *.example.com) match any subdomain (app.example.com, staging.example.com) but not the bare domain (example.com).

Trust Proxy

When running behind a reverse proxy (nginx, ALB, Cloudflare, etc.), enable trustProxy to read the client IP from X-Forwarded-For instead of the socket address. This ensures rate limiting and logging use the real client IP.

Rate Limiting

Per-IP request throttling is enabled by default to prevent abuse. The rate limiter uses a sliding window approach with deterministic cleanup.

  • /health bypass — Health check requests are served before rate limiting is checked, ensuring monitoring probes always succeed regardless of per-IP quotas
  • Retry-After header — Rate-limited responses (429 Too Many Requests) include a Retry-After header indicating seconds until the window resets
  • Environment override — Set MCP_RATE_LIMIT_MAX to customize the per-IP request limit (default: 100 requests/minute)

Body Size Enforcement

Request body size is enforced with two layers:

  1. Content-Length header check — fast rejection for well-behaved clients
  2. Streaming byte tracking — catches missing/spoofed headers and chunked encoding

Default maximum body size: 1 MB (1,048,576 bytes).


Configuration Options

CLI Arguments

Argument Environment Variable Default Description
--transport MCP_TRANSPORT stdio Transport type (stdio, http, or sse)
--port PORT 3000 HTTP server port
--server-host MCP_HOST localhost Host to bind HTTP transport to
--auth-token MCP_AUTH_TOKEN Simple bearer token for HTTP auth
--stateless false Stateless HTTP mode (no sessions, no SSE)
--trust-proxy TRUST_PROXY false Trust X-Forwarded-For for client IP
--cors-origins CORS_ORIGINS * Comma-separated allowed origins

Using with OAuth 2.1

HTTP mode supports OAuth 2.1 authentication for enterprise deployments:

node dist/cli.js \
  --transport http \
  --port 3000 \
  --mysql mysql://user:password@localhost:3306/database \
  --oauth-enabled \
  --oauth-issuer https://your-keycloak.com/realms/mysql-mcp \
  --oauth-audience mysql-mcp

See the OAuth page for complete setup instructions.


Connecting MCP Clients

Using MCP Inspector

Test your HTTP server with MCP Inspector:

# Start the server
node dist/cli.js --transport http --port 3000 --mysql mysql://...

# In another terminal, connect Inspector
npx @modelcontextprotocol/inspector http://localhost:3000/sse

Health Check

curl http://localhost:3000/health
# {"status":"healthy","timestamp":"2026-03-05T..."}

Docker Deployment

Basic Deployment

# Build image
docker build -t mysql-mcp .

# Run container with port mapping
docker run -d \
  --name mysql-mcp-server \
  -p 3000:3000 \
  mysql-mcp \
  --transport http \
  --port 3000 \
  --server-host 0.0.0.0 \
  --mysql mysql://user:password@host.docker.internal:3306/database

Docker Compose

services:
  mysql-mcp:
    image: writenotenow/mysql-mcp:latest
    ports:
      - "3000:3000"
    command:
      - --transport
      - http
      - --port
      - "3000"
      - --mysql
      - mysql://user:password@mysql:3306/database
    environment:
      - MYSQL_POOL_SIZE=20
    depends_on:
      - mysql

  mysql:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: database
    volumes:
      - mysql-data:/var/lib/mysql

volumes:
  mysql-data:

Cloud Deployment

AWS ECS / Fargate

  1. Push Docker image to ECR
  2. Create ECS task definition with port 3000 exposed
  3. Configure ALB to route traffic to the container
  4. Set environment variables for MySQL connection

Google Cloud Run

# Build and push to GCR
gcloud builds submit --tag gcr.io/PROJECT_ID/mysql-mcp

# Deploy to Cloud Run
gcloud run deploy mysql-mcp \
  --image gcr.io/PROJECT_ID/mysql-mcp \
  --port 3000 \
  --set-env-vars MYSQL_HOST=...,MYSQL_USER=...,MYSQL_PASSWORD=... \
  --command "--transport,http,--port,3000"

Azure Container Instances

az container create \
  --resource-group myResourceGroup \
  --name mysql-mcp \
  --image writenotenow/mysql-mcp:latest \
  --ports 3000 \
  --environment-variables \
    MYSQL_HOST=... \
    MYSQL_USER=... \
    MYSQL_PASSWORD=... \
  --command-line "--transport http --port 3000"

Troubleshooting

Connection Refused

Problem: Client cannot connect to the server

Solutions:

  • Verify server is running: curl http://localhost:3000/health
  • Check firewall rules allow port 3000
  • Ensure --server-host 0.0.0.0 if connecting from another machine
  • Check Docker port mapping: -p 3000:3000

406 Not Acceptable (Streamable HTTP)

Problem: POST /mcp returns 406

Solution: Include the required Accept header:

Accept: application/json, text/event-stream

The StreamableHTTPServerTransport requires clients to accept both JSON and SSE response formats.

CORS Errors

Problem: Browser-based clients show CORS errors

Solution: Add allowed origins:

--cors-origins "https://your-client-domain.com"

Session Not Found

Problem: Requests return 400 Bad Request or 404 Not Found with session-related errors

Solutions:

  • Ensure you're sending the Mcp-Session-Id header from the initialize response
  • SSE sessions cannot be used on /mcp and vice versa (cross-protocol guard)
  • Sessions expire when the client disconnects

OAuth Authentication Failures

Problem: Requests return 401 Unauthorized

Solutions:

  • Verify OAuth issuer URL is correct
  • Check token audience matches --oauth-audience
  • Ensure JWKS URI is accessible from the server
  • See OAuth troubleshooting section

See Also

MySQL MCP Documentation

Unlock autonomous database orchestration with an enterprise-grade MySQL MCP server. Featuring blazing-fast sandboxed Code Mode, uncompromising schema enforcement, and seamless ecosystem integrations to power secure, intelligent AI workflows.

🏠 Home


Launch Your Setup


Connect Ecosystem Tools


Enforce Security & Compliance


Scale Your Operations


Explore External Links

Clone this wiki locally