Postgres (read-only) について
Postgres (read-only)は、分析やエージェントによるデータベース調査向けのPostgres MCPサーバーです。多層の書き込み防止機構と、チーム固有のスキーマ情報や業務定義を記録する編集可能なコンテキストファイルを備えています。
主な機能
- 01読み取り専用SQLクエリ
- 02多層の書き込み防止
- 03テーブルとスキーマの探索ツール
- 04編集可能な共有コンテキストファイル
- 05エージェントによるテーブル注記
- 06クエリ結果と実行時間の上限
活用シーン
- 01分析用のデータベーステーブル調査
- 02書き込み権限を与えずにデータを照会
- 03エージェントとの業務定義の共有
- 04未文書化のテーブルや列の意味を記録
MCP 対応
読み取り専用クエリ、スキーマ探索、テーブルコンテキストのツールを提供し、テーブルスキーマをMCPリソースとして公開します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
4掲載時のツール一覧 · 2 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| sql | string | はい | 単一の読み取り専用 SQL 文。 |
詳細
@contextflo/postgres-mcp
Postgres 向けの分析用 MCP サーバーです。設計上読み取り専用で、あなたとエージェントの両方が書き込めるコンテキストファイルを備えているため、モデルはチームと同じように回答できます。
Contextflo が開発・保守しています。
アーカイブされた @modelcontextprotocol/server-postgres のそのまま置き換えられるサーバーです。このサーバーにはSQL インジェクションの脆弱性があり、COMMIT; DROP SCHEMA public CASCADE が読み取り専用トランザクションをすり抜けることを許していました。
100 秒のデモを見る:セットアップ、events テーブルが見た目どおりのものではないとエージェントが見抜いてメモとして保存する様子、そしてサーバーが拒否する DROP を紹介します。
npx @contextflo/postgres-mcp postgresql://localhost/mydb
このサーバーを選ぶ理由
確実に機能する読み取り専用。 アーカイブされたサーバーでは、SQL 文字列の属性として読み取り専用を強制していました。このサーバーでは、ワイヤープロトコル、接続、Postgres パーサー、データベースロールの属性として強制します。互いに独立した4つのレイヤーが、それぞれ単独でそのペイロードを阻止します。このリポジトリでは、そのエクスプロイトをテストケースとして使用しています。
納得できる回答。 fct_orders_v2 がチームで実際に使っているテーブルであることや、revenue が純額ではなく総額であることをモデルが知らなければ、自信満々に誤った SQL を書いてしまいます。.contextflo/context.md はあなたが編集する Markdown ファイルで、このサーバーはそのファイルだけをモデルに渡します。エージェントもこのファイルに追記します。スキーマに記載されていない情報を学ぶと、add_table_context がメモを追記するので、ほかの変更と同じように差分で確認できます。
セットアップ
1. 読み取り専用ロールを作成します。 データベース所有者として、psql またはプロバイダーの SQL エディターで実行してください。データベース名と実際のパスワードを使い、エージェントに表示するスキーマごとにスキーマの行を繰り返してください。
CREATE ROLE mcp_readonly LOGIN PASSWORD 'change-me';
GRANT CONNECT ON DATABASE mydb TO mcp_readonly;
GRANT USAGE ON SCHEMA public TO mcp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO mcp_readonly;
これにより、読み取り専用がこのサーバーのコードだけでなく、データベースの属性になります。
2. プロジェクトフォルダーの .env に、そのロールの接続文字列を設定します。
DATABASE_URL='postgresql://mcp_readonly:[email protected]:5432/mydb'
引用符は残してください。ホスト型プロバイダーでは ?sslmode=require&... が追加されるため、引用符が必要です。サーバーは起動時のディレクトリにある .env から DATABASE_URL を読み込むので、接続文字列がコマンドラインに表示されることはありません。
可能であれば、プライマリではなく読み取りレプリカまたはブランチを指定してください。読み取り専用で書き込みは防げますが、負荷は防げません。データを調べるエージェントは、全テーブルスキャンや負荷の高い結合を実行する可能性があり、プライマリではアプリケーションとリソースを奪い合います。Neon と Supabase では数回のクリックでデータベースを分岐または複製でき、RDS、Cloud SQL、その他ほとんどのホストでも読み取りレプリカを利用できます。ステートメントタイムアウト(デフォルトは30秒、--statement-timeout)と行数上限により、各クエリの実行時間と返されるデータ量は制限されますが、クエリの負荷が軽くなるわけではありません。
3. コンテキストファイルを生成します。
npx @contextflo/postgres-mcp init
.contextflo/context.md が作成され、COMMENT ON の値をもとに内容が設定されます。ファイルを編集してください。特にビジネス定義のセクションが重要です。
4. クライアントに追加します。 Claude Code はプロジェクトフォルダー内でサーバーを起動するため、.env とコンテキストファイルを自動的に見つけます。
claude mcp add postgres --scope project -- npx -y @contextflo/postgres-mcp
--scope project を指定すると、グローバル設定ではなくフォルダー内に .mcp.json が書き込まれます。
Cursor、Claude Desktop、VS Code
ほかのクライアントではプロジェクトフォルダー外でサーバーが起動する場合があります(Claude Desktop は / で起動します)。そのため、接続文字列とコンテキストファイルを明示的に指定してください。
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "@contextflo/postgres-mcp", "--context-file", "/path/to/project/.contextflo/context.md"],
"env": { "DATABASE_URL": "postgresql://mcp_readonly:[email protected]:5432/mydb" }
}
}
}
Cursor は .cursor/mcp.json を使用します。Claude Desktop は claude_desktop_config.json を使用します(macOS:
~/Library/Application Support/Claude/、Windows:%APPDATA%\Claude\)。VS Code は .vscode/mcp.json を使用し、mcpServers の代わりに servers を指定します。
| ツール | 機能 |
|---|---|
query | 読み取り専用のステートメントを1つ実行します。対象は SELECT、WITH ... SELECT、EXPLAIN、SHOW です。 |
list_tables | 読み取り可能なテーブルを説明とともに一覧表示します。pattern は名前または説明のどこにでも一致します。 |
get_table_context | テーブルの列、型、キー、外部キーの参照先、列挙値、整備された説明を記述します。 |
add_table_context | エージェントが見つけた注意点(amount はセント単位、status には文書化されていない値があるなど)をコンテキストファイルに書き込めるようにします。 |
個別の検索ツールはありません。これは意図的な設計です。information_schema と pg_catalog は通常のテーブルなので、
%revenue% のような名前の列をすべて探したり、主キーのないテーブルを一覧表示したりするような、より具体的な処理は、
モデル自身がクエリを作成できます。
SELECT table_schema, table_name, column_name
FROM information_schema.columns
WHERE column_name ILIKE '%revenue%';
テーブルスキーマは、アーカイブされたサーバーに合わせて postgres://<host>/<table>/schema リソースとしても公開されます。これらの URI に固定されている場合に利用できます。ほとんどのクライアントはリソースを自動で取得しないため、ツールで検出できるようにしています。
読み取り専用を強制する仕組み
4 つのレイヤーがあります。それぞれ単独で、アーカイブされたサーバーの脆弱性を防ぎます。
1. 拡張クエリプロトコル。 ユーザーの SQL は pg-cursor 経由で実行されます。pg-cursor は常に Parse/Bind/Execute を発行するため、Postgres 自体が複数のステートメントを含む入力を拒否します。アーカイブされたサーバーは、文字列をそのまま client.query(sql) に渡していました。node-postgres がステートメントを準備するのはバインド値がある場合だけなので、その方法では シンプル プロトコルの経路が使われ、; でステートメントが区切られていました。バグの全容はこれだけです。
2. 接続レベルの読み取り専用。 起動パケットで default_transaction_read_only=on を設定し、すべてのステートメントを明示的な BEGIN READ ONLY の中で実行します。このトランザクションは必ず ROLLBACK で終了し、COMMIT はしません。ロールバックによって、トランザクション内で行われた SET も取り消されるため、ステートメントがプールされた接続の設定を弱め、次にその接続を使う人に影響することはありません。
3. 実際の Postgres パーサーによるステートメントの許可リスト。 libpg-query は、JavaScript で近似した SQL パーサーではなく、WASM にコンパイルされた実際の Postgres C パーサーです。トップレベルのノードだけでなく、解析ツリー全体をたどります。これにより、データを変更する CTE を検出できます。
WITH x AS (INSERT INTO users VALUES (1) RETURNING *) SELECT * FROM x
これは SelectStmt として解析されます。ステートメントの種類だけを確認するバリデーターでは、実行されてしまいます。未知のノード型は安全側に倒して拒否します。
関数はデータベース独自のカタログと照合して確認します。Postgres はすべての関数を immutable、stable、volatile のいずれかに分類し、副作用を持てるのは volatile の関数だけです。そのため、分析に必要な無害な関数の短いリスト(random()、clock_timestamp()、テーブルサイズ関数)に含まれない volatile 関数は拒否されます。これにより、dblink、読み取り専用トランザクション内でも WAL に書き込む pg_logical_emit_message、アドバイザリロック、統計情報のリセット、そして将来の Postgres で追加される機能まで、個別に名前を挙げなくても対処できます。所有者の権限で実行される SECURITY DEFINER 関数は、どのような分類であっても拒否されます。既知の回避手段も固定リストで確認します。
4. 読み取り専用のデータベースロール。 上記のレイヤーはコードであり、コードにはバグがあります。書き込みできないロールは Postgres によって強制されます。そのため、ロールの作成はセットアップの最初の手順です。スーパーユーザーとして接続すると、サーバーは起動時に警告します。また、接続に使うロールが書き込み可能な場合、init はロールの設定例を表示します。
これで防げないこと。 関数のチェックでは、クエリが直接呼び出す関数と、その関数に付けられた分類を確認します。STABLE と宣言されているユーザー定義関数が書き込みを行う場合でも許可されます。また、ビュー内、演算子の背後、型キャスト経由など、間接的に呼び出される関数も同様です。ただし、読み取り専用トランザクションにより、この方法でテーブルデータを変更しようとする操作は拒否されます。すり抜ける可能性があるのは、WAL へのメッセージ書き込みなど、よりまれな種類の副作用です。これらを防ぐのがレイヤー 4 です。そのため、読み取り専用ロールは任意の追加設定ではなく、推奨されるセットアップです。また、読み取り専用であることは
機密性を保証しません。接続ロールが読み取れるものは、モデルも読み取れます。そのため、エージェントに見せたいものだけを許可してください。
@modelcontextprotocol/server-postgres からの移行
パッケージ名を置き換えてください。ツール名は引き続き query で、引き続き sql を受け取り、JSON の行を返します。接続文字列も引き続き最初の引数です。
意図的に変更した点は4つあります。
- 複数ステートメントの SQL と
SET/RESETは、明確なエラーとともに拒否されます。 アーカイブされたサーバーではこれらが「動作」していましたが、それが脆弱性の原因でした。 - 結果はデフォルトで1000行、50,000文字に制限され、2,000文字を超える単一の値は短縮されます。 切り詰めが発生した場合は出力に明示され、黙って処理されることはありません。行は整形表示ではなく1行ずつ返されるため、トークンコストはおよそ半分になります。引き続き JSON 配列です。日付とタイムスタンプはサーバーのタイムゾーンで再表示されず、Postgres から受け取った値がそのまま返されます。
- スキーマ検出はリソースだけでなく、ツールでも行えます。 ほとんどのクライアントはリソースを自動的にアタッチしないため、旧サーバーを使用するモデルがスキーマを把握していないことがよくありました。
publicだけでなく、システム以外のすべてのスキーマが表示され、COMMENT ONの列の説明も反映されます。
オプション
--max-rows <n> Maximum rows returned per query (default: 1000)
--max-output-chars <n> Character budget for one query result (default: 50000)
--statement-timeout <ms> Server-side statement timeout (default: 30000)
--context-file <path> Curated schema context (default: .contextflo/context.md)
--no-context-writes Do not offer add_table_context; the context file is only read
--log-file <path> Query audit log (default: .contextflo/log.md once that directory exists)
--no-log Never write a query log
--http Serve over streamable HTTP instead of stdio
--port <n> HTTP port (default: 8080)
--host <addr> HTTP bind address (default: 127.0.0.1)
接続文字列を指定しない場合、DATABASE_URL が環境変数またはカレントディレクトリの .env から接続文字列を読み込みます。サーバーは初回使用時に接続します。接続文字列がなくても起動してツールの一覧を表示し、各ツール呼び出しで不足しているものを通知します。--http を指定した場合、AUTH_TOKEN の値がベアラートークンとして必須になります。
接続プール。 PgBouncer(および Supabase、Neon などが提供するプール接続文字列)は、このサーバーが通常送信するスタートアップパラメーターを拒否します。その場合、パラメーターなしで再接続し、その旨を標準エラー出力に表示します。安全性が損なわれることはありません。すべてのステートメントは引き続き独自のステートメントタイムアウトを設定した BEGIN READ ONLY 内で実行され、CI ではトランザクションモードの PgBouncer を通して読み取り専用テストスイートを実行します。
コンテキストファイル
# Database context
Revenue means gross, before refunds.
"Active customer" means an order in the last 90 days.
## Tables
### public.orders
One row per customer order. Source of truth for revenue, not `orders_legacy`.
- revenue_usd: Gross revenue, before refunds. Net lives in `order_refunds`.
- status: One of pending, paid, refunded.
## Tables より上の内容は、サーバーの指示としてモデルに渡され、さらに list_tables の冒頭にも渡されます。これは、一部のクライアントがサーバーの指示をモデルに表示しないためです。HTML コメントは除外されます。## Tables の下では、### 見出しにテーブル名を記述し、その下の文章でテーブルを説明します。また、- column: meaning の行で列を説明します(ダッシュの代わりに区切り文字を使うこともできます)。COMMENT ON より、ここに書いた内容が優先されます。編集内容は次のツール呼び出しから反映され、再起動は不要です。
相対パスで指定した --context-file は、クライアントがサーバーを起動するディレクトリを基準に解決されます。Claude Desktop はサーバーを / で起動するため、その場合はホームディレクトリ(~/.contextflo/context.md)にフォールバックします。リポジトリ内のファイルを使う場合は、絶対パスを指定してください。サーバーは起動時に使用するパスを表示します。
エージェントが内容を追加します。 スキーマに記載されていない情報(amount の単位がセントである、古い行では status に 'void' が入ることもある、すべてのクエリで deleted_at IS NULL が必要であるなど)をモデルが見つけると、add_table_context がその情報をこのファイルに追加し、以降のセッションではその情報を前提にできます。注記は常に追記され、あなたが書いた内容を置き換えることはありません。また、存在するテーブルと列についてのみ追加されます。他の変更と同様に扱い、ファイルを git で管理して差分を確認してください。モデルが間違うこともあり、データ内のテキストによって追加内容が誘導される可能性もあるため、誰かが確認するまでは提案として扱ってください。--no-context-writes を指定すると、このツールは無効になります。
init は既存のコメントからコンテキストを初期化し、すでにコメントがある列のみを対象とします。4000個すべての列に空のプレースホルダーがあるファイルは、誰も編集しません。
コードやドキュメントからコンテキストをより豊富に生成し、チームで共有できるのが、contextflo.com です。
クエリログ
.contextflo/ が作成されると、すべてのステートメントが結果、行数、所要時間とともに .contextflo/log.md に追記されます。このログはあなた向けのもので、モデルには渡されません。--no-log を指定すると無効になります。
Docker
docker run -i --rm ghcr.io/contextflo/postgres-mcp postgresql://host.docker.internal/mydb
リモートエンドポイントについては、docker-compose.example.yml を参照してください。
0.0.0.0 にバインドする前に、何をしているのか確認してください。これはポート上でデータベースに直接接続するものです。AUTH_TOKEN を設定し、プライベートネットワーク内にとどめ、手前で TLS を終端してください。そうしないと、トークンが通信上で平文になるためです。トークンなしで公開されると、サーバーは警告を出力します。
デフォルトのループバックバインドでは、Host または Origin がローカルでないリクエストは拒否されます。これにより、ブラウザーを経由して、ウェブページが DNS リバインディングを使って 127.0.0.1 上のサーバーにアクセスするのを防ぎます。
開発
npm install
npm run build
npm test
npm run db:up # throwaway Postgres for the integration suite (needs Docker)
TEST_DATABASE_URL=postgres://postgres:postgres@localhost:55432/postgres npm test
npm run db:down
統合テストスイートは意図的にパーサーをバイパスし、データベースを直接操作します。そのため、バリデーターではなく、通信層とトランザクション層を検証します。TEST_DATABASE_URL が設定されていない場合はスキップされ、CI での実行が必須です。
セキュリティ
脆弱性は非公開で報告してください。SECURITY.md を参照してください。
ライセンス
MIT
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。