-
Notifications
You must be signed in to change notification settings - Fork 1
HTTP Transport
postgres-mcp supports HTTP transport for remote access, web-based clients, and multi-client scenarios. Both modern (Streamable HTTP) and legacy (SSE) MCP protocols are served simultaneously from a single server instance.
| Scenario | Transport |
|---|---|
| IDE integration (Cursor, Claude Desktop, AntiGravity) |
--transport stdio (default) |
| Remote/networked access | --transport http |
| Web-based MCP clients | --transport http |
| Multiple concurrent clients | --transport http |
| Serverless/stateless deployments | --transport http --stateless |
Python mcp.client.sse SDK |
--transport http (legacy SSE) |
node dist/cli.js \
--transport http \
--port 3000 \
--postgres "postgres://user:pass@localhost:5432/db"docker run --rm -p 3000:3000 \
-e POSTGRES_URL=postgres://user:pass@host:5432/db \
writenotenow/postgres-mcp:latest \
--transport http --port 3000docker run --rm -p 3000:3000 \
-e POSTGRES_URL=postgres://user:pass@host:5432/db \
-e MCP_AUTH_TOKEN=my-secret-token \
writenotenow/postgres-mcp:latest \
--transport http --port 3000node dist/cli.js --transport http --port 3000 --stateless --postgres "postgres://..."Docker Note: Use
host.docker.internalto connect to PostgreSQL running on your host machine.
The server exposes two MCP transport protocols simultaneously on the same port:
| Protocol | Spec Version | Use Case |
|---|---|---|
| Streamable HTTP | MCP 2025-03-26 | Modern clients, recommended |
| Legacy SSE | MCP 2024-11-05 | Backward compatibility (e.g., Python mcp.client.sse) |
Both protocols share the same MCP server instance, tool registrations, and database connection pool.
Single-endpoint protocol using the Mcp-Session-Id header for session management.
| Method | Endpoint | Purpose |
|---|---|---|
POST |
/mcp |
JSON-RPC requests (initialize, tools/list, tools/call, etc.) |
GET |
/mcp |
SSE stream for server-initiated 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.
-
Initialize —
POST /mcpwithinitializerequest (no session header) -
Receive Session ID — Server responds with
Mcp-Session-Idheader -
Send Notification —
POST /mcpwithnotifications/initialized(include session header) -
Use Tools —
POST /mcpwithtools/list,tools/call, etc. (include session header) -
Terminate —
DELETE /mcp(include session header)
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"}
}
}'Two-endpoint protocol using Server-Sent Events for server-to-client communication.
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/sse |
Opens SSE stream, returns /messages?sessionId=<id> endpoint event |
POST |
/messages?sessionId=<id> |
Send JSON-RPC messages to the session |
-
Open SSE Stream —
GET /sse→ Server sends anendpointevent with the POST URL -
Parse Endpoint — Extract
/messages?sessionId=<id>from the SSE event data -
Initialize —
POST /messages?sessionId=<id>withinitializerequest - Read Response — Server pushes the response through the SSE stream
- Use Tools — Continue sending JSON-RPC requests via POST, reading responses from SSE
from mcp import ClientSession
from mcp.client.sse import sse_client
async with sse_client("http://localhost:3000/sse") as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print(f"Connected! {len(tools.tools)} tools available")| Method | Endpoint | Purpose |
|---|---|---|
GET |
/health |
Health check (bypasses rate limiting, always available for monitoring) |
curl http://localhost:3000/health{
"status": "healthy",
"database": "connected",
"version": "2.0.0"
}Docker HEALTHCHECK: When running with --transport http, the Docker image uses native node -e "fetch(...)" to verify the /health endpoint (confirming both HTTP server and database connectivity). In stdio mode, it falls back to a Node.js process check.
| Variable | Default | Description |
|---|---|---|
MCP_HOST |
localhost |
Server bind host (0.0.0.0 for containers) |
MCP_PORT |
3000 |
Server listen port |
LOG_LEVEL |
info |
Log verbosity (debug, info, warning, error) |
| Feature | Default | Description |
|---|---|---|
| Rate limiting | Enabled | Per-IP request throttling with Retry-After header on 429 |
| Body size limit | 1 MB | Maximum request body size |
| CORS | Enabled | Cross-origin request headers (supports wildcard subdomains) |
| HSTS | Disabled | HTTP Strict Transport Security (opt-in via enableHSTS) |
| Trust proxy | Disabled | X-Forwarded-For client IP extraction (opt-in via trustProxy) |
| Server timeouts | Enabled | Request (120s), keep-alive (65s), and headers (66s) timeouts |
| Security headers | Enabled | 7 headers: X-Content-Type-Options, X-Frame-Options, CSP, Cache-Control, Referrer-Policy (no-referrer), Permissions-Policy, opt-in HSTS |
| Health check bypass | Enabled |
/health always serves regardless of rate-limit state |
See OAuth-and-Security for OAuth 2.1 authentication configuration.
The HTTP transport is verified through a dual-testing model to ensure complete protocol compliance without sacrificing test stability:
-
Unit Testing (Vitest): Core adapter logic, input validation, schemas, and routing are rigorously unit-tested, generating the repository's primary
LCOVcoverage metrics (>90%). -
E2E Validation (Playwright): End-to-end integration is verified using Playwright. This suite launches a standalone
dist/cli.jsbackground process and uses the official@modelcontextprotocol/sdkto connect to it acting as a live client.
Because the E2E server runs in an isolated Node process, its execution paths do not artificially inflate the Vitest line coverage numbers. However, the E2E suite provides absolute pass/fail functional guarantees over the server's network boundaries, including:
- CORS and security header injection (7 headers)
- Body parsing limit enforcement (413 Payload Too Large)
- Streamable HTTP (2025-03-26) session tracking limits
- Legacy SSE (2024-11-05) URL parameter session validation
- Rate limiting with Retry-After and health-check bypass
- HSTS opt-in behavior
- Referrer-Policy enforcement (no-referrer)
- trustProxy X-Forwarded-For handling
- Live tool dispatching through the active socket
- Payload contract validation (response shapes for all 22 tool groups)
- Bearer token authentication (
--auth-token) with public path exemptions - Stateless mode (
--stateless) endpoint restrictions
This yields an effective ~100% functional coverage safety net for the HTTP transport layer in real-world conditions.
- Quick-Start — Installation and stdio configuration
- OAuth-and-Security — Protecting HTTP endpoints with OAuth 2.1
- Troubleshooting — HTTP transport connection issues