mcpField
P
Postgres DBA MCP

Postgres DBA MCP

ローカル
github.com/a-lafonデータベース01 日前に更新

Postgres DBA MCP について

Postgres DBA MCPは、単一のPostgreSQLデータベースを調べるAIクライアント向けの読み取り専用MCPサーバーです。件数を制限したCSV出力とSQL制限により、無制限な出力や書き込みを避けてデータベースを調査できます。

主な機能

  1. 019つのDBAツールとロック分析
  2. 02読み取り専用SQL実行
  3. 03簡潔で上限付きのCSV結果
  4. 04クエリプランと仮想インデックス
  5. 05ヘルスチェックと遅いクエリの検出

活用シーン

  1. 01データベースのスキーマとクエリ結果を調査
  2. 02クエリプランとインデックス候補を確認
  3. 03ロック、健全性、遅いクエリを点検

MCP 対応

MCPクライアントに、SQL実行、クエリプラン確認、インデックス検証、ヘルスチェックなどの上限付きPostgreSQL DBAツールを提供します。制限モードはデフォルトで有効です。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

mcp.json · 共通設定
{
"mcpServers": {
"a-lafon-postgres-dba": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"DATABASE_URI",
"ghcr.io/a-lafon/postgres-dba-mcp:0.3.3"
],
"env": {
"DATABASE_URI": "<DATABASE_URI>"
}
}
}
}
起動コマンドdocker run -i --rm -e DATABASE_URI ghcr.io/a-lafon/postgres-dba-mcp:0.3.3
設定が必要な環境変数
DATABASE_URIシークレットPostgreSQL connection URI of the database to serve

ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。

パッケージ
ocighcr.io/a-lafon/postgres-dba-mcp:0.3.3

ツール

ツール一覧はまだ取得されていません

ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。

詳細

postgres-dba-mcp

AI アシスタント向けの、制限付き読み取り専用 PostgreSQL アクセス。

Claude Code が Pagila 映画データベースに最も多くの映画に出演している俳優を尋ね、次に Gina Degeneres の映画の中で最も多くレンタルされた作品を尋ねます。各 execute_sql 呼び出しは、回答の前に行数のフッターを含む簡潔な CSV を返します

Claude Code、Codex、または任意の MCP クライアントから PostgreSQL データベースを調査し、SQL を実行し、クエリプランを読み取り、仮想インデックスをテストし、最も遅いクエリを見つけ、データベースの健全性を確認できます。モデルのコンテキストをあふれさせたり、読み取り専用モードを回避したりすることはありません。

AI クライアントに単一の PostgreSQL データベースへの簡潔で制限付きのアクセスを提供する、Model Context Protocol サーバーです。crystaldba/postgres-mcp 0.3.0 のドロップイン代替品で、同じ名前、同じパラメーターの9つのツールに、独自の analyze_locks を加えたものを、1つの静的バイナリで提供します。切り替えはクライアント設定を1行変更するだけです。モデルが受け取る内容は変わります(簡潔な CSV、制限付きの結果、呼び出しごとに1つのステートメント)。詳細はMIGRATION.mdをご覧ください。

理由

crystaldba/postgres-mcp は、スキーマ、SQL、仮想インデックスを使ったプラン、上位クエリ、ヘルスチェック、インデックスに関するアドバイスなど、アシスタントに適切な DBA ツールを提供します。しかし、日常的に使ううえで3つの問題がありました。

  • 結果に制限がなく、冗長です。 行はサイズ制限なしで Python の repr として返されます。大きなテーブルに対する単純な SELECT でも、数十万トークンが返されるか、失敗します。
  • 制限モードを回避できます。 関数名は FROM 内でチェックされないため、「読み取り専用」の呼び出しでも pg_read_file や pg_advisory_lock などの関数を実行できてしまいます(CVE-2026-85620)。
  • 動作が重いです。 約300 MBの Python コンテナーで、メモリ使用量は約250 MBです。

モデルが受け取る内容は次のように変わります。

  • 明示的なフッター付きの簡潔な CSV。すべての結果は行数、バイト数、セル長で制限され、切り捨てが発生するたびに通知されます。
  • デフォルトで有効な制限モード。各ステートメントを PostgreSQL 自身のパーサー(libpg_query)で解析し、読み取り専用トランザクション内で、その中のあらゆる関数呼び出しをチェックします。
  • 数ミリ秒で起動する、単一の静的 Go バイナリ。

測定結果

同じ PostgreSQL 17 データベースと同じ読み取り専用ロールを使用し、crystaldba/postgres-mcp 0.3.0 と比較しました。エージェントタスクを除き、2026-11-01 に 959bb72 で測定しています。

  • execute_sql の応答あたりのトークン数が 61 % 少ない:両サーバーが回答した4つのシナリオ(S01、S04、S05、S06)の中央値です。すべての結果は最大32 kBに制限されます。crystaldbaは最大268kトークンを返すか、失敗します。
  • 制限モードでは、攻撃用ステートメント 131件中131件 を拒否しました。crystaldbaは10件を実行します。
  • メモリ使用量は 9 MB で、crystaldbaの246 MBに対して少なくなっています。どちらもLinuxコンテナ内で測定しました。バイナリを直接実行した場合のコールドスタートは 13 ms で、docker run を含むcrystaldbaの2.3秒に対して短くなっています。
  • Claude Codeを使ったエージェントタスクは 27件中27件 を解決し、crystaldbaは27件中25件でした。ここに記載した測定のうち、これだけは2026-10-22に0.1.0で実施したもので、ボラティリティゲートとanalyze_locksの導入前です。

一方で、ツール定義は39 %大きく(10個のツールで2732トークン、crystaldbaの9個では1962トークン)、エージェントタスク全体のトークン数は同程度です。--toolsからlocksを除けば、0.3.0のツールセットをそのまま利用できます。

手法、すべての数値(postgres-dba-mcpの結果が劣る場合も含む)、グラフについてはBENCHMARK.mdをご覧ください。測定に使ったハーネスはbenchにあり、入力データはbench/data、攻撃用および正当なデータセットはtestdata/safesql、シナリオとタスクはBENCHMARK.mdに記載されています。cd bench && go run ./cmd/bench allを実行すると、モデルアカウントなしでセキュリティ、メモリ、コールドスタート、シナリオを再測定できます。正確なトークン数を測定するには、--tokens=claudeまたはANTHROPIC_API_KEYが必要です。エージェントタスクとその判定にはclaude CLIが必要です。前提条件と各レベルで必要なものについては、bench/README.mdをご覧ください。

モデルが受け取る内容

両方のサーバーに同じSELECT * FROM typesを実行します。crystaldba 0.3.0:

[{'id': 1, 'txt': 'hello', 'empty': '', 'num': Decimal('532.10'), 'arr': [1, 2], 'iv': datetime.timedelta(days=1, seconds=7200), 'ts': datetime.datetime(2026, 3, 2, 10, 14, tzinfo=zoneinfo.ZoneInfo(key='Etc/UTC')), 'bin': b'\xde\xad\xbe\xef'}, {'id': 2, 'txt': None, 'empty': None, 'num': None, 'arr': None, 'iv': None, 'ts': None, 'bin': None}]

postgres-dba-mcp:

id,txt,empty,num,arr,iv,ts,bin
1,hello,"",532.10,"{1,2}",1 day 02:00:00,2026-03-02 10:14:00+00,\xdeadbeef
2,\N,\N,\N,\N,\N,\N,\N
-- 2 rows, 2 ms

値はPostgreSQLのテキスト形式を維持し、NULL(\N)は空文字列と区別されます。また、フッターには返された行数と、結果が切り詰められたかどうかが表示されます。

インストール

Homebrewを使用する場合(macOSおよびLinux):

brew install a-lafon/tap/postgres-dba-mcp

インストールスクリプトを使用する場合(macOSおよびLinux、amd64およびarm64):リリースアーカイブをダウンロードし、checksums.txtと照合して、バイナリを~/.local/binに配置します(変更するにはPOSTGRES_DBA_MCP_INSTALL_DIRを指定します。特定のリリースを指定するにはPOSTGRES_DBA_MCP_VERSION=v0.1.0を指定します)。

curl -fsSL https://raw.githubusercontent.com/a-lafon/postgres-dba-mcp/main/install.sh | sh

Dockerを使用する場合(linux/amd64およびlinux/arm64、root以外で実行、シェルなし):

docker run -i --rm -e DATABASE_URI ghcr.io/a-lafon/postgres-dba-mcp:latest

URI内のlocalhostはコンテナ自体を指します。Docker Desktopではhost.docker.internalを、Linuxでは--network=hostを使用してください(下記参照)。

Windowsを含むリリースアーカイブからインストールする場合:システムに対応するアーカイブをリリースページからダウンロードします(amd64ではdarwin、linux、windows、arm64ではdarwinとlinuxに対応)。checksums.txt(SHA-256)と照合し、postgres-dba-mcpバイナリをPATHに配置してください。

Goツールチェーンを使用する場合(Go 1.26以降):go install github.com/a-lafon/postgres-dba-mcp/cmd/postgres-dba-mcp@latestを実行します。

Linuxバイナリとイメージでは、ネイティブのPostgreSQLパーサーでSQLを解析します(起動時のメモリ使用量は約20 MBです)。macOSおよびWindowsのバイナリと、通常のgo installではWASM版のパーサーを使用します。起動時のメモリ使用量は最大で約300 MBですが、その後はメモリが解放されます。Cコンパイラーがある場合は、CGO_ENABLED=1 go install -tags pgquery_cgo,netgo,osusergo github.com/a-lafon/postgres-dba-mcp/cmd/postgres-dba-mcp@latestを実行すると、ネイティブ版をビルドできます。

postgres-dba-mcp --versionでインストールを確認してください。シェルからコマンドが見つからない場合、~/.local/binがPATHに含まれていません。PATHに追加するか、フルパスを指定してサーバーを登録してください。

Claude Code ですぐに始める

データベース URI を環境変数に設定して、サーバーを登録します。

claude mcp add pg --env DATABASE_URI=postgres://pgdba_ro:secret@localhost:5432/pgdba -- postgres-dba-mcp

または、.mcp.json に記述します。

{
  "mcpServers": {
    "pg": {
      "command": "postgres-dba-mcp",
      "env": { "DATABASE_URI": "${DATABASE_URI}" }
    }
  }
}

URI は最初の引数に指定することもできます(postgres-dba-mcp postgres://...)。両方が指定されている場合は、DATABASE_URI が優先されます。サーバーは接続せずに起動し、最初のツール呼び出し時に接続を開きます。

URI の接続オプション(sslmode、sslrootcert、connect_timeout、…)は有効です。また、search_path も有効で、接続時ではなく各トランザクションに適用されます。トランザクションプーラーが接続時の設定を拒否するためです。同じ理由で、timezone などのサーバー設定は無視されたと報告されます。TLS オプションのスペルミスは、代わりに起動時に拒否されます。そのままにするとサーバー設定として扱われ、接続時に証明書の検証が行われなくなるためです。

Docker 経由では、コンテナー内の localhost はコンテナー自体を指し、イメージによる書き換えは行われません。Docker Desktop では URI に host.docker.internal を使用してください。Linux では --network=host またはブリッジネットワーク上のホストのアドレスを使用してください。

{
  "mcpServers": {
    "pg": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-e", "DATABASE_URI", "ghcr.io/a-lafon/postgres-dba-mcp:latest"],
      "env": { "DATABASE_URI": "postgres://pgdba_ro:[email protected]:5432/pgdba" }
    }
  }
}

Codex ですぐに始める

データベース URI を環境変数に設定して、サーバーを登録します。

codex mcp add pg --env DATABASE_URI=postgres://pgdba_ro:secret@localhost:5432/pgdba -- postgres-dba-mcp

または、~/.codex/config.toml に記述します。

[mcp_servers.pg]
command = "postgres-dba-mcp"
env = { DATABASE_URI = "postgres://pgdba_ro:secret@localhost:5432/pgdba" }

Codex は、tool_timeout_sec(デフォルトでは 60 秒)を過ぎるとツールの応答を待つのをやめます。--statement-timeout をそれより長く設定する場合は、同じエントリーの tool_timeout_sec も引き上げてください。

複数のデータベース

1 つのプロセスで扱えるデータベースは 1 つです。複数のデータベースを使うには、データベースごとにサーバーエントリーを追加してください。各エントリーには独自のツールが割り当てられるため、サーバーごとに権限ルールを設定できます。すべてのサーバーで読み取り専用ロールを使用し、書き込みを意図する場合に限り --access-mode=unrestricted を使用してください。

{
  "mcpServers": {
    "pg-prod": {
      "command": "postgres-dba-mcp",
      "env": { "DATABASE_URI": "${PROD_DATABASE_URI}" }
    },
    "pg-staging": {
      "command": "postgres-dba-mcp",
      "args": ["--access-mode=unrestricted"],
      "env": { "DATABASE_URI": "${STAGING_DATABASE_URI}" }
    }
  }
}

Claude Code では、権限ルールはサーバー名を指定するため、上記の 2 つのエントリーを個別に扱えます。たとえば、.claude/settings.json で pg-prod のすべてのツールを許可し、pg-staging の各 execute_sql の前に確認を求めるよう設定できます。

{
  "permissions": {
    "allow": ["mcp__pg-prod"],
    "ask": ["mcp__pg-staging__execute_sql"]
  }
}

URI 以外での認証情報の指定

パスワードを URI や .mcp.json に記述する必要はありません。ドライバー(pgx)が標準の libpq の情報源を直接読み取るため、このサーバーはそれらのためのオプションを追加しません。

パスワードを含まない URI を指定し、パスワードを ~/.pgpass(または PGPASSFILE で指定したファイル)か PGPASSWORD から読み取る方法です。

DATABASE_URI=postgres://[email protected]:5432/pgdba

~/.pgpass の行は host:port:database:user:password の形式です。また、他のユーザーがファイルを読み取れないようにする必要があります(chmod 600)。PGPASSWORD=... postgres-dba-mcp も同じように機能します。

pg_service.conf(または PGSERVICEFILE で指定したファイル)のサービス名を使う方法です。

DATABASE_URI="service=prod"
[prod]
host=db.example.com
port=5432
dbname=pgdba
user=pgdba_ro
sslmode=verify-full

サーバーのログにパスワードが記録されることはありません。起動時の行には user@host:port/db のみが表示されます。

読み取り専用ロール

restricted モードではサーバーが書き込みを拒否しますが、本当の防壁となるのは Postgres ロールです。読み取りのみ可能なロールをサーバーに割り当ててください。

CREATE ROLE pgdba_ro LOGIN PASSWORD 'change-me';
GRANT pg_read_all_stats TO pgdba_ro;                      -- pg_stat_statements text and other sessions' statistics
GRANT USAGE ON SCHEMA public TO pgdba_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO pgdba_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO pgdba_ro;   -- tables created later

pg_read_all_stats がない場合、analyze_locks は PID、ロック、チェーンを引き続き報告しますが、他のロールのセッションの待機状況、状態、トランザクション経過時間、クエリテキストは \N として返され、その旨が -- session details hidden: grant pg_read_all_stats to <role> … の行に表示されます。PostgreSQL 15 以降では、pg_read_all_stats の NOINHERIT メンバーも何も読み取れず、同様に報告されます。PostgreSQL 14 では、メンバーであれば権限が付与されます。いずれの場合も、この行が表示されるのはレポートで実際に情報が隠された場合のみです。

エージェントに見せる必要のある他のスキーマごとに、USAGE、SELECT、ALTER DEFAULT PRIVILEGES の行を繰り返してください。最初の接続後、ロールがスーパーユーザーである場合、行レベルセキュリティをバイパスする場合、テーブルを所有する場合、または強力な組み込みロール(pg_read_server_files、pg_write_server_files、pg_execute_server_program、pg_signal_backend、rds_superuser、azure_pg_admin)に属する場合、サーバーは標準エラーに警告を出します。また、PostgreSQL が 18 より新しい場合も警告を出します。18 は、restricted モードの拒否リストが組み込み関数と照合された最新のバージョンです。

機密列

制限モードで禁止されるのは書き込みであり、読み取りではありません。ロールが読み取れる列は、すべてモデルのコンテキストに入る可能性があります。アクセスさせたくない列がある場合、ロールが防壁になります。SELECT ON ALL TABLES ではなく、専用のロールを作成し、参照を許可する対象だけを付与してください。

列レベルの権限を付与する場合(この場合、Postgres は SELECT * を SQLSTATE 42501 で拒否するため、エージェントは読み取り可能な列を指定する必要があります):

CREATE ROLE pgdba_agent LOGIN PASSWORD 'change-me';
GRANT pg_read_all_stats TO pgdba_agent;
GRANT USAGE ON SCHEMA public TO pgdba_agent;
GRANT SELECT (id, name, created_at) ON public.accounts TO pgdba_agent;   -- not the email column

または、機密列を含まないビューを使用します:

CREATE VIEW public.accounts_safe AS SELECT id, name, created_at FROM public.accounts;
GRANT SELECT ON public.accounts_safe TO pgdba_agent;

pg_read_all_stats を使用すると、pg_stat_statements で他のユーザーが実行したステートメントのテキストを参照できます。このテキストにはリテラル値が含まれる場合があることに注意してください。

フラグ

フラグはデータベース URI の前後どちらにも指定でき、形式は --name=value または --name value です。

フラグデフォルト意味
--access-moderestrictedrestricted(SQL チェックを適用した読み取り専用)または unrestricted(execute_sql でデータベースを変更可能)
--transportstdiostdio、streamable-http、または sse(非推奨)
--toolsallツールグループをカンマ区切りで指定します:schema、query、explain、stats、health、index、locks、または all
--max-rows100結果のデフォルト行数制限(最小 1、最大 --max-rows-limit)
--max-rows-limit1000クライアントが指定できる max_rows の上限
--max-bytes327681 つのツール結果のサイズ上限(バイト単位、最小 1024)
--max-cell200短縮する前のセルのデフォルト最大文字数(最小 16、最大 --max-cell-limit)
--max-cell-limit10000クライアントが指定できる max_cell の上限
--statement-timeout30s各ステートメントのサーバー側タイムアウト(0 より大きい Go の期間)
--allow-functionなし制限モードで許可する関数名またはパターン。複数回指定できます。schema.name と * ワイルドカードを使用できます
--deny-functionなし制限モードで拒否する関数名またはパターン。複数回指定でき、構文は同じです
--host127.0.0.1HTTP トランスポート:バインドするアドレス
--port8000HTTP トランスポート:リッスンするポート
--allow-remotefalseHTTP トランスポート:ループバック以外のアドレスへのバインドを許可します
--allowed-hostsなしHTTP トランスポート:受け入れる追加の Host 値(host または host:port、カンマ区切り)
--allowed-originsなしHTTP トランスポート:受け入れる追加の Origin 値(scheme://host[:port]、カンマ区切り)
--sse-hostなし--host の別名(crystaldba との互換性のため)
--sse-portなし--port の別名(crystaldba との互換性のため)
--versionpostgres-dba-mcp <version> を出力して終了します

環境変数:DATABASE_URI(接続文字列)と、ドライバーが読み取る libpq 変数(PGPASSWORD、PGPASSFILE、PGSERVICEFILE、PGSSLMODE など)。

ツール

ツールはグループに属しており、--tools で無効にできます。

  • list_schemas(schema):スキーマを所有者、種類、テーブル数とともに一覧表示します。先頭行の -- 2 schemas not readable by <role>: a, b は、指定されたロールに USAGE 権限がないスキーマを示します。
  • list_objects(schema):スキーマ内のテーブル、ビュー、シーケンス、または拡張機能を一覧表示します。テーブルの行数はプランナーによる推定値で、先頭に ~ が付きます。先頭行の -- 1 table not readable by <role>: t は、指定されたロールが SELECT できないテーブルまたはビューを示します(名前は最大5件までで、それ以降は … になります)。
  • get_object_details(schema):テーブル、ビュー、シーケンス、または拡張機能を簡潔な形式(列、キー、インデックス、制約、トリガー)で表示します。
  • execute_sql(query):SQL ステートメントを1つ実行します。制限モードでは読み取り専用で、結果には上限があります。
  • explain_query(explain):クエリのプランを要約します。必要に応じて analyze(SELECT のみ)、verbose テキスト、または仮想インデックスを指定できます。
  • get_top_queries(stats):pg_stat_statements から、現在のデータベースのステートメントをリソース、合計時間、または平均時間で順位付けします。pg_read_all_stats がない場合、他のロールのクエリテキストは PostgreSQL によってマスクされ、その旨が出力されます。
  • analyze_db_health(health):インデックス、接続、バキューム、デッド行、シーケンス、レプリケーション、キャッシュヒット率、制約を確認し、問題のみを報告します。
  • analyze_query_indexes(index):指定された最大10件のクエリに対してインデックスを推奨します。
  • analyze_workload_indexes(index):pg_stat_statements の頻繁に実行される低速なステートメントに対してインデックスを推奨します。
  • analyze_locks(locks):現在のデータベースで進行中のロック待機を、ブロックされている各セッションと直接のブロッカーにつき1行で報告します。各待機チェーンの起点、ロック、継続時間も示します。PostgreSQL はロック待機の開始時刻を記録しないため、waiting はブロックされたステートメントの経過時間であり、待機時間の上限を示します。このツールは読み取りのみを行い、セッションをキャンセルしたり終了したりすることはありません。待機がない場合は -- no lock wait in progress と返します。pg_read_all_stats がない場合も PID とロックは報告されますが、他のロールのセッションの継続時間、状態、クエリテキストは \N として返されます。

仮想インデックスとインデックス関連ツールには hypopg 拡張機能が必要です。get_top_queries と analyze_workload_indexes には pg_stat_statements が必要です(shared_preload_libraries にも指定してください)。

出力形式

ツールはプレーンテキストを返し、構造化された JSON は返しません。

  • テーブルはヘッダー行付きの CSV です。カンマまたは二重引用符を含む場合、先頭または末尾にスペースがある場合、または空の場合、フィールドは引用符で囲まれます。
  • NULL は \N と表記します。値内の改行、復帰、タブはそれぞれ \n、\r、\t と表記するため、すべてのレコードは 1 行になります。
  • --max-cell 文字を超えるセルは切り詰められ、末尾に …[+<n> chars] が付きます。これには bytea の値(16 進テキスト)も含まれます。execute_sql は max_cell(既定値は --max-cell、上限は --max-cell-limit)を受け付けます。セルが短縮された場合は、-- 1 cell shortened (txt); pass max_cell (up to 10000) to read more のようなフッター行が追加されます。
  • tsvector の値は [tsvector: <n> lexemes] と要約されます(1 つの場合は [tsvector: 1 lexeme])。内容を読むには col::text を選択してください。
  • すべてのテーブルは -- で始まるフッターで終わります(list_schemas と list_objects はヘッダーの前に -- で始まる注記行を 1 行追加する場合もあります)。例:-- 12 rows, 38 ms、-- 100 rows, truncated (more than 100), 38 ms、-- 57 rows, truncated (32768-byte limit), 38 ms。結果が切り詰められた場合は、クエリを絞り込むための提案行が追加されます。行を返さないステートメントは、コマンドタグ(-- UPDATE 3, 3 ms)で終わります。
  • explain_query は、呼び出し全体の実時間を示すフッター -- plan, <duration> で終わります。
  • 結果のサイズは --max-bytes を超えず、行数は max_rows(既定値は --max-rows)を超えません。
  • エラーはツールエラー(isError: true)として返されます。そのテキストは ERROR: で始まり、Postgres が提供する場合は (SQLSTATE <code>)、DETAIL:、HINT:、POSITION: の各行が続きます。Postgres が上限を指定せずに報告するステートメントタイムアウトの場合は、クエリの制限方法と --statement-timeout を示す HINT: が付加されます。

例:

id,name,created_at
1,account-1,2026-03-02 10:14:00+00
2,account-2,2026-03-02 10:14:00+00
-- 2 rows, 4 ms

セキュリティモデル

外側から内側へのレイヤー:

  1. データベースロール。 読み取り可能なデータを決定します。上記の読み取り専用ロールを使用してください。
  2. 読み取り専用トランザクション。 無制限モードの execute_sql を除くすべてのツールは、常にロールバックされる BEGIN TRANSACTION READ ONLY の内部で実行されます。Postgres 自体が書き込みを拒否します。
  3. ステートメントのチェック(制限モード)。 execute_sql が受け付けるのは、SELECT の形式(VALUES、TABLE、WITH ... SELECT を含む)のステートメントが正確に 1 つ、それらのいずれかに対する EXPLAIN、または SHOW のみです。SELECT ... INTO、行ロック(FOR UPDATE)、データ変更を行う WITH、トランザクション制御、SET、COPY など、これ以外はすべて拒否し、その理由を伝えます。ステートメントは拡張クエリプロトコル経由で Postgres に送信されます。このプロトコルでは、1 つのメッセージに含められるステートメントは 1 つだけなので、チェックを通過した呼び出しに 2 つ目のステートメントを紛れ込ませることはできません。拒否時に --access-mode=unrestricted なら実行できたと示す場合、後述の拒否リストに含まれる関数を除き、このフラグが拒否の対象となります。
  4. ステートメント全体を対象とする関数の拒否リスト。 ステートメント内で名前が指定された関数は、FROM、サブクエリ、CTE、引数のいずれにあるかを問わず、すべてチェックされます。演算子、キャスト、ビュー経由で呼び出されるためにステートメント内で名前が指定されていない関数は、チェックされません(後述の制限を参照)。組み込みリストには、大規模オブジェクト(lo_*)、set_config、アドバイザリロック、pg_notify、バックエンドのキャンセルと終了、サーバーファイルの読み取り(pg_read_file、pg_ls_*)、レプリケーションスロットと WAL の制御、統計情報のリセット、インデックスのメンテナンス(brin_summarize_*、gin_clean_pending_list)、プランナー統計情報の書き込み(pg_restore_*_stats、pg_clear_*_stats)、pg_upgrade と initdb のヘルパー、dblink*、query_to_xml*、ts_rewrite、tablefunc と xml2 の SQL 文字列関数(crosstab*、connectby、xpath_table)、およびリスクのある拡張機能のファイル関数や HTTP 関数が含まれます。PostgreSQL 14 から 18 までの pg_catalog のすべての揮発性関数は、このリストに含まれているか、安全であるとレビュー済みです。また、新たな関数が追加されるとテストが失敗します。
  5. その他のすべての関数に対する揮発性ゲート(制限モード)。 読み取り専用トランザクションは SQL の書き込みを拒否しますが、拡張機能が C 内、別のセッション内、またはデータベースの外部で行う処理は拒否しません。そこで、上記のいずれのルールでも判断できない呼び出しは、ステートメントを実行するトランザクション内で pg_proc に照らして解決され、PostgreSQL に同梱されておらず、レビューで許可されてもいない VOLATILE 関数に到達した場合は拒否されます。IMMUTABLE および STABLE 関数は許可されるため、PostGIS、pgvector、独自の読み取り専用関数を引き続き使用できます。デフォルトの CREATE FUNCTION により VOLATILE のままになっている関数は、関数名を含むメッセージとともに拒否されます。読み取りのみを行う関数には STABLE を宣言するか、--allow-function を渡してください。解決には検索パスが使われます。また、サーバーが選択するオーバーロードは引数の型によって異なり、カタログクエリでは特定できないため、到達可能な関数のいずれか 1 つでも揮発性かつ未レビューであれば、その名前は拒否されます。検索パス上のスキーマで組み込み関数名を再定義すると、その名前への呼び出しは許可されず、拒否されます。関数を評価するステートメントに限り、この照会が行われます。
  6. サーバー側の制限。 すべてのトランザクションで、statement_timeout(--statement-timeout)、lock_timeout(10 秒)、idle_in_transaction_session_timeout(60 秒)が設定されます。最大 5 接続のプールでは、接続がプールに戻るたびに(DISCARD ALL、hypopg_reset())クリーンアップが行われるため、セッション状態が次の呼び出しに漏れることはありません。
  7. 出力の上限。 行数、バイト数、セルの長さには上限が設定されています(出力形式を参照)。execute_sql に送られた SELECT は、max_rows + 1 行ずつ取得するサーバーサイドカーソルで読み取られるため、Postgres が計算する行数はそれを超えず、LIMIT を付けるようにステートメントが書き換えられることもありません。他のツールでは、それぞれのクエリに上限が設定されます。 --allow-function と --deny-function は、拒否リストとゲートの両方を調整します。ユーザー指定の拒否パターンはユーザー指定の許可パターンより優先され、ユーザー指定の許可パターンは組み込みリストより優先され、組み込みリストはゲートより優先されます。組み込みの拒否を再び有効にする許可パターンがある場合、起動時に stderr に通知されます。

制限モードで保護されないもの:

  • 読み取り。 ロールが読み取れるテーブルや列は、すべて読み取り可能です。ロールを使用してください(「機密列」を参照)。
  • 信頼した関数。 ゲートは認識していない VOLATILE 関数を拒否しますが、STABLE と宣言した関数や --allow-function に渡した関数は、SECURITY DEFINER を含め、ユーザーの判断を信頼します。読み取りのみを行う関数に限って STABLE としてください。
  • 名前を指定せずに呼び出される関数。 ビューの本体、演算子、キャストはいずれも、ステートメントで名前が指定されていない関数を実行するため、チェックでは検出されません。STABLE 関数が揮発性関数を呼び出す場合も同様です。PostgreSQL は直接的な書き込みは拒否しますが、間接的な副作用は拒否しないためです。いずれの場合も、データベース内でそのオブジェクトを作成できるユーザーが必要となるため、ロールが防御策になります。
  • 高負荷なクエリ。 負荷は高くても合法な SELECT は、--statement-timeout まで実行されます。
  • 無制限モード。 --access-mode=unrestricted を指定すると、execute_sql はロールの権限で任意の単一ステートメントを実行し、コミットします。切り詰めによって制限されるのは読み取る内容であり、実行される内容ではありません。関数を指定したステートメントは最後まで実行されるため、データベースには、表示された行だけでなくすべての処理が渡ります。関数呼び出しがビュー、演算子、キャストの背後に隠れているステートメントは、取得された行の範囲で停止します。ステートメントはトランザクション内で実行されるため、トランザクションブロック内での実行を拒否するコマンド(VACUUM、CREATE INDEX CONCURRENTLY、REINDEX CONCURRENTLY、ALTER SYSTEM、CREATE DATABASE)は「cannot run inside a transaction block」エラーで失敗します。これは 0.3.0 と同じ動作です。サーバーは起動時に stderr に警告を出力します。
  • HTTP トランスポートには認証がありません(以下を参照)。

HTTP トランスポート

--transport=streamable-http は http://<host>:<port>/mcp で MCP を提供します。--transport=sse は crystaldba クライアント向けに、非推奨の SSE エンドポイントを /sse で提供します。デフォルトでは、サーバーは 127.0.0.1:8000 にバインドします。

postgres-dba-mcp --transport=streamable-http --port=8000 postgres://pgdba_ro@localhost:5432/pgdba

認証はありません。そのため、サーバーは次のように動作します。

  • ループバックアドレスにのみバインドします。--allow-remote を指定しない限り、ループバック以外の --host は拒否されます。指定した場合は警告がログに記録されます。
  • Host ヘッダーが、リッスンポート、バインドホスト、または --allowed-hosts のエントリーを含む localhost、127.0.0.1、[::1] のいずれでもないリクエストを拒否します(403)。
  • Origin ヘッダーが http://localhost:<port>、http://127.0.0.1:<port>、http://[::1]:<port>、または --allowed-origins のエントリーではないリクエストを拒否します(403)。Origin のないリクエスト(ネイティブクライアント)は許可されます。
  • リクエスト本文のサイズを 1 MiB に制限します。

Docker では、コンテナーのすべてのインターフェースでサーバーがリッスンする必要があるため、--host=0.0.0.0 --allow-remote が必要です。このロールでデータベースを使用できるのはポートにアクセスできるユーザー全員となるため、ホスト側ではループバックにのみポートを公開してください。

docker run --rm -p 127.0.0.1:8000:8000 \
  -e DATABASE_URI=postgres://pgdba_ro:[email protected]:5432/pgdba \
  ghcr.io/a-lafon/postgres-dba-mcp:latest \
  --transport=streamable-http --host=0.0.0.0 --allow-remote --port=8000

Linux では、host.docker.internal をブリッジネットワーク上のホストアドレスに置き換えるか、--network=host を指定して実行してください。クライアントが送信する Host ヘッダーにはホスト側のポートが含まれるため、公開するホスト側のポートは --port と同じにする必要があります(-p 8000:8000)。-p 9000:8000 のように異なるポートを割り当てる場合は、--allowed-hosts=localhost:9000 を追加してください。

移行とセキュリティポリシー

  • crystaldba/postgres-mcp 0.3.0 から移行する場合:確認できるすべての変更点は MIGRATION.md に記載されています。
  • 脆弱性の報告:SECURITY.md を参照してください。
  • コードに取り組む場合:CONTRIBUTING.md を参照してください。
  • 1 人がベストエフォートでメンテナンスしています。イシューやプルリクエストを歓迎します。

ライセンス

MIT ライセンスです。詳細は LICENSE を参照してください。一部の SQL は crystaldba/postgres-mcp(MIT、Crystal Corp.)から、一部は PgHero(MIT、Andrew Kane)から改変しており、両方のライセンスは NOTICE に掲載されています。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。