MCP Toolbox for Databases 中的 bigquery-execute-sql:GoogleSQL 动态执行工具与 writeMode、allowedDatasets 安全机制详解
【免费下载链接】mcp-toolboxMCP Toolbox for Databases is an open source MCP server for databases.项目地址: https://gitcode.com/GitHub_Trending/ge/mcp-toolbox
在 MCP Toolbox for Databases(下文简称 Toolbox)的 BigQuery 集成中,bigquery-execute-sql是最灵活的一类工具:它允许 LLM 直接提交任意 GoogleSQL 语句,而非受限于固定的预构建查询。本文以官方文档 bigquery-execute-sql 工具说明 为主体,结合 工具实现源码、BigQuery 数据源实现 与 配套测试,完整讲解该工具的配置方式、sql/dry_run参数、readOnly×writeMode行为矩阵、allowedDatasets数据集白名单的底层校验链路,以及 MCP 工具注解的动态行为,帮助你在 Agent 工作流中安全地启用 SQL 执行能力。
工具概述与参数说明
bigquery-execute-sql工具的作用是在 BigQuery 上执行一条 GoogleSQL 语句。它接受两个参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
sql | string | 是 | 要执行的 GoogleSQL 语句。参数描述会根据 source 的writeMode与allowedDatasets动态变化(见下文)。 |
dry_run | boolean | 否 | 设为true时,查询只被校验而不实际运行,返回关于该执行的元信息(dry-run job 信息)。默认false。 |
从源码 buildParams 可以看到,sql参数的描述文本是动态生成的:
- 当 source 处于
blocked模式时,描述会追加 "In 'blocked' mode, only SELECT statements are allowed; other statement types will fail."; - 当 source 处于
protected模式时,描述会追加 "Only SELECT statements and writes to the session's temporary dataset are allowed (e.g.,CREATE TEMP TABLE ...)."; - 当配置了
allowedDatasets时,描述会追加约束说明:仅一个数据集时会提示"表必须用数据集限定名书写(如my_dataset.my_table)",多个数据集时会列出全部允许的project.dataset列表。
这意味着 LLM 在tools/list阶段看到的就是与其权限边界一致的参数说明,而不是事后在tools/call时才被拒绝。
配置示例与字段参考
最小可用的工具配置如下(继承自官方文档 Example 一节):
kind: tool name: execute_sql_tool type: bigquery-execute-sql source: my-bigquery-source description: Use this tool to execute sql statement.该工具引用一个type: bigquery的数据源。完整的 BigQuery source 配置(含readOnly、writeMode、allowedDatasets、useClientOAuth、maximumBytesBilled等字段说明)见 BigQuery Source 文档,BigQuery 预置工具集配置可参考 bigquery 预置配置。
工具的字段参考(与官方文档 Reference 一节一致):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type | string | 是 | 必须为"bigquery-execute-sql"。 |
source | string | 是 | SQL 所执行的数据源名称,必须是bigquery类型 source。 |
description | string | 是 | 传给 LLM 的工具描述。 |
另外,从 Config 结构体 可以看到,工具配置还支持可选的annotations字段(MCP 工具注解覆盖项),用于自定义readOnlyHint、destructiveHint等;未显式指定时的默认行为见下文"MCP 工具注解"一节。
readOnly × writeMode 行为矩阵
官方文档给出的核心行为矩阵如下:
readOnly | writeMode | 工具行为 | MCP 工具注解 |
|---|---|---|---|
false(默认) | allowed(默认) | 允许所有 SQL 语句 | 默认注解(无特殊提示) |
true | blocked(readOnly: true时的默认值) | 只允许SELECT;其他类型语句(如INSERT、UPDATE、CREATE)一律拒绝 | readOnlyHint: true |
true | protected | 启用基于会话(session)的执行:所有表都可SELECT,但写操作只允许落在该会话的临时数据集(如CREATE TEMP TABLE ...) | readOnlyHint: true |
注意:相互冲突的配置(如
readOnly: true搭配writeMode: allowed)会在服务器启动阶段被直接拒绝。
该矩阵在源码中可以得到完整印证:
- 默认值推导:在 newConfig 中,未显式设置
writeMode时默认取allowed;若同时声明readOnly: true,则writeMode自动推导为blocked。 - 启动期冲突校验:在 Source.Initialize 中,
readOnly布尔值必须与writeMode的只读语义一致——readOnly: true只能搭配blocked或protected,readOnly: false只能搭配allowed,否则返回 "conflicting source configuration" 错误。 - 运行时语句类型拦截:在 Tool.Invoke 中,
blocked模式下 dry-run 得到的statementType若不是SELECT,直接返回 "write mode is 'blocked', only SELECT statements are allowed";protected模式下则检查 dry-run 返回的DestinationTable,若写入目标不属于会话临时数据集(session.DatasetID)则拒绝。
此外,writeMode: protected有一个硬性限制:它不能与useClientOAuth一起使用,因为客户端 OAuth 场景下每次工具调用都会创建新客户端、无法维持跨调用的同一会话;该约束同样在 Initialize 中校验。
执行流程:先 dry-run 校验,再真实执行
理解dry_run参数的关键是:该工具内部永远先执行一次 BigQuery dry-run job,dry_run: true只是决定"校验后是否继续真实执行"。从 Invoke 调用链 看:
- 通过 source 的
RetrieveClientAndService取得 BigQuery 客户端(客户端 OAuth 模式下使用调用方 token 创建缓存客户端); - 若 source 处于
protected模式,先取/建 BigQuery session,并把session_id作为ConnectionProperty注入后续查询; - 调用 bqutil.DryRunQuery 提交一个
dryRun: true的 job(同时透传 source 级maximumBytesBilled上限),从返回的 job 中读出statementType与引用表信息; - 依据 writeMode 与 allowedDatasets 执行权限判定(见下两节);
- 若调用方传了
dry_run: true,把整个 dry-run job 以格式化 JSON 返回,不执行查询; - 否则经 source 的
RunSQL真实执行,并把mcp-toolbox-tool: bigquery-execute-sql作为 job label 附加(配合 SQL Commenter 标签,可在INFORMATION_SCHEMA.JOBS与账单导出中按工具归因,见 source 文档 Advanced Usage)。
因此dry_run是一个零成本(不扫描数据、不计费执行)的"预检"开关:Agent 可以借此先确认查询语法、预估扫描量(statistics中的 estimated bytes),再决定真实执行。
执行结果的返回形态由 source 层的 RunSQL 决定:结果行以"列名 → 值"的有序 map 数组返回,行数受 source 的maxQueryResultRows限制(默认 50);SELECT查得 0 行时返回文本 "The query returned 0 rows.";DML/DDL 等无结果集语句返回 "Query executed successfully and returned no content."。数值上,NUMERIC/BIGNUMERIC(底层*big.Rat)会被规范化为最多 38 位精度、去尾零的十进制字符串,BYTES保持 Base64。
protected 模式下的会话管理
writeMode: protected依赖 BigQuery 的会话能力,会话由 newBigQuerySessionProvider 管理,其生命周期策略值得注意:
- 会话通过一个带
CreateSession: true的 dry-run job 创建,拿到session_id与会话临时数据集 ID; - 已有会话先做一次带
session_id的SELECT 1校验 dry-run:成功则复用并刷新LastUsed; - 绝对寿命为 7 天,距寿命终点 30 分钟内的会话会被主动换新(源码注释说明假设单任务不超过 30 分钟,避免任务中途失效);
- 按 source 文档 的说法,会话在 24 小时不活跃或 7 天后被服务端终止,下次请求会新建会话,前一会话的临时数据随之丢失;
- 所有关联该 source 的工具共享同一个会话,因此
protected模式不推荐多用户共享的服务端部署。
allowedDatasets:数据集白名单的双重校验
当 source 配置了allowedDatasets时,bigquery-execute-sql在执行前会对查询做静态分析,访问白名单外数据集的查询会被拒绝。官方文档指出被一并禁止的操作包括:
- 数据集级操作(如
CREATE SCHEMA、ALTER SCHEMA); - 无法静态分析出访问表的操作(如
EXECUTE IMMEDIATE、CREATE PROCEDURE、CALL)。
源码中这一机制由两条互补的校验链路实现,均发生在 dry-run 之后的权限判定段(Invoke L173-L218):
链路一:dry-run 元数据(最可靠来源)。从 dry-run job 的statistics.query中收集ReferencedTables、DdlTargetTable、DdlDestinationTable三处表引用,汇总成project.dataset.table形式的去重集合。同时按statementType直接拦截无法静态分析的类型:CREATE_SCHEMA/DROP_SCHEMA/ALTER_SCHEMA,CREATE_FUNCTION/CREATE_TABLE_FUNCTION/CREATE_PROCEDURE,以及CALL——拒绝理由都是"其内容无法被安全分析"。
链路二:内置 SQL 词法解析器(兜底)。TableParser 是一个手写状态机,能识别字符串/原始字符串(含r'''、反引号)、单行/多行注释、子查询括号嵌套等,用于捕获 dry-run 可能绕过的表和视图引用。它还会额外拦截几类危险模式:
- 标识符中出现
EXTERNAL_QUERY一律拒绝(外部查询无法追踪目标); INFORMATION_SCHEMA查询仅允许数据集级视图(TABLES、COLUMNS、PARTITIONS等白名单,见 datasetLevelInformationSchemaViews),且必须带数据集前缀——SELECT * FROM region-us.INFORMATION_SCHEMA.SCHEMATA这类项目/区域级查询会被拒绝;- 词法层面的
CREATE/ALTER/DROP SCHEMA|DATASET、EXECUTE IMMEDIATE、CREATE [OR REPLACE] PROCEDURE|FUNCTION同样会被解析器直接报错。
最终,两条链路得到的所有表 ID 逐一调用 IsDatasetAllowed 比对白名单,任一越界即返回类似 "query accesses dataset 'project.dataset', which is not in the allowed list" 的错误。白名单本身在 source 初始化时还会逐个通过 API 验证存在性(见 Initialize L222-L254),避免"配置了不存在的白名单"这类静默失效。
集成测试 TestInvokeDatasetRestrictions 用模拟 BigQuery REST 服务覆盖了上述边界:允许数据集内表、INFORMATION_SCHEMA.TABLES(带前缀)放行;白名单外表、混合 JOIN 越权表、区域级INFORMATION_SCHEMA.SCHEMATA、EXTERNAL_QUERY全部被拒绝,可作为实现行为的直接验证依据。
MCP 工具注解的动态行为
bigquery-execute-sql的 MCP 注解不是写死的,而是根据 source 的只读状态动态生成。GetAnnotations 的逻辑是:若 source 判定为只读(writeMode为blocked或protected,见 Source.IsReadOnly),则在基础注解上强制覆盖readOnlyHint: true、destructiveHint: false,且保留调用方显式指定的其他提示位(如idempotentHint、openWorldHint)。TestGetAnnotations 验证了全部组合:allowed模式保持默认的 destructive 注解;blocked/protected模式翻转为只读注解;显式只读注解在只读 source 下不被改变。这让客户端(IDE、Agent 框架)能依据注解在 UI 上做风险分级,与行为矩阵中"MCP Tool Annotations"一列一一对应。
适用场景与使用限制
官方文档对该工具的定位非常明确:它面向带人工介入(human-in-the-loop)的开发者辅助工作流,不应直接用于生产环境的无人值守 Agent。这一限制与该工具"任意 SQL 可执行"的本质相符——即便叠加blocked/protected模式与allowedDatasets,LLM 生成的 SQL 仍可能产生昂贵的全表扫描。落地时建议配合 source 层的其余安全阀:
maximumBytesBilled:单查询扫描字节上限,超限的查询在 dry-run 阶段即失败(该值同时注入内部 dry-run 与真实执行,见 RunSQL L625-L627);maxQueryResultRows:限制返回给 LLM 的行数(默认 50),控制上下文体积;readOnly: true或writeMode: blocked:把工具收敛为纯查询入口。
小结
bigquery-execute-sql通过"配置期约束(writeMode、allowedDatasets)+ 调用期双重静态分析(dry-run 元数据 + 内置解析器)+ 动态 MCP 注解"三层设计,把自由 SQL 执行的风险收敛到可审计的范围:dry_run提供零成本预检,blocked/protected控制写权限,allowedDatasets圈定数据边界,而 job label 使每次执行都能在INFORMATION_SCHEMA.JOBS中归因到具体工具。相关实现集中在 internal/tools/bigquery/bigqueryexecutesql/bigqueryexecutesql.go、internal/sources/bigquery/bigquery.go 与 internal/tools/bigquery/bigquerycommon/table_name_parser.go,可作为进一步深入阅读的入口。
【免费下载链接】mcp-toolboxMCP Toolbox for Databases is an open source MCP server for databases.项目地址: https://gitcode.com/GitHub_Trending/ge/mcp-toolbox
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考