mcpField
C
Cost Guard MCP

Cost Guard MCP

本地服务
github.com/mcpsmiths数据库11 周前更新

关于 Cost Guard MCP

Cost Guard MCP 是供 Agent 查询 BigQuery、Snowflake 和 Databricks 的本地 MCP 服务。它在执行前估算查询成本与结果规模,并标注估算准确度、按调用设置限制。

主要功能

  1. 01预估成本并标注准确度等级
  2. 02限制查询成本、字节数和行数
  3. 03序列化结果上限为 8 MiB
  4. 04检查凭据和引擎能力
  5. 05单一本地 stdio 进程

适用场景

  1. 01运行 Agent 查询前检查数据仓库成本
  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 Agent 提供查询成本和结果大小的预检防护——在查询实际执行之前进行检查。

为什么

使用数据仓库 MCP 的 AI Agent 可能会在不知情的情况下触发全表扫描,花费数百美元;也可能返回数百万行数据,塞满自身的上下文窗口。现有的数据仓库 MCP 都无法在运行查询 之前 告诉 Agent“这会花费多少”或“会返回多少数据”。

有何不同

  • 每项成本估算都会说明其准确度等级——PRECISE(BigQuery dryRun)、UPPER_BOUND(Snowflake EXPLAIN)或 HEURISTIC(Databricks EXPLAIN COST),确保 Agent 不会过度相信启发式估算值。
  • 每次调用都可单独设置上限——run_query_bounded 可在每次调用时接受 max_bytes_billed / max_rows / max_estimated_cost_usd;无需共享会话状态。max_rows 默认为 1000,序列化结果上限为 8 MiB;每个结果中的 executed 字段都会说明查询是否已运行。
  • 零基础设施——单个本地 stdio 进程即可运行。无需数据库、网关或 Docker Compose。

工具

  • check_credentials(engine, warehouse?) — 验证凭据和连通性,不会执行真实查询或试运行;配置新引擎后应先调用此工具。支持 BigQuery、Snowflake 和 Databricks。执行内容:BigQuery 会进行一次元数据调用;Snowflake 会登录,在指定了仓库时运行 USE WAREHOUSE(未指定时运行 SELECT CURRENT_WAREHOUSE()),读取已固定仓库的 SHOW WAREHOUSES 行,并且仅当该行显示 STARTED 时才运行一条会话上下文语句;否则返回 ok: true,详情根据该行生成(执行该语句可能会恢复已暂停的仓库);Databricks 会通过 Warehouses API(控制平面的 GET 请求)读取已配置的 SQL 仓库,并且仅当其状态为 RUNNING 时才打开会话(SELECT current_user());否则返回 ok: false,且不建立会话(建立会话可能会启动已停止的仓库)。两者都将无法读取的状态视为未运行。设置 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 语句,且默认只接受读取语句——首个关键字必须是 SELECT、WITH、VALUES、SHOW、DESCRIBE、DESC 或 EXPLAIN(请参阅已知限制)。warehouse_size(Snowflake/Databricks)和 edition(Snowflake)只能提高预估值——美元金额按服务器观测到的、查询所固定到的仓库计价(使用 SHOW WAREHOUSES 返回的大小/类型/代际信息,或 Databricks Warehouses API 返回的信息),并采用运维人员配置的 SNOWFLAKE_EDITION,取两者中较高者。无法按模型计价的仓库类型(Snowpark 优化型、Databricks Classic/Pro)会提供字节数预估,但不提供美元金额。对于状态不是 STARTED 的 Snowflake 仓库,会跳过云服务提供商和查询历史记录查询(采用观测到的大小对应的 Gen1 费率、启发式运行时间,并附带说明);未提供 warehouse 参数时也会跳过查询历史记录查询;对于状态不是 RUNNING 或无法读取状态的 Databricks 仓库,不会打开会话,也不会返回字节数或美元金额;请参阅已知限制。
  • 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;成功时以及因两种截断情况拒绝时(row_cap_exceeded、result_size_exceeded——查询已运行),executed: true;其他所有拒绝情况均为 false(查询从未提交,或 BigQuery 拒绝运行)。支持 BigQuery、Snowflake 和 Databricks。每次调用只接受一条 SQL 语句,且默认只接受读取语句——首个关键字必须是 SELECT、WITH、VALUES、SHOW、DESCRIBE、DESC 或 EXPLAIN(请参阅已知限制)。max_estimated_cost_usd 上限会根据观测到的仓库预估值进行检查;如果没有美元金额,则会拒绝运行。此工具不受仓库状态限制:预估和执行都可能恢复/启动仓库。结果行包含常量 rows_provenance: warehouse_data_untrusted:这些是仓库返回的数据,绝不是指令。

设置

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 是 Agent 通过此服务发送 SQL 时唯一的授权边界,因此会强制执行,而不只是写在文档中。在打开任何 Snowflake 连接之前,都会拒绝 ACCOUNTADMIN、SECURITYADMIN 和 ORGADMIN(不区分大小写,无论是否加引号):检查会在首次加载 Snowflake 配置时运行,也就是首次调用 Snowflake 工具时,因此无论该值为何,服务进程本身都会启动;错误角色会导致 check_credentials 返回 ok: false(或 estimate_query_cost/run_query_bounded 返回工具错误),而不是在启动时退出。每个会话也只会激活该角色 — 登录请求会将辅助角色固定为 NONE,因此,如果用户的 Snowflake DEFAULT_SECONDARY_ROLES 为 ('ALL'),该用户不再会将持有的所有其他角色授予模型编写的 SQL。理想情况下,SNOWFLAKE_USER 不应持有 SNOWFLAKE_ROLE 以外的角色。
  • SNOWFLAKE_SECONDARY_ROLES(可选,默认值为 NONE;另一个唯一可用值是 ALL)用于选择退出上述限制,适用于只能通过辅助角色访问对象的部署。建议改为将这些对象的权限授予 SNOWFLAKE_ROLE;只有在接受 Agent 的 SQL 将以该用户持有的所有角色运行时,才设置为 ALL。check_credentials 会报告当前生效的辅助角色集合(CURRENT_SECONDARY_ROLES()),方便确认实际启用的角色。
  • 每个会话还会设置 Snowflake 端的语句截止时间(STATEMENT_TIMEOUT_IN_SECONDS:check_credentials/estimate_query_cost 为 60 秒,run_query_bounded 为 120 秒,与其看门狗一致),避免进程在调用中途退出后查询仍持续运行 — 并继续产生费用。该值按先读取、再调低的方式应用:服务会先读取会话的有效值,只有当该值为 0(无上限)、无法解析或高于目标值时,才会执行 ALTER SESSION。因此,更严格的账户级或用户级策略(例如 30 秒)绝不会被调高;请注意,在 Snowflake 的参数层级中,会话级值原本会覆盖账户级/用户级值(只有仓库级值采用取较低值的规则)。
  • Snowflake 会话还会将每次调用限制为一条语句:EXPLAIN USING JSON 和 run_query_bounded 的执行都会以 num_statements=1 提交,且会话使用 MULTI_STATEMENT_COUNT=1 登录,因此,对于服务端扫描器(参见“已知限制”)未提前拒绝的、以 ; 分隔的文本,Snowflake 本身也会拒绝。没有退出此限制的选项;本来就不支持多语句 Snowflake 文本。
  • COST_GUARD_SNOWFLAKE_ALLOW_WAREHOUSE_RESUME=true(可选,默认关闭;该值必须严格为 true)允许 check_credentials 和 estimate_query_cost 在 SHOW WAREHOUSES 显示状态不是 STARTED 的仓库上运行 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 仓库会提供字节数估算,但不会显示金额,详见 Known Limitations。

默认情况下,check_credentials 和 estimate_query_cost 不会打开会话,除非通过 Warehouses API 读取到的仓库状态为 RUNNING(如果无法读取状态,也会拒绝);设置 COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_START=true(必须精确匹配该值),即可让它们启动已停止的仓库,从而产生计算费用。run_query_bounded 不受此限制。详见 Known limitations。

并发(可选)

COST_GUARD_MAX_IN_FLIGHT_QUERIES(默认值为 8)用于限制同时处于执行中的 run_query_bounded 调用数量——即使宿主已取消调用,只要工作线程尚未退出(最长受 120 s 看门狗限制),也会计入。超过上限的调用会立即失败并返回提及此变量的工具错误,且不会打开任何仓库连接。该值必须是正整数(否则服务无法启动);只有当宿主确实会同时运行更多会产生费用的查询时,才应调高此值。

只读语句(可选禁用)

默认情况下,estimate_query_cost 和 run_query_bounded 只接受只读语句:忽略注释和任意开头的括号后,第一个关键字必须是 SELECT、WITH、VALUES、SHOW、DESCRIBE、DESC 或 EXPLAIN;并且只有当 CTE 列表之后的语句最终解析为 SELECT 或 VALUES 时,才会接受 WITH(Spark SQL 允许 WITH ... INSERT)。其他语句会在任何 dry run、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;每次打标签发布后,release.yml 都会在 PyPI 收录该版本后发布此条目(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 模式(运行 dryRun 无需绑定账单卡,而 estimate_query_cost 只会执行此操作)。如果你还没有项目,请前往 console.cloud.google.com 创建一个。

  2. 获取 Application Default Credentials:在本地运行 gcloud auth application-default login,或创建一个服务账号密钥,并将 GOOGLE_APPLICATION_CREDENTIALS 指向其 JSON 文件。

  3. 将服务添加到 MCP 客户端——请参阅 .mcp.json.example,只需填写 GOOGLE_APPLICATION_CREDENTIALS(本快速入门中完全不用填写 Snowflake 变量)。

  4. 重启 MCP 客户端,使其加载新的服务配置,然后让 Agent 调用 bigquery 上的 check_credentials。这会在不运行任何实际查询的情况下确认配置是否正确——你应该会收到 "ok": true,以及一行注明项目名称的详细信息。如果收到的是 "ok": false,detail 字段会准确说明缺少什么(通常是 GOOGLE_APPLICATION_CREDENTIALS 没有传递到服务进程——请参阅上方的凭证说明,并仔细检查该值是否已设置在客户端自己的服务配置块中,而不只是设置在 shell 中)。

  5. 让 Agent 调用 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 服务——任何兼容 MCP 的客户端都可以使用,不仅限于 Claude Desktop。最终,每个客户端运行的都是相同的 command/args/env;区别仅在于外层文件格式,因此我们提供了一份规范定义——.mcp.json.example——而不是为下方每种工具分别维护一份副本。

没有一个所有工具都会自动读取的通用文件(每种工具都有自己的文件位置),但其中四种工具有三种使用的 mcpServers 封装格式与 .mcp.json.example 完全相同,因此只需将其复制到相应位置即可。填入实际凭证值,然后:

客户端文件位置相较于 .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字段相同,但使用 TOML 语法而非 JSON(如下所示)——或者运行 codex mcp add cost-guard-mcp -- uvx cost-guard-mcp

Codex 是唯一真正的例外(使用 TOML,而非 JSON),因此仍需为其单独配置:

[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 记录: tool=<name> outcome=<success|error|cancelled> elapsed_ms=<n>——如果 MCP 客户端在调用完成前取消了调用,则 outcome 为 cancelled(只有 run_query_bounded 可能以这种方式退出,且仓库端的查询可能仍在运行;请参阅已知限制)。
    • 每次仓库客户端故障(BigQuery/Snowflake/Databricks)都会记录一条 WARNING 记录: 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 记录: engine=<engine> check_credentials_failed detail=<presented>——格式与返回的 detail 字段相同,经过长度限制并加引号。
    • 每次 run_query_bounded 拒绝都会记录一条 INFO 记录,注明引擎和具体拒绝原因(cost_cap_exceeded、byte_cap_exceeded、row_cap_exceeded 或 result_size_exceeded);两种执行后拒绝还会记录截断后的计数。
    • 每个引擎的 120 秒执行看门狗都会在取消仍在运行的查询前记录一条 WARNING 记录。
    • 上述任何日志都不会记录凭据、连接字符串或未经脱敏的仓库客户端原始异常消息——生成工具调用方所见内容时使用的同一套 redact_secrets + present_remote_text 处理链,也会在记录日志前应用。
    • 导入服务模块时(任何后端调用之前),仓库客户端库自身的日志器(snowflake.connector、databricks、databricks.sql、databricks.sdk、google、urllib3、httpx、httpcore)会被固定为 WARNING 级别:其每连接和每请求的 INFO 杂项日志——也就是可能掩盖嵌入式凭据的大量日志——不会到达 stderr,而其 WARNING 和 ERROR 记录(包含异常文本的记录)仍会输出。此设置不会影响项目自身的日志器。两种 Google 库机制会与此设置交互: google-auth 会让已有级别的 google 日志器继续传播,因此 google.* WARNING 和 ERROR 记录现在会通过下方的过滤器到达 stderr;否则它们会在 google 日志器处被 google-auth 拦截。Google 客户端库的可选设置 GOOGLE_SDK_PYTHON_LOGGING_SCOPE 会为指定的 google.* 日志器创建独立的 DEBUG stderr 处理器,并关闭传播——它不受上述级别设置和过滤器控制,因此不要为此服务设置该选项。
    • 作为上述两项措施的纵深防御:任何到达 stderr 处理器的记录,无论由哪个日志器生成(项目自身、mcp SDK 或客户端库),其消息、异常回溯(exc_info=True / logger.exception)和堆栈文本都会先统一渲染一次,再由该处理器上的过滤器通过同一 redact_secrets 进行脱敏,然后才会写出。过滤器绝不会丢弃记录,也不会引发异常;如果无法渲染记录的格式字符串或回溯,则会输出 <log record withheld: message could not be rendered>,并一并隐藏回溯和堆栈文本,而不是让标准库输出原始的 msg/args 错误转储。
    • mcp SDK 也会向同一 stderr 写入自己的记录,而本项目的单行规则不会对这些记录进行格式控制(但上述脱敏过滤器会应用于它们)。本项目引发的每个工具错误后面都会跟着 SDK 的 Tool '<name>' failed: '<text>' INFO 记录(SDK 会对文本调用 repr(),因此它仍保持单行,但会重复调用方已收到的内容);如果某次调用的参数被 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 span 中,但根据文档,在注册实际的导出器之前,该中间件不会执行任何操作 — 除非你主动启用,否则本项目不会注册任何导出器。设置 OTEL_EXPORTER_OTLP_ENDPOINT 为你的 OTel Collector 端点(例如 http://localhost:4317)即可启用:此时,cost-guard-mcp 会构造一个指向该端点的 gRPC OTLP 导出器,并将其注册为全局跟踪提供程序,然后启动服务。若不设置该环境变量,则一切保持不变 — 不会构造导出器,也无需安装下面列出的两个额外依赖项。需要安装 otel extra:
    uv sync --extra otel
    # or: pip install "cost-guard-mcp[otel]"
    

已知限制

  • Snowflake 成本估算会根据调用方近期自己的查询历史(INFORMATION_SCHEMA.QUERY_HISTORY,无需提升权限)进行校准,但前提是此前运行过完全相同的 SQL 文本;否则会退回到粗略的字节大小分档启发式估算。只有完全重复的查询才会触发此机制;真正的新查询始终使用启发式估算。计算平均值时会刻意排除结果缓存命中(否则缓存的近乎即时重复查询会使校准结果偏低,进而低估未来的运行时间)。查询历史写入有延迟——刚刚运行的查询可能尚未出现在查找结果中,此时会安全地退回到启发式估算,而不会报错。
  • 经调查发现,Databricks 校准无法实现:在已测试的账户上,其 Query History REST API 无条件将查询文本返回为 "<REDACTED>",即使是调用方自己的查询,且即使设置了 include_metrics=True 也是如此——已通过直接读取 SDK 源码从服务端确认,无法通过客户端参数绕过。因此未为 Databricks 实现此功能;如果未来有付费工作区确认这是可切换的设置,或许会重新考虑。
  • Databricks 成本估算始终为 HEURISTIC(精度最低的等级)——Databricks 没有试运行,而且 EXPLAIN COST 的大小统计信息经常不可用(未执行 ANALYZE TABLE、流式数据源、非 Delta 外部表)。缺少这些信息时,字节估算和美元估算均为 None——表示不可用,而不是零——run_query_bounded 会拒绝对该查询设置任何 max_bytes_billed 或 max_estimated_cost_usd 上限,而不是将其与不依赖查询内容的费率下限进行比较。要运行此类查询,请省略上限;不会提供恢复旧下限的开关(DECISIONS.md #5)。
  • Databricks 定价仅适用于 Serverless SQL warehouse。通过 Warehouses API(GET /api/2.0/sql/warehouses/{id})查找 DATABRICKS_HTTP_PATH 所绑定的 warehouse(拥有该 warehouse 的 CAN_USE 权限即可):其集群大小是估算下限(warehouse_size 只能调高估算),Classic/Pro warehouse(DBU 费率不同,且还有此处未建模的独立云 VM 成本)不会给出美元估算,因此 max_estimated_cost_usd 上限会拒绝执行——要在那里运行,请省略美元上限。如果查找失败(例如主体没有 CAN_VIEW/CAN_USE 权限,或 http_path 不是 /sql/1.0/warehouses/<id> 路径),估算会保留调用方声明的大小,并附带说明——适用于 run_query_bounded,或者在 COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_START=true 时适用于只读工具;默认情况下,check_credentials 和 estimate_query_cost 会拒绝执行(参见下方关于 warehouse 状态的条目)。
  • Databricks 不支持按查询覆盖 warehouse——SQL warehouse 在连接时由 DATABRICKS_HTTP_PATH 固定。
  • Snowflake 的 UPPER_BOUND 估算不包括 Cortex AI Function(“AI Credits”)成本。
  • Snowflake warehouse 的大小、类型和代际(Gen1 与更新、更昂贵的 Gen2)从固定 warehouse 的 SHOW WAREHOUSES 行读取;云提供商通过 CURRENT_REGION() 获取(均为普通的非特权 SQL)。未传入 warehouse 参数时,使用会话当前 warehouse(CURRENT_WAREHOUSE())。观测到的大小是估算下限(warehouse_size 只能调高估算),代际则决定 credit 费率——在 AWS/GCP 上,Gen2 的费用约为 Gen1 的 1.35 倍,在 Azure 上约为 1.25 倍。Snowpark 优化型或其他非 STANDARD warehouse,以及无法识别的大小,会给出字节估算,但不提供美元估算,因此 max_estimated_cost_usd 上限会拒绝执行——要在那里运行,请省略美元上限。若因任何原因查找失败(权限、超时、无法识别的响应格式、没有当前 warehouse),估算会采用调用方声明的大小和 Gen1 费率,并明确附上说明,而不会报错——由于大多数区域的新标准 warehouse 现在默认使用 Gen2,无法检测代际意味着实际成本可能高于此估算。已于 2026-09-18 在真实试用账户上进行实时验证:其默认 warehouse(COMPUTE_WH)在 AWS 上确实是 Gen2,检测结果采用了 Gen2 费率;SHOW WAREHOUSES 的大小/类型文本格式以及 CURRENT_WAREHOUSE() 的解析方式由 tests/integration/test_snowflake_live.py 固定(由 integration.yml 每周及按需运行)。同一行中的 state 和 auto_resume 列用于下文所述的 warehouse 状态保护。
  • Snowflake 版本由操作人员配置(SNOWFLAKE_EDITION,默认为 standard),因为服务器无法在没有 ORGADMIN 权限的情况下读取该信息;每次调用的 edition 参数只能提高美元/credit 价格,不能降低价格。若 EXPLAIN 计划中的 bytesAssigned 为 null 或非整数,则不会提供字节或美元估算(除非存在该精确查询的历史运行时间),而不会给出毫无信息依据的基准估算。
  • check_credentials 和 estimate_query_cost 带有 readOnlyHint: true,且不会运行你的查询,但它们在 Snowflake 和 Databricks 上并非不消耗计算资源:它们会在指定的、默认的或由 DATABRICKS_HTTP_PATH 指定的 warehouse 会话上运行轻量语句(会话上下文 SELECT、EXPLAIN、SHOW WAREHOUSES、CURRENT_REGION()、QUERY_HISTORY 读取;在 Databricks 上则为 SELECT current_user() / EXPLAIN COST),可能会恢复已暂停的 AUTO_RESUME warehouse,或启动已停止的 SQL warehouse,并在其再次暂停或自动停止前计费。默认情况下(DECISIONS.md #20),服务器会先读取 warehouse 状态,并据此限制可能触发恢复的语句。Snowflake:只有当固定 warehouse 的 SHOW WAREHOUSES 状态为 STARTED 时,才会运行 SELECT CURRENT_REGION()、QUERY_HISTORY 读取和 check_credentials 的会话上下文 SELECT;若状态为 SUSPENDED、RESUMING、SUSPENDING 或 RESIZING,会话未固定 warehouse,或者无法读取状态(没有或有歧义的 SHOW 行、标识符被允许列表拒绝、state 列为 NULL、查找失败),则会跳过这些语句;此外,如果调用 estimate_query_cost 时未提供 warehouse 参数,也会跳过 QUERY_HISTORY 读取。此时,估算会按观测到的大小采用 Gen1 费率和字节分档启发式估算,并在说明中注明(只读估算中的 Gen2 warehouse 可能被低估至多约 1.35 倍;此估算绝不会用于上限判断);check_credentials 则会返回 ok: true,并根据 SHOW WAREHOUSES 行生成详情(登录成功;未探测会话身份)。USE WAREHOUSE、EXPLAIN USING JSON、SHOW WAREHOUSES、SELECT CURRENT_WAREHOUSE() 和会话参数语句始终会运行。Databricks:通过 Warehouses API(GET /api/2.0/sql/warehouses/{id},这是不会启动 warehouse 的控制平面调用)读取状态;除非状态为 RUNNING——无法读取状态时也一样——否则 check_credentials 会返回 ok: false,estimate_query_cost 不会返回字节或美元估算,且不会打开会话;过渡状态 STARTING 视为未运行,请等待启动完成后重试。因此,没有 warehouse 的 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 不受此限制:无论如何都会执行查找并打开会话来进行估算,因为执行查询本身也会唤醒 warehouse。对于仍会运行的语句(USE WAREHOUSE、EXPLAIN USING JSON、SHOW WAREHOUSES、SELECT CURRENT_WAREHOUSE();Snowflake 文档说明,仅包含上下文函数的 SELECT 和 SHOW 可在 warehouse 未运行时执行)以及受限制的语句,究竟哪些会实际恢复已暂停的 AUTO_RESUME warehouse,须由所有者在临时 warehouse 上逐条测试归因;此事已记录为 DECISIONS.md #20 中的后续任务。在测试完成前,此限制会选择跳过,而不是冒险执行。
  • BigQuery Editions/按容量计费的项目无法得到美元估算——只能得到字节数(容量计费没有固定的 $/byte 费率)。计费模式检查(Reservations SearchAllAssignments,按位置查询,且没有查询所有位置的形式)针对试运行作业实际运行的位置——也就是查询会消耗其 slots 的位置;仅当作业未返回位置时才退回到 US 多区域,并在说明中注明。#96 之前始终使用 US,因此分配到其他位置的预留会被漏掉。每次试运行都会记录一条 INFO 日志,注明所检查的位置、位置来源(job 或 fallback)和检查结果;容量计费和检查失败的说明也会注明位置。
  • BigQuery 试运行对受行级安全保护的表始终报告已处理 0 字节,这是为了防止侧信道攻击(Google 的成本文档:“dry runs always return 0 bytes to prevent side-channel attacks”)。从 #96 起(DECISIONS.md #22),dry_run 会将语句引用至少一张表且返回 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 时也一样(“estimate is lower bound of what the query would cost”——这是下限,不是上限):保留字节数,标记为 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 情况不再提供美元估算,美元上限会在分发前拒绝它们(见上一条);此派生上限是对通过检查的估算值的第二道保护——包括 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 会采用保守的基于文本的启发式判断(查询文本中出现 ML.GENERATE_TEXT 或 CREATE FUNCTION + REMOTE),而不是依赖试运行响应中的 referencedRoutines 字段。2026-09-23,在 CI 中配置好 BigQuery 凭据后,我们尝试进行实时验证:特意遵循最小权限原则的服务账户确实没有 bigquery.datasets.create 权限,因此无法创建临时例程来测试试运行时 referencedRoutines 的填充情况;仅为回答这个研究问题而授予更广泛的权限,也会违背本项目在其他各处坚持的最小权限原则。这仍是一个开放的后续任务,留待之后能安全测试此问题的项目所有者处理。
  • 每次调用 estimate_query_cost 和 run_query_bounded 只能接受一条 SQL 语句。成本上限会根据所提交文本的一次估算进行检查,之后会将完全相同的文本交给引擎——顶层 ; 后跟着的第二条语句会在从未针对它检查过的上限下执行。因此,一个小型的默认拒绝扫描器(src/cost_guard_mcp/sql_scope.py,不依赖 SQL 解析器)会在任何试运行/EXPLAIN 或执行之前,拒绝含有顶层 ; 且后面还有 SQL 的文本;单个末尾 ;(后面可选跟空格或注释)则允许。扫描器只识别各方言实际支持的字面量和注释格式(所有方言均支持 '...' 及其中的 \'/''、"..."、--//* */;BigQuery 的反引号、# 注释、三引号及 r/b/rb 前缀;Databricks 的反引号、r 原始字符串和 X'..' 十六进制字面量;Snowflake 的 $$...$$ 和 // 注释),并会拒绝任何无法确定解析的文本:未结束的字面量/注释、标识符紧贴引号(THEN'x'、DATE'2020-01-01'——请加空格)、原始/十六进制字面量或带引号标识符的结束引号前有反斜杠,或块注释中出现 /*;此外,自 #104 起,还会拒绝包含以下 Unicode 行分隔符的行注释:U+000B、U+000C、U+001C–U+001E、U+0085、U+2028 或 U+2029(没有有文档说明的词法分析器会在这些字符处结束注释,而紧跟这些字符的引号可能会隐藏第二条语句,使其避开引擎的 ;);BigQuery 或 Snowflake 行注释中的裸回车符(无法验证这两者词法分析器的注释结束行为,因此在这两者中只有 LF 和 CRLF 会结束注释;Spark SQL 在 CR 或 LF 处结束注释,所以 Databricks 接受此情况);以及紧接行注释结束换行符前的反斜杠(Spark 会继续该注释)。这些拒绝有意采取安全方向,因此会包含误报。确实需要运行多语句脚本的操作人员(例如 BigQuery DECLARE ...; SELECT ...;)可以在服务器上设置 COST_GUARD_ALLOW_MULTI_STATEMENT_SQL=1 来跳过检查,但要接受此时上限只会根据服务提供商对整个脚本的试运行/EXPLAIN 报告内容计价。此退出选项只会跳过本服务器的扫描器,并且有以下两项限制:Snowflake 不适用,因为每次调用固定的 MULTI_STATEMENT_COUNT=1(参见 Setup)仍会拒绝该文本(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)。
  • 默认情况下(DECISIONS.md #13),estimate_query_cost 和 run_query_bounded 仅接受读取语句:去除注释和左括号后,第一个关键字必须是 SELECT、WITH、VALUES、SHOW、DESCRIBE、DESC 或 EXPLAIN;而 WITH 只有在其 CTE 列表之后,跟着扫描器能够识别的 SELECT 或 VALUES 时才接受(每个 CTE 主体也必须符合要求)——所有引擎都会拒绝 WITH ... INSERT/UPDATE/DELETE/MERGE(Spark SQL 允许此类语句)。嵌套超过 16 层的 WITH 也会被拒绝(计入 CTE 主体和带括号的主语句):扫描会对每一层的封闭文本重新检查一次,因此此限制可避免一条由模型控制的字符串在 run_query_bounded 的在途槽位中产生二次方耗时,也可避免超出解释器递归限制而产生通用工具错误,而不是带有明确标签的拒绝结果。可能误报的情况均采取安全方向:TABLE t、Spark 的 FROM t SELECT ...、BigQuery 管道语法(FROM t |> ...)、SET/USE 会话语句(请改用完全限定名称)、WITH ... MATERIALIZED,以及嵌套超过 16 层的 CTE。允许执行不代表一定受支持:EXPLAIN <DML> 会获准(仅在支持 EXPLAIN 的引擎上只生成计划);在 BigQuery(GoogleSQL 不支持此类语句)和 Snowflake(EXPLAIN USING JSON SHOW ...)上,SHOW/DESCRIBE/DESC/EXPLAIN 会在估算阶段失败,且不会包装在 LIMIT 子查询中(其行数上限 max_rows 默认为 1000,由分页获取强制执行)。SELECT 仍可能通过远程函数、UDF 或凭据有权调用的系统函数产生副作用——凭据授权是权限边界(请部署只读主体;参见 Setup)。COST_GUARD_ALLOW_WRITE_STATEMENTS=1 可关闭此限制;对于被单语句退出选项放行的文本,该设置也会将此限制引发的词法分析错误改回直接传递。出于相同原因,结果行带有常量 rows_provenance: warehouse_data_untrusted:这些数据来自 warehouse 返回值,绝不是指令。
  • run_query_bounded 在所有引擎以及 ok、row_cap_exceeded 和 result_size_exceeded 路径下,均以 JSON 安全形式返回结果行(这三种情况下 executed 都为 true——查询已运行;status 用于区分结果,reason 说明原因,executed 表示是否产生了计费):BYTES/BINARY 单元格始终返回标准 Base64 文本(即使字节恰好是有效 UTF-8 也一样);NaN/±Inf 浮点数分别返回字符串 "nan"/"inf"/"-inf";嵌套的 STRUCT/ARRAY/MAP 值会递归处理,非字符串键会转为字符串;DECIMAL/DATE/TIME/TIMESTAMP/以 timedelta 表示的 INTERVAL/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 行),并设置 reason: result_size_exceeded;字节上限不可关闭——请拆分查询。MCP SDK 会将行渲染两次——在 structuredContent 中使用紧凑格式,在文本块中使用 indent=2,之后又会在 JSON-RPC 帧中再次对文本块进行 JSON 字符串转义——因此传输响应大小约为测量字节数的 2 到 4 倍(在约 8 KB 的行上测得 2.0 倍,在窄的七列行上测得 3.8 倍;单列整数行接近 5 倍):8 MiB 结果离开进程时约为 17-32 MiB。ok、row_cap_exceeded 和 result_size_exceeded 时,executed 为 true(已发生计费执行);其他所有拒绝结果(包括 BigQuery 自身的 maximumBytesBilled 拒绝)中,executed 为 false(作业未运行)。引擎会提前停止获取:Snowflake 和 Databricks 使用 fetchmany(500) 分页,BigQuery 传入 max_results=max_rows + 1 和 page_size=500;三者都会在遇到 max_rows + 1 哨兵行或运行中的大小估算超过上限时停止。该估算严格低于精确测量值(已对规范化器生成的每种单元格类型进行属性测试),因此大小限制不会导致悄悄截断后返回 ok;估算包含 JSON 框架,对窄行来说是精确的,但在低估时(由控制字符组成的文本,JSON 会将其转义为每个 6 字节;二进制为 4/3 倍),引擎在触发停止前可能会持有最多约为上限 6 倍的序列化字节,以及一个驱动程序分页。序列化字节不等同于进程内存:窄行背后的 Python 对象约为其 JSON 大小的 25 倍,并且在计费执行结束后、任何看门狗之外进行规范化和测量。实测中,在 {"a": 1} 行上显式设置 max_rows=5,000,000,约在 105 万行处停止,并在一次调用中额外耗费约 440 MB 内存和约 4 秒的计费后处理时间。请将显式值控制在数万以内;若显式 max_rows 超过 100,000,服务器会记录一条 WARNING。只有单条 SELECT/WITH/VALUES 语句会包装为 SELECT * FROM (...) AS cost_guard_row_cap LIMIT max_rows + 1,因此该语句必须能作为派生表运行。若语句自身使用 WITH RECURSIVE,则不会进行包装:BigQuery 只允许在语句顶层使用 WITH RECURSIVE,并会在编译时拒绝包装后的形式,不产生费用(由 0.6.0 预发布标签实时检查发现,#124)。此类语句、SHOW/DESCRIBE/EXPLAIN、退出检查的脚本以及 WITH ... INSERT 都会原样运行,并由获取过程进行限制。对于未包装的语句,Snowflake 仍会在服务端计算完整结果(受 120 秒语句超时限制);客户端只会下载其迭代到的分块。Snowflake 的轮询后获取和 Databricks 的联接后获取,会在剩余的 120 秒预算内运行;预算从提交时开始计时,并在每次完整分页后的请求前检查截止时间;不会在首次分页前中断(Snowflake 连接器会在首次分页内惰性执行 RESULT_SCAN),如果完整结果可在一个较短分页内返回,即使该分页在截止时间后才到达也会照常返回(较短分页后的请求只是用于探测是否已到结果末尾),因此获取阶段可能会超过 120 秒,超时部分最多为一次 RESULT_SCAN 往返(连接器网络超时 60 秒,STATEMENT_TIMEOUT_IN_SECONDS 为 120 秒)再加一个分页下载。
  • 若查询仍在运行,run_query_bounded 会在 120 秒后放弃并取消查询(BigQuery:QueryJob.cancel();Snowflake:SYSTEM$CANCEL_QUERY;Databricks:从看门狗线程调用 Cursor.cancel()),而不是无限等待——否则,因 slot 争用或 warehouse 冷启动/暂停而卡住的查询会阻塞工具调用,并持续消耗 warehouse 时间,从而违背“有上限”工具的初衷。Databricks 会话还会携带服务端 STATEMENT_TIMEOUT(check_credentials/estimate_query_cost 为 60 秒,run_query_bounded 为 120 秒),这样即使进程在看门狗触发前退出,warehouse 本身也会终止语句;连接器重试预算限制为 60 秒 / 5 次(默认值为 15 分钟,目的是等待冷启动结束)——因此,如果已停止的 Classic/Pro warehouse 启动时间超过约 60 秒,check_credentials/estimate_query_cost 现在会通过重试预算错误快速失败,而不再继续等待;但仍会开始启动 warehouse,并产生费用——默认情况下,只读工具不会再尝试启动非 RUNNING 状态的 warehouse(见上一条),因此此情况适用于 run_query_bounded,以及设置了 COST_GUARD_DATABRICKS_ALLOW_WAREHOUSE_START=true 的操作人员。
  • 此服务器发出的其他所有 BigQuery API 往返请求(check_credentials 背后的服务账户查询、试运行和执行作业提交、计费模式检查背后的 Reservations 查询,以及自 #103 起,result() 中的轮询调用 jobs.get/jobs.getQueryResults;修复前这些调用继承了 600 秒默认超时,即使文档说明 120 秒看门狗是上限)均限制为 30 秒的重试时间,并且会禁用客户端库自身的作业重新提交(job_retry=None),因此,即使 Google 无法访问,此处“最长 120 秒”的说法也仍然成立——0.4.0 之前,这些调用继承了 google-cloud-bigquery 的 600 秒默认重试时间(作业提交时会翻倍),可能阻塞工具调用 10-20 分钟,而且已被放弃的 run_query_bounded 线程仍可能在主机取消调用数分钟后提交计费作业。单个被黑洞丢弃的连接仍可能先经历一次 120 秒请求超时,之后才开始 30 秒重试窗口,因此故障情况下的总时间上限约为 150 秒,而不是严格的 120 秒;若轮询重试耗尽,服务器会先对作业发起一次有界的尽力取消请求,再报告错误。
  • 底层 mcp SDK 可能会在客户端于工具调用完成前关闭 stdin 时丢弃在途工具调用的响应(上游问题 modelcontextprotocol/python-sdk#2678,自 2026-05 起开放,尝试多种修复后仍未解决)——对于在整个会话期间保持 stdin 打开的规范客户端,目前没有已知的实际影响;但鉴于此服务器的工具调用最长可能运行 120 秒,仍值得留意(Databricks OAuth M2M 是唯一可能超出此上限的路径:databricks-sdk 的 OIDC 发现有独立的 300 秒重试预算,且其令牌请求不设超时,两者均无法通过 Config 调整——这是上游限制;warehouse 状态保护实施后,此情况也会影响 check_credentials 的状态读取,而不仅是会话;PAT 身份验证完全受时间限制)。
  • 客户端针对在途 run_query_bounded 调用发送的 MCP 取消通知,会立即在 MCP 记账层面解除关联;自 0.4.0 起,服务器会在即将分发执行前(完成估算和所有上限检查后)再次检查是否已取消,因此,若取消在估算阶段到达,完全不会启动计费查询。取消到达时已经开始运行的查询,会继续在已放弃的后台线程中运行,直到每个引擎的看门狗(约 120 秒,见上文)将其取消。为避免依赖此进程继续运行,每个已提交作业还会携带一个与看门狗相同的服务提供商端期限——BigQuery 的 jobTimeoutMs、Snowflake 的 STATEMENT_TIMEOUT_IN_SECONDS,以及作为会话参数的 Databricks STATEMENT_TIMEOUT(只会缩短时间,绝不会放宽操作人员设置的更严格账户/用户/warehouse 策略;参见 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 层面的解除关联会立即归还主机的并发令牌,因此若没有此上限,取消后重新发起调用的主机可能会占用无限数量的 warehouse 连接和运行中的查询(每条查询仍受各自上限和 120 秒看门狗约束)。超过上限的调用会产生一条指出该变量的工具错误,而不是 status: "refused" 结果,因为此时还没有估算值(DECISIONS.md #4 和 #14)。

更多文档

许可证

MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。