Skip to main content
Glama
Sa3fa

pg-analytics-mcp

by Sa3fa

pg-analytics-mcp

設定駆動型の読み取り専用Postgres MCPサーバー(Claude向け)。Streamable HTTP経由でPostgresスキーマをClaudeに公開します。スキーマとenum値は起動時にライブデータベースからイントロスペクトされ、クライアント固有の設定はすべて単一のYAMLファイルにまとめられます。

cloudflared → リバースプロキシ構成のCloudflare Accessの背後で動作するように設計されています(完全なプロビジョニング手順書が含まれています)。ただし、サーバー自体にCloudflareへの依存はなく、どこでも動作します。

クライアント非依存。 server/ 配下には、特定のクライアントを知るものは何もありません。新しいクライアントに対応するには: リポジトリをコピーし、設定ファイルを書き、.env を設定します。

なぜ存在するのか

先行実装は、ベンダーパッケージを回避するために3つのプロセスを積み重ねていました:

supergateway  →  enrich.py  →  postgres-mcp  →  Postgres

postgres-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 boot

Cloudflare側については、docs/PLAYBOOK-NEW-CLIENT.md に従ってください。

設定

.env — ホスト固有のもので、VPS間で変わる唯一のものです:

変数

目的

DATABASE_URI

読み取り専用ロール。Supavisorプーラーでは、ユーザー名に .PROJECT_REF を必ず含める必要があります。

MCP_CONTAINER_NAME

コンテナ、イメージタグ、traefikルーター名

MCP_HOSTNAME

公開ホスト名。トランスポートセキュリティの許可リストに自動追加されます

TRAEFIK_NETWORK

traefikが監視する外部Dockerネットワーク

MCP_CONFIG

イメージ内のクライアントYAMLへのパス

MCP_LOCAL_PORT

ホスト側の公開ポート(デフォルト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 exist

limits.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 を参照してください。

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Connects 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.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables read-only, SELECT-only querying of any Postgres database through MCP-compatible clients like Claude, with schema introspection and guarded SQL execution.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables 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.
    2
    MIT