mcpField
D
DB Explorer

DB Explorer

ローカル
github.com/AYUSH-148データベース71 週間前に更新

DB Explorer について

DB Explorerは、PostgreSQL、MySQL、SQLiteに対応する読み取り専用MCPサーバーです。AIコーディングエージェント向けにスキーマ確認やクエリ、マイグレーションのレビューを提供し、単一の読み取り専用SELECT以外を解析段階で拒否して結果件数も制限します。

主な機能

  1. 01テーブル、ビュー、列、キー、インデックスの確認
  2. 02SELECTの解析と件数制限付き実行
  3. 03ネイティブな実行計画の表示
  4. 04スキーマ問題の検出とインデックス提案
  5. 05実行せずにマイグレーションスクリプトを検証

活用シーン

  1. 01自然言語でリレーショナルDBのスキーマを調査
  2. 02データを検索して遅いクエリを調査
  3. 03主キーや外部キーのインデックス不足を特定
  4. 04データベース移行の作成とレビュー

MCP 対応

MCPサーバーは7つのデータベースツールを公開し、SQLAlchemy経由でPostgreSQL、MySQL、SQLiteに接続します。ローカルstdioと、トークン認証付きのリモートHTTPに対応し、データベースへのアクセス前にクエリを安全性検査します。

設定

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

mcp.json · 共通設定
{
"mcpServers": {
"ayush-148-db-explorer": {
"command": "uvx",
"args": [
"db-explorer-mcp-server"
],
"env": {
"DATABASE_URL": "<DATABASE_URL>"
}
}
}
}
起動コマンドuvx db-explorer-mcp-server
設定が必要な環境変数
DATABASE_URLシークレットSQLAlchemy URL of the database to explore. Unset opens the bundled demo database.

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

パッケージ
pypidb-explorer-mcp-server0.1.0

ツール

7

掲載時のツール一覧 · 1 週間前に更新

パラメーター型必須説明
limitintegerいいえ1 ページあたりのテーブル数。1~1000。
detailbooleanいいえ列数ではなく、各テーブルの列、キー、インデックスを一覧表示します。複数のテーブルに関する質問では、テーブルごとに table_name を呼び出すのではなく、こちらを使用してください。ページが大きくなるため、大規模なスキーマは name_pattern で絞り込んでください。
offsetintegerいいえスキップするテーブル数。前のページの next_offset を渡します。
table_nameanyいいえテーブルまたはビューの正確な名前。列、キー、インデックスを返し、テーブルの場合は行数も返します。リスト用パラメーターは無視されます。
name_patternanyいいえ大文字と小文字を区別せず、名前でテーブルを絞り込みます。通常のテキストは部分文字列に一致します('order')。*、?、[] は glob パターンとして機能します('order_*')。
include_sample_databooleanいいえtable_name と併用すると、サンプル行も 3 件返します。

詳細

DB Explorer MCP

CI M8ven Score

PostgreSQL、MySQL、SQLite 用の読み取り専用 Model Context Protocol(MCP)サーバーです。AI コーディングアシスタントが、データベースに書き込みを一切行わずに、リレーショナルデータベースを調査、クエリ、監査できます。

MCP クライアントをデータベースに接続し、自然な言葉で質問してください。クライアントの LLM が SQL を作成し、このサーバーが解析して、単一の読み取り専用 SELECT 以外は拒否したうえで、行数の上限を設けて実行し、構造化された結果を返します。スキーマの調査、実行計画、インデックスの推奨、マイグレーションのレビューも利用できます。

MCP client LLM  ->  FastMCP tools  ->  safety layer  ->  SQLAlchemy  ->  database
   (writes SQL)       (7 tools)       (rejects writes)   (any dialect)

このサーバー自体は LLM API を呼び出さないため、設定する API キーは不要です。推論は接続先のクライアントで行われます。SQLAlchemy を通じて SQLite、PostgreSQL、MySQL に対応しています。

テストおよび評価済みです。 すべてのプッシュとプルリクエストで336件のテストを実行し、さらに CI で実際の PostgreSQL と MySQL に対するテストを26件実行します(テスト)。エージェント評価では、Claude はこれらのツールを通じて現実的なデータベースタスクを14件中14件完了し、ツール呼び出しの失敗はなく、タスクあたりの平均呼び出し回数は2回、独立した評価者による平均スコアは5点満点中4.6点でした(エージェント評価)。

このサーバーの利点

アシスタントにデータベースの認証情報をそのまま渡すと、混乱したりプロンプトインジェクションを受けたりした場合に、テーブルを削除されるおそれがあります。読み取り専用レプリカを渡すと、スキーマ情報や実行計画の分析ができなくなります。このサーバーはその中間を実現します。完全な調査機能とクエリ機能を提供しつつ、モデルに適切な振る舞いを求めるのではなく、パーサーレベルで変更を構造的に不可能にします。

アーキテクチャ

┌──────────────────────────────────────────────────────────────┐
│  MCP client  (Claude Code / Claude Desktop / Inspector)      │
│  owns the LLM: reads schema, authors SQL, interprets results │
└───────────────────────────┬──────────────────────────────────┘
                            │  MCP  ·  stdio (local)
                            │        ·  streamable HTTP + bearer token (remote)
┌───────────────────────────▼──────────────────────────────────┐
│ server.py  —  FastMCP instance + one engine per token group  │
│                                                              │
│   explore_schema   execute_query    explain_query            │
│   validate_schema  suggest_index    migration_context        │
│   validate_migration                                         │
└──────┬───────────────────────┬───────────────────┬───────────┘
       │                       │                   │
       │  read path            │  metadata path    │  review path
       │                       │                   │
┌──────▼────────────┐  ┌───────▼─────────┐  ┌──────▼──────────┐
│ safety.py         │  │ inspector.py    │  │ migration.py    │
│ ── trust boundary │  │ schema_health.py│  │ parses up/down, │
│ sqlparse AST      │  │ index_suggest.py│  │ never executes  │
│ SELECT-only       │  │ explain.py      │  │                 │
│ 1 stmt · no cmnts │  │                 │  │                 │
│ denylist · LIMIT  │  │                 │  │                 │
└──────┬────────────┘  └───────┬─────────┘  └─────────────────┘
       │                       │
       └───────────┬───────────┘
                   │  SQLAlchemy Core
┌──────────────────▼───────────────────────────────────────────┐
│  Target database   ·   PostgreSQL  /  MySQL  /  SQLite       │
└──────────────────────────────────────────────────────────────┘

safety.py に渡される SQL はクライアントのモデルが作成するため、信頼できない入力です。そのため、パターンマッチングではなく解析を行い、拒否されたクエリがデータベースに到達することはありません。

モードトランスポート認証
ローカルstdio不要。クライアントがサーバーを起動します
リモートストリーミング可能な HTTPベアラートークン、または Prefect Horizon のプラットフォームエッジで OAuth

どちらのモードでも同じツールコードが動作し、変更するのは MCP_TRANSPORT だけです。

クイックスタート

Python 3.11 以降と uv が必要です。

PyPI から

リポジトリをクローンする必要はありません。uvx がパッケージをダウンロードし、専用の環境で実行します。

claude mcp add db-explorer --env DATABASE_URL="postgresql+psycopg2://user:pass@localhost:5432/example" -- uvx db-explorer-mcp-server

まず同梱のデモ用データベースで試す場合は、--env を省略してください。他のクライアントの設定では、"command": "uvx" と "args": ["db-explorer-mcp-server"] を使用します。

ソースから
git clone https://github.com/AYUSH-148/DB-Explorer-MCP
cd DB-Explorer-MCP
uv sync
uv run python seed_demo_db.py   # creates sample.db, a small shop with deliberate schema flaws
uv run pytest                   # 336 tests on SQLite; 26 more with TEST_*_URL set

Windows で uv が PATH にない場合は、先頭に py -m を付けてください(py -m uv sync)。

サーバーを自分で起動する必要はありません。MCP クライアントが stdio 経由で起動するため、クライアントを接続するに進んでください。手動で実行する場合、uv run server.py は何も表示せず、標準入力でクライアントを待ち受けます。Ctrl+C で停止できます。

デフォルトのデータベースは sample.db です。以下のクライアント設定、または .env.example をコピーして作成した .env ファイルで DATABASE_URL を指定すると、独自のデータベースに接続できます。

postgresql+psycopg2://user:password@localhost:5432/example
mysql+pymysql://user:password@localhost:3306/example
sqlite:///C:/data/example.db

パスワード内の特殊文字はパーセントエンコードしてください(@ → %40、# → %23、/ → %2F)。

クライアントを接続する

Claude Code — ローカル
claude mcp add db-explorer --env DATABASE_URL="postgresql+psycopg2://user:pass@localhost:5432/example" -- uv --directory "C:/path/to/DB-Explorer-MCP" run server.py

セッションで /mcp を実行し、7つのツールが表示されることを確認してください。すべてのプロジェクトで使用できるようにするには、-s user を追加します。

Claude Code — リモート
claude mcp add --transport http db-explorer https://your-deployment.fastmcp.app/mcp

/mcp → 認証を実行して OAuth フローを開始します。トークンはキャッシュされ、自動的に更新されます。

ベアラートークンを使用して自分でホストするサーバーでは、ヘッダーとして送信してください。環境変数から読み込むことで、チームで共有する .mcp.json に秘密情報を含めずに済みます。

{
  "mcpServers": {
    "db-explorer": {
      "type": "http",
      "url": "https://dbx.example.com/mcp",
      "headers": { "Authorization": "Bearer ${DBX_TOKEN}" }
    }
  }
}
Claude Desktop

ローカル接続の場合は、claude_desktop_config.json に次のように記述します。

{
  "mcpServers": {
    "db-explorer": {
      "command": "uv",
      "args": ["--directory", "C:/path/to/DB-Explorer-MCP", "run", "server.py"],
      "env": { "DATABASE_URL": "postgresql+psycopg2://user:pass@localhost:5432/example" }
    }
  }
}

カスタムコネクターを使わずにリモートデプロイへ接続するには、stdio 経由でプロキシします。

{
  "mcpServers": {
    "db-explorer": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://your-deployment.fastmcp.app/mcp"]
    }
  }
}
VS Code

.vscode/mcp.json はリポジトリにチェックインされており、Windows の py ランチャーを通じて stdio でサーバーを起動します。macOS または Linux では、command を uv に、args を ["run", "server.py"] に変更してください。

MCP Inspector
npx @modelcontextprotocol/inspector

Streamable HTTP トランスポートと /mcp URL を使うか、uv run server.py を使った stdio で接続してください。Inspector ではツールの生の応答と、言い換えられていないエラーが表示されるため、サーバー側の問題かクライアント側の問題かをすばやく判断できます。

Python
import asyncio
from fastmcp import Client

async def main():
    async with Client("https://your-deployment.fastmcp.app/mcp", auth="oauth") as client:
        print([tool.name for tool in await client.list_tools()])
        print(await client.call_tool("explore_schema", {}))

asyncio.run(main())

試してみる

Quickstart のデモ用 sample.db に接続すると、次のようなプロンプトをそのまま使えます。

  • 「どのテーブルがありますか?また、主キーがないテーブルはどれですか?」
  • 「orders から合計額が最も高い 5 行を表示してください。」
  • 「このクエリが遅いのはなぜですか? SELECT * FROM orders WHERE created_at >= '2026-01-01'」
  • 「このデータベースでインデックスがない外部キーはどれですか? CREATE INDEX 文を提示してください。」
  • 「orders に shipped_at 列を追加するマイグレーションを作成し、up スクリプトと down スクリプトを検証してください。」

ガードレールの動作を確認するには、DELETE FROM users の実行を依頼してください。呼び出しは Unsafe query blocked: Only SELECT queries are allowed. Got: DELETE というエラーで失敗し、データベースは変更されません。

エージェントスキル

skill/db-explorer/SKILL.md は、モデルがこれらのツールをうまく使う方法を教える任意のエージェントスキルです。クエリの前にスキーマを一度読み取ること、返された行からではなく SQL で合計を計算すること、結果が truncated の場合はその旨を伝えること、エラーのヒントに従って再試行を避けることを教えます。このスキルはモデルをガイドするだけであり、サーバーには引き続き接続する必要があります。

Claude Code では、skill/db-explorer フォルダーをすべてのプロジェクトで使う場合は ~/.claude/skills/db-explorer/ に、1 つのプロジェクト内で使う場合はそのプロジェクトの .claude/skills/db-explorer/ にコピーしてください。

ツール

ツール引数戻り値
explore_schematable_name?, include_sample_data=false, name_pattern?, detail=false, limit=200, offset=0SQL の作成に使用する dialect と、テーブルおよびビューの一覧(それぞれに kind と列数を含む)、または 1 つのリレーションの kind、列、PK、FK、インデックス、行数(テーブルのみ)、最大 3 件のサンプル行を返します。1 つのリレーション、または detail=true のページには、データベースに保存されているテーブルと列の comment も含まれます
execute_querysql, row_limit=100(最大 1000)検証済みの SELECT 1 件に対する columns、rows、count、truncated、有効な row_limit を返します。結果に同じ列名が繰り返し含まれる場合は、duplicate_columns と note も返します(重複する列名には id:1 のような、有効な SQL ではないラベルが付きます)。また、サイズ上限を超えないように行数を削減した場合は note を返します
explain_querysqlネイティブの実行計画と、解決された dialect
validate_schematable_name?, name_pattern?, limit=200, offset=0severity、code、message、suggestion を含むスキーマの問題を返します。table_name を指定しない場合は、explore_schema と同様にページ分割されたテーブルのページを 1 件返します(ビューは除外)
suggest_indexquery? xor table_name?理由を含む CREATE INDEX の推奨
migration_contextname_pattern?, limit=200, offset=0クライアント側でマイグレーションを作成するための、列、キー、インデックスのページ 1 件(行数は含まない)と、ページ分割された explore_schema と同様の dialect
validate_migrationup_sql, down_sqlスクリプトごとの解析済みステートメントの種類を返します。SELECT、コメント、対応していない括弧は拒否されます。構文チェックではなく、実行されることはありません

validate_schema は 4 つのコードを報告します。missing_primary_key、unindexed_foreign_key、wide_table(50 列以上)、no_indexes です。外部キーの列から始まるインデックス、主キー、または一意制約がある場合、その外部キーにはインデックスがあると判定されます。部分インデックス、GIN/GiST/BRIN インデックス、FULLTEXT/SPATIAL インデックスは対象外です。

explore_schema は、デフォルトでは低コストで利用できます。引数を指定しない場合、テーブル名と列数を返します。行数が返されるのは table_name を指定した場合のみです(テーブルのみ。上限は 100,000 で、上限に達した場合は row_count_capped: true が付きます)。大規模なスキーマを絞り込むには name_pattern を使用します(order は名前にその文字列を含むものに一致し、order_* はグロブとして扱われます)。limit/offset でページ分割し、detail=true を指定すると、1 回の呼び出しでページ全体の列、キー、インデックスを取得できます。ビューとマテリアライズドビューは、それぞれに kind を付けてテーブルとともに一覧表示されます。

安全モデル

すべての execute_query、explain_query、suggest_index 呼び出しは、データベースにアクセスする前にsafety.pyを通ります。次の条件をすべて満たさないクエリは拒否されます。

  • 単一のステートメント。 SELECT 1; DROP TABLE users → Exactly one SQL statement is required
  • SELECT のみ。 文字列の先頭ではなく、解析されたステートメントの種類に基づいて判定します → Only SELECT queries are allowed. Got: DELETE
  • SQL コメントなし。 文字列リテラルや引用符で囲まれた名前の外にある --、/*、*/ は拒否され、コメントを紛れ込ませる経路を塞ぎます。# はコメントの開始文字となる MySQL と MariaDB でのみ拒否されます。
  • トークン列内にブロック対象のキーワードがないこと。 ALTER、CREATE、DELETE、DROP、EXEC、EXECUTE、GRANT、INSERT、INTO、REVOKE、TRUNCATE、UPDATE が対象です。単語全体に一致するため、SELECT grant_date FROM permissions は許可されます。4つのキーワードは特に重要です。INSERT、UPDATE、DELETE は、ステートメントの種類が SELECT のままの PostgreSQL のデータ変更 CTE に隠せるほか、SELECT * INTO archive FROM users はテーブルを作成します。残りのキーワードは SELECT 型のステートメントには現れませんが、sqlparse の型判定に不具合が生じた場合に備えた第2の防御線として残しています。
  • ロック句なし。 FOR UPDATE、FOR NO KEY UPDATE、FOR SHARE、FOR KEY SHARE、および MySQL の LOCK IN SHARE MODE は拒否されます。ロック読み取りはほかの書き込みをブロックするためです。キーワード単体ではなく句として判定するため、share という名前の列は引き続き使用できます。

許可されたクエリはすべて SELECT * FROM (<your query>) AS limited_query LIMIT <row_limit + 1> でラップされるため、上限のないスキャンでクライアントのコンテキストが埋め尽くされることはありません。クエリ内の LIMIT は内側の結果を絞り込みますが、row_limit(上限は1000)によって返される結果も制限されます。追加の確認用の行が返された場合、その行は破棄され、結果に "truncated": true が付与されるため、上限で切り詰められた結果を完全なものと誤認することはありません。

キーワードのブロックリストでは、構文上は正しくても有害なクエリを検出できないため、検証は3つある防御層の第1段階にすぎません。

  • ステートメントのタイムアウト。 SELECT pg_sleep(600) は上記のすべてのチェックを通過するため、構文とは別に実行時間を制限します。PostgreSQL では statement_timeout(トランザクションごとに設定されるため、PgBouncer、Supabase、Neon のプーラー経由でも機能します)、MySQL では max_execution_time、SQLite ではプログレスハンドラーによる期限を使用します。QUERY_TIMEOUT_SECONDS で設定し、db.pyで適用されます。

  • 読み取り専用トランザクション。 読み取りは、PostgreSQL では BEGIN READ ONLY、MySQL では START TRANSACTION READ ONLY、SQLite では PRAGMA query_only を通じて実行されます。書き込みそのものをデータベースが拒否するため、これはブロックリストでは実現できない保証です。

  • 権限。 最外層の境界として機能します。.env.example](.env.example)を参照してください。キーワードチェックでは確実に捕捉できない pg_read_file() のようなサーバー側のファイル読み取りを阻止するのは、SELECT のみを許可されたユーザーです。

    読み取り可能な対象を制限する場合も、権限を使います。トークンは、そのグループのデータベースユーザーが SELECT できるものを読み取れるため、各グループに専用のユーザーを割り当ててください(名前付きトークンとグループを参照)。認証情報や個人を特定できる情報(PII)は、サーバー側のテーブルまたは列のブロックリストではなく、権限付与で隠してください。パーサーは SELECT *、SELECT u FROM users u、row_to_json(u) の背後にある列を把握できませんが、データベースはこれらすべてに対する権限付与を強制します。

    -- PostgreSQL: whole tables, minus one
    GRANT SELECT ON ALL TABLES IN SCHEMA public TO explorer;
    REVOKE SELECT ON users FROM explorer;
    -- then only the safe columns of it
    GRANT SELECT (id, name, created_at) ON users TO explorer;
    

    MySQL でも同じ列リストを使用します。GRANT SELECT (id, name) ON app.users TO 'explorer'@'%';。権限を取り消された列に触れるクエリは sql_error(permission denied)で失敗します。

すべてのツール呼び出しは、stderr の db_explorer.audit ロガーに1行を出力します。内容は、ツール、終了状態(ok またはエラーコード)、呼び出し元(HTTP 経由の場合は user=<token name> group=<group>)、および引数(最大2000文字)です。引数はそのまま記録されるため、ログもデータと同じように機密性の高いものとして扱ってください。

エラー

ツールのエラーは、次に何をすべきかが示されて初めてモデルにとって有用です。そのため、すべての失敗には、単なるメッセージではなく、コードと修正のヒントが含まれます。

[table_not_found] Table not found: usrs. Did you mean: users.
Hint: Call explore_schema() to list the tables in this database.

[sql_error] The database rejected the query: no such column: totl.
Hint: Call explore_schema(table_name=...) to confirm the table and column names before retrying.

[query_timeout] The query exceeded the statement timeout of 15s: interrupted.
Hint: Add a WHERE clause, aggregate instead of scanning, or query a smaller table.
コード原因
table_not_foundテーブルが見つかりません。データベースに存在する、最も近い名前が示されます
sql_errorデータベースがクエリを拒否しました。列が存在しない、型が一致しない、構文が正しくないなどの原因が考えられます
query_timeoutクエリの実行時間が QUERY_TIMEOUT_SECONDS を超えたため、キャンセルされました
database_unavailableサーバーがデータベースに接続できないか、クエリの実行中に接続が切れました。詳細はサーバーログにのみ記録されます
unsafe_querysafety.py によってブロックされました。ヒントには、該当した具体的なルールが示されます
query_too_complexSQL が 50,000 文字を超えているか、パーサーが処理するには長すぎるか、ネストが深すぎます(例:値が約 3,000 個ある IN (...))
too_many_concurrent_calls呼び出し元のトークンですでに MAX_CONCURRENT_CALLS_PER_CLIENT 件の呼び出しが実行中です
unknown_group呼び出し元のトークンで指定されたグループには、サーバーのデータベースログインがありません
invalid_argumentrow_limit が 1 未満など、引数が範囲外です。または、ツールの入力スキーマに適合しない引数(必須の引数がない、型が異なる、名前が不明など)です
missing_argument / conflicting_argumentssuggest_index には query または table_name のどちらか一方だけが必要です
comments_not_allowed / unparsable_sql / select_in_migrationvalidate_migration がスクリプトを拒否しました
server_busyDB_POOL_TIMEOUT_SECONDS の間、すべてのデータベース接続が使用中でした。クエリは実行されていません。変更せずに再試行してください
internal_errorサーバーのバグです。トレースバックはサーバーログに記録され、呼び出し元にはコードのみが表示されます

設定

変数デフォルト備考
DATABASE_URLconfig.py と同じディレクトリにある sample.db(読み取り専用、stdio のみ)MCP_TRANSPORT が stdio 以外の場合は必須です。指定されていないと、起動時に明示的に失敗します
MCP_TRANSPORTstdiostdio、streamable-http、または sse
MCP_HOST127.0.0.1HTTP トランスポートのみ
MCP_PORT8000HTTP トランスポートのみ
QUERY_TIMEOUT_SECONDS15個々のステートメントの上限時間であり、呼び出し全体の制限時間でもあります。時間を使い切ると、それ以降のステートメントは開始されません。正の整数を指定してください
DB_POOL_SIZE5データベースへの接続を維持する数
DB_MAX_OVERFLOW10負荷時に追加で開く接続数。返却時に閉じられます
DB_POOL_TIMEOUT_SECONDS5空き接続を待つ時間。この時間を超えると server_busy で失敗します
MAX_CONCURRENT_CALLS_PER_CLIENT51 つのトークンで同時に実行できるツール呼び出し数(サーバープロセスごと)。stdio には上限がありません
MCP_AUTH_TOKEN—共通トークンを 1 つ指定します。HTTP 経由でサービスを提供する場合は、MCP_AUTH_TOKENS とこの変数のどちらかが必須です。server.py:mcp を自ら読み込むホストでも同様です。32 文字以上である必要があります
MCP_AUTH_TOKENS—名前付きトークンをカンマ区切りで指定します。形式は name:token または name:group:token です。MCP_AUTH_TOKEN の代わりに使用し、併用しないでください。名前付きトークンとグループを参照してください
DATABASE_URL_<GROUP>—トークングループのデータベース URL です。例:DATABASE_URL_ANALYST。MCP_AUTH_TOKENS で指定したすべてのグループに対して必須です
MCP_ALLOW_UNAUTHENTICATEDfalse信頼できるネットワーク、または独自の認証を前段に配置したホスト(Horizon)向けに、トークン要件を明示的に無効化します

一度に接続を保持できる呼び出し数は、最大 DB_POOL_SIZE + DB_MAX_OVERFLOW(デフォルトでは 15 件)です。この数を超える呼び出しは DB_POOL_TIMEOUT_SECONDS の間待機し、その後 server_busy で失敗します。すべてのサーバーインスタンスの合計が、データベースの max_connections を超えないようにプールを設定してください。

SQLite のフォールバックはローカル開発専用です。config.py と同じディレクトリにある sample.db を読み取り専用で開きます。起動時に .env が読み込まれます(最初に config.py と同じディレクトリにあるファイル、次に作業ディレクトリにあるファイル)。実際の環境変数は、どちらの値よりも優先されます。

HTTP 経由で提供する

$env:MCP_TRANSPORT = "streamable-http"
$env:MCP_HOST = "0.0.0.0"
$env:MCP_PORT = "8000"
$env:DATABASE_URL = "postgresql+psycopg2://user:password@host:5432/example"
$env:MCP_AUTH_TOKEN = python -c "import secrets; print(secrets.token_urlsafe(32))"
uv run server.py

macOS または Linux でも同様です。

export MCP_TRANSPORT=streamable-http MCP_HOST=0.0.0.0 MCP_PORT=8000
export DATABASE_URL="postgresql+psycopg2://user:password@host:5432/example"
export MCP_AUTH_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
uv run server.py

HTTP エンドポイントは、設定されたデータベースの SELECT をポートに到達できるすべてのユーザーに公開するため、MCP_AUTH_TOKEN または MCP_AUTH_TOKENS が設定されていない場合、サーバーは保護されない状態で起動せず、起動に失敗します。クライアントは Authorization: Bearer <token> としてトークンを送信し、auth.py で定時間比較されます。本当に信頼できるネットワーク上で上書きする場合は MCP_ALLOW_UNAUTHENTICATED=true を設定してください。その場合、サーバーは起動のたびに標準エラー出力に警告を表示します。

server.py:mcp を直接読み込むホスト(Horizon、fastmcp run)は、トークンまたは MCP_ALLOW_UNAUTHENTICATED=true が設定されていない限り、すべての HTTP リクエストに 401 を返します。

ループバックにバインドした場合、外部の Host ヘッダーと Origin ヘッダーは拒否されるため、DNS リバインディングによってウェブページがローカルサーバーにアクセスすることはできません。トランスポートはステートレスなので、ロードバランサーの背後にあるどのレプリカでも任意のリクエストに応答できます。GET /health はロードバランサーのプローブ用に、トークンなしで {"status": "ok"} を返します。データベースには一切アクセスしません。

名前付きトークンとグループ

各ユーザーまたはサービスに個別のトークンを割り当て、必要に応じて、読み取り可能なデータをデータベースユーザーが決定するグループを指定します。

MCP_AUTH_TOKENS=alice:analyst:<token>,carol:analyst:<token>,fran:finance:<token>,ci:<token>
DATABASE_URL=postgresql+psycopg2://dbx_readonly:...@db/app          # tokens with no group (ci)
DATABASE_URL_ANALYST=postgresql+psycopg2://dbx_analyst:...@db/app
DATABASE_URL_FINANCE=postgresql+psycopg2://dbx_finance:...@db/app

各エントリは name:token または name:group:token です。トークン名は監査ログの各行に記録されます。グループによって、呼び出しの実行に使われるデータベースのログインが決まり、その呼び出し元が読み取れるデータは、データベース独自の権限設定(および PostgreSQL の行レベルセキュリティ)によって決まります。MCP_AUTH_TOKEN のみを設定した場合は、グループなしの default という名前のトークンとして扱われます。

不正な形式のエントリ、重複する名前またはトークン、32 文字未満のトークン、対応する DATABASE_URL_<GROUP> がないグループ、あるいは MCP_AUTH_TOKEN と MCP_AUTH_TOKENS の両方が設定されている場合、サーバーは起動を拒否します。トークンは定時間比較され、起動時に読み込まれるため、変更後は再起動してください。各グループには個別の接続プールがあります。

PostgreSQL では、explore_schema はグループが SELECT できないテーブルも一覧表示します(カタログは全員が読み取れます)が、その行は保護されたままです。DEPLOYMENT.md には、両データベース用の GRANT 文、Horizon での OAuth、HTTPS を使った VM のセットアップ手順が記載されています。

ホスト型 Supabase に関する注意: 直接接続(db.<ref>.supabase.co)は IPv6 専用のため、IPv4 のみを使用するコンテナからは接続に失敗し、psycopg2.OperationalError の内容が空のように見えます。ダッシュボードの Connect パネルにあるプーラーホストを使用してください。また、ユーザー名が postgres.<project-ref> に変わる点に注意してください。

テスト

uv run pytest

安全性レイヤー、シリアライズ、読み取り専用の適用とタイムアウト、HTTP 認証とトークングループ、すべてのツール、エラー報告を対象とした 336 件のテストがあります。各テストでは一時的な SQLite データベースを使用するため、テストスイートの実行に認証情報や稼働中のサーバーは必要ありません。

tests/test_live_databases.py では、SQLite では検証できない読み取り専用トランザクション、ステートメントのタイムアウト、NUMERIC/TIMESTAMP 値、カタログのリフレクションを、実際の PostgreSQL と MySQL に対してテストします。使い捨てのデータベースを指定するには TEST_POSTGRES_URL や TEST_MYSQL_URL を設定してください(dbx_live_* テーブルを作成して削除します)。CI では両方を実行します。

エージェントの評価

テストでは各ツールを検証し、評価ではモデルがツールを適切に使うかどうかを検証します。evals/run.py は、evals/scenarios.py にある各タスクを、ヘッドレスモード(claude -p)の Claude Code を通じて実行します。このサーバーのみをツールソースとして、シード済みの evals/eval.db を対象にします。各実行では、ツールの選択、呼び出し予算、データベースから読み取った正解に基づいて採点し、2 回目の claude -p 呼び出しで実行全体を 1~5 点で評価します。

uv run python evals/seed_eval_db.py      # build evals/eval.db (the runner does this if it is missing)
uv run python evals/run.py               # every scenario, on Sonnet
uv run python evals/run.py --only top_customers --model opus

PATH 上に claude CLI があり、ログイン済みである必要があります。実行はそのログインの使用量上限に算入され、API キーは必要ありません。実行ごとに結果が異なるため、評価は CI の対象外です。レポートは evals/results/ に書き込まれます。

最新の結果

エージェントおよび判定役として Claude Sonnet 5.5 を使用、2026-10-14。すべてのツール呼び出しとその結果を含む完全なレポート:evals/reports/2026-10-14-sonnet.json。

14/14 合格 · ツールエラー 0 件 · 繰り返し呼び出し 0 件 · ツール呼び出し合計 28 回 · 判定役の平均スコア 4.64 / 5

タスクエージェントが選択したツール呼び出し回数 / 予算判定役
テーブルとビューを一覧表示するexplore_schema1/35/5
ステータスごとの注文数explore_schema、execute_query2/45/5
配達済みの購入額が多い上位 3 人の顧客explore_schema、execute_query3/54/5
最も売上の多いカテゴリexplore_schema、execute_query2/55/5
平均評価が最も高いカテゴリexplore_schema、execute_query2/55/5
一度も注文していないユーザーexplore_schema、execute_query2/45/5
顧客名を含む直近 5 件の注文explore_schema、execute_query2/45/5
「ユーザーテーブル」の行数(実際の名前:users)explore_schema、execute_query2/45/5
日付で絞り込んだクエリが遅い理由explore_schema、explain_query2/45/5
構造上の問題がないかスキーマを監査するvalidate_schema、explore_schema2/44/5
reviews のどの外部キーにインデックスが必要かsuggest_index、explore_schema2/34/5
phone 列を追加するマイグレーションmigration_context、validate_migration2/45/5
「キャンセルされた注文をすべて削除する」(読み取り専用のため拒否する必要あり)explore_schema、execute_query2/54/5
pro プランのユーザーexplore_schema、execute_query2/34/5

正解が 1 つの値に定まる回答はすべて、判定だけでなくデータベースと照合しました。スコア 4 は、必要以上に 1 回多く呼び出した場合、または質問に対する回答以外の説明を加えた場合です。

既知の制限:explore_schema は UNIQUE 制約や CHECK 制約をまだ報告しないため、スキーマ監査で、実際には存在する制約がないとモデルが誤って主張することがあります。

プロジェクトの構成

server.py         FastMCP instance, engine, and the 7 tool definitions
safety.py         query validation and row-limited execution
db.py             engine construction, statement timeouts, read-only transactions
serialization.py  driver values to JSON-safe primitives
auth.py           bearer-token verification for HTTP transports
inspector.py      schema reflection (columns, PK, FKs, indexes, samples)
explain.py        dialect-aware EXPLAIN
index_suggest.py  index recommendations from plans or FK metadata
schema_health.py  objective schema issue reporting
indexes.py        index coverage shared by schema_health and index_suggest
migration.py      migration context and non-executing script validation
errors.py         coded errors with hints, and driver-error classification
config.py         environment configuration with fail-fast checks
seed_demo_db.py   builds the demo sample.db used in Quickstart
skill/            optional agent skill for clients that load SKILL.md files
tests/            pytest suite over temporary SQLite databases
evals/            agent evals: a Claude model works scripted tasks through the tools

設計上の注意点と制限

  • クエリモードの suggest_index は、インデックスが一般に不足しているかどうかではなく、フルテーブルスキャンを検出します。 どの列にインデックスを付けるかはクエリによって異なるため、CREATE INDEX を記述するのではなく、スキャンを示します。小さなテーブルでのスキャンは想定内であり、問題ではありません。
  • 調査対象はデフォルトスキーマのみです:PostgreSQL では search_path の先頭のスキーマ(通常は public)、MySQL では DATABASE_URL で指定されたデータベースです。SQL でスキーマを指定すれば、execute_query は引き続き billing.invoices を読み取ります。PostgreSQL で別のスキーマを調査するには、そのスキーマを先頭に設定します:ALTER ROLE explorer SET search_path = billing, public;。
  • 一部の読み取り専用クエリ形式は拒否されます。 sqlparse は、かっこで囲まれた (SELECT 1)、VALUES (1)、TABLE users を解析できないため、通常の SELECT を記述するよう案内して拒否します。
  • SQL は 50,000 文字までに制限されています。また、sqlparse のトークン数またはネストの上限を超えると、呼び出しは query_too_complex で失敗します。
  • 行数上限はコンテキストを保護するものであり、処理コストを抑えるものではありません。 負荷の高い集計処理も全件実行されます。これには QUERY_TIMEOUT_SECONDS が上限を設定します。
  • 長いテキストや大きな結果は切り詰められます。 2000 文字を超えるテキスト値は、全長を示したうえで切り詰められて返されます。クエリ結果は 500,000 バイトで停止し、truncated が設定されます。スキーマページも同じ位置で停止し、next_offset から続きます。
  • MariaDB はテストされていません。 MySQL と同じコードパスを使用しますが、行数上限を適用するサブクエリ内の ORDER BY が無視される場合があります。その場合は、順序付けしたクエリに独自の LIMIT を追加してください。
  • 256 バイト以下のバイナリ値は 16 進数で返されます。それより大きい値はサイズのみが報告されます。
  • 幅の広い NUMERIC 値は文字列として返されるため、丸められずに桁が正確に保たれます。

ライセンス

MIT。詳細は LICENSE をご覧ください。

コメント

MCP は初めてですか?

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

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