PostgreSQL MCP Server
A production-ready Model Context Protocol (MCP) server that connects AI assistants to any PostgreSQL database — cloud-hosted or self-hosted. Built on Cloudflare Workers and the Agents SDK, it provides 25 database tools with enterprise-grade authentication via Cloudflare Access and role-based access control through OIDC identity provider groups.
Key Features
- Universal PostgreSQL compatibility — connects to Neon, Supabase, Amazon RDS, Google Cloud SQL, Azure Database, self-hosted instances, or any PostgreSQL-compatible database
- 25 database tools organized into 4 permission tiers (universal, read-only, read-write, admin)
- Role-based access control — tools are conditionally registered per session based on IdP group membership
- SQL safety layer — blocks dangerous operations (DROP DATABASE, privilege escalation, multi-statement injection) at the parser level
- Enterprise SSO — OAuth 2.1 with PKCE via Cloudflare Access, supporting any OIDC identity provider (Okta, Authentik, Azure AD, etc.)
- Connection pooling — Hyperdrive manages connection pooling and TCP proxying to your database
- Private database support — reach databases on private networks via Cloudflare Tunnel
- Serverless — runs on Cloudflare Workers with Durable Objects for per-user MCP sessions, no infrastructure to manage
Architecture
MCP Client (Claude, Cursor, Windsurf, etc.)
|
| Streamable HTTP + OAuth 2.1
v
Cloudflare Worker
|-- OAuthProvider --> Cloudflare Access --> OIDC IdP (Okta, Authentik, Azure AD, ...)
|-- McpAgent (Durable Object — one per authenticated session)
| |-- Role-based tool registration via IdP groups
| |-- SQL Safety Parser (blocks dangerous patterns)
| |-- Prompts (query assistant, schema explorer)
| |-- Resources (schema dump, health dashboard)
|-- pg-client --> Hyperdrive --> PostgreSQL
|
+-----------+-----------+
| |
Public databases Private databases
(Neon, Supabase, (via Cloudflare Tunnel
RDS, Cloud SQL) + VPC Service)
Database Compatibility
This MCP server works with any PostgreSQL database that Hyperdrive can reach:
| Database | Connection Method | Tunnel Required? |
|---|---|---|
| Neon | Direct (public endpoint) | No |
| Supabase | Direct (public endpoint) | No |
| Amazon RDS (public) | Direct | No |
| Amazon RDS (VPC) | Cloudflare Tunnel | Yes |
| Google Cloud SQL | Direct or Tunnel | Depends on config |
| Azure Database for PostgreSQL | Direct | No |
| Self-hosted (public IP) | Direct | No |
| Self-hosted (private network) | Cloudflare Tunnel | Yes |
| Docker/local | Cloudflare Tunnel | Yes |
Role-Based Access Control
Tools are conditionally registered per MCP session based on the authenticated user's IdP groups. Users only see and can invoke tools matching their permission tier.
| IdP Group | Tools Available | Count |
|---|---|---|
| (any authenticated user) | Universal | 3 |
pg-readonly |
Universal + Read-Only | 10 |
pg-readwrite |
Universal + Read-Only + Read-Write | 15 |
pg-admin |
All tools | 25 |
Groups are extracted from the groups claim in the OIDC ID token. Multiple group formats are supported (Authentik, Okta, comma-separated strings, arrays).
Tools Reference
Universal Tools (3) — Always Available
| Tool | Description |
|---|---|
userInfo |
Current user's email, name, groups, and computed access level |
connectionInfo |
PostgreSQL version, database name, server time, current database user |
healthCheck |
MCP server and database connectivity check with response time |
Read-Only Tools (7) — Requires pg-readonly, pg-readwrite, or pg-admin
| Tool | Description |
|---|---|
listTables |
List all tables, views, and materialized views with row counts and sizes |
describeTable |
Full column definitions, indexes, and foreign key constraints |
executeQuery |
Execute validated read-only SQL (SELECT, WITH...SELECT) with parameterized values |
explainQuery |
EXPLAIN or EXPLAIN ANALYZE with JSON output for query optimization |
searchSchema |
Case-insensitive search across table and column names |
listIndexes |
Index listing with type, uniqueness, size, and scan count statistics |
getTableStats |
Live/dead row counts, dead row ratio, vacuum/analyze timestamps |
Read-Write Tools (5) — Requires pg-readwrite or pg-admin
| Tool | Description |
|---|---|
insertRows |
Parameterized INSERT with optional RETURNING clause |
updateRows |
Parameterized UPDATE with WHERE clause (auto-adjusts parameter indices) |
deleteRows |
DELETE with mandatory confirmation flag as a safety check |
executeSql |
Execute arbitrary non-DDL SQL (INSERT, UPDATE, DELETE, SELECT) |
upsertRows |
INSERT ... ON CONFLICT DO UPDATE with conflict target and update columns |
Admin Tools (10) — Requires pg-admin
| Tool | Description |
|---|---|
databaseHealth |
Connection utilization, buffer cache hit ratios, database size, uptime |
indexHealth |
Unused indexes, duplicate indexes, tables needing indexes |
vacuumHealth |
Dead row ratios, last vacuum timestamps, tables needing vacuum |
sequenceHealth |
Sequences approaching max value (prevents silent insert failures) |
createIndex |
Create indexes (CONCURRENTLY by default for non-blocking operations) |
dropIndex |
Drop indexes (CONCURRENTLY by default) |
executeDDL |
Execute DDL statements (CREATE TABLE, ALTER TABLE, etc.) |
analyzeTable |
Run ANALYZE to update query planner statistics |
getActiveQueries |
List running queries with duration, state, and wait events |
cancelQuery |
Cancel a running query by PID |
Prompts (2) — Always Available
| Prompt | Description |
|---|---|
queryAssistant |
Generate accurate SQL from natural language using live schema context |
schemaExplorer |
Guided database overview with tables, relationships, and exploration suggestions |
Resources (2) — Always Available
| Resource URI | Description |
|---|---|
schema://tables |
Full schema dump with tables, columns, types, and indexes (JSON) |
health://overview |
Database health dashboard with connections, cache, and table stats (JSON) |
SQL Safety
A dedicated SQL parser validates every query before execution:
Always blocked (all roles):
DROP DATABASE,DROP ROLE,DROP USERCREATE ROLE,CREATE USER,ALTER ROLE,ALTER USERGRANT,REVOKECOPY TO/FROM PROGRAM- Multi-statement injection (semicolons inside query body)
Role-enforced:
- Read-only — only
SELECT,EXPLAIN,SHOW, and CTEs without mutation - Read-write — adds
INSERT,UPDATE,DELETE(no DDL) - Admin — adds DDL (
CREATE TABLE,ALTER TABLE,DROP TABLE, etc.)
Getting Started
Prerequisites
- A Cloudflare account (Workers paid plan for Durable Objects)
- Node.js 18+ and npm
- Wrangler CLI (
npm install -g wrangler) - A PostgreSQL database (any provider)
- An OIDC identity provider configured in Cloudflare Access
Step 1: Clone and Install
git clone https://github.com/vnikhilbuddhavarapu/postgresql-mcp.git
cd postgresql-mcp
cp wrangler.jsonc.example wrangler.jsonc
npm install
Step 2: Create Cloudflare Resources
KV Namespace (stores OAuth state):
wrangler kv namespace create OAUTH_KV
Copy the id into wrangler.jsonc under kv_namespaces[0].id.
Hyperdrive Config (connection pooling):
# For a publicly reachable database:
wrangler hyperdrive create pg-mcp \
--connection-string="postgres://user:password@host:5432/dbname"
# For a private database via Cloudflare Tunnel:
# First create a VPC Service, then:
wrangler hyperdrive create pg-mcp \
--connection-string="postgres://user:password@<PRIVATE_IP>:5432/dbname" \
--vpc-id="<VPC_SERVICE_ID>"
Copy the Hyperdrive id into wrangler.jsonc under hyperdrive[0].id.
Step 3: Configure Cloudflare Access (OIDC)
- Go to Cloudflare Zero Trust > Access > Applications > Add SaaS Application
- Choose OIDC and select your identity provider
- Set the Redirect URI to
https://<your-worker>.workers.dev/callback - Enable scopes:
openid,email,profile,groups - Note down the following from the application configuration:
- Client ID
- Client Secret
- Token Endpoint
- Authorization Endpoint
- JWKS/Key Endpoint
- Configure your IdP to include a
groupsclaim with values:pg-readonly,pg-readwrite, and/orpg-admin
Step 4: Set Secrets
cp .dev.vars.example .dev.vars
# Edit .dev.vars with your Access credentials
Step 5: Deploy
# Generate types
wrangler types
# Deploy
wrangler deploy
# Push secrets to production
wrangler secret bulk .dev.vars
Step 6: Connect an MCP Client
Use the MCP endpoint URL with any compatible client:
https://<your-worker>.workers.dev/mcp
Or test with the MCP Inspector:
npx @modelcontextprotocol/inspector
# Enter your /mcp URL, authenticate via the OAuth flow
Development
npm install # Install dependencies
wrangler types # Generate TypeScript types from bindings
npm run type-check # TypeScript type checking
npm test # Run Vitest unit tests (48 tests)
npm run lint # Lint with oxlint
npm run format # Format with oxfmt
wrangler dev # Local development server (note: Hyperdrive requires deploy for full testing)
wrangler deploy # Deploy to Cloudflare
Project Structure
postgresql-mcp/
├── src/
│ ├── index.ts # McpAgent (Durable Object) + OAuthProvider entry point
│ ├── env.d.ts # Environment type declarations
│ ├── auth/
│ │ ├── access-handler.ts # OAuth /authorize + /callback flow with Cloudflare Access
│ │ └── workers-oauth-utils.ts # CSRF, PKCE, state management, token exchange
│ ├── db/
│ │ ├── pg-client.ts # PostgreSQL client wrapper (Hyperdrive connection pooling)
│ │ ├── sql-parser.ts # SQL classification and safety validation
│ │ └── schemas.ts # Zod schemas for all tool inputs and database types
│ ├── tools/
│ │ ├── universal.ts # 3 tools — userInfo, connectionInfo, healthCheck
│ │ ├── readonly.ts # 7 tools — listTables, describeTable, executeQuery, ...
│ │ ├── readwrite.ts # 5 tools — insertRows, updateRows, deleteRows, ...
│ │ └── admin.ts # 10 tools — databaseHealth, createIndex, executeDDL, ...
│ ├── prompts/
│ │ ├── query-assistant.ts # Natural language to SQL with live schema context
│ │ └── schema-explorer.ts # Guided database exploration
│ └── resources/
│ ├── schema.ts # schema://tables — full schema dump
│ └── health.ts # health://overview — database health dashboard
├── tests/
│ └── sql-parser.test.ts # 48 unit tests for SQL safety parser
├── .dev.vars.example # Template for environment secrets
├── wrangler.jsonc.example # Template for Worker configuration (copy to wrangler.jsonc)
├── tsconfig.json # TypeScript configuration
└── package.json # Dependencies and scripts
Tech Stack
- Runtime: Cloudflare Workers + Durable Objects
- MCP SDK: Agents SDK (
McpAgent) +@modelcontextprotocol/sdk - Authentication:
@cloudflare/workers-oauth-provider+ Cloudflare Access (Generic OIDC SaaS) - Database: Hyperdrive +
pgdriver - Validation: Zod v4
- Testing: Vitest
- Linting/Formatting: oxlint + oxfmt
Related Documentation
- Model Context Protocol
- Building Remote MCP Servers on Cloudflare
- Cloudflare Hyperdrive — Connect to PostgreSQL
- Cloudflare Access — SaaS Applications
- Cloudflare Tunnel — Private Networks
License
MIT