Run the MCP Gateway#
Scope: advanced template only. The gateway (
tools/mcp-gateway.py) is generated bypgmi init --template advancedand becomes application code you own. New to this subsystem? Start with the overview .
Quick Start#
1. Deploy the Advanced Template#
pgmi init --template advanced myproject
cd myproject
pgmi deploy --connection "postgresql://user:pass@localhost:5432/mydb"2. Start the HTTP Gateway#
The pgmi init --template advanced command generates a tools/ directory with the Python gateway:
cd tools
pip install -r requirements.txt
export DATABASE_URL="postgresql://user:pass@localhost:5432/mydb"
python mcp-gateway.pyRequirements:
- Python 3.8+
psycopg[binary]>=3.0(psycopg 3) — the only dependency, pinned intools/requirements.txt
The gateway uses the Python standard library’s http.server; it does not
use Flask, gunicorn, or psycopg2.
3. Test the Connection#
# Initialize handshake
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"protocolVersion":"2024-11-05"}}'
# List available tools
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"2","method":"tools/list"}'
# Call a tool (register your own first — see MCP-HANDLERS.md)
curl -X POST http://localhost:8080/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"your_tool","arguments":{}}}'4. Configure Your AI Client#
For Claude Desktop, add to ~/.config/claude/claude_desktop_config.json:
{
"mcpServers": {
"my-database": {
"url": "http://localhost:8080/mcp"
}
}
}Endpoints#
| Endpoint | Method | Purpose |
|---|---|---|
/mcp | POST | MCP JSON-RPC endpoint. Honors Accept (responds application/json; no SSE), validates and echoes the MCP-Protocol-Version header |
/mcp | GET | 405 Method Not Allowed — the gateway offers no server-initiated SSE stream |
/health | GET | Health check (for load balancers) |
Transport-level behavior (version negotiation, Accept handling, what is
intentionally not implemented) is specified on the
protocol page
.
Authentication Headers#
The gateway extracts authentication from HTTP headers:
| Header | Maps to |
|---|---|
X-User-Id | context.user_id |
X-Tenant-Id | context.tenant_id |
How handlers consume that context — and how to require it — is covered in Author MCP handlers .
Transaction Policy#
A handler may declare a minimum transaction isolation level with the
minTransactionIsolation metadata key (read committed |
repeatable read | serializable; case- and separator-insensitive) and a
read-only policy with readOnly: true.
The bundled HTTP gateway resolves the policy before opening the dispatch
transaction (api.mcp_request_policy(request, requested_level) on a short
autocommit connection) and opens with the resolved characteristics:
max(route floor, client-requested) isolation, READ ONLY when the route
declares it, and DEFERRABLE for SERIALIZABLE READ ONLY (which can never
abort with 40001 and needs no retries). Routes therefore just work for callers
that send nothing; the X-PGMI-Transaction-Isolation header is escalation,
not obligation — an unsupported value is rejected client-side with HTTP 400
(pgmi.transaction_isolation_unsupported).
The database gateway can only validate the characteristics — SET TRANSACTION is illegal inside functions — so its checks remain as the
fail-closed invariant for clients that skip the lookup: a too-weak transaction
is rejected with a -32600 error carrying
data.code = 'pgmi.transaction_isolation_too_weak' (or
pgmi.transaction_read_only_required for a read-only route dispatched in a
read-write transaction). See lib/api/00-transaction-isolation.sql for the
full contract.
Production Deployment#
The shipped gateway is a single-process http.server reference implementation.
For production, consider:
- Reverse Proxy: Place behind nginx/Caddy that validates JWTs and injects X-User-Id
- Connection Pooling: Use PgBouncer for connection management
- TLS: Terminate SSL at the load balancer
- Scaling: run multiple gateway processes behind the proxy, or adapt the
handler to your own ASGI/WSGI server — the gateway exposes no WSGI
appobject, so it cannot be served withgunicornas-is.
Server Configuration#
Configure server identity via session settings:
SET mcp.server_name = 'my-database-server';
SET mcp.server_version = '2.0.0';Or set in postgresql.conf for persistence:
mcp.server_name = 'production-db'
mcp.server_version = '1.0.0'Troubleshooting#
Common Issues#
“Method not found” error
- Check that your handler is registered:
SELECT * FROM api.mcp_route; - Verify the handler type matches the method (tool for tools/call, etc.)
“Authentication required” error
- Pass context with user_id:
'{"user_id":"test|123"}'::jsonb - Or set
requiresAuth: falsefor public tools
Handler not appearing in discovery
- Check the handler was created:
SELECT * FROM api.handler WHERE handler_type IN ('mcp_tool', 'mcp_resource', 'mcp_prompt'); - Verify MCP route exists:
SELECT * FROM api.mcp_route;
Debugging#
Enable debug logging:
SET client_min_messages = DEBUG;
SELECT (api.mcp_handle_request('{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"my_tool","arguments":{}}}'::jsonb)).envelope;Check exchange logs:
SELECT * FROM api.mcp_exchange ORDER BY enqueued_at DESC LIMIT 10;See Also#
- Author MCP handlers — write the tools the gateway serves
- MCP SQL API reference — the dispatcher and builders
- Security — secrets handling and security patterns