FastAPI Database MCP Server
Provides read-only SQL query access to Postgres and DuckDB databases via MCP tools, with extensive security hardening for public endpoints.
README
FastAPI Database MCP Server
Read-only SQL query API for Postgres and DuckDB, exposed as MCP tools for AI clients.
Live endpoint: db-mcp.tigzig.com
Security Hardening
This server is designed to run with a public, open MCP endpoint (/mcp) — no API key, no auth. A separate secured endpoint (/mcp-secure) uses Auth0 OAuth for controlled access. The open endpoint relies entirely on the defense stack below.
Defense Layers
- Edge rate limiting — Cloudflare WAF or equivalent, configured per your needs (recommended before traffic hits origin)
- Application rate limiting — per-IP and global rate limits via SlowAPI (configurable via env vars)
- Per-IP concurrency cap — limits simultaneous in-flight queries per IP (default: 4)
- Global concurrency cap — limits total simultaneous queries server-wide (default: 10)
- SQL prefix allowlist — only SELECT, WITH, SHOW, DESCRIBE, EXPLAIN allowed
- SQL keyword blocklist — INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, EXEC, COPY, GRANT, REVOKE, and 20+ more blocked (hardcoded in
BLOCKED_KEYWORDS) - Resource exhaustion blocking — CROSS JOIN, REPEAT, REGEXP_REPLACE, MD5, SHA256 blocked to prevent CPU/bandwidth bombs
- SQL parser structural validation (sqlglot) — regex-based checks cannot understand SQL structure (CTEs, aliases, nested subqueries). The sqlglot parser builds an AST (Abstract Syntax Tree) and validates structurally: blocks multiple table sources in a single SELECT (catches cartesian products via
FROM a, b, CTE alias bypass likeWITH t AS (...) SELECT FROM t a, t b, and aliased self-joins), detects blocked functions even when nested or aliased. Supports both Postgres and DuckDB dialects. Falls back to regex-only mode gracefully if sqlglot is not installed. - SQL comment rejection —
--and/*blocked to prevent comment-based validation bypass - System catalog blocking — pg_catalog, information_schema, pg_stat, duckdb_tables(), and other system objects blocked (hardcoded in
BLOCKED_PG_SOURCESandBLOCKED_DUCKDB_SOURCES) - Auto-append LIMIT — queries without an outer LIMIT automatically get one (configurable via
MAX_JSON_ROWS/MAX_TSV_ROWS) - Response size limit — responses exceeding the byte limit are rejected with HTTP 413 (configurable via
MAX_RESPONSE_BYTES, default: 1MB) - ORDER BY validation — function calls in ORDER BY blocked except whitelisted aggregates; subqueries in ORDER BY blocked
- Subquery depth limit — max 3 SELECT keywords per query (hardcoded)
- Data-generating function blocking — GENERATE_SERIES, RANGE, UNNEST blocked (hardcoded)
- Query timeouts — configurable timeout on both Postgres and DuckDB, queries exceeding this are killed (default: 15s)
- DuckDB query interrupt — on timeout,
connection.interrupt()kills the C++ engine query, not just the Python coroutine - Postgres read-only mode —
default_transaction_read_only = on+ dedicated read-only database user - DuckDB read-only mode —
read_only=True+enable_external_access=false - DuckDB resource limits — memory and thread limits (configurable via env vars, defaults: 512MB, 2 threads)
- Container resource limits — Docker/container-level RAM and CPU caps (configured in your hosting platform)
- Error message sanitization — generic error messages returned, no internal details leaked
- Auth0 OAuth on
/mcp-secure— JWT verification (RS256), audience/issuer validation, email whitelist (optional, enabled via env vars) - Failed-auth rate limiter — in-memory counter blocks IPs after repeated failed JWT attempts on
/mcp-secure(configurable viaAUTH_FAIL_MAXandAUTH_FAIL_WINDOW)
What's Hardcoded vs Configurable
Hardcoded in code (edit app.py to change):
- SQL keyword blocklist (
BLOCKED_KEYWORDS) - System catalog blocklists (
BLOCKED_PG_SOURCES,BLOCKED_DUCKDB_SOURCES) - Allowed SQL prefixes (SELECT, WITH, SHOW, DESCRIBE, EXPLAIN)
- ORDER BY allowed functions (SUM, COUNT, AVG, MIN, MAX, COALESCE, NULLIF, CASE)
- Subquery depth limit (3)
- sqlglot structural checks (multi-table per SELECT, blocked functions via AST)
Configurable via environment variables (see table below):
- Rate limits, concurrency caps, query timeouts, row limits, response size limit, connection pool settings, Auth0 config
Database-Level Hardening (Postgres)
If your backend connects to a hosted Postgres (Supabase, Neon, etc.), you should also harden at the database level:
- Enable RLS on all tables
- Revoke default grants from anon/authenticated roles
- Use a dedicated read-only database user (SELECT-only grants on specific tables)
- Set
default_transaction_read_only = onon application roles - Set
statement_timeouton application roles (matches yourPG_STATEMENT_TIMEOUT_MS) - Add indexes on columns used in WHERE, GROUP BY, ORDER BY for large tables
DuckDB-Level Hardening
DuckDB runs in-process. The server opens it with read_only=True and enable_external_access=false, and sets memory_limit and threads to prevent a single query from consuming all resources. On timeout, connection.interrupt() + connection.close() kills the query at the C++ engine level.
What It Does
Two databases, two endpoints, one MCP server:
- Postgres (Supabase) — ~1M rows of ODI cricket ball-by-ball data (2013-2025)
- DuckDB — ~1M rows of T20 cricket ball-by-ball data (2013-2025)
Both tables have identical schemas (23 columns) covering match details, player info, runs, extras, and dismissals.
The endpoints are mounted as MCP tools via fastapi-mcp, so any MCP-compatible AI client (Claude Code, Claude Desktop, etc.) can connect and query directly.
Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /api/query/postgres |
SQL query on ODI data (Supabase) |
| POST | /api/query/duckdb |
SQL query on T20 data (DuckDB) |
| GET | /health |
Health check with DB connectivity status |
| GET | /mcp |
MCP SSE endpoint for AI clients (open, no auth) |
| GET | /mcp-secure |
MCP SSE endpoint with Auth0 OAuth (secured) |
Query Format
{
"sql": "SELECT striker, SUM(runs_off_bat) as runs FROM ball_by_ball WHERE season = '2023' GROUP BY striker ORDER BY runs DESC LIMIT 10",
"format": "json"
}
Set "format": "tsv" for compact tab-delimited output (~70% fewer tokens).
Connecting as MCP Client
Claude Code
claude mcp add --transport sse db-mcp https://your-server.com/mcp
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"db-mcp": {
"type": "sse",
"url": "https://your-server.com/mcp"
}
}
}
Local Development
git clone https://github.com/amararun/shared-fastapi-database-mcp.git
cd shared-fastapi-database-mcp
python -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install -r requirements.txt
cp .env.example .env
# Edit .env with your database connection strings
uvicorn app:app --host 0.0.0.0 --port 8000
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
SUPABASE_POSTGRES |
Yes | — | Postgres connection string (use a read-only user) |
DUCKDB_FILE |
Yes | ./data/t20_cricket.duckdb |
Path to .duckdb file |
RATE_LIMIT |
No | 30/minute |
Per-IP rate limit (SlowAPI format, e.g. 60/hour, 100/minute) |
GLOBAL_RATE_LIMIT |
No | 200/minute |
Global rate limit across all IPs |
PG_STATEMENT_TIMEOUT_MS |
No | 15000 |
Postgres query timeout in milliseconds |
DUCKDB_QUERY_TIMEOUT_MS |
No | 15000 |
DuckDB query timeout in milliseconds |
MAX_JSON_ROWS |
No | 1000 |
Max rows returned in JSON format |
MAX_TSV_ROWS |
No | 1000 |
Max rows returned in TSV format |
MAX_RESPONSE_BYTES |
No | 1048576 |
Max response size in bytes (default: 1MB) |
MAX_CONCURRENT_PER_IP |
No | 4 |
Max simultaneous queries per IP |
MAX_CONCURRENT_GLOBAL |
No | 10 |
Max simultaneous queries server-wide |
PG_POOL_MIN |
No | 3 |
Postgres connection pool minimum size |
PG_POOL_MAX |
No | 6 |
Postgres connection pool maximum size |
PG_POOL_ACQUIRE_TIMEOUT |
No | 15 |
Seconds to wait for a pool connection before returning 503 |
DUCKDB_MEMORY_LIMIT |
No | 512MB |
DuckDB memory limit |
DUCKDB_THREADS |
No | 2 |
DuckDB thread limit |
DUCKDB_TEMP_DIR |
No | /tmp/duckdb |
DuckDB temporary directory |
DUCKDB_MAX_TEMP_DIR_SIZE |
No | 2GB |
DuckDB temp directory size cap |
CORS_ALLOW_ORIGINS |
No | * |
Comma-separated allowed origins |
LOG_LEVEL |
No | INFO |
Logging level |
AUTH0_DOMAIN |
No | — | Auth0 tenant domain (enables /mcp-secure when set) |
AUTH0_AUDIENCE |
No | — | Auth0 API identifier |
AUTH0_CLIENT_ID |
No | — | Auth0 application client ID |
AUTH0_CLIENT_SECRET |
No | — | Auth0 application client secret |
AUTH_FAIL_MAX |
No | 5 |
Max failed auth attempts per IP before blocking |
AUTH_FAIL_WINDOW |
No | 86400 |
Failed auth tracking window in seconds (default: 24 hours) |
API_MONITOR_URL |
No | — | tigzig-api-monitor endpoint URL |
API_MONITOR_KEY |
No | — | tigzig-api-monitor API key |
RENDER_EXTERNAL_URL |
No | — | Base URL for MCP (auto-detected on Render) |
Auth0 OAuth (Secured Endpoint)
The /mcp-secure endpoint adds Auth0 OAuth on top of all existing security layers. It is optional — if AUTH0_DOMAIN is not set, only the open /mcp endpoint is mounted.
How It Works
- MCP client discovers OAuth metadata at
/.well-known/oauth-authorization-server - Client redirects user to Auth0 login
- Auth0 authenticates user, checks email whitelist, issues JWT
- Client sends JWT as Bearer token with every request
- Server validates JWT signature (RS256 via JWKS), audience, and issuer
Auth0 Setup
- Create an Auth0 API with your server URL as the identifier (audience)
- Create an Auth0 Application (Regular Web Application)
- Add your MCP client's callback URLs (e.g.,
https://claude.ai/api/mcp/auth_callback) - Create a post-login Action with an email whitelist
- Set the AUTH0 environment variables on the server
Connecting to the Secured Endpoint
Claude.ai (Web) — Settings > Connectors > Add custom connector:
- URL:
https://your-server.com/mcp-secure - OAuth Client ID: your Auth0 app's client ID
Claude Desktop:
{
"mcpServers": {
"db-mcp-secure": {
"command": "npx",
"args": ["mcp-remote", "https://your-server.com/mcp-secure", "8080"]
}
}
}
Demo Limitations
- JWKS cache does not auto-refresh (refresh on restart only)
- No token scope validation (any valid token grants access to all tools)
- Fake Dynamic Client Registration (returns pre-configured credentials for MCP spec compatibility)
- No token revocation handling (tokens accepted until expiry)
Stack
- FastAPI + uvicorn
- asyncpg (Postgres connection pool)
- DuckDB (read-only, thread-pool executor)
- fastapi-mcp v0.4.0 (MCP server mounting, OAuth support)
- sqlglot (SQL parser for structural validation)
- python-jose (JWT verification)
- SlowAPI (rate limiting)
Author
Built by Amar Harolikar
Explore 30+ open source AI tools for analytics, databases & automation at tigzig.com
License
MIT License
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.