mcpField
C
Cost Guard MCP

Cost Guard MCP

ローカル
github.com/mcpsmithsデータベース11 週間前に更新

Cost Guard MCP について

Cost Guard MCP は、BigQuery、Snowflake、Databricks を利用するエージェント向けのローカル MCP サーバーです。実行前にクエリのコストと結果量を見積もり、精度区分を示して呼び出しごとの上限を適用します。

主な機能

  1. 01精度区分付きの事前コスト見積もり
  2. 02コスト、バイト数、行数の上限設定
  3. 03シリアライズ結果を 8 MiB に制限
  4. 04認証情報とエンジン機能の確認
  5. 05単一のローカル stdio プロセス

活用シーン

  1. 01エージェントのクエリ実行前にデータウェアハウスのコストを確認
  2. 02コストやバイト数の上限超過クエリを拒否
  3. 03返却行数とシリアライズ結果のサイズを制限

MCP 対応

コスト見積もり、認証情報とエンジン機能の確認、上限付きクエリ実行のツールを MCP クライアントに提供します。

設定

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

mcp.json · 共通設定
{
"mcpServers": {
"mcpsmiths-cost-guard": {
"command": "uvx",
"args": [
"cost-guard-mcp"
],
"env": {
"GOOGLE_APPLICATION_CREDENTIALS": "<GOOGLE_APPLICATION_CREDENTIALS>",
"SNOWFLAKE_ACCOUNT": "<SNOWFLAKE_ACCOUNT>",
"SNOWFLAKE_USER": "<SNOWFLAKE_USER>",
"SNOWFLAKE_ROLE": "<SNOWFLAKE_ROLE>",
"SNOWFLAKE_SECONDARY_ROLES": "<SNOWFLAKE_SECONDARY_ROLES>",
"SNOWFLAKE_PRIVATE_KEY_PATH": "<SNOWFLAKE_PRIVATE_KEY_PATH>",
"SNOWFLAKE_PRIVATE_KEY_PASSPHRASE": "<SNOWFLAKE_PRIVATE_KEY_PASSPHRASE>",
"SNOWFLAKE_PASSWORD": "<SNOWFLAKE_PASSWORD>",
"SNOWFLAKE_EDITION": "<SNOWFLAKE_EDITION>",
"DATABRICKS_SERVER_HOSTNAME": "<DATABRICKS_SERVER_HOSTNAME>",
"DATABRICKS_HTTP_PATH": "<DATABRICKS_HTTP_PATH>",
"DATABRICKS_TOKEN": "<DATABRICKS_TOKEN>",
"DATABRICKS_CLIENT_ID": "<DATABRICKS_CLIENT_ID>",
"DATABRICKS_CLIENT_SECRET": "<DATABRICKS_CLIENT_SECRET>",
"COST_GUARD_ALLOW_MULTI_STATEMENT_SQL": "<COST_GUARD_ALLOW_MULTI_STATEMENT_SQL>",
"COST_GUARD_ALLOW_WRITE_STATEMENTS": "<COST_GUARD_ALLOW_WRITE_STATEMENTS>",
"COST_GUARD_MAX_IN_FLIGHT_QUERIES": "<COST_GUARD_MAX_IN_FLIGHT_QUERIES>",
"COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_START": "<COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_START>",
"COST_GUARD_SNOWFLAKE_ALLOW_WAREHOUSE_RESUME": "<COST_GUARD_SNOWFLAKE_ALLOW_WAREHOUSE_RESUME>"
}
}
}
}
起動コマンドuvx cost-guard-mcp
設定が必要な環境変数
GOOGLE_APPLICATION_CREDENTIALSシークレットBigQuery: path to a service-account key file (Application Default Credentials). Required to use the bigquery engine.
SNOWFLAKE_ACCOUNTSnowflake: account identifier. Required to use the snowflake engine.
SNOWFLAKE_USERSnowflake: username. Required to use the snowflake engine.
SNOWFLAKE_ROLESnowflake: role to assume. Required to use the snowflake engine; has no default and must never be ACCOUNTADMIN (ACCOUNTADMIN, SECURITYADMIN and ORGADMIN are rejected before any Snowflake connection is opened - the check runs when the Snowflake configuration is first loaded, on the first Snowflake tool call; the server process itself starts regardless, so a bad role surfaces as a failed check_credentials or a tool error, not as a startup exit).
SNOWFLAKE_SECONDARY_ROLESSnowflake: secondary roles activated at login. NONE (default) means only SNOWFLAKE_ROLE is active in the session; ALL is the explicit opt-out that activates every role granted to SNOWFLAKE_USER.
SNOWFLAKE_PRIVATE_KEY_PATHシークレットSnowflake: path to an RSA private key file for key-pair auth (preferred over SNOWFLAKE_PASSWORD). Either this or SNOWFLAKE_PASSWORD is required to use the snowflake engine.
SNOWFLAKE_PRIVATE_KEY_PASSPHRASEシークレットSnowflake: passphrase for an encrypted SNOWFLAKE_PRIVATE_KEY_PATH key file, if the key is encrypted.
SNOWFLAKE_PASSWORDシークレットSnowflake: password (discouraged; prefer SNOWFLAKE_PRIVATE_KEY_PATH). Either this or SNOWFLAKE_PRIVATE_KEY_PATH is required to use the snowflake engine.
SNOWFLAKE_EDITIONSnowflake: account edition that sets the USD/credit price of estimates - standard (default), enterprise, business_critical or vps. The per-call edition argument can only raise it.
DATABRICKS_SERVER_HOSTNAMEDatabricks: SQL warehouse server hostname (e.g. my-workspace.cloud.databricks.com, no scheme, no trailing slash). Required to use the databricks engine.
DATABRICKS_HTTP_PATHDatabricks: SQL warehouse HTTP path (from the warehouse's Connection Details tab, e.g. /sql/1.0/warehouses/<warehouse-id>). Required to use the databricks engine.
DATABRICKS_TOKENシークレットDatabricks: personal access token (simplest auth option). Either this or DATABRICKS_CLIENT_ID + DATABRICKS_CLIENT_SECRET is required to use the databricks engine.
DATABRICKS_CLIENT_IDDatabricks: OAuth machine-to-machine service-principal client ID (preferred for automated use). Requires DATABRICKS_CLIENT_SECRET as well.
DATABRICKS_CLIENT_SECRETシークレットDatabricks: OAuth machine-to-machine service-principal client secret. Requires DATABRICKS_CLIENT_ID as well.
COST_GUARD_ALLOW_MULTI_STATEMENT_SQLSet to 1 to let estimate_query_cost/run_query_bounded accept text containing more than one SQL statement. Off by default: the cost cap is only known to cover the single statement the dry run/EXPLAIN priced. Skips only this server's scanner: Snowflake still rejects multi-statement text (sessions are pinned to MULTI_STATEMENT_COUNT=1), and only a single SELECT/WITH/VALUES statement is wrapped in a LIMIT subquery - a script runs unwrapped and its row cap (max_rows, default 1000) is enforced by the paged fetch.
COST_GUARD_ALLOW_WRITE_STATEMENTSSet to 1 (exactly that value) to let estimate_query_cost/run_query_bounded accept statements that are not reads. Off by default: only text whose first keyword (after comments and opening parentheses) is SELECT, WITH, VALUES, SHOW, DESCRIBE, DESC or EXPLAIN is estimated or run - a WITH must be followed by a SELECT or VALUES - because warehouse rows re-enter the model's context and can steer the next call. The gate is defence in depth: the authorisation boundary is the credential's grants (deploy a read-only principal). With COST_GUARD_ALLOW_MULTI_STATEMENT_SQL=1 every statement in the text must be a read, so BigQuery scripts (DECLARE/SET/BEGIN) need both opt-outs. Any other value leaves the gate on.
COST_GUARD_MAX_IN_FLIGHT_QUERIESMaximum number of run_query_bounded calls allowed in flight at once (positive integer, default 8). A cancelled call keeps its slot until its warehouse query completes or the 120 s watchdog fires; a call over the cap fails with a tool error naming this variable before any warehouse connection is opened.
COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_STARTSet to true (exactly that value) to let the read-only Databricks tools (check_credentials, estimate_query_cost) open a session on a SQL warehouse that is not RUNNING, which starts the warehouse and bills compute until it auto-stops. Off by default: the server first reads the warehouse's state through the Warehouses API (GET /api/2.0/sql/warehouses/<id>, CAN_VIEW/CAN_USE suffices) and, unless it is RUNNING - also when the state cannot be read - check_credentials returns ok=false and estimate_query_cost returns no byte or dollar figure, without opening a session. run_query_bounded is not gated: its own pre-execution estimate and the execution may start the warehouse. Any other value leaves the guard on.
COST_GUARD_SNOWFLAKE_ALLOW_WAREHOUSE_RESUMESet to true (exactly that value) to let the read-only Snowflake tools run SELECT CURRENT_REGION(), the INFORMATION_SCHEMA.QUERY_HISTORY read and check_credentials' session-context SELECT on a warehouse that is not STARTED, which can resume a suspended AUTO_RESUME warehouse and bill compute until it auto-suspends. Off by default: those statements are skipped unless the pinned warehouse's SHOW WAREHOUSES state is STARTED (also skipped when no warehouse is pinned or the state cannot be read), and the QUERY_HISTORY read is skipped whenever estimate_query_cost is called without a warehouse argument; estimate_query_cost then prices at Gen1 rates with the byte-tier heuristic and says so in a caveat, and check_credentials returns ok=true with a detail built from the SHOW WAREHOUSES row. USE WAREHOUSE, EXPLAIN USING JSON, SHOW WAREHOUSES and SELECT CURRENT_WAREHOUSE() always run; run_query_bounded is not gated. Any other value leaves the guard on.

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

パッケージ
pypicost-guard-mcp0.6.0

ツール

4

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

パラメーター型必須説明
engineenumはいbigquery | snowflake | databricks
warehouseanyいいえ

詳細

cost-guard-mcp

PyPI Python MCP Registry MCP Marketplace License: MIT tests

BigQuery、Snowflake、Databricks において、クエリを実行する前に AI エージェントのクエリコストと結果サイズを事前に制御します。

理由

ウェアハウス MCP を使う AI エージェントは、気付かないうちにフルテーブルスキャンを実行して数百ドルのコストを発生させたり、数百万行を返して自身のコンテキストウィンドウをあふれさせたりすることがあります。既存のウェアハウス MCP には、クエリの実行前にエージェントへ「コストはいくらか」「どれくらいのデータが返るか」を伝えるものがありません。

このツールの特長

  • コスト見積もりには精度レベルが明示されます — PRECISE(BigQuery dryRun)、UPPER_BOUND(Snowflake EXPLAIN)、HEURISTIC(Databricks EXPLAIN COST)のいずれかが示されるため、エージェントがヒューリスティックな数値を過信することはありません。
  • 呼び出しごとの上限設定 — run_query_bounded では、呼び出しごとに max_bytes_billed / max_rows / max_estimated_cost_usd を指定できます。共有セッション状態は不要です。max_rows の既定値は 1000 で、シリアライズされた結果は 8 MiB に制限されます。各結果の executed は、クエリが実行されたかどうかを示します。
  • インフラストラクチャー不要 — ローカルの stdio プロセスが 1 つだけです。データベースもゲートウェイも Docker Compose も必要ありません。

ツール

  • check_credentials(engine, warehouse?) — 実際のクエリやドライランを実行せずに認証情報と接続性を確認します。新しいエンジンを設定した後、最初に呼び出してください。BigQuery、Snowflake、Databricks に対応しています。実行内容:BigQuery はメタデータ呼び出しを 1 回行います。Snowflake はログインし、warehouse が指定されている場合は USE WAREHOUSE を実行し、指定されていない場合は SELECT CURRENT_WAREHOUSE() を実行します。ピン留めされた warehouse の SHOW WAREHOUSES の行を読み取り、その行が STARTED を示している場合にのみセッションコンテキスト文を 1 回実行します。それ以外の場合は、その行から詳細を作成して ok: true を返します(この文によって一時停止中の warehouse が再開される可能性があります)。Databricks は Warehouses API を通じて設定済みの SQL warehouse を読み取り(コントロールプレーンの GET)、状態が RUNNING の場合にのみセッションを開きます(SELECT current_user())。それ以外の場合はセッションを開かずに ok: false を返します(セッションによって停止中の warehouse が起動される可能性があります)。どちらも、状態を読み取れない場合は実行中ではないものとして扱います。COST_GUARD_SNOWFLAKE_ALLOW_WAREHOUSE_RESUME=true / COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_START=true で有効にできます。詳細は「既知の制限事項」を参照してください。
  • describe_engine_capabilities(engine) — このエンジンで何が正確で、何が概算なのかを返します。
  • estimate_query_cost(engine, sql, warehouse?, warehouse_size?, edition?) — 精度レベルを示したクエリの実行前コスト見積もりです。BigQuery、Snowflake、Databricks に対応しています。呼び出しごとに SQL 文をちょうど 1 つ受け付け、デフォルトでは読み取り文のみを受け付けます。最初のキーワードは SELECT、WITH、VALUES、SHOW、DESCRIBE、DESC、EXPLAIN のいずれかです(「既知の制限事項」を参照)。warehouse_size(Snowflake/Databricks)と edition(Snowflake)は、見積もりを引き上げる場合にのみ指定できます。金額は、サーバーが認識するクエリのピン留め先 warehouse(SHOW WAREHOUSES のサイズ/タイプ/世代、または Databricks Warehouses API)と、オペレーターが設定した SNOWFLAKE_EDITION のうち、高い方の料金に基づいて算出されます。モデル化された料金がない warehouse タイプ(Snowpark-optimized、Databricks Classic/Pro)では、バイト数の見積もりは返されますが、金額は返されません。STARTED ではない Snowflake warehouse では、クラウドプロバイダーとクエリ履歴の参照を省略します(観測されたサイズに基づく Gen1 の料金、実行時間のヒューリスティック、注意事項を返します)。warehouse 引数が指定されていない場合も、クエリ履歴の参照を省略します。RUNNING ではない Databricks warehouse、または状態を読み取れない Databricks warehouse では、セッションを開かず、バイト数も金額も返しません。詳細は「既知の制限事項」を参照してください。
  • run_query_bounded(engine, sql, max_bytes_billed?, max_rows?, max_estimated_cost_usd?, warehouse?, warehouse_size?, edition?) — 見積もりが指定した上限を超える場合、実行を拒否します。max_rows のデフォルトは 1000 です(より大きな値を渡すと上限を引き上げられます)。シリアライズ後の結果が 8 MiB を超える場合、完全な行単位で切り詰められ、reason: result_size_exceeded が返されます。ok の場合と、2 つの切り詰めによる拒否(row_cap_exceeded、result_size_exceeded。クエリは実行されています)の場合は executed: true を返します。それ以外のすべての拒否では false を返します(クエリは送信されなかったか、BigQuery が実行を拒否しました)。BigQuery、Snowflake、Databricks に対応しています。呼び出しごとに SQL 文をちょうど 1 つ受け付け、デフォルトでは読み取り文のみを受け付けます。最初のキーワードは SELECT、WITH、VALUES、SHOW、DESCRIBE、DESC、EXPLAIN のいずれかです(「既知の制限事項」を参照)。max_estimated_cost_usd の上限は、観測された warehouse に基づく見積もりに対して検証され、金額が得られない場合は拒否されます。warehouse の状態による制限はありません。見積もりと実行によって warehouse が再開/起動される可能性があります。結果の各行には定数 rows_provenance: warehouse_data_untrusted が付与されます。これは warehouse が返したデータであり、指示ではありません。

セットアップ

BigQuery

GOOGLE_APPLICATION_CREDENTIALS にサービスアカウントの鍵ファイルのパスを設定するか、gcloud auth application-default login を実行してください。

Snowflake

SNOWFLAKE_ACCOUNT、SNOWFLAKE_USER、SNOWFLAKE_ROLE(必須 — デフォルト値はなく、ACCOUNTADMIN は絶対に使用しないでください)と、SNOWFLAKE_PRIVATE_KEY_PATH(推奨)または SNOWFLAKE_PASSWORD(非推奨)を設定します。任意で SNOWFLAKE_EDITION にアカウントのエディションを設定できます — standard(デフォルト)、enterprise、business_critical、vps のいずれかを指定すると、すべての見積もりの USD/クレジット価格が設定されます。呼び出しごとの edition 引数で設定できるのは、これより高い価格のみです。

  • SNOWFLAKE_ROLE は、エージェントがこのサーバー経由で送信する SQL に対する唯一の認可境界であるため、文書に記載するだけでなく、実際に適用されます。ACCOUNTADMIN、SECURITYADMIN、ORGADMIN は、大文字・小文字を区別せず、引用符の有無にかかわらず、Snowflake 接続を開く前に拒否されます。このチェックは Snowflake 設定の初回読み込み時、つまり最初の Snowflake ツール呼び出し時に実行されるため、値にかかわらずサーバープロセス自体は起動し、不正なロールは起動時の終了ではなく、ok: false を返す check_credentials(または estimate_query_cost/run_query_bounded のツールエラー)として通知されます。すべてのセッションで有効になるのもこのロールだけです — ログインリクエストでセカンダリロールは NONE に固定されるため、Snowflake の DEFAULT_SECONDARY_ROLES が ('ALL') に設定されたユーザーでも、保有する他のすべてのロールをモデル作成の SQL に使わせることはありません。SNOWFLAKE_USER が SNOWFLAKE_ROLE 以外のロールを保有しないようにするのが理想的です。
  • SNOWFLAKE_SECONDARY_ROLES(任意、デフォルトは NONE。設定できるもう一つの値は ALL)は、セカンダリロールを通じてのみオブジェクトにアクセスしている環境向けのオプトアウト設定です。代わりに、それらのオブジェクトへの権限を SNOWFLAKE_ROLE に付与することを推奨します。ALL を設定するのは、エージェントの SQL がユーザーの保有するすべてのロールで実行されることを許容できる場合に限ってください。check_credentials は、有効なセカンダリロールのセット(CURRENT_SECONDARY_ROLES())を報告するため、現在の設定を確認できます。
  • すべてのセッションには、Snowflake 側のステートメント期限も設定されます(check_credentials/estimate_query_cost は 60 秒、run_query_bounded はウォッチドッグと同じ 120 秒)。これにより、呼び出し中にプロセスが停止しても、クエリの実行と課金が続くのを防ぎます。値は「読み取り後に引き下げる」方式で適用されます。まずセッションの実効値を読み取り、その値が 0(無制限)、解析不能、または目標値を超えている場合にのみ ALTER SESSION を実行します。そのため、アカウントまたはユーザーレベルのより厳しいポリシー(たとえば 30 秒)が引き上げられることはありません。なお、Snowflake のパラメーター階層では、セッションレベルの値はアカウント/ユーザーレベルの値を上書きします(最も低い値が適用されるのはウェアハウスレベルのみです)。
  • Snowflake セッションでは、1 回の呼び出しにつきステートメントを 1 つだけ実行するよう固定されています。EXPLAIN USING JSON と run_query_bounded の実行は num_statements=1 を指定して送信され、セッションは MULTI_STATEMENT_COUNT=1 でログインします。そのため、サーバー側のスキャナー(既知の制限事項を参照)で事前に拒否されなかった ; 区切りのテキストも、Snowflake 自身が拒否します。オプトアウト設定はありません。複数ステートメントを含む Snowflake のテキストは、これまでもサポートされていません。
  • COST_GUARD_SNOWFLAKE_ALLOW_WAREHOUSE_RESUME=true(任意、デフォルトでは無効。値は正確に true である必要があります)を設定すると、SHOW WAREHOUSES の状態が STARTED ではないウェアハウスでも、check_credentials と estimate_query_cost が SELECT CURRENT_REGION()、QUERY_HISTORY の読み取り、セッションコンテキストの SELECT を実行できるようになります(また、warehouse 引数を指定せずに QUERY_HISTORY を読み取れます)。これらの操作によりウェアハウスが再開され、コンピュート料金が発生する可能性があります。デフォルトでスキップされる内容については、既知の制限事項を参照してください。
Databricks

DATABRICKS_SERVER_HOSTNAME と DATABRICKS_HTTP_PATH(SQL ウェアハウスの「Connection Details」タブに記載)を設定し、DATABRICKS_TOKEN(個人用アクセストークン。最も簡単です)または DATABRICKS_CLIENT_ID + DATABRICKS_CLIENT_SECRET(サービスプリンシパルを介した OAuth マシン間認証。自動化での使用に推奨)を設定します。課金対象となるのは Serverless SQL ウェアハウスのみです。Classic/Pro ウェアハウスではバイト数の見積もりは表示されますが、金額は表示されません。「既知の制限事項」を参照してください。

デフォルトでは、check_credentials と estimate_query_cost は、Warehouses API で読み取ったウェアハウスの状態が RUNNING でない限り、セッションを開きません(状態を読み取れない場合も同様に拒否されます)。停止中のウェアハウスを起動できるようにするには、COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_START=true(値は厳密にこの文字列)を設定します。この場合、コンピューティング料金が発生します。run_query_bounded はこの制限の対象外です。「既知の制限事項」を参照してください。

同時実行(任意)

COST_GUARD_MAX_IN_FLIGHT_QUERIES(デフォルトは 8)は、同時に実行できる run_query_bounded の呼び出し数を制限します。ホストがすでにキャンセルした呼び出しも、そのワーカースレッドが終了するまでカウントされます(最長でウォッチドッグの 120 秒間)。上限を超えた呼び出しは、ウェアハウス接続を開く前に、この変数名を示すツールエラーで直ちに失敗します。正の整数を指定する必要があります(指定しない場合、サーバーは起動を拒否します)。課金対象のクエリを正当により多く同時実行する必要がある場合に限り、値を増やしてください。

読み取り専用ステートメント(任意の無効化設定)

デフォルトでは、estimate_query_cost と run_query_bounded は読み取りステートメントのみを受け付けます。コメントと先頭の括弧を無視した後に現れる最初のキーワードは、SELECT、WITH、VALUES、SHOW、DESCRIBE、DESC、EXPLAIN のいずれかでなければなりません。また、WITH は、CTE リストに続くステートメントが SELECT または VALUES と判定された場合にのみ受け付けられます(Spark SQL では WITH ... INSERT が可能です)。それ以外は、ドライラン、EXPLAIN、実行のいずれよりも前に拒否されます。ウェアハウスの行がモデルのコンテキストに再び取り込まれ、次の呼び出しを誘導する可能性があるためです(DECISIONS.md #13)。このチェックは多層防御であり、認可の境界ではありません。認可を決めるのはデプロイ時の認証情報に付与された権限です。そのため、読み取り専用のプリンシパルをデプロイしてください。BigQuery では data-viewer と job-user のロール、Snowflake では USAGE/SELECT のみを持つロール(管理者ロールは使用しない)、Databricks ではウェアハウスのカタログに対する SELECT/USE 権限を使用します。このサーバーに認証情報を使った書き込みを実行させる場合は、COST_GUARD_ALLOW_WRITE_STATEMENTS=1(値は厳密にこの文字列)を設定できます(呼び出しごとに読み取られ、それ以外の値ではチェックが有効のままです)。COST_GUARD_ALLOW_MULTI_STATEMENT_SQL=1 の場合、テキスト内のすべてのステートメントが読み取りでなければなりません。そのため、BigQuery のスクリプト(DECLARE/SET/BEGIN)では、両方の無効化設定が必要です。

MCP ホストでの認証情報について

使用する MCP クライアント/ホスト(Claude Desktop など)にかかわらず、このサーバーは独自のサブプロセスとして起動されます。シェルの環境変数は、.zshrc/.bashrc に設定されていても自動的には引き継がれません。代わりに、ホストのサーバー設定に直接記述してください。正確な設定ブロックは .mcp.json.example を、各ツールでの設定場所は以下の「他の AI コーディングツールでの使用」セクションを参照してください。このリポジトリの .gitignore では、そのセクションに記載されているホスト設定(.mcp.json、.cursor/mcp.json、.vscode/mcp.json)、秘密鍵(*.p8/*.pem/*.key)、credentials*.json、*service-account*.json、.env.* を除外しています(プレースホルダー値を含むコミット済みの .env.example は表示されます)。そのため、記載されている形式を使えば、実際の認証情報を誤ってコミットすることはありません。別のファイル名を使う場合は、自分で .gitignore に追加してください。

インストール

uvx cost-guard-mcp

公式 MCP Registry にも io.github.mcpsmiths/cost-guard-mcp として公開されています。このリスティングは、PyPI に該当バージョンが公開された後、タグ付きリリースごとに release.yml によって公開されます(DECISIONS.md #12)。

ローカルで開発する場合:

git clone https://github.com/mcpsmiths/cost-guard-mcp.git
cd cost-guard-mcp
uv sync
uv run cost-guard-mcp

または Docker を使用する場合:

docker build -t cost-guard-mcp .
docker run -i --rm --init -e GOOGLE_APPLICATION_CREDENTIALS=/creds.json -v /path/to/service-account.json:/creds.json cost-guard-mcp

クイックスタート(最初の見積もりまで約5分)

実際にツールを呼び出すまでの最短手順を説明します。独自のデータは不要です (公開 BigQuery データセットを使用します)。Snowflake のトライアル登録も必要ありません。

  1. BigQuery API を有効にした GCP プロジェクトを用意します。 無料枠の Sandbox モードを含め、どのプロジェクトでも使用できます(estimate_query_cost が実行するのは dryRun だけなので、実行に課金用カードは不要です)。プロジェクトをお持ちでない場合は、 console.cloud.google.com で作成してください。

  2. Application Default Credentials を取得します。 ローカルで gcloud auth application-default login を実行するか、サービスアカウントキーを作成し、その JSON ファイルを GOOGLE_APPLICATION_CREDENTIALS に指定します。

  3. MCP クライアントにサーバーを追加します。 .mcp.json.example を参照し、 GOOGLE_APPLICATION_CREDENTIALS だけを設定します(このクイックスタートでは Snowflake 変数はすべて省略してください)。

  4. MCP クライアントを再起動して新しいサーバー設定を読み込ませてから、エージェントに bigquery の check_credentials を呼び出すよう依頼します。これにより、実際のクエリを実行せずに設定を確認できます。プロジェクト名を含む詳細とともに "ok": true が返されるはずです。代わりに "ok": false が返された場合は、detail フィールドに不足している内容が具体的に示されます(通常は GOOGLE_APPLICATION_CREDENTIALS がサーバープロセスに渡っていません。上記の認証情報に関する注意を参照し、シェルだけでなく、クライアント独自のサーバー設定ブロック内にも値が設定されていることを再確認してください)。

  5. エージェントに、公開データセットを使って estimate_query_cost を呼び出すよう依頼します。 例:

    cost-guard-mcp の estimate_query_cost ツールを bigquery で使って、次のクエリを実行してください: SELECT name, SUM(number) AS total FROM bigquery-public-data.usa_names.usa_1910_2013 GROUP BY name ORDER BY total DESC LIMIT 10

  6. 次のような応答が返れば成功です。 数値は異なりますが、accuracy_tier は PRECISE となるはずです。

    {
      "engine": "bigquery",
      "accuracy_tier": "PRECISE",
      "estimated_bytes": 320866545,
      "estimated_cost_usd": 0.001842,
      "currency": "USD",
      "caveats": []
    }
    

その他の AI コーディングツールでの使用

cost-guard-mcp は標準的な stdio MCP サーバーなので、Claude Desktop だけでなく、MCP 対応のクライアントならどれでも使用できます。どのクライアントも最終的には同じ command/args/env を実行します。異なるのは設定ファイルの形式だけです。そのため、以下のツールごとに個別のコピーを管理するのではなく、共通の定義として .mcp.json.example を使用します。

すべてのツールが自動的に読み込む単一のファイルはありません(各ツールはそれぞれ独自の場所を参照します)が、4つのうち3つは .mcp.json.example とまったく同じ mcpServers ラッパーを使用するため、そこにコピーするだけで設定できます。実際の認証情報を入力してから、次の手順を行ってください。

クライアント設定場所.mcp.json.example からの変更点
Claude Code.mcp.json(プロジェクト)なし。そのままコピーするか、claude mcp add-json cost-guard-mcp '<the "cost-guard-mcp" object>' を実行
Claude Desktopclaude_desktop_config.jsonなし。そのままコピー
Cursor.cursor/mcp.json または ~/.cursor/mcp.jsonサーバーオブジェクト内に "type": "stdio" を追加
GitHub Copilot(VS Code).vscode/mcp.jsonトップレベルキー mcpServers を servers に変更し、"type": "stdio" を追加
OpenAI Codex CLI~/.codex/config.tomlフィールドは同じですが、JSON ではなく TOML 構文を使用します(以下を参照)。または codex mcp add cost-guard-mcp -- uvx cost-guard-mcp を実行

Codex は唯一の例外で(JSON ではなく TOML を使用します)、独自のブロックが必要です。

[mcp_servers.cost-guard-mcp]
command = "uvx"
args = ["cost-guard-mcp"]

[mcp_servers.cost-guard-mcp.env]
GOOGLE_APPLICATION_CREDENTIALS = "/path/to/service-account.json"
BIGQUERY_PROJECT = "your-project-id"
SNOWFLAKE_ACCOUNT = "your-account"
SNOWFLAKE_USER = "your-user"
SNOWFLAKE_ROLE = "your-role"
SNOWFLAKE_SECONDARY_ROLES = "NONE"
SNOWFLAKE_PRIVATE_KEY_PATH = "/path/to/rsa_key.p8"
DATABRICKS_SERVER_HOSTNAME = "your-workspace.cloud.databricks.com"
DATABRICKS_HTTP_PATH = "/sql/1.0/warehouses/your-warehouse-id"
DATABRICKS_TOKEN = "your-personal-access-token"
COST_GUARD_MAX_IN_FLIGHT_QUERIES = "8"
COST_GUARD_ALLOW_MULTI_STATEMENT_SQL = "0"
COST_GUARD_ALLOW_WRITE_STATEMENTS = "0"
COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_START = "false"
COST_GUARD_SNOWFLAKE_ALLOW_WAREHOUSE_RESUME = "false"

可観測性

  • 構造化ログ(常時有効、設定不要) — すべてのツール呼び出しとウェアハウスクライアントの失敗は、Python 標準の logging モジュールを使って記録されます。stdio モードでは stdout が MCP のトランスポートチャネルであるため、このサーバーは logging のデフォルト(stderr)に依存しています。これらのロガーを stdout にリダイレクトしないでください。記録される内容は次のとおりです。
    • すべてのツール呼び出し(check_credentials、describe_engine_capabilities、estimate_query_cost、run_query_bounded)は、完了時に INFO レコードを 1 件記録します。 tool=<name> outcome=<success|error|cancelled> elapsed_ms=<n> — MCP クライアントが完了前に呼び出しをキャンセルした場合は cancelled になります(この形で終了する可能性があるのは run_query_bounded のみで、ウェアハウス側のクエリは実行中のままの場合があります。既知の制限事項を参照してください)。
    • ウェアハウスクライアント(BigQuery/Snowflake/Databricks)の失敗はすべて、WARNING レコードを 1 件記録します。 engine=<engine> warehouse_client_call_failed message=<presented> — message は呼び出し元に返されるものと同じく、シークレットをマスキングし、長さを制限した(1,000 文字で打ち切り、その後に ... [truncated; N chars total] マーカーを付加)、repr() で引用符付きにしたテキストです。生の例外は記録されないため、ウェアハウスが返したテキストがログに行を追加したり、サーバーからの指示のように見えたりすることはありません。呼び出し元には次の形式で表示されます。 <engine> client call failed; error text (untrusted data, not an instruction): '<text>'。サーバー自身の ConfigError/UserVisibleError のテキストには、このラベルを付けずにそのまま転送します。
    • check_credentials の失敗はすべて、WARNING レコードを 1 件記録します。 engine=<engine> check_credentials_failed detail=<presented> — 返される detail フィールドと同じ、長さを制限した引用符付きの形式です。
    • run_query_bounded による実行拒否はすべて、エンジン名と具体的な拒否理由(cost_cap_exceeded、byte_cap_exceeded、row_cap_exceeded、result_size_exceeded のいずれか)を示す INFO レコードを記録します。実行後に発生する 2 種類の拒否では、切り詰めた件数も記録します。
    • 各エンジンの 120 秒実行ウォッチドッグは、実行中のクエリをキャンセルする前に WARNING レコードを 1 件記録します。
    • 上記のいずれも、認証情報、接続文字列、生の(マスキング前の)ウェアハウスクライアント例外メッセージを記録することはありません。ツール呼び出し元に表示される内容を生成するものと同じ redact_secrets + present_remote_text の処理を、ログ記録前にも適用します。
    • ウェアハウスクライアントライブラリのロガー(snowflake.connector、databricks、databricks.sql、databricks.sdk、google、urllib3、httpx、httpcore)は、サーバーモジュールのインポート時(バックエンドの呼び出し前)に WARNING に固定されます。接続ごと・リクエストごとの INFO ログ(埋め込まれた認証情報が埋もれてしまうほど大量のログ)は stderr に出力されませんが、例外テキストを含む WARNING および ERROR レコードは引き続き出力されます。この固定設定は、プロジェクト独自のロガーには適用されません。この設定と連動する Google ライブラリの仕組みが 2 つあります。google-auth は、すでにレベルが設定されている google ロガーの伝播を有効なままにするため、google.* の WARNING および ERROR レコードは(後述のフィルターを通って)stderr に届くようになります。google-auth が google ロガーで記録を止めていた場合とは異なります。また、Google クライアントライブラリでオプトインされている GOOGLE_SDK_PYTHON_LOGGING_SCOPE は、指定された google.* ロガーに独自の DEBUG 用 stderr ハンドラーを設定し、伝播を無効にします。これは固定設定とフィルターの両方の対象外となるため、このサーバーでは設定しないでください。
    • 上記 2 つの対策に加え、多層防御として、どのロガー(このプロジェクト、mcp SDK、クライアントライブラリ)が生成したレコードであっても、stderr ハンドラーに届いたすべてのレコードについて、メッセージ、例外トレースバック(exc_info=True/logger.exception)、スタックテキストを一度レンダリングし、ハンドラー上のフィルターで同じ redact_secrets に通してから出力します。フィルターがレコードを破棄したり、例外を発生させたりすることはありません。フォーマット文字列やトレースバックをレンダリングできないレコードは、標準ライブラリが生の msg/args をエラーダンプする代わりに、<log record withheld: message could not be rendered> として出力し、トレースバックとスタックテキストも併せて伏せます。
    • mcp SDK は独自のレコードを同じ stderr に出力します。このプロジェクトの 1 行ルールはそれらの形式を整えません(上記のマスキングフィルターは適用されます)。このプロジェクトが発生させるツールエラーにはすべて、SDK による Tool '<name>' failed: '<text>' という INFO レコードが続きます(SDK はテキストを repr() で引用するため 1 行に収まりますが、呼び出し元に返された内容が重複します)。引数が pydantic に拒否された呼び出しでは、Tool '<name>' rejected arguments: ['sql'] のみが記録されます。ツール本体は実行されていないため、tool=<name> outcome= レコードは記録されません。また、その呼び出しで呼び出し元に返される検証テキスト(生の改行、問題のある入力値のそのままの表示)は、プロジェクト独自の長さ制限付き引用形式ではなく、SDK によるものです。このプロジェクトがツールエラーに変換しなかった例外(as_tool_error が変換するのは独自の SanitizedEngineError、ConfigError、UserVisibleError のみ)は、SDK によってトレースバックとともに記録されます。 stderr を解析する運用担当者は、上記の tool=<name> outcome= レコードをキーにし、それ以外の行はすべて自由形式のテキストとして扱ってください。
  • OpenTelemetry トレーシング(オプトイン、デフォルトでは無効) — 基盤となる mcp SDK では、すべてのサーバーで OpenTelemetryMiddleware がデフォルトで有効になっており、受信メッセージごとに SERVER スパンでラップします。ただし、実際のエクスポーターが登録されるまでは、このミドルウェアはドキュメントに記載されているとおり何もしません。このプロジェクトでも、明示的に有効にしない限りエクスポーターは登録しません。有効にするには、OTEL_EXPORTER_OTLP_ENDPOINT に OTel Collector のエンドポイント(例:http://localhost:4317)を設定します。すると cost-guard-mcp は、そのエンドポイントを指定した gRPC OTLP エクスポーターを持つ TracerProvider を作成し、サーバーの起動前にグローバルのトレーサープロバイダーとして登録します。環境変数を設定しなければ、何も変わりません。エクスポーターは作成されず、以下の追加依存関係 2 つをインストールする必要もありません。otel エクストラが必要です。
    uv sync --extra otel
    # or: pip install "cost-guard-mcp[otel]"
    

既知の制限事項

  • Snowflake のコスト見積もりは、同一の SQL テキストが過去に実行されたことがある場合、呼び出し元自身の最近のクエリー履歴(INFORMATION_SCHEMA.QUERY_HISTORY、昇格された権限は不要)を使って調整されます。それ以外の場合は、大まかなバイトサイズ階層のヒューリスティックにフォールバックします。これは同一クエリーが完全一致で繰り返された場合にのみ適用され、新規のクエリーには常にヒューリスティックが使われます。結果キャッシュのヒットは平均から意図的に除外されます(キャッシュ済みのほぼ瞬時の繰り返しが含まれると、将来の実行時間を過小評価する方向に調整されてしまうためです)。クエリー履歴の取り込みには遅延があるため、直前に実行したクエリーが検索時点ではまだ表示されないことがあります。その場合、エラーにはならず、安全にヒューリスティックへフォールバックします。
  • Databricks の調整について調査したところ、実現できないことが分かりました。テスト対象のアカウントでは、Query History REST API は呼び出し元自身のクエリーであっても、include_metrics=True を指定しても、クエリーのテキストを常に "<REDACTED>" として返します。これはクライアント側のパラメーターでは回避できないことを、SDK のソースを直接読み、サーバー側で確認しました。このため Databricks では実装していません。将来、有料ワークスペースで切り替え可能な設定であることが確認できれば、再検討する可能性があります。
  • Databricks のコスト見積もりは、常に最も精度の低い階層である HEURISTIC です。Databricks にはドライランがなく、EXPLAIN COST のサイズ統計も利用できない場合がよくあります(ANALYZE TABLE を実行していない場合、ストリーミングソース、非 Delta 外部テーブルなど)。この統計がない場合、バイト数と金額の見積もりはどちらも None になります。これは 0 ではなく、利用不可を意味します。そのクエリーに対する max_bytes_billed または max_estimated_cost_usd の上限指定を run_query_bounded は受け付けず、クエリーに依存しないレート下限との比較も行いません。このようなクエリーを実行するには上限を省略してください。以前の下限値を復元するフラグは意図的に用意していません(DECISIONS.md #5)。
  • Databricks の料金モデルの対象は Serverless SQL ウェアハウスのみです。DATABRICKS_HTTP_PATH がバインドするウェアハウスは Warehouses API(GET /api/2.0/sql/warehouses/{id})で検索されます(ウェアハウスに対する CAN_USE で十分です)。そのクラスターサイズが見積もりの下限となり(warehouse_size で引き上げることのみ可能です)、Classic/Pro ウェアハウス(DBU 料金が異なり、ここではモデル化されていないクラウド VM 費用も別途かかります)では金額が算出されません。そのため、max_estimated_cost_usd の上限を指定すると拒否されます。そこで実行するには金額の上限を省略してください。検索に失敗した場合(CAN_VIEW/CAN_USE のないプリンシパル、または /sql/1.0/warehouses/<id> 形式ではない http_path など)、注意書きを付けて呼び出し元が指定したサイズを維持した見積もりになります。これは run_query_bounded の場合、または COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_START=true の場合の読み取り専用ツールに当てはまります。デフォルトでは、check_credentials と estimate_query_cost は代わりに拒否します(下記のウェアハウス状態に関する箇条書きを参照)。
  • Databricks にはクエリーごとのウェアハウス上書き機能がありません。接続時に DATABRICKS_HTTP_PATH によって SQL ウェアハウスが固定されます。
  • Snowflake の UPPER_BOUND 見積もりには Cortex AI Function(「AI Credits」)のコストは含まれません。
  • Snowflake のウェアハウスサイズ、タイプ、世代(Gen1 と、より新しく料金の高い Gen2)は、固定されたウェアハウスの SHOW WAREHOUSES の行と、クラウドプロバイダーを示す CURRENT_REGION()(いずれも通常の権限で実行できる SQL)から読み取られます。warehouse 引数が指定されていない場合は、セッションの現在のウェアハウス(CURRENT_WAREHOUSE())が使われます。確認されたサイズが見積もりの下限となり(warehouse_size で引き上げることのみ可能です)、世代によってクレジットレートが決まります。Gen2 の料金は AWS/GCP では Gen1 の約 1.35 倍、Azure では約 1.25 倍です。Snowpark に最適化されたウェアハウス、その他の非 STANDARD ウェアハウス、または認識されないサイズの場合、バイト数は見積もられますが金額は算出されません。そのため、max_estimated_cost_usd の上限を指定すると拒否されます。そこで実行するには金額の上限を省略してください。権限、タイムアウト、認識されない応答形式、現在のウェアハウスがないなど、何らかの理由で検索に失敗した場合は、エラーにはならず、呼び出し元が指定したサイズを Gen1 の料金で使う旨を明示した注意書き付きで見積もりがフォールバックします。ほとんどのリージョンで新しい標準ウェアハウスのデフォルトが現在 Gen2 であるため、世代を検出できなければ実際のコストがこの見積もりより高くなる可能性があります。実アカウントを使い、2026-09-18 にライブ検証しました。デフォルトのウェアハウス(COMPUTE_WH)は AWS 上で実際に Gen2 であり、検出結果に Gen2 の料金が適用されました。SHOW WAREHOUSES のサイズ/タイプのテキスト形式と CURRENT_WAREHOUSE() の解決方法は、tests/integration/test_snowflake_live.py に固定されています(integration.yml により毎週および必要に応じて実行)。同じ行の state 列と auto_resume 列は、下記のウェアハウス状態ガードにも使われます。
  • Snowflake のエディションは、サーバーから ORGADMIN なしでは確認できないため、オペレーターが設定します(SNOWFLAKE_EDITION、デフォルトは standard)。呼び出しごとの edition 引数で USD/クレジット価格を引き上げることはできますが、引き下げることはできません。EXPLAIN のプランにある bytesAssigned が NULL または整数以外の場合、同一クエリーの過去の実行時間がある場合を除き、情報量のない基本値ではなく、バイト数も金額も見積もられません。
  • check_credentials と estimate_query_cost には readOnlyHint: true が設定されており、クエリー自体は実行しませんが、Snowflake と Databricks でコンピュートコストがかからないわけではありません。これらのツールは、指定された、デフォルトの、または DATABRICKS_HTTP_PATH のウェアハウスセッション上で軽量なステートメント(セッションコンテキストの SELECT、EXPLAIN、SHOW WAREHOUSES、CURRENT_REGION()、QUERY_HISTORY の読み取り、Databricks では SELECT current_user()/EXPLAIN COST)を実行します。これにより、サスペンド中の AUTO_RESUME ウェアハウスが再開したり、停止中の SQL ウェアハウスが起動したりして、再びサスペンドまたは自動停止するまでコンピュート料金が発生することがあります。したがって、デフォルトでは(DECISIONS.md #20)、サーバーはまずウェアハウスの状態を読み取り、再開を引き起こす可能性があるステートメントの実行をその状態に応じて制御します。Snowflake では、SELECT CURRENT_REGION()、QUERY_HISTORY の読み取り、および check_credentials のセッションコンテキスト SELECT は、固定されたウェアハウスの SHOW WAREHOUSES の状態が STARTED の場合にのみ実行されます。SUSPENDED、RESUMING、SUSPENDING、RESIZING の場合、セッションにウェアハウスが固定されていない場合、状態を読み取れない場合(SHOW の行がない、または曖昧、識別子が許可リストで拒否される、state 列が NULL、検索に失敗した場合)はスキップされます。さらに、warehouse 引数なしで estimate_query_cost が呼ばれた場合は、QUERY_HISTORY の読み取りもスキップされます。その場合、見積もりは確認されたサイズに Gen1 料金を適用し、バイト階層のヒューリスティックを使って価格付けされ、その旨を注意書きに記載します(Gen2 ウェアハウスでは、読み取り専用のこの見積もりが最大約 1.35 倍過小評価になる可能性があります。上限の判定には使われません)。また、check_credentials は ok: true を返し、SHOW WAREHOUSES の行から作成した詳細を示します(ログイン成功、セッション ID は未確認)。USE WAREHOUSE、EXPLAIN USING JSON、SHOW WAREHOUSES、SELECT CURRENT_WAREHOUSE()、セッションパラメーターのステートメントは常に実行されます。Databricks では状態を Warehouses API(GET /api/2.0/sql/warehouses/{id}。ウェアハウスを起動しないコントロールプレーンの呼び出し)で読み取ります。状態が RUNNING 以外の場合、または読み取れない場合、check_credentials は ok: false を返し、estimate_query_cost はバイト数も金額も返さず、セッションも開きません。移行中の STARTING は実行中とはみなされません。起動してから再試行してください。したがって、ウェアハウスに対する CAN_VIEW/CAN_USE のないプリンシパルは、オプトインしない限り、読み取り専用ツールから ok: false/見積もりなしを受け取ります(run_query_bounded は呼び出し元が指定したレートを使うフォールバックを維持します)。オプトインは呼び出しごとに読み取られ、値が正確に true の場合のみ受け付けられます。設定は COST_GUARD_SNOWFLAKE_ALLOW_WAREHOUSE_RESUME=true および COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_START=true です。run_query_bounded はこの制御の対象外です。実行によっていずれにせよウェアハウスが起動するため、検索とセッションを含めて見積もりが行われます。引き続き実行されるステートメント(USE WAREHOUSE、EXPLAIN USING JSON、SHOW WAREHOUSES、SELECT CURRENT_WAREHOUSE()。Snowflake のドキュメントでは、コンテキスト関数のみを使う SELECT と SHOW はウェアハウスが稼働していなくても処理されるとされています)および制御対象のステートメントが、サスペンド中の AUTO_RESUME ウェアハウスを実際に再開するかどうかは、オーナーが使い捨てウェアハウスで各ステートメントの課金を検証するテスト項目です。DECISIONS.md #20 のフォローアップとして記録されています。検証が済むまでは、安全側に倒してスキップします。
  • BigQuery Editions/容量課金プロジェクトでは、バイト数のみ取得でき、金額は見積もれません(容量課金には固定の $/byte レートがありません)。課金モデルの確認(Reservations SearchAllAssignments。全ロケーションを対象にする形式はなく、ロケーションごとに検索)は、ドライランジョブが実行されたロケーション、つまりクエリーが使用するスロットのロケーションに対して行われます。ジョブがロケーションを報告しない場合に限り、US マルチリージョンにフォールバックし、その旨を注意書きに記載します。#96 より前は常に US が使われていたため、別のロケーションに割り当てられた予約が検出されませんでした。ドライランごとに INFO ログが 1 件記録され、確認したロケーション、その取得元(job または fallback)、結果が示されます。容量課金と確認失敗の注意書きにもそのロケーションが記載されます。
  • BigQuery のドライランは、行レベルセキュリティで保護されたテーブルについて、意図的に処理済みバイト数を常に 0 と報告します(Google のコストに関するドキュメント:「サイドチャネル攻撃を防ぐため、ドライランは常に 0 バイトを返します」)。#96(DECISIONS.md #22)以降、dry_run はテーブルを 1 つ以上参照するステートメントの 0 バイトを、そのような形式として扱います。報告どおり estimated_bytes: 0 とし、accuracy_tier: HEURISTIC(非表示の値はクエリーが読み取るバイト数の見積もりではありません)、estimated_cost_usd: null とし、理由を示す注意書きを付けます。これにより、max_estimated_cost_usd の上限は実行前に拒否されます(reason: cost_cap_exceeded、executed: false、DECISIONS.md #5)。$0.00 の見積もりとして誤って受け入れられることはありません。この形式は、実際に無料である 0 バイトのドライランと区別できないため、後者も同じように扱われます。BigQuery がテーブルメタデータから回答するフィルターなしの COUNT(*)、空のテーブルに対する SELECT、すべてのパーティションを除外するパーティションフィルターも金額を返さず、金額の上限も拒否します(行レベルセキュリティがあると断定せず、注意書きでこれらの原因も挙げます)。これらには max_bytes_billed を使用してください。BigQuery が totalBytesProcessedAccuracy: LOWER_BOUND(「見積もりはクエリーのコストの下限」つまり上限ではなく下限)を報告した場合も同様です。バイト数は維持され、HEURISTIC としてタグ付けされますが、金額は返されません。このような実行には max_bytes_billed を設定してください。BigQuery がこれをサーバー側で maximumBytesBilled として適用します(バイト数のゲートは報告された 0/下限値を受け入れ、プロバイダーはジョブの実行前に上限を評価します)。UNKNOWN の精度やサーバーが認識しない値には UPPER_BOUND タグと金額が維持されます。テーブルを参照しないステートメント(SELECT 1)は正当に $0.00 と価格付けされます。読み取り専用の estimate_query_cost も同じドライランを使い、同じ情報を示します。
  • オンデマンド BigQuery プロジェクトでは、run_query_bounded の max_estimated_cost_usd もサーバー側で適用されます。ジョブには、金額の上限から算出した maximumBytesBilled が設定されます(上限 ÷ $6.25/TiB を次の MiB 単位に切り上げ、BigQuery の 10 MiB 最小値を適用)。呼び出し元が max_bytes_billed も指定した場合はさらに低い値が使われますが、それを超えて引き上げられることはありません。#96 以降、行レベルセキュリティと LOWER_BOUND の形式は金額が返されず、前の箇条書きのとおり、金額ゲートで実行前に拒否されます。この算出された上限は、通過する見積もりに対する第 2 の防御層です。対象は UNKNOWN または認識されない精度、テーブルを参照しないステートメントで統計情報がない場合、そして一般的なドライランと実行の差です。この上限を超える課金が発生するクエリーは BigQuery に拒否され、課金されません。エラーではなく通常の status: "refused" 結果として返されます(上限が金額上限に由来する場合は reason: cost_cap_exceeded、独自のバイト上限に由来する場合は byte_cap_exceeded)。残る注意点として、BigQuery は参照されたテーブルごとに最低 10 MiB を課金するため、多数のテーブルを使うクエリーに厳しい上限を設定すると、ゲートを通過した後でプロバイダーに拒否される場合があります。これは現在、プロバイダーのエラー文字列ではなく、構造化された拒否として表示されます。NaN の max_estimated_cost_usd は、すべての見積もりに対して「上限未満」と比較されるため、無条件で拒否されます。+inf は金額の見積もりと比較すると「金額上限なし」として扱われ、サーバー側のバイト上限も算出されませんが、金額の上限自体は指定されたままです。したがって、見積もりに金額がない場合(容量課金、課金モデル確認の失敗、行レベルセキュリティの形式、LOWER_BOUND)は、他の金額上限と同様に呼び出しが拒否されます。パラメーターを省略するか、max_bytes_billed を使ってください。
  • BigQuery のリモート関数と BigQuery ML のリモートモデル推論(例:ML.GENERATE_TEXT)には別途 Cloud Run/Vertex AI の課金が発生しますが、バイト数ベースのこの金額見積もりには含まれません。dry_run は、ドライラン応答の referencedRoutines フィールドではなく、テキストに基づく保守的なヒューリスティック(クエリーテキスト内の ML.GENERATE_TEXT、または CREATE FUNCTION と REMOTE)でこれを警告します。2026-09-23、CI に BigQuery 認証情報が用意された後に実環境での解決を試みました。意図的に最小権限にしてあるサービスアカウントには bigquery.datasets.create がないため、一時的なルーティンを作成してドライラン時に referencedRoutines が設定されるか検証することはできませんでした。この調査のためだけに権限を広げることは、プロジェクトが他のすべての箇所で維持している最小権限の方針に反します。この点は、安全に検証できるプロジェクトを持つ次の担当者に向けた未解決のフォローアップです。
  • estimate_query_cost と run_query_bounded は、1 回の呼び出しにつき SQL ステートメントを 1 つだけ受け付けます。コスト上限は送信テキストの見積もり 1 つに対して確認され、その後、同一のテキストがエンジンに渡されます。トップレベルの ; の後ろに 2 つ目のステートメントを続けると、そのステートメントに対する上限確認がないまま実行されてしまいます。そのため、小規模なフェイルクローズ型スキャナー(src/cost_guard_mcp/sql_scope.py、SQL パーサーへの依存なし)が、ドライラン/EXPLAIN や実行の前に、トップレベルの ; の後ろに SQL が続くテキストを拒否します。末尾の ; 1 つ(後ろに空白/コメントがあっても可)は問題ありません。このスキャナーが理解するのは各方言で実際に使えるリテラルとコメント形式のみです(全方言で '...' と \'/''、"..."、--//* */。BigQuery ではバッククォート、# コメント、トリプルクォート、r/b/rb プレフィックス。Databricks ではバッククォート、r の raw 文字列、X'..' の 16 進数。Snowflake では $$...$$ と // コメント)。確実に解析できない内容は拒否されます。対象は、終端されていないリテラル/コメント、引用符の直前に識別子が連結されている場合(THEN'x'、DATE'2020-01-01'。空白を入れてください)、raw/16 進数リテラルまたは引用符付き識別子の閉じ引用符の直前のバックスラッシュ、ブロックコメント内の /* です。また、#104 以降、行コメント内に Unicode の行区切り文字 U+000B、U+000C、U+001C–U+001E、U+0085、U+2028、U+2029 のいずれかがある場合も拒否されます(これらの文字でコメントが終わると記載したレキサーはなく、その直後に置かれた引用符によってエンジンの ; の後ろにある 2 つ目のステートメントが隠れる可能性があります)。BigQuery または Snowflake では、行コメント内に単独の復帰文字がある場合も拒否されます(それらのレキサーでコメントの終了条件を検証できていないため、そこでコメントを終了するのは LF と CRLF のみです。Spark SQL は CR または LF のどちらでも行コメントを終了するため、Databricks では受け付けます)。行コメントを終了する改行の直前にバックスラッシュがある場合も拒否されます(Spark ではコメントが継続するためです)。これらの拒否は、安全側に倒すための意図的な誤検出です。複数ステートメントのスクリプト(例:BigQuery の DECLARE ...; SELECT ...;)を実行することを承知しているオペレーターは、サーバーで COST_GUARD_ALLOW_MULTI_STATEMENT_SQL=1 を設定してチェックをスキップできます。その場合、上限の価格計算はプロバイダーのドライラン/EXPLAIN がスクリプト全体に対して報告した内容だけに基づきます。このオプトアウトでスキップされるのはこのサーバーのスキャナーのみで、次の 2 つの制限があります。Snowflake では呼び出しごとの MULTI_STATEMENT_COUNT=1 固定(セットアップを参照)が引き続きテキストを拒否します(Actual statement count 2 did not match the desired statement count 1.)。また、行数上限はスクリプト全体には適用されません。単一の SELECT/WITH/VALUES ステートメント(再帰なしの WITH)のみが SELECT * FROM (...) AS cost_guard_row_cap LIMIT n でラップされます。それ以外では、取得は max_rows + 1 行で停止します(max_rows のデフォルトは現在 1000 です)。
  • estimate_query_cost と run_query_bounded は、デフォルトでは読み取り専用ステートメントのみ受け付けます(DECISIONS.md #13)。コメントと開き括弧の後にある最初のキーワードは、SELECT、WITH、VALUES、SHOW、DESCRIBE、DESC、EXPLAIN のいずれかでなければなりません。また WITH は、CTE リストの後にウォーカーが識別できる SELECT または VALUES が続く場合のみ受け付けられます(すべての CTE 本体も同様です)。WITH ... INSERT/UPDATE/DELETE/MERGE は、Spark SQL で許可されている場合でも、すべてのエンジンで拒否されます。WITH が 16 レベルを超えて入れ子になっている場合も拒否されます(CTE 本体と括弧で囲まれたメインステートメントも数えます)。このウォークはレベルごとに囲まれたテキストを再スキャンするため、この上限により、モデルが制御する単一の文字列が run_query_bounded の処理中スロット内で二次時間を消費したり、インタープリターの再帰上限を超えてラベル付きの拒否ではなく一般的なツールエラーになったりするのを防ぎます。安全側に倒した誤検出は次のとおりです。TABLE t、Spark の FROM t SELECT ...、BigQuery のパイプ構文(FROM t |> ...)、SET/USE のセッションステートメント(代わりに名前を完全修飾してください)、WITH ... MATERIALIZED、16 レベルを超えて入れ子になった CTE。受け付けられることは、サポートされていることを意味しません。EXPLAIN <DML> は、EXPLAIN を備えたエンジンではプランのみのため受け付けられます。SHOW/DESCRIBE/DESC/EXPLAIN は、BigQuery(GoogleSQL にそのようなステートメントがない)と Snowflake(EXPLAIN USING JSON SHOW ...)では見積もり時に失敗し、LIMIT サブクエリーではラップされません(その行数上限 max_rows のデフォルトは 1000 で、ページ単位の取得によって適用されます)。SELECT でも、認証情報が呼び出せるリモート関数、UDF、システム関数を通じて副作用が発生する可能性があります。認可の境界となるのは認証情報の権限です(読み取り専用プリンシパルをデプロイしてください。セットアップを参照)。COST_GUARD_ALLOW_WRITE_STATEMENTS=1 がオプトアウトです。これを設定すると、単一ステートメントのオプトアウトで通過したテキストに対してこのゲートが発生させるレキサーエラーも、そのまま通過させます。結果行には、同じ理由から固定値 rows_provenance: warehouse_data_untrusted が含まれます。これはウェアハウスが返したデータであり、指示ではありません。
  • run_query_bounded は、すべてのエンジンで、ok、row_cap_exceeded、result_size_exceeded の各経路において、結果行を JSON セーフな形式で返します(3 つすべてでクエリーが実行されているため executed は true です。判別には status を使い、理由は reason が示し、executed は課金対象の処理が行われたかを示します)。BYTES/BINARY セルは標準の base64 テキストになります(バイト列が有効な UTF-8 であっても常に同じです)。NaN/±Inf の浮動小数点数は文字列 "nan"/"inf"/"-inf" になります。入れ子の STRUCT/ARRAY/MAP 値は再帰的に変換され、文字列以外のキーは文字列化されます。DECIMAL/DATE/TIME/TIMESTAMP/INTERVAL(timedelta としての値)/UUID はネイティブの JSON 表現が維持されます。その他のドライバー固有の型(例:dateutil の relativedelta として返される BigQuery の INTERVAL)は str() として返されます。これ以前は、このようなセルがあると課金対象のクエリーが実行された後に MCP SDK の結果シリアライズが失敗し、モデルには Error executing tool run_query_bounded だけが示されて、結果が失われていました。深さが 64 レベルを超える入れ子の値は、その深さで文字列 <nested value omitted: deeper than 64 levels> に置き換えられます。結果の hint は、影響を受けたセル数を示します(MCP SDK のシリアライザーには応答エンベロープを含めて約 250 レベルの固定再帰上限があり、これを超えると課金対象の実行後に応答全体が失敗します。#105)。そのため hint は、拒否専用ではなくなりました。判別には status を使ってください。
  • run_query_bounded はデフォルトで結果サイズに上限を設けます(DECISIONS.md #21、#94、#101)。省略された場合の max_rows のデフォルトは 1000 です。最大 2^63-2 までの明示的な値は変更せず適用され、オプトアウトになります。シリアライズされた結果は 8 MiB(8,388,608 バイト)に制限されます。通常の行をコンパクト JSON(UTF-8)として測定し、超過した場合は収まる行全体の最長の先頭部分を返します(最初の行だけで上限を超える場合は 0 行)。バイト数上限にはオプトアウトがありません。代わりにクエリーを分割してください。MCP SDK は行を 2 回レンダリングします。structuredContent ではコンパクト形式、テキストブロックでは indent=2 を指定し、そのテキストブロックは JSON-RPC フレーム内で再度 JSON 文字列としてエスケープされます。そのため、ワイヤー上の応答は測定バイト数の 2~4 倍になります(約 8 KB の行では 2.0 倍、狭い 7 列の行では 3.8 倍を測定。1 列の整数行では 5 倍近くになります)。8 MiB の結果は、プロセスから出る際にはおよそ 17~32 MiB になります。課金対象の実行があった ok、row_cap_exceeded、result_size_exceeded では executed は true です。それ以外のすべての拒否では false です。BigQuery 自身による maximumBytesBilled の拒否も含まれます(ジョブは実行されていません)。エンジンは取得を早期に停止します。Snowflake と Databricks は fetchmany(500) でページングし、BigQuery は max_results=max_rows + 1 と page_size=500 を渡します。3 つすべてで max_rows + 1 番目の判定用行に達するか、実行中のサイズ推定が上限を超えると停止します。この推定は実測値の厳密な下限です(正規化で生成するすべてのセル型に対してプロパティテスト済み)。そのため、サイズ上限で停止した場合に、気付かないまま ok として切り詰められることはありません。推定には JSON のフレーミングが含まれるため、狭い行では正確です。過小評価される場合(制御文字からなるテキスト。JSON では各文字が 6 バイトにエスケープされます。バイナリでは 4/3)は、停止するまでエンジンが上限のおよそ 6 倍のシリアライズ済みバイト数とドライバーの 1 ページ分を保持することがあります。シリアライズ済みバイト数はプロセスメモリ量ではありません。狭い行では、その背後にある Python オブジェクトは JSON サイズのおよそ 25 倍になり、正規化と測定は課金対象の実行後、ウォッチドッグの対象外で行われます。実測では、{"a": 1} の行で明示的に max_rows=5,000,000 を指定した場合、約 105 万行で停止し、1 回の呼び出しで課金後の処理に約 440 MB と約 4 秒を要しました。明示値は数万程度にしてください。サーバーは明示的な max_rows が 100,000 を超えると WARNING を 1 件記録します。単一の SELECT/WITH/VALUES ステートメントのみが SELECT * FROM (...) AS cost_guard_row_cap LIMIT max_rows + 1 でラップされるため、派生テーブルとして有効でなければなりません。自身の WITH が WITH RECURSIVE であるステートメントはラップされません。BigQuery では WITH RECURSIVE はステートメントのトップレベルでのみ許され、ラップされた形式はコンパイル時に課金なしで拒否されるためです(0.6.0 のタグ付け前ライブゲートで判明、#124)。これと、SHOW/DESCRIBE/EXPLAIN、オプトアウトされたスクリプト、WITH ... INSERT はラップされずに実行され、フェッチによって上限が適用されます。ラップされていないステートメントでは、Snowflake はサーバー側で引き続き完全な結果を計算します(120 秒のステートメントタイムアウトで制限されます)。クライアントがダウンロードするのは反復処理するチャンクのみです。Snowflake のポーリング後のフェッチと Databricks の結合後のフェッチは、残りの 120 秒の予算内で実行されます。予算は送信時点で設定されたデッドラインに照らして、完全なページの後に続く各ページ要求の前に確認されます。最初のページが中断されることはありません(Snowflake のコネクターはそのページ内で RESULT_SCAN を遅延実行します)。1 つの短いページに収まる完全な結果は、そのページがデッドライン後に届いても返されます(短いページの後の要求は結果終端の確認にすぎないためです)。したがって、フェッチフェーズは、RESULT_SCAN の往復 1 回(コネクターのネットワークタイムアウトは 60 秒、STATEMENT_TIMEOUT_IN_SECONDS は 120 秒)と 1 ページのダウンロード分だけ、120 秒を超えることがあります。
  • run_query_bounded は、実行中のクエリーを 120 秒経過後に諦めてキャンセルします(BigQuery:QueryJob.cancel()、Snowflake:SYSTEM$CANCEL_QUERY、Databricks:ウォッチドッグスレッドから Cursor.cancel())。スロット競合中やコールド/サスペンド状態のウェアハウスで止まったクエリーを無期限に待つと、ツール呼び出しがブロックされ、ウェアハウス秒単位の課金も続き、「上限付き」ツールの目的に反するためです。Databricks のセッションにはサーバー側の STATEMENT_TIMEOUT も設定されます(check_credentials/estimate_query_cost は 60 秒、run_query_bounded は 120 秒)。これにより、ウォッチドッグが作動する前にこのプロセスが終了しても、ウェアハウス自身がステートメントを終了します。またコネクターのリトライ予算は 60 秒/5 回に制限されています(デフォルトは 15 分で、コールドスタートを待つ用途です)。そのため、起動に約 60 秒を超える停止中の Classic/Pro ウェアハウスでは、check_credentials/estimate_query_cost は待ち続けず、リトライ予算エラーで速やかに失敗します。ただし、試行自体は開始され、ウェアハウスの起動にも課金されます。デフォルトでは、前の箇条書きのとおり、読み取り専用ツールは RUNNING 以外のウェアハウスに対してこの試行を行いません。そのため、これは run_query_bounded と COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_START=true を設定したオペレーターに適用されます。
  • このサーバーが実行する、その他すべての BigQuery API の往復(check_credentials が使用するサービスアカウント検索、ドライランおよび実行ジョブの送信、課金モデル確認で使う Reservations の検索、そして #103 以降は result() 内のポーリング呼び出しである jobs.get/jobs.getQueryResults。この修正前は、120 秒のウォッチドッグが上限として記載されていても、600 秒のデフォルトが適用されていました)は、リトライを含め 30 秒に制限されています。また、クライアントライブラリ自身によるジョブの再送信は無効です(job_retry=None)。したがって、ここにある「最大 120 秒」という記述は、Google に接続できない場合にも当てはまります。0.4.0 より前は、google-cloud-bigquery のデフォルトである 600 秒のリトライが引き継がれ(ジョブ送信では 2 倍)、ツール呼び出しが 10~20 分間ブロックされる可能性がありました。また、破棄された run_query_bounded スレッドが、ホストによる呼び出しキャンセルから数分後に課金対象のジョブを送信する可能性もありました。接続が 1 つブラックホール化すると、30 秒のリトライ期間が適用される前に、1 回のリクエストタイムアウトで 120 秒かかる場合があります。そのため、障害時の上限は厳密な 120 秒ではなく、およそ 150 秒です。ポーリングのリトライが尽きた場合、エラーを表示する前に、サーバーはジョブのキャンセルを 1 回、制限時間内でベストエフォートで試みます。
  • 基盤となる mcp SDK は、ツールの実行が終わる前にクライアントが stdin を閉じると、処理中のツール呼び出し応答を破棄することがあります(上流の問題 modelcontextprotocol/python-sdk#2678。2026-05 から未解決で、複数の修正が試されました)。セッション中に stdin を開いたままにする適切なクライアントでの実環境の問題は知られていませんが、このサーバーのツール呼び出しは最大 120 秒かかる可能性があるため、知っておく価値があります。Databricks OAuth M2M はその上限を超える可能性がある唯一の経路です。databricks-sdk の OIDC 検出には独自の 300 秒のリトライ予算があり、トークン要求にはタイムアウトがありません。どちらも Config では調整できない上流の制限です。ウェアハウス状態ガードの導入後は、これはセッションだけでなく check_credentials の状態確認にも適用されます。PAT 認証には完全に上限が設定されています。
  • 処理中の run_query_bounded 呼び出しに対してクライアントから MCP キャンセル通知が送られると、MCP の管理レベルでは速やかに切り離されます。0.4.0 以降、サーバーは実行をディスパッチする直前(見積もりとすべての上限確認の後)にもキャンセルを再確認します。そのため、見積もりフェーズ中に届いたキャンセルは、課金対象のクエリーを一切開始しません。キャンセル通知が届いた時点ですでに実行中のクエリーは、エンジンごとのウォッチドッグ(約 120 秒。上記参照)がキャンセルするまで、破棄されたバックグラウンドスレッドで実行され続けます。このプロセスが稼働し続けることに依存しない保護策として、送信されたすべてのジョブにはウォッチドッグと同じ期限がプロバイダー側にも設定されます。BigQuery では jobTimeoutMs、Snowflake では STATEMENT_TIMEOUT_IN_SECONDS、Databricks ではセッションパラメーターの STATEMENT_TIMEOUT です(オペレーターのアカウント/ユーザー/ウェアハウスのより厳しいポリシーを引き上げることはなく、引き下げることのみ可能です。DECISIONS.md #14 を参照)。そのため、stdin を閉じた後、SIGTERM/SIGKILL でプロセスを終了させるホスト(参照 MCP クライアントでは EOF の 2~4 秒後)は、クエリーがその期限を超えて課金され続ける状態を残せません。コンテナーを実行する場合は、インストール例の --init フラグを維持してください。これがないとサーバーが PID 1 となって SIGTERM を無視し、docker stop は SIGKILL で終了します。MCP ホストがタイムアウトまたはキャンセルされた呼び出しを再試行すると、新しい課金対象の呼び出しが発行されます。以前の呼び出しが再開されることはありません。
  • run_query_bounded が同時に受け付ける呼び出し数は COST_GUARD_MAX_IN_FLIGHT_QUERIES(デフォルト 8)までです。キャンセルされた呼び出しも、破棄されたスレッドが終了するまではスロットを保持します。MCP レベルで切り離すと、ホストの並行処理トークンはすぐに返されます。そのため、この上限がなければ、ホストがキャンセルと再発行を繰り返すことで、上限と約 120 秒のウォッチドッグが各クエリーに適用されるとしても、ウェアハウス接続と実行中クエリーを無制限に固定できてしまいます。上限を超えた呼び出しは、推定値がまだないため status: "refused" の結果ではなく、変数名を示すツールエラーで失敗します(DECISIONS.md #4 および #14)。

その他のドキュメント

ライセンス

MIT

コメント

MCP は初めてですか?

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

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