pg-analytics-mcp
pg-analytics-mcp
Claude용 설정 기반, 읽기 전용 Postgres MCP 서버입니다. Postgres 스키마를 Streamable HTTP를 통해 Claude에 노출하며, 스키마와 enum 값은 부팅 시 라이브 데이터베이스에서 인트로스펙션하고, 클라이언트별 설정은 단일 YAML 파일에 담습니다.
Cloudflare Access 뒤에서 cloudflared → 리버스 프록시 스택으로 실행되도록 설계되었습니다(전체 프로비저닝 플레이북 포함). 그러나 서버 자체에는 Cloudflare 의존성이 없으며 어디서든 실행됩니다.
클라이언트 비종속적. server/ 아래의 어떤 것도 특정 클라이언트를 알지 못합니다. 새 클라이언트를 서비스하려면: 저장소를 복사하고, 설정 파일을 작성하고, .env를 설정하세요.
왜 이게 필요한가
이전 버전은 벤더 패키지를 우회하기 위해 세 개의 프로세스를 쌓았습니다:
supergateway → enrich.py → postgres-mcp → Postgrespostgres-mcp는 stdio/SSE만 지원하며(Cloudflare는 Streamable HTTP를 요구함), 설정 표면이 전혀 없고, supergateway는 MCP 세션마다 자식 프로세스를 포크했지만 회수되지 않았습니다 — 역할 제한 20개에 대해 23개 자식 프로세스 / 15개 연결이 측정되었고, 이는 "약 9회 호출 후 모든 것이 실패, SELECT 1 포함"으로 드러났습니다.
이 서버는 단일 프로세스에 단일 공유 풀을 사용합니다. 측정 결과: 30회 도구 호출 후 1개 프로세스.
Related MCP server: mcp-postgres
아키텍처
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.* views보안 경계는 이 서버가 아니라 데이터베이스 역할입니다.
빠른 시작
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 boot그런 다음 Cloudflare 쪽은 docs/PLAYBOOK-NEW-CLIENT.md를 따르세요.
설정
.env — 호스트별 설정, VPS 간에 달라지는 유일한 것:
변수 | 용도 |
| 읽기 전용 역할. Supavisor 풀러에서는 사용자 이름에 반드시 |
| 컨테이너, 이미지 태그, traefik 라우터 이름 |
| 공개 호스트 이름; 전송 보안 허용 목록에 자동 추가됨 |
| traefik이 감시하는 외부 도커 네트워크 |
| 이미지 내부의 클라이언트 YAML 경로 |
| 호스트 측 게시 포트(기본값 8000) |
config/<client>.yaml — 도메인 영역. 여기에 열이나 enum 값을 나열하지 마세요: 부팅 시 라이브 데이터베이스에서 인트로스펙션되므로 오래될 수 없습니다. 인트로스펙션이 알 수 없는 것, 즉 비즈니스 의미와 함정만 작성하세요.
도구
기본 제공:
execute_sql(sql)— 원시 읽기 전용 SQL. 설명은 부팅 시 작성한 산문 더하기 생성된 스키마와 enum 목록으로 조합됩니다.list_views()— 열, 행 수, enum이 포함된 모든 읽기 가능한 객체.describe_view(name)— 한 객체의 열.
설정 정의: tools.queries 아래의 모든 항목은 타입이 지정된 매개변수를 가진 실제 MCP 도구가 됩니다. 매개변수는 psycopg 명명된 자리표시자를 통해 바인딩됩니다 — 문자열 보간이 절대 아님 — 그리고 min/max는 바인딩 전에 강제됩니다.
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)이것이 n8n과의 격차를 메우는 부분입니다: 도구 추가는 산문 + SQL이지, Python이 아닙니다.
설명이 여기 있는 이유
도구 설명은 모델이 도구를 사용할 수 있을 때마다 보는 유일한 컨텍스트입니다 — 모든 클라이언트, 모든 대화, 스킬 로딩이나 프로젝트 지시 없이. 외부 문서에 보관된 도메인 지식은 모델이 종종 갖지 못하는 지식입니다.
각 설명의 절반은 작성된 것이고(판단), 절반은 생성된 것입니다(사실). 생성된 절반 덕분에 boxy 플랫폼/프로세서와 daily 빈도가 이전의 수기 프롬프트에서처럼 누락될 수 없습니다.
운영
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 edit설정이나 스키마 변경은 재시작이 필요합니다 — 인트로스펙션은 의도적으로 프로세스 수명 동안 캐시되므로, 실행 중에 동작이 달라질 수 없습니다.
다섯 가지 경계 테스트
뷰, 권한, 설정을 변경한 후 다시 실행하세요. 다섯 가지 모두 실패해야 합니다:
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는 존재하지만 기본값은 꺼짐: 역할이 경계이며, 그 위의 SQL 검증기는 이득 없이 유효한 읽기 전용 구문을 차단합니다 — 이것이 postgres-mcp의 제한 모드가 폐기된 이유입니다.
피로 대가로 얻은 함정들
Compose 레이블 키는 변수 치환되지 않습니다. 레이블은 목록 형식(
- "traefik...=value")이어야 합니다. 그렇지 않으면 말 그대로${MCP_CONTAINER_NAME}이라는 이름의 라우터가 생기고 traefik이 404를 반환합니다.DNS-리바인딩 보호는 MCP SDK에서 기본적으로 켜져 있습니다. 프록시 뒤에서 전달되는
Host가 허용되어야 합니다.MCP_HOSTNAME과MCP_LOCAL_PORT는 자동으로 추가됩니다.MCP 앱을 자체 Starlette 아래에 마운트하면 수명 주기가 대체됩니다. 세션 관리자를 명시적으로 시작해야 합니다(
server.session_manager.run()). 그렇지 않으면 모든 요청이 "Task group is not initialized"로 500을 반환합니다.set_read_only/set_autocommit은 연결에서execute()보다 먼저 호출되어야 합니다. 그렇지 않으면 풀이 "connection in transaction status INTRANS"로 실패합니다.pg_class.reltuples는 뷰에 대해 의미가 없습니다. 따라서 행 추정치는 부팅 시 제한된count(*)로 대체됩니다.Supavisor는
application_name을 "Supavisor"로 다시 씁니다. 따라서 풀러를 통한 클라이언트별 연결 귀속은 불가능합니다.MCP SDK 2.0은
FastMCP를MCPServer로 이름을 바꾸고mcp.server.fastmcp밖으로 이동시켰습니다.requirements.txt가 그 이유로 전체 잠금 파일입니다.
라이선스
MIT — LICENSE 참조.
This server cannot be deployed
Maintenance
Related MCP Connectors
Query your warehouse or a CSV with Claude/ChatGPT over MCP, governed by table-level ACL + audit.
Read-only analytics for Convex apps, queryable via MCP from Claude, Cursor, and other clients.
Text-to-SQL MCP server: read-only queries on PostgreSQL, MySQL and SQL Server with your real schema
- dataOAuthco.thinair
PostgreSQL, MySQL, and SQL Server in one session. 26 read-only MCP tools for AI agents.
Related MCP Servers
- AlicenseAqualityBmaintenanceExposes PostgreSQL query execution, EXPLAIN, and schema inspection tools to MCP-compatible clients like Claude Desktop.31MIT
- FlicenseNot gradedqualityDmaintenanceConnects to a PostgreSQL database and exposes schema inspection and safe SELECT query execution as MCP tools and resources for use with Claude Desktop or any MCP-compatible client.-
- AlicenseNot gradedqualityCmaintenanceEnables read-only, SELECT-only querying of any Postgres database through MCP-compatible clients like Claude, with schema introspection and guarded SQL execution.MIT
- AlicenseAqualityBmaintenanceEnables Claude Desktop and Claude Code to inspect a Postgres schema and run read-only SQL queries against a demo database through the tools describe_schema and query_readonly. Every proposed query passes the same deterministic guardrails — SELECT-only, allow-listed tables, enforced row limits, statement timeouts and a read-only role — before execution.2MIT