pg-analytics-mcp
pg-analytics-mcp
設定駆動型の読み取り専用Postgres MCPサーバー(Claude向け)。Streamable HTTP経由でPostgresスキーマをClaudeに公開します。スキーマとenum値は起動時にライブデータベースからイントロスペクトされ、クライアント固有の設定はすべて単一のYAMLファイルにまとめられます。
cloudflared → リバースプロキシ構成のCloudflare Accessの背後で動作するように設計されています(完全なプロビジョニング手順書が含まれています)。ただし、サーバー自体にCloudflareへの依存はなく、どこでも動作します。
クライアント非依存。 server/ 配下には、特定のクライアントを知るものは何もありません。新しいクライアントに対応するには: リポジトリをコピーし、設定ファイルを書き、.env を設定します。
なぜ存在するのか
先行実装は、ベンダーパッケージを回避するために3つのプロセスを積み重ねていました:
supergateway → enrich.py → postgres-mcp → Postgrespostgres-mcp は stdio/SSE のみに対応しており(CloudflareはStreamable HTTPを要求します)、設定面がまったくありません。さらに supergateway はMCPセッションごとに子プロセスをフォークしましたが、その子プロセスは回収されることがなく、ロール上限20に対して 子プロセス23 / 接続15 を計測しました。これは「約9回の呼び出しは成功するが、その後は SELECT 1 を含めてすべて失敗する」という形で表面化しました。
このサーバーは1つのプロセスと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 bootCloudflare側については、docs/PLAYBOOK-NEW-CLIENT.md に従ってください。
設定
.env — ホスト固有のもので、VPS間で変わる唯一のものです:
変数 | 目的 |
| 読み取り専用ロール。Supavisorプーラーでは、ユーザー名に |
| コンテナ、イメージタグ、traefikルーター名 |
| 公開ホスト名。トランスポートセキュリティの許可リストに自動追加されます |
| traefikが監視する外部Dockerネットワーク |
| イメージ内のクライアントYAMLへのパス |
| ホスト側の公開ポート(デフォルト8000) |
config/<client>.yaml — ドメインです。ここに列やenum値を列挙しないでください: これらは起動時にライブデータベースからイントロスペクトされるため、古くなることはありません。イントロスペクションでは知り得ないこと、つまりビジネス上の意味と落とし穴だけを書いてください。
ツール
組み込み:
execute_sql(sql)— 生の読み取り専用SQL。その説明は、起動時にあなたが作成した散文に加えて、生成されたスキーマとenumリストから組み立てられます。list_views()— 列、行数、enumを含む、読み取り可能なすべてのオブジェクト。describe_view(name)— 1つのオブジェクトの列。
設定で定義: 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設定やスキーマの変更には再起動が必要です — イントロスペクションは意図的にプロセスの生存期間中キャッシュされるため、実行中に動作がずれることはありません。
5つの境界テスト
ビュー、権限、設定を変更した後は再実行してください。5つすべてが失敗しなければなりません:
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は自動的に追加されます。独自のStarletteの下にMCPアプリをマウントすると、そのライフスパンが置き換わります。 セッションマネージャーを明示的に起動する必要があります(
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