-
Notifications
You must be signed in to change notification settings - Fork 2
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.
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.
Important: HTTP transport mode requires
--allowed-io-rootsto be set to explicitly authorize filesystem access boundaries. The server will fail to start without it.
# Build the project first
pnpm 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# 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/databasenode dist/cli.js --transport http --port 3000 --auth-token my-secret --mysql mysql://user:password@localhost:3306/databasenode dist/cli.js --transport http --port 3000 --stateless --mysql mysql://user:password@localhost:3306/databaseThe HTTP transport supports two MCP protocol versions simultaneously, allowing both modern and legacy clients to connect to the same server.
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 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"}}}'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.
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/health |
Health check (returns {"status":"healthy"}) |
GET |
/.well-known/oauth-protected-resource |
OAuth 2.1 metadata (when OAuth is enabled) |
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) |
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 |
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 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.
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.
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.
-
/healthbypass — Health check requests are served before rate limiting is checked, ensuring monitoring probes always succeed regardless of per-IP quotas -
Retry-Afterheader — Rate-limited responses (429 Too Many Requests) include aRetry-Afterheader indicating seconds until the window resets -
Environment override — Set
MCP_RATE_LIMIT_MAXto customize the per-IP request limit (default: 100 requests/minute)
Request body size is enforced with two layers:
- Content-Length header check — fast rejection for well-behaved clients
- Streaming byte tracking — catches missing/spoofed headers and chunked encoding
Default maximum body size: 1 MB (1,048,576 bytes).
For production deployments, use a structured .env file following fleet groupings:
# Server
MCP_TRANSPORT=http
MYSQLMCP_PORT=3000
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| Argument | Environment Variable | Default | Description |
|---|---|---|---|
--transport |
MCP_TRANSPORT |
stdio |
Transport type (stdio, http, or sse) |
--port |
MYSQLMCP_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 |
--enable-hsts |
MCP_ENABLE_HSTS |
false |
Enable HTTP Strict Transport Security |
--metrics-export |
MCP_METRICS_EXPORT |
false |
Enable prometheus metrics endpoint /metrics
|
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-mcpSee the OAuth page for complete setup instructions.
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/ssecurl http://localhost:3000/health
# {"status":"healthy","timestamp":"2026-03-05T..."}# 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/databaseservices:
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:- Push Docker image to ECR
- Create ECS task definition with port 3000 exposed
- Configure ALB to route traffic to the container
- Set environment variables for MySQL connection
# 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"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"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.0if connecting from another machine - Check Docker port mapping:
-p 3000:3000
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.
Problem: Requests return 400 Bad Request or 404 Not Found with session-related errors
Solutions:
- Ensure you're sending the
Mcp-Session-Idheader from theinitializeresponse - SSE sessions cannot be used on
/mcpand vice versa (cross-protocol guard) - Sessions expire when the client disconnects
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
- Configuration - General configuration options
- OAuth - OAuth 2.1 authentication setup
- MCP-Inspector - Testing with MCP Inspector
- MCP Protocol Specification - Official MCP docs
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.
- Installation
- Configuration
- Architecture
- HTTP Transport
- Tool Filtering
- Code Mode
- Tools
- Prompts
- Resources
- Observability & Telemetry