bq-guard-mcp
Provides policy-guarded access to Google BigQuery, letting agents list datasets and tables, inspect schemas, dry-run queries, and run read-only queries with byte, row, and statement-type limits enforced.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@bq-guard-mcprun a dry run on SELECT COUNT(*) FROM analytics.sessions"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
bq-guard-mcp
An MCP server that gives AI agents BigQuery access through a policy. Every query is parsed, checked, and dry-run before anything is billed.
Letting an agent query a warehouse directly means trusting it not to scan a 40 TB table, run a
DELETE, or read the PII dataset. bq-guard-mcp is the one thing the agent talks to. It
exposes five tools, and each request goes through the guards in bq-guard.yaml. A refusal
says what to change, so the agent can fix the query instead of failing blind.
run_query("SELECT * FROM analytics.events")
-> Refused by bq-guard policy: the query would scan 3.41 TB, over the 5.00 GB limit;
filter on partition or clustering columns, or select fewer columnsTools
Tool | What it does |
| Datasets the policy allows. Denied datasets are hidden. |
| Tables in |
| Schema (with nested fields), row count, size, partitioning, and clustering. |
| Bytes the query would scan, the tables it touches, and whether |
| Runs one statement after every guard. Returns rows as JSON, |
Related MCP server: bigquery-mcp
Guards on run_query
Parse with sqlglot (BigQuery dialect), not regex. SQL that doesn't parse is refused, and so are scripts and multiple statements.
Statement type. Only queries run by default (
read_only: true). Everything else is blocked:INSERT,UPDATE,DELETE,MERGE,CREATE_*,DROP_*,ALTER,TRUNCATE,EXPORT,GRANT, and so on. Withread_only: false, only the types listed inallow_statementsrun.Dataset and table allow and deny lists. These check every table the SQL names, and every table the dry run reports, so a view over a denied table is caught too. Deny wins over allow. A wildcard table like
events_*is refused while table deny rules exist, because it could include a denied table.LIMIT. A
SELECTwithout a top-levelLIMITgetsLIMIT default_limitappended to the original text. The SQL is not regenerated, so it runs exactly as written plus the limit.Dry run first, always. If the estimate exceeds
max_bytes_billed, the query is refused before any job exists.maximum_bytes_billedon the real job, always, so BigQuery itself enforces the cap even if the estimate was wrong.Row cap. At most
max_rowsrows come back.total_rowsandtruncatedsay what was left out.Labels. Every job carries your
labelsplusbq_guard=trueandbq_guard_tool=run_query|dry_run, so you can find agent traffic inINFORMATION_SCHEMA.JOBSand billing exports.Audit log. One JSON line per request and decision.
dry_run runs checks 1, 3, and 4 before estimating. It reports statement-type and byte-limit
problems as run_query_blockers instead of refusing, because a dry run executes nothing.
Install
pip install git+https://github.com/rk-chavali/bq-guard-mcp@v0.1.0or with uv: uv tool install git+https://github.com/rk-chavali/bq-guard-mcp@v0.1.0. Python 3.11+.
Authentication uses Application Default Credentials. Locally, run
gcloud auth application-default login. On a server, use a service account (workload
identity, or GOOGLE_APPLICATION_CREDENTIALS). No credentials are needed to start the server.
They are loaded on the first tool call, and a clear error names the fix if they are missing.
Connect it to your agent
Claude Code
claude mcp add --transport stdio --scope project bq-guard -- bq-guard-mcp --policy bq-guard.yamlor commit a .mcp.json at the project root:
{
"mcpServers": {
"bq-guard": {
"command": "bq-guard-mcp",
"args": ["--policy", "bq-guard.yaml"]
}
}
}Cursor
.cursor/mcp.json in the project, or ~/.cursor/mcp.json for every project:
{
"mcpServers": {
"bq-guard": {
"command": "bq-guard-mcp",
"args": ["--policy", "/absolute/path/to/bq-guard.yaml"]
}
}
}VS Code
.vscode/mcp.json:
{
"servers": {
"bq-guard": {
"type": "stdio",
"command": "bq-guard-mcp",
"args": ["--policy", "${workspaceFolder}/bq-guard.yaml"]
}
}
}Docker
docker build -t bq-guard-mcp .
docker run -i --rm \
-v "$HOME/.config/gcloud:/home/app/.config/gcloud:ro" \
-v "$PWD/bq-guard.yaml:/app/bq-guard.yaml:ro" \
bq-guard-mcpThe image runs as a non-root user, reads /app/bq-guard.yaml, and writes the audit log to
/tmp. Mount a volume and set BQ_GUARD_AUDIT_LOG to keep the log. The same snippets are in
examples/clients/.
Policy reference
The policy is found in this order: --policy PATH, $BQ_GUARD_POLICY, ./bq-guard.yaml. If
none exists, the built-in defaults apply: read-only, a 1 GB byte cap, LIMIT 1000, and 1000
rows. Unknown keys and invalid values stop the server at startup with a clear message.
project: acme-analytics # default: the ADC project
location: US
max_bytes_billed: 5000000000 # per query; dry run must be under it and the job is capped at it
read_only: true # only SELECT (default)
allow_statements: [] # with read_only: false, e.g. [INSERT, MERGE, "CREATE_*"]
datasets: # patterns: "dataset" or "project.dataset"
allow: ["analytics", "marts"]
deny: ["*_pii", "raw_*"]
tables: # patterns: "dataset.table" or "project.dataset.table"
deny: ["*.users", "*.*_secrets"]
enforce_limit: true
default_limit: 1000
max_rows: 500
timeout_seconds: 120
labels: # lowercase keys and values, BigQuery label rules
team: data-platform
audit_log: bq-guard-audit.jsonl # relative to the policy file; null disables; $BQ_GUARD_AUDIT_LOG overrides
audit_sql: hash # hash (default) or fullPatterns are shell-style globs (*, ?) and are case-sensitive, like BigQuery names. An empty
allow list allows everything that isn't denied. The full example is
examples/bq-guard.yaml.
Audit log
{"ts": "2026-09-23T14:02:11+00:00", "tool": "run_query", "decision": "blocked", "sql_sha256": "5f1c...", "reason": "DELETE statements are blocked: the server is in read-only mode"}
{"ts": "2026-09-23T14:02:40+00:00", "tool": "run_query", "decision": "allowed", "sql_sha256": "a93e...", "statement_type": "SELECT", "bytes_estimated": 52428800, "bytes_billed": 52428800, "rows_returned": 500, "job_id": "job_ab12", "limit_added": true}SQL is stored as a SHA-256 hash by default, because queries can contain customer data in
literals. Set audit_sql: full to keep the text. A failure to write the log is reported on
stderr and never blocks a request.
Security notes
BigQuery IAM is the real boundary. Give the server's identity only
roles/bigquery.jobUseron the billing project androles/bigquery.dataVieweron the datasets the agent should read. Grant write roles only if you allow writes in the policy. The policy is defense in depth, and it produces refusals the agent understands. It is not a substitute for IAM.The server runs over stdio and opens no network port.
Tool errors report BigQuery's message but never credentials. Query results go to the agent, and so to the model provider, so keep sensitive datasets out of the allow list.
Limitations
Table checks see the tables in the SQL and in the dry run's
referenced_tables. Access that neither shows, such as data read inside a remote function, is only limited by IAM.LIMITis added only at the top level of aSELECT. It limits rows returned, not bytes scanned, which is whatmax_bytes_billedis for.Multi-statement scripts,
DECLARE, and procedural SQL are refused rather than analyzed.Region-level
INFORMATION_SCHEMAviews (region-us.INFORMATION_SCHEMA.JOBS) are treated as a table in a dataset named after the region. Deny them explicitly if you need to.
Development
uv sync
uv run ruff check . && uv run mypy && uv run pytestThe tests use a fake BigQuery backend and an in-memory MCP client. They need no GCP credentials and make no network calls.
License
MIT
Available Tools
5 toolsdescribe_tableA
Schema, row count, size, partitioning, and clustering for dataset.table or
project.dataset.table.
| Name | Required | Description | Default |
|---|---|---|---|
| table | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure. It lists the output fields but does not state whether the operation is read-only, what permissions are required, what errors might occur, or any side effects. This is a significant gap given the description is the only source of behavioral information.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, focused sentence that front-loads the key output properties and the parameter format. No word is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (one param, output schema present), the description covers the core intent and parameter format, but it lacks usage guidance and behavioral detail (e.g., read-only nature, permissions). These gaps prevent it from being fully self-contained, though the presence of an output schema mitigates the need to explain return values.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema provides only a 'table' string with no description. The description adds crucial meaning by specifying the acceptable formats ('dataset.table' or 'project.dataset.table'), guiding the agent on how to construct a valid value. This more than compensates for the 0% schema description coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly specifies that the tool returns schema, row count, size, partitioning, and clustering for a given table, and gives the exact format of the table identifier. This distinguishes it from siblings like list_datasets, list_tables, dry_run, and run_query, which perform different functions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies the tool is used for inspecting a table's metadata, but it provides no explicit guidance on when to choose this over siblings or any exclusions. There is no mention of prerequisites or scenarios where alternatives would be more appropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
dry_runA
Estimate bytes scanned for one statement without running it, and report whether run_query would allow it and why not.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral disclosure burden. It clearly states the tool does not execute the statement and that it reports both allowability and the reason for denial. This covers the most critical behavioral traits, though it omits details like permissions or failure modes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One tightly constructed sentence that front-loads the action, states the key non-execution behavior, and names the compared sibling. Every word earns its place with no redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple with a single required parameter and an output schema, so the description does not need to explain return values. Purpose, behavior, and relationship to run_query are all covered sufficiently for correct selection and invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate. It adds the meaningful constraint that the input is a single statement, but it does not describe sql syntax, format, or dialect. The parameter name 'sql' is self-explanatory, but the description adds only marginal value beyond it.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb ('estimate') and resource ('bytes scanned for one statement'), and explicitly contrasts itself with run_query. This makes the tool's purpose unmistakable and clearly distinguishes it from the sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'without running it' and 'whether run_query would allow it' clearly implies the tool is a preflight check for run_query. It gives a clear usage context, though it does not explicitly state exclusions or when to prefer alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_datasetsA
List datasets the policy allows. project defaults to the configured project.
| Name | Required | Description | Default |
|---|---|---|---|
| project | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full behavioral burden. It does disclose two useful traits—results are filtered by policy and the project parameter defaults to the configured project—but it does not clarify whether the operation is strictly read-only, how errors are handled, or how policy filtering behaves.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two short sentences with no filler. The core action is front-loaded and the parameter default is stated immediately after, making it easy to scan and parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
This is a low-complexity tool with one optional parameter and an output schema, so the description does not need to explain return values. It provides the essential invocation facts—what is listed and what happens when project is omitted—though it could briefly mention the distinction from list_tables.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It adds meaning by stating that project defaults to the configured project when omitted, which is not evident from the schema, but it does not explain what 'configured project' means or how explicit null behaves.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('List') and resource ('datasets') plus the policy constraint, so the agent knows exactly what the tool returns. It is also naturally differentiated from siblings like list_tables and describe_table without needing to open the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No explicit guidance is given about when to choose list_datasets over list_tables, describe_table, dry_run, or run_query. The only usage hint is the project-default behavior, which concerns the parameter rather than tool selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_tablesB
List tables in a dataset, given as dataset or project.dataset.
| Name | Required | Description | Default |
|---|---|---|---|
| dataset | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It only mentions the action (listing) and the input format, but does not disclose whether the operation is read-only, any permission requirements, or what happens if the dataset does not exist. It is a minimal disclosure, offering no insight into side effects or error behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, concise sentence that front-loads the action and resource. It contains no filler or redundant information, and every word contributes to understanding. It is an example of appropriately sized, efficient writing.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with one parameter and an output schema, the description provides the essential action and input format. However, it lacks context about return values (though an output schema exists), error conditions, or any special behavior. It is adequate but not thorough; an agent would need to rely on the output schema for return details and might be uncertain about edge cases.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema provides only a title 'Dataset' with no description, so schema coverage is 0%. The description compensates by clarifying that the dataset parameter can be given as `dataset` or `project.dataset`, which adds meaning beyond the schema. However, it does not elaborate on the exact format (e.g., project ID requirements) or any constraints, so it only partially covers the parameter semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb 'List' and the resource 'tables in a dataset', and specifies the input format (`dataset` or `project.dataset`). It is distinguishable from siblings: list_datasets lists datasets, describe_table describes a single table, etc. The purpose is unambiguous and specific, though it does not explicitly contrast with siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides no guidance on when to use this tool versus alternatives. It only states what it does without any context about selecting it over list_datasets or describe_table. There is no mention of when not to use it or any conditions for use.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
run_queryA
Run one statement after the policy checks and a dry run. Returns at most the row cap;
truncated says when more rows exist.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden and does add useful behavior: execution follows policy checks and a dry run, results are capped, and truncated indicates more rows exist. It does not disclose side effects or whether write statements are permitted, leaving some behavioral details to inference.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The entire behavior is captured in one tight, front-loaded sentence plus a short result note. Every clause adds information about preconditions, output bounds, or truncation signaling.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a one-parameter query execution tool with an output schema, the description covers the key preconditions and result semantics. It is complete enough for an agent to call it correctly, though it leaves the exact policy-check behavior implied.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema provides no description for sql, so the description must compensate. 'Run one statement' clarifies that the sql parameter holds a single statement rather than a script, but no syntax, format, or constraints are given; the field name itself carries most of the meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with the specific action 'Run one statement' and identifies the target as a SQL statement via the sql parameter. It also differentiates from the dry_run sibling by stating execution happens after policy checks and a dry run.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
By saying execution occurs 'after the policy checks and a dry run', the description places run_query in a clear workflow alongside dry_run. It does not explicitly list when-not-to-use cases, but the intended context is sufficiently clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.1.0- First observed
describe_table - First observed
dry_run - First observed
list_datasets - First observed
list_tables - First observed
run_query
TDQS
Scored across 5 tools
Each tool targets a distinct action: listing datasets, listing tables, describing a table, dry-running a query, and running a query. There is no overlap or ambiguity between them.
All tool names follow a consistent verb_noun snake_case pattern (list_datasets, list_tables, describe_table, dry_run, run_query). The naming is predictable and uniform.
Five tools is well-scoped for a BigQuery guard server, covering metadata exploration and query execution without excess or redundancy. Each tool earns its place.
The surface covers the core workflow: discover datasets, browse tables, inspect schema, estimate cost, and run queries. Minor gaps like dataset-level metadata or query cancellation are not essential for the stated purpose.
Maintenance
Related MCP Connectors
Scoped agent execution. Server-side credentials, policy, budgets and verifiable receipts.
Deterministic allow/require_approval/deny verdicts for agent actions, before they happen.
Security gateway for AI agents: policy, approval, and audited execution, no secrets shared.
Deterministic safety, correctness & cost gate that vets Postgres SQL before your AI agent runs it.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables LLMs to explore BigQuery datasets and tables, run safe read-only queries, and optionally perform vector search using BigQuery embeddings.80 PyPI10MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to query and analyze Google BigQuery data, including schema browsing, running queries, and comparing datasets through natural language.MIT
- FlicenseNot gradedqualityCmaintenanceEnables reviewing BigQuery SQL queries for performance issues, cost estimation, and suggested rewrites via an MCP interface.1-
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to query BigQuery read-only via MCP with enforced dataset boundaries, result limits, and audit labels.MIT