Postgres (read-only transactions) について
postgres-mcp-safeは、AIツールからPostgresを検索し、スキーマを確認してクエリを説明するMCPサーバーです。Postgresが強制する読み取り専用トランザクションと呼び出しごとの上限により、非推奨のserver-postgresを置き換えます。
主な機能
- 01読み取り専用SQLクエリ
- 02テーブルとビューの一覧
- 03列・制約・インデックスの確認
- 04クエリプランの説明
- 05時間・行数・バイト数の制限
活用シーン
- 01本番データベースの安全な調査
- 02テーブル構造とインデックスの確認
- 03クエリ実行プランの分析
MCP 対応
MCPクライアントにquery、list_tables、describe_table、explainツールとスキーマリソースを提供します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
postgres-mcp-safe
非推奨の @modelcontextprotocol/server-postgres と置き換えて使えますが、実際に読み取り専用です。
AI ツール(Claude、Cursor、Zed など)から Postgres データベースにクエリを実行し、スキーマを読み取ってクエリを説明できます。MCP 設定でパッケージ名を置き換えるだけで、ほかはそのまま使えます。
server-postgres からの移行
{
"mcpServers": {
"postgres": {
"command": "npx",
- "args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"]
+ "args": ["-y", "postgres-mcp-safe", "postgresql://localhost/mydb"]
}
}
}
Docker:docker run -i --rm ghcr.io/poudel0/postgres-mcp-safe postgresql://host.docker.internal:5432/mydb。
Linux、macOS、Windows 用のバイナリはリリースページにあります。
注意点が一つあります。 URL でスーパーユーザー(Docker のデフォルトの postgres ユーザーなど)としてログインすると、読み取り専用ロールを作成する(推奨)か、URL の前に --allow-superuser を追加するまで、すべての呼び出しが拒否されます。スーパーユーザーは読み取り専用トランザクションを回避できるため、その点を偽ることはありません。
旧サーバーの query ツールと postgres://.../<table>/schema リソースは同じように動作します。意図的に加えた小さな変更については、相違点をご覧ください。
理由
旧サーバーの「読み取り専用」モードは、COMMIT; DROP TABLE ... によって破られる可能性があります
(Datadog Security Labs、2025-08-21)。
修正は上流にマージされましたが公開されず、パッケージは非推奨となり、公式な後継はありません。
フラグ
フラグは URL の前に指定します。
| フラグ | デフォルト | |
|---|---|---|
--timeout | 30s | 呼び出しごとの制限時間です。 |
--max-rows | 1000 | 呼び出しごとに返す行数です。超過した行は注記とともに切り捨てられます。 |
--max-bytes | 262144 | 呼び出しごとに返す JSON のバイト数、および単一行の最大サイズです。 |
--allow-explain-analyze | off | explain で EXPLAIN ANALYZE を実行できるようにします。クエリが実行されます。 |
--allow-superuser | off | スーパーユーザーロール、またはそのメンバーを許可します。この場合、読み取り専用は保証されません。 |
安全性を確保する仕組みの一環として、呼び出しごとに新しい接続を開きます。TLS 経由でリモートデータベースに接続する場合、数回の往復が必要になり、レイテンシが 50 ms の場合、呼び出しごとに約 0.5 秒かかります。Postgres 17 以降では、URL に sslnegotiation=direct を追加すると、往復を一回減らせます。
制限は無効にできません。0 を指定するとエラーになります。URL にはキーワード DSN(host=db dbname=app)も使用でき、標準の PG* 環境変数(PGPASSWORD など)も libpq と同様に読み込まれます。
ツール
| ツール | 入力 | |
|---|---|---|
query | sql | 読み取り専用の単一ステートメントです。行の JSON 配列を返します。 |
list_tables | schema? | スキーマと種類を含むテーブルおよびビューです。 |
describe_table | table、schema?(public) | 列、制約、インデックスです。 |
explain | sql、analyze? | クエリプランです。analyze には --allow-explain-analyze が必要です。 |
読み取り専用の適用方法
SQL パーサーではなく、Postgres が適用します。呼び出しのたびに:
- 新しい接続を開き(セッション状態は呼び出しをまたいで保持されません)、
READ ONLYトランザクションを開始します。 - SQL を単一のプロトコルレベルの Parse メッセージとして送信します。Postgres はここで複数のステートメントを拒否するため、
COMMIT; ...は何も実行される前に失敗します。 - Postgres が行を返すと判断した場合にのみ実行します。
COMMIT、SET、DO、COPY、CALL、PREPAREなどは 実行されずに拒否されます。 statement_timeout、クライアント側の期限、行数とバイト数の上限を適用します。- 接続を閉じ、トランザクションを終了します。
bypass suite は既知のすべてのペイロードを実際のデータベースに対して実行し、それぞれの後にカナリアテーブルを確認します:
| ペイロード | 結果 |
|---|---|
COMMIT; DROP SCHEMA public CASCADE; | 拒否:複数のステートメント |
COMMIT / SET TRANSACTION READ WRITE / DO $$...$$ / CALL p() | 拒否:行を返さない |
COPY (SELECT 1) TO PROGRAM '...'(スーパーユーザーとして) | 拒否:行を返さない |
DELETE ... RETURNING * / WITH d AS (DELETE ...) SELECT ... | 読み取り専用トランザクションエラー |
SELECT nextval('s') | 読み取り専用トランザクションエラー |
SELECT set_config('transaction_read_only','off',true) | エラー |
SELECT pg_sleep(600)(set_config('statement_timeout','0') の後でも) | --timeout でキャンセル |
SELECT pg_advisory_lock(1) | 呼び出し終了時に解放 |
SELECT generate_series(1,10000000) / repeat('x', 10000000) | 上限で制限/拒否され、メモリ使用量は一定に保たれます |
explain と ANALYZE SELECT ... | 構文エラー:ANALYZE を紛れ込ませることはできません |
回避方法を見つけましたか?イシューを作成してください。修正前にテストケースとして追加されます。
読み取り専用ロールも使用する
このサーバーは各呼び出しをトランザクションレベルで読み取り専用にします。トランザクションの範囲外にある操作もあり、それらを防ぐのはデータベースロールだけです:
dblink_exec(...)は読み取り専用ではない別のセッションを開きます。pg_terminate_backend(...)は他の接続を終了させます。- スーパーユーザーはサーバーファイルを読み取れます(
pg_read_file、pg_ls_dir)。読み取り専用が防ぐのは書き込みであり、読み取りではありません。 - スーパーユーザーは、読み取り専用トランザクション内でも
lo_exportでサーバーファイルに書き込むことができます。
そのため、読み取りのみ可能なロールで接続してください:
CREATE ROLE mcp_ro LOGIN PASSWORD '...';
GRANT pg_read_all_data TO mcp_ro; -- PG 14+; or GRANT SELECT on what it needs
ALTER ROLE mcp_ro SET default_transaction_read_only = on;
最後の2点を防ぐため、--allow-superuser を渡さない限り、サーバーはスーパーユーザーロール(およびそのメンバー)を拒否します。
server-postgres との違い
- ツール呼び出しのエラーは、
isError: trueと Postgres のメッセージおよび SQLSTATE を含むツールの結果として返されるため、 モデルは SQL を修正できます。以前のサーバーは JSON-RPC エラーを返していました。 resources/readには、publicテーブルの列のみが列順で表示されます。以前のサーバーは、他のスキーマにある同名のテーブルの列も混在させていました。- リソース URI 内のパーセントエンコードされたテーブル名はデコードされます(以前のサーバーでは一致しませんでした)。
- 値は Postgres の出力形式で、型が明確なものは型付きです。ブール値、数値(
bigintとnumericを含む)、jsonは JSON 値になり、それ以外は"2026-01-02 03:04:05+00"、"\\x0102"、"{x,y}"のような文字列になります。NaNは"NaN"です(以前のサーバーではnullを返していました)。 - 行を返さないステートメント(
SET、DO、COPYなど)は拒否されます。以前のサーバーはそれらを実行し、[]を返していました。
開発
docker run -d --name pg-mcp-test -e POSTGRES_PASSWORD=postgres -p 127.0.0.1:5434:5432 postgres:17-alpine
export DATABASE_URL='postgresql://postgres:[email protected]:5434/postgres?sslmode=disable'
make test # all tests, including the bypass suite
make golden # re-record the old server's output (needs npx)
DATABASE_URL はスーパーユーザーでなければなりません。フィクスチャは public スキーマをリセットし、mcp_ro ロールを作成します。使い捨てのデータベースを使用してください。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。