Agentic Data Plane
Preview

Google BigQuery Managed MCP Server

Connect agents to BigQuery so they can browse datasets and answer questions with GoogleSQL. Every result row comes back as a record keyed by column name, so a data policy can mask or filter one column, and a byte limit refuses any query that would scan too much data.

After reading this page, you will be able to:

  • Set up Google Cloud access for the BigQuery server with per-user OAuth or a service account

  • Create the Google BigQuery managed MCP server with a byte limit on every query

  • Run a query from the Inspector and mask or filter its columns with a data policy

The Google BigQuery managed MCP server is in Preview. If you don’t see Google BigQuery in the marketplace picker, contact Redpanda support.

What this MCP server does

The Google BigQuery managed type calls Google’s hosted BigQuery MCP server. Google enables that server when you enable the BigQuery API. The managed type exposes these tools:

Tool What it does

list_datasets

List the datasets in a project. project_id defaults to the server’s project. Supports page_size (1 to 1,000, default 50) and page_token.

list_tables

List the tables and views in a dataset.

get_table

Return a table’s schema (column names, types, modes, descriptions, and nested fields), size, timestamps, partitioning, and clustering. The result never includes rows.

query

Run a read-only SELECT or WITH query and return columns, records, and total_bytes_processed. Optional max_rows. To read the next page, or to keep waiting for a query that’s still running, call query again with the same sql and with next_page_token as page_token.

execute_sql

Run a data manipulation language (DML) or data definition language (DDL) statement, such as INSERT, UPDATE, DELETE, MERGE, CREATE, ALTER, or DROP. The result holds the number of affected rows, the bytes processed, and the job ID, and never includes rows. If running is true, the statement was still running when the call stopped waiting, so the number of affected rows isn’t known yet. Check the job in BigQuery before you run the statement again, or it may run twice. Available only when Allow Writes is on.

query is the only tool that returns table data. Later pages also come from query, so a data policy on query covers every read. Google’s get_dataset_info tool isn’t exposed, because a dataset’s access list holds user email addresses.

Before it runs any statement, the server asks BigQuery for a dry run, which checks the SQL and estimates the bytes it would process. The dry run adds one call to BigQuery before every statement, and its result decides what happens next:

  • A statement whose estimate is above Max Bytes Processed is refused before it runs.

  • Errors from the dry run, such as a syntax error or an unknown column, describe the SQL rather than the data, so they reach the agent, which can fix its query.

  • Errors raised while a statement runs are withheld, because BigQuery can quote cell values in them. For example, a failed CAST(email AS INT64) names the email address that failed to convert.

How rows become records

BigQuery returns each row as a positional list of strings. The server decodes every row against the result’s schema into a record keyed by column name:

BigQuery type Value in a record

INT64

Number, or a string for values beyond 253 so they stay exact

FLOAT64

Number, with "NaN", "Infinity", and "-Infinity" as strings

BOOL

Boolean

TIMESTAMP

RFC 3339 string, for example "2026-01-02T03:04:05Z"

NUMERIC, BIGNUMERIC, DATE, DATETIME, TIME, BYTES (base64), GEOGRAPHY (well-known text, WKT), INTERVAL, RANGE

String

JSON

The JSON text, as a string. Its keys are data, not schema, so a data policy masks or drops the whole value.

STRUCT

Object. A NULL struct is an object whose fields are all null.

ARRAY

Array

Every record carries every column, with NULL as null. A column name that a data policy can’t address, such as `Annual Income` with a space, is refused before the query runs, with a suggestion to alias it, for example SELECT `Annual Income` AS annual_income.

Prerequisites

  • A Google Cloud project with the BigQuery API enabled. New projects have it enabled already. The project runs and pays for every query the server sends.

  • Billing enabled on the project if agents will run execute_sql. The BigQuery sandbox doesn’t support DML. See BigQuery sandbox.

  • The gcloud CLI, signed in as a user who can grant Identity and Access Management (IAM) roles on that project. See Install the gcloud CLI.

  • Permission in Agentic Data Plane to create secrets, OAuth providers, and MCP servers.

  • For per-user OAuth, familiarity with user-delegated OAuth.

Choose how the server authenticates

The server supports two authentication modes:

Mode Who BigQuery sees Use it for

User OAuth (recommended)

Each calling user, with their own Google token from the token vault

Agents that act for a person. BigQuery’s own IAM, column-level access control, row-level security, and data masking all apply per user.

Service account

One shared Google service account for every caller

Agents with no person behind them, such as agents run by a trigger. BigQuery can’t tell callers apart, so data policies matter more.

Grant BigQuery roles

Whichever mode you choose, grant these IAM roles on the project to each user (for user OAuth) or to the service account:

Role Purpose

MCP Tool User (roles/mcp.toolUser)

Calling Google’s MCP tools

BigQuery Job User (roles/bigquery.jobUser)

Running queries in the project

BigQuery Data Viewer (roles/bigquery.dataViewer)

Reading table data. You can grant it on individual datasets instead of the project.

BigQuery Data Editor (roles/bigquery.dataEditor)

Only for execute_sql: changing data and creating or dropping tables

For example, to grant roles/bigquery.jobUser to one user, replace <project-id> and <email>:

gcloud projects add-iam-policy-binding <project-id> \
  --member="user:<email>" \
  --role="roles/bigquery.jobUser"

To grant roles to many users at once, use a Google group as the member instead, for example group:analysts@example.com.

Set up per-user OAuth

If you use a service account, skip this section.

Create the OAuth client

This configuration refers to a third-party product. Specific UI elements may not be up to date.
  1. In the Google Auth Platform for the project, configure the consent screen:

    1. On the Branding page, give the app a name your users recognize, for example Redpanda Agentic Data Plane.

    2. On the Audience page, choose Internal so only users in your Google Workspace organization can connect. If Internal isn’t available, choose External and add each user as a test user.

    3. On the Data Access page, add the https://www.googleapis.com/auth/bigquery scope.

  2. Open Clients and create a client with Application type set to Web application.

  3. Under Authorized redirect URIs, add the AI Gateway OAuth callback for your environment:

    https://aigw.<cluster-id>.clusters.rdpa.co/oauth/v1/callback

    Replace <cluster-id> with your cluster’s identifier. If you’re not sure of the exact URL, add it later. The OAuth provider you create in Configure the OAuth provider shows it on its detail page.

  4. Copy the client’s Client ID and Client secret.

Store the client secret

Store the client secret in the Agentic Data Plane secret store under an UPPER_SNAKE_CASE ID, for example GOOGLE_BIGQUERY_CLIENT_SECRET, with the Single line value format. See Create a secret.

Configure the OAuth provider

Register a OAuth provider for Google in Agentic Data Plane. See Configure an OAuth Provider for the full form.

  1. Open Integrations setup in the sidebar and click the Outbound providers tab.

  2. Click Add provider, then choose the Google preset.

    The preset fills in Google’s authorization, token, and revocation endpoints, the openid, email, and profile scopes, PKCE, and the Client Secret (POST) authentication method. It also requests offline access, so each user’s connection gets a refresh token and doesn’t expire after an hour.

  3. Set Name, for example google-bigquery.

  4. Enter the Client ID from the Google client, and set Client secret reference to the secret, for example GOOGLE_BIGQUERY_CLIENT_SECRET.

  5. Under Scopes, click Add scope and add https://www.googleapis.com/auth/bigquery.

  6. Click Create provider.

If you haven’t added the callback URL to the Google client yet, copy the Authorization callback URL from the provider’s detail page and add it under the client’s Authorized redirect URIs.

Set up a service account

If you use per-user OAuth, skip this section.

This configuration refers to a third-party product. Specific UI elements may not be up to date.
  1. Create a service account. Replace <project-id> with your project ID:

    gcloud iam service-accounts create adp-bigquery --project=<project-id>
  2. Grant it the roles from Grant BigQuery roles, for example:

    for role in roles/mcp.toolUser roles/bigquery.jobUser roles/bigquery.dataViewer; do
      gcloud projects add-iam-policy-binding <project-id> \
        --member="serviceAccount:adp-bigquery@<project-id>.iam.gserviceaccount.com" \
        --role="$role"
    done
  3. Create a JSON key for it:

    gcloud iam service-accounts keys create adp-bigquery-key.json \
      --iam-account=adp-bigquery@<project-id>.iam.gserviceaccount.com
  4. Store the whole key file in the Agentic Data Plane secret store under an UPPER_SNAKE_CASE ID, for example GOOGLE_BIGQUERY_SA_KEY, with the Multiline value format. See Create a secret.

  5. Delete the local key file once the secret is stored.

AI Gateway exchanges the key for short-lived access tokens and checks the key when the server starts. If the secret doesn’t hold a valid service account JSON key, the server’s tools can’t be listed and every call fails.

Create the server

Before you start, finish Set up per-user OAuth or Set up a service account.

  1. Open MCP servers in the sidebar.

  2. Click Add MCP server.

  3. Click the Google BigQuery card in the marketplace picker.

  4. Replace the suggested Name, and optionally add a Description.

  5. Set Project ID to the project that runs and pays for queries, for example my-analytics-project.

  6. Optional: Set Max Bytes Processed. Any statement whose dry run estimates more bytes processed is refused. Leave it empty for 10 GiB (10,737,418,240 bytes).

  7. Optional: Set Max Rows, the most rows one query call or page returns, from 1 to 3,000. Leave it empty for 1,000.

  8. Optional: Turn on Allow Writes to add the execute_sql tool. Leave it off for a read-only server.

  9. Under Auth, select one mode:

    • User OAuth: Set Provider Name to the provider you created, for example google-bigquery, and add https://www.googleapis.com/auth/bigquery under Required Scopes.

    • Service Account: Set Key Secret Ref to the secret that holds the key, for example GOOGLE_BIGQUERY_SA_KEY.

  10. Click Create server.

Every query and execute_sql statement runs in, and is billed to, the server’s Project ID. You can’t change the billing project with a tool argument. A query can still read tables in other projects that the identity can read. To browse those projects, pass a project_id to list_datasets, list_tables, or get_table.

With user OAuth, each user’s connection must hold every required scope. A connection with fewer scopes returns scope_upgrade_required, and the user reconnects to grant the missing one. Every required scope must also be in the OAuth provider’s Scopes.

Configure from the CLI

You need rpk ai 0.2.100 or later to create this server from the CLI. Run rpk ai version to check your version. To update, run rpk ai upgrade.

To create a read-only server with per-user OAuth and a 1 GiB byte limit, pass the configuration as JSON in --managed.config:

rpk ai mcp-server create google-bigquery \
  --enabled \
  --description "BigQuery with per-user OAuth" \
  --managed.config '{
    "@type": "type.googleapis.com/redpanda.mcps.google_bigquery.v1.GoogleBigQueryMCPConfig",
    "projectId": "my-analytics-project",
    "maxBytesProcessed": "1073741824",
    "maxRows": 1000,
    "allowWrites": false,
    "userOauth": {
      "providerName": "google-bigquery",
      "requiredScopes": ["https://www.googleapis.com/auth/bigquery"]
    }
  }'

For a service account, replace the userOauth object with "serviceAccount": {"keySecretRef": "GOOGLE_BIGQUERY_SA_KEY"}.

Run a query

  1. With user OAuth, open the server in Agentic Data Plane. If a connection banner appears, click Connect, sign in to Google, and approve the consent screen.

    If you call a tool before connecting, the call fails with a connection-required result that carries an authorization URL. That URL starts the same Google consent flow.

  2. Open the server’s Inspector tab and run list_datasets with no arguments. It lists the datasets in the server’s project. See Test an MCP Server’s Tools with the Inspector.

  3. Run query with a GoogleSQL statement:

    {"sql": "SELECT customer_id, income, region FROM `my-analytics-project.crm.customers` LIMIT 2"}

The result looks like this:

{
  "columns": [
    {"name": "customer_id", "type": "INTEGER", "mode": "NULLABLE"},
    {"name": "income", "type": "FLOAT", "mode": "NULLABLE"},
    {"name": "region", "type": "STRING", "mode": "NULLABLE"}
  ],
  "records": [
    {"customer_id": 288, "income": 86764.66, "region": "EMEA"},
    {"customer_id": 301, "income": 41200, "region": "AMER"}
  ],
  "total_bytes_processed": "48",
  "next_page_token": "",
  "truncated": false,
  "running": false
}

The columns list reports the type names that BigQuery’s API uses, such as INTEGER for INT64, FLOAT for FLOAT64, and RECORD for STRUCT.

When next_page_token isn’t empty, call query again with the same sql, unchanged, and with page_token set to the token. Repeat until next_page_token comes back empty. The last page can have no rows. Each page call checks the SQL again, with its own dry run.

A call waits up to a minute for a query to finish, and less when the caller’s own time limit is shorter. If the query is still running, the result has running set to true, no records, and a next_page_token. Call query again with the same sql and that token to keep waiting. A call without the token starts a second query, which BigQuery also bills.

If truncated is true, the result holds more rows than one page returned, and no next_page_token is available to read the rest. Narrow the query, or raise max_rows up to the server’s Max Rows.

To run the same call from the CLI, use rpk ai mcp-server tools call. See Test from the CLI.

rpk ai mcp-server tools call google-bigquery query \
  --args '{"sql":"SELECT customer_id, income, region FROM `my-analytics-project.crm.customers` LIMIT 2"}'

Govern columns with data policies

Because every column has a name, a data policy on query can target one column. Add policies on the server’s Data Policies tab. See Shape Tool Data with Data Policies.

For example, this policy masks the income column and keeps only the EMEA rows in query results for one user:

rpk ai mcp-server update google-bigquery --data-policies '{
  "displayName": "emea-rows-masked-income",
  "tools": ["query"],
  "principals": ["User:analyst@example.com"],
  "shaping": {
    "response": {
      "fieldActions": {
        "rules": [
          {"selector": {"jsonPath": "$.records.income"}, "mask": {"redact": {}}}
        ]
      },
      "rowFilters": [
        {"path": "$.records", "expression": "@.region == \"EMEA\""}
      ]
    }
  }
}'
The --data-policies flag replaces the server’s whole list of data policies. Pass every policy the server should keep.

For analyst@example.com, query then returns only the rows whose region is EMEA, with income as [REDACTED]. To mask a field inside a STRUCT column, select it by path, for example $.records.account.region.

The agent writes the SQL, so it also chooses the column names that policies match on. Keep these points in mind:

  • Keep mask and drop rules strict. A strict rule denies the call when a returned record is missing its field, and an absence-safe rule lets the result through instead. With an absence-safe rule on $.records.income, a query such as SELECT income AS x returns the column unmasked under another name. A strict rule finds no income field in that result, so the call is denied.

  • Create strict rules in the YAML editor or the CLI. Rules you create in the form are absence-safe by default, so make the rule strict in the YAML editor, or create it with --data-policies as in the example. See Select fields.

  • Allowlist mode limits names, not values. With Drop fields without policy and a Keep rule on $.records.customer_id, a query such as SELECT income AS customer_id still returns income under the kept name. Allowlist mode also drops the result’s columns, next_page_token, total_bytes_processed, truncated, and running fields unless you keep them. Keep $.next_page_token and $.running if agents need to page through results or wait for slow queries.

  • Rely on BigQuery’s own controls to limit access. With user OAuth, every query runs as the calling user, so BigQuery IAM, column-level access control, row-level security, and data masking apply per user. Data policies shape what a permitted query returns.

  • Keep Allow Writes off unless you need it. A data policy on query doesn’t govern execute_sql. A statement such as CREATE TABLE …​ AS SELECT or EXPORT DATA can copy a masked column into a new table or a Cloud Storage bucket.

When a data policy applies to a caller and a tool, the agent never sees that tool’s own errors. Every tool error reaches the agent as [policy] response withheld: required data-access transforms could not be applied. That includes the errors meant to help the agent fix its SQL, such as a syntax error, the alias suggestion, and the byte limit. To see the full error, run the query as a user no policy targets.

Troubleshooting

Symptom What to check

the query would process <n> bytes, more than this MCP server’s limit of <limit>; …​

The dry run estimated more bytes than Max Bytes Processed. Filter on partition or clustering columns, select fewer columns, or query a smaller table. To allow larger queries, raise Max Bytes Processed.

BigQuery rejected the query: …​

The dry run found a problem with the SQL, such as a syntax error or an unknown table or column. The message comes from BigQuery. Fix the query and retry.

result column "<name>" cannot be addressed by data policies; alias it to a plain name …​

A result column has a name with spaces or other characters a data policy can’t select. Alias it in the query, as the message suggests.

BigQuery failed while running the statement (its error text is withheld because it can quote table data; …​)

The statement passed the dry run but failed while running, for example on a failed type conversion. Check the query against the table’s schema from get_table, then retry.

the query’s result columns differ from the ones its dry run predicted …​

Dynamic SQL, such as EXECUTE IMMEDIATE or a script, returned columns that the dry run didn’t predict. Data policies match on column names, so write the query as a single static SELECT.

BigQuery stopped the query after its five-minute limit; …​

The query ran for five minutes, and BigQuery stopped it. See Limitations. Narrow it before you run it again, for example by filtering on partition or clustering columns or by selecting fewer columns.

the query did not finish and BigQuery returned no job to wait on; narrow it and retry

The query was still running when the call stopped waiting, and BigQuery returned no job, so there’s no next_page_token to resume with. This is unusual, because a slow query normally returns running set to true and a token. Narrow the query and retry.

page_token is not a token this tool returned

The page_token isn’t a next_page_token that query returned, for example because it was cut short. Pass the token exactly as it was returned, or run the query again without page_token.

page_token was not returned for this sql; …​

A call with page_token must send the sql of the call that returned the token, unchanged. Send the same sql exactly as before.

the page_token’s query does not pass this server’s checks (byte limit, column names); …​

The server checks the query again on every page, and it now fails, for example because the table grew past Max Bytes Processed. Narrow the query and run it again without page_token.

google bigquery MCP error (status 403): …​

The identity lacks a role. Check the roles in Grant BigQuery roles for the user or the service account.

BigQuery rejected the query: followed by an access-denied message

The identity lacks a role the query needs, such as BigQuery Job User on the project or BigQuery Data Viewer on a table it reads. See Grant BigQuery roles.

rpk ai mcp-server get shows Tools: (discovery failed: internal: internal error), and every call fails with Internal Server Error

If the server uses a service account, check that the secret holds the whole service account JSON key file, stored with the Multiline value format. AI Gateway checks the key when the server starts and doesn’t report why it rejected the key.

service account token exchange failed (status <code>)

Google refused the key, for example because it was deleted or the service account was disabled. Create a new key and update the secret.

connection_required

The user hasn’t connected their Google account yet. Have the user open the server in Agentic Data Plane, click Connect, and approve Google’s consent screen. Later calls reuse the stored token. See user-delegated OAuth.

scope_upgrade_required

The user’s connection lacks a scope the server requires. If the scope is missing from the OAuth provider’s Scopes, add it. Then have the user click Connect again and approve the consent screen.

[policy] response withheld: required data-access transforms could not be applied

A data policy applies to the caller, so the tool’s error is withheld, or a strict rule matched nothing, for example after the query aliased a governed column. Run the query as a user no policy targets to see the error.

unable to resolve "type.googleapis.com/redpanda.mcps.google_bigquery.v1.GoogleBigQueryMCPConfig": "not found"

Your rpk ai version is older than 0.2.100 and doesn’t recognize this server’s configuration type. Run rpk ai upgrade, then retry.

Limitations

  • Google’s BigQuery MCP server returns at most 3,000 rows per call. It doesn’t support Google Drive external tables.

  • BigQuery stops any statement the server runs after five minutes.

  • The server checks Max Bytes Processed against the dry run’s estimate before a statement runs. It doesn’t enforce the limit while the statement runs.