Skip to content

HTTP Transport

Chris edited this page Jul 5, 2026 · 159 revisions

HTTP Transport

🚀 Value Proposition

Expose mysql-mcp remotely via enterprise-grade HTTP transport. Support modern and legacy MCP clients simultaneously. Ensure security with OAuth 2.1 and strict rate limits. Run stateless or session-based setups. Connect multiple AI agents to one database safely.

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.


Decide 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.


Accelerate Your Deployment (Quick Start)

Important: HTTP transport mode requires --allowed-io-roots to be set to explicitly authorize filesystem access boundaries. The server will fail to start without it.

Local Installation

# Build the project first
pnpm run build

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

Docker

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

With Simple Bearer Auth

node dist/cli.js --transport http --port 3001 --allowed-io-roots /data --auth-token my-secret --mysql mysql://user:password@localhost:3306/database

Stateless Mode

node dist/cli.js --transport http --port 3001 --stateless --allowed-io-roots /data --mysql mysql://user:password@localhost:3306/database

Understand 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:3001/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:3001/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:3001/sse

# Send a message (in another terminal, using the sessionId from the SSE stream)
curl -X POST "http://localhost:3001/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)

Fortify Your 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

Session Timeouts

The HTTP transport enforces strict session lifecycle management to prevent memory leaks from disconnected clients:

  • Idle TTL: 30 minutes. If a session receives no requests, it is terminated.
  • Absolute TTL: 24 hours. Hard limit for any session duration.
  • Reaper Interval: 1 minute. A background sweep cleans up orphaned sessions. Sessions with in-flight requests are protected from early termination.

CORS

CORS is currently hardcoded to allow all origins (*) to facilitate easy connections from web-based clients and inspectors. Restricting origins via CLI flags is not yet supported.

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. Rate limiting is distributed across deployments via Redis if REDIS_URL is provided, with a graceful in-memory fallback.

  • /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).


Optimize Configuration Options

Environment Variables

For production deployments, use a structured .env file following fleet groupings:

# Server
MCP_TRANSPORT=http
MYSQLMCP_PORT=3001
MCP_HOST=0.0.0.0
TRUST_PROXY=true
MCP_RATE_LIMIT_MAX=100

# Database
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=app_user
MYSQL_PASSWORD=secure_password
MYSQL_DATABASE=production

CLI Arguments

Argument Environment Variable Default Description
--transport MCP_TRANSPORT stdio Transport type (stdio, http, or sse)
--port MYSQLMCP_PORT 3001 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
--enable-hsts MCP_ENABLE_HSTS false Enable HTTP Strict Transport Security
--metrics-export MCP_METRICS_EXPORT false Enable prometheus metrics endpoint /metrics
--allowed-io-roots ALLOWED_IO_ROOTS Explicitly authorize filesystem boundaries
--oauth-enabled OAUTH_ENABLED false Enable OAuth 2.1 authentication
--oauth-issuer OAUTH_ISSUER OAuth 2.1 issuer URL
--oauth-audience OAUTH_AUDIENCE OAuth 2.1 audience
--audit-log AUDIT_LOG_PATH Enable JSONL forensic logging and tokens

Secure Your Access: Using with OAuth 2.1

HTTP mode supports OAuth 2.1 authentication for enterprise deployments:

node dist/cli.js \
  --transport http \
  --port 3001 \
  --allowed-io-roots /data \
  --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.


Connect Your MCP Clients

Using MCP Inspector

Test your HTTP server with MCP Inspector:

# Start the server
node dist/cli.js --transport http --port 3001 --allowed-io-roots /data --mysql mysql://...

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

Health Check

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

Launch Your Docker Deployment

Basic Deployment

# Build image
docker build -t mysql-mcp .

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

Docker Compose

services:
  mysql-mcp:
    image: writenotenow/mysql-mcp:latest
    ports:
      - "3001:3001"
    command:
      - --transport
      - http
      - --port
      - "3001"
      - --allowed-io-roots
      - /data
      - --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:

Scale with Cloud Deployment

AWS ECS / Fargate

  1. Push Docker image to ECR
  2. Create ECS task definition with port 3001 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 3001 \
  --set-env-vars MYSQL_HOST=...,MYSQL_USER=...,MYSQL_PASSWORD=... \
  --command "--transport,http,--port,3001,--allowed-io-roots,/data"

Azure Container Instances

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

Resolve Issues Fast (Troubleshooting)

Connection Refused

Problem: Client cannot connect to the server

Solutions:

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

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.

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

Expand Your Knowledge (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