pg-analytics-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pg-analytics-mcpshow monthly donation trends for the last 6 months"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
pg-analytics-mcp
A config-driven, read-only Postgres MCP server for Claude. Expose a Postgres schema to Claude over Streamable HTTP, with schema and enum values introspected from the live database at boot, and everything client-specific in a single YAML file.
Designed to run behind Cloudflare Access on a cloudflared → reverse-proxy
stack (a full provisioning playbook is included), but the server itself has no
Cloudflare dependency and runs anywhere.
Client-agnostic. Nothing under server/ knows about any particular client.
To serve a new one: copy the repo, write a config file, set .env.
Why this exists
The predecessor stacked three processes to work around a vendor package:
supergateway → enrich.py → postgres-mcp → Postgrespostgres-mcp speaks only stdio/SSE (Cloudflare requires Streamable HTTP), has
no configuration surface at all, and supergateway forked a child per MCP
session that was never reaped — measured 23 children / 15 connections against
a role limit of 20, which surfaced as "works for ~9 calls then everything fails,
including SELECT 1".
This server is one process with one shared pool. Measured: 1 process after 30 tool calls.
Related MCP server: Brand MCP Server
Architecture
Claude → portal.<zone> Cloudflare MCP Server Portal (OAuth)
→ mcp-origin.<zone> Access app + Managed OAuth
→ cloudflared tunnel
→ traefik Host-header routing
→ this container uvicorn, Streamable HTTP at /mcp
→ Postgres read-only role → analytics.* viewsThe security boundary is the database role, not this server.
Quick start
cp .env.example .env # set DATABASE_URI + the deployment vars
$EDITOR config/example.yaml # domain prose for this client
docker compose up -d --build
curl -s localhost:8000/healthz # ok
curl -s localhost:8000/introspection # what the server decided at bootThen follow docs/PLAYBOOK-NEW-CLIENT.md for the Cloudflare side.
Configuration
.env — host-specific, the only thing that changes between VPSes:
Variable | Purpose |
| Read-only role. On the Supavisor pooler the username must carry |
| Container, image tag, and traefik router name |
| Public hostname; auto-added to the transport-security allowlist |
| External docker network traefik watches |
| Path to the client YAML inside the image |
| Host-side publish port (default 8000) |
config/<client>.yaml — the domain. Do not list columns or enum values
here: they are introspected from the live database at boot, so they cannot go
stale. Write only what introspection cannot know — business meaning and traps.
Tools
Built-in:
execute_sql(sql)— raw read-only SQL. Its description is assembled at boot from your authored prose plus the generated schema and enum lists.list_views()— every readable object with columns, row counts, enums.describe_view(name)— columns of one object.
Config-defined: every entry under tools.queries becomes a real MCP tool with
typed parameters. Parameters bind via psycopg named placeholders — never string
interpolation — and min/max are enforced before binding.
tools:
queries:
monthly_trend:
description: |
Donations per month. The most recent month is PARTIAL.
params:
months: {type: integer, default: 6, min: 1, max: 36}
sql: |
select ... where donated_at >= date_trunc('month', now())
- make_interval(months => %(months)s - 1)This is the bit that closes the gap with n8n: adding a tool is prose + SQL, not Python.
Why descriptions live here
Tool descriptions are the one context a model sees whenever the tool is available — every client, every conversation, no skill loading and no project instructions. Domain knowledge kept in an external document is knowledge the model often does not have.
Half of each description is authored (judgement), half generated (facts). The
generated half is why the boxy platform/processor and daily frequency can no
longer go missing the way they did in the hand-written prompt that preceded this.
Operations
curl -s localhost:8000/introspection | python3 -m json.tool # objects, enums, tools, limits
docker top <container> # must stay at 1 process
docker compose up -d --build # after a config editA config or schema change needs a restart — introspection is cached for the process lifetime, deliberately, so behaviour cannot drift mid-run.
The five boundary tests
Re-run after any change to views, grants, or config. All five must fail:
update donations set amount = 0 where false; -- permission denied for view
select count(*) from public.donations; -- permission denied for table
select count(*) from public.website_orders; -- permission denied for table
create table analytics.t (id int); -- read-only transaction
select phone_number from customers limit 1; -- column does not existlimits.select_only exists but defaults off: the role is the boundary, and
a SQL validator on top blocks valid read-only constructs for no gain — that is
why postgres-mcp's restricted mode was abandoned.
Gotchas paid for in blood
Compose label keys are not variable-substituted. Labels must be list-form (
- "traefik...=value"), or you get a router literally named${MCP_CONTAINER_NAME}and traefik 404s.DNS-rebinding protection is on by default in the MCP SDK. The forwarded
Hostbehind a proxy must be allowed;MCP_HOSTNAMEandMCP_LOCAL_PORTare added automatically.Mounting the MCP app under your own Starlette replaces its lifespan. The session manager must be started explicitly (
server.session_manager.run()) or every request 500s with "Task group is not initialized".set_read_only/set_autocommitmust precede anyexecute()on a connection, or the pool fails with "connection in transaction status INTRANS".pg_class.reltuplesis meaningless for views, so row estimates fall back to a boundedcount(*)at boot.Supavisor rewrites
application_nameto "Supavisor", so per-client connection attribution through the pooler is not possible.MCP SDK 2.0 renamed
FastMCPtoMCPServerand moved it out ofmcp.server.fastmcp.requirements.txtis a full lock for that reason.
License
MIT — see LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables Claude to interact with PostgreSQL databases by executing SQL queries, exploring schemas, and monitoring database health. It provides tools for data manipulation and schema management via a secure SSE connection.287MIT
- FlicenseNot gradedqualityDmaintenanceEnables Claude Desktop to query a PostgreSQL brand database through MCP. Supports local stdio and remote HTTP/SSE deployments with API key authentication for secure database access.
- FlicenseNot gradedqualityDmaintenanceEnables natural language querying of PostgreSQL databases through the Model Context Protocol. It translates user questions into validated SQL, executes read-only queries safely, and returns results to MCP-compatible clients like Claude Desktop.
- AlicenseAqualityAmaintenanceQuery and manage PostgreSQL databases from Claude Code, Cursor, and any MCP client, with read-only by default and built-in schema introspection, EXPLAIN, and performance diagnostics.211,8093MIT
Related MCP Connectors
Analytical memory for AI agents: a real Postgres queried in plain English over MCP. One command.
Query PostgreSQL databases in plain English — LLM-generated, safety-validated SQL.
MCP server for managing Prisma Postgres.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Sa3fa/pg-analytics-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server