BigQuery Readonly MCP
Provides read-only access to Google BigQuery, allowing listing of projects, datasets, tables, schema retrieval, dry-run estimates, and safe SELECT query execution with configurable limits and error handling.
Click on "Install 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., "@BigQuery Readonly MCPRun a query to get the top 10 products by sales from dataset retail.orders"
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.
BigQuery Readonly MCP
BigQuery Readonly MCP is a FastAPI-based Custom MCP server for safely querying BigQuery from ChatGPT. It is designed as a reusable internal analytics MCP: each tool accepts project_id. The initial validation project was ice-sh; the current pilot default project is ice-mp.
Scope
GitHub repository:
Growth-Management/bigquery-readonly-mcpCloud Run deploy project:
ice-shInitial BigQuery validation project:
ice-shCurrent pilot default BigQuery project:
ice-mpInitial pilot user:
sinohara@impress.co.jpAllowed email domain:
impress.co.jpAllowed project IDs: empty for the current pilot, so the approved user may specify any project readable through their own BigQuery IAM
Allowed dataset IDs: empty by default, so BigQuery IAM controls dataset access
Default
maximumBytesBilled: 1GBDefault
max_results: 1000Default query timeout: 60 seconds
Current session backend: Firestore persistence enabled, refresh-token backed access-token renewal enabled, with
SESSION_TTL_SECONDS=86400
Related MCP server: BigQuery MCP Server
Security Model
The MCP server uses Google OAuth and BigQuery calls run with the authenticated user's access token. It does not use a service account to impersonate users for BigQuery query execution.
Users should receive the minimum required IAM permissions:
roles/bigquery.jobUseron the project where jobs are createdroles/bigquery.dataVieweron only the datasets they may inspect
The server enforces readonly SQL before execution. run_readonly_query and dry_run_query only allow one SELECT or WITH statement and reject DML, DDL, EXPORT, LOAD, grants, revokes, and procedure calls.
The server can also enforce application-side allowlists:
ALLOWED_PROJECT_IDS: when set, tool calls for projects outside the list are rejected before any BigQuery API call is made. When empty, project access is governed by the logged-in user's BigQuery IAM.ALLOWED_DATASET_IDS: when set,list_datasetsis filtered andlist_tables/get_table_schemacalls outside the list are rejected before any BigQuery API call is made.ALLOWED_USER_EMAILS: when set, tool calls from users outside the list are rejected before any BigQuery API call is made.
Allowlist rejections are recorded in audit logs with success=false and a rejection_reason.
MCP Tools
list_projectslist_datasetslist_tablesget_table_schemadry_run_queryrun_readonly_query
All tools accept project_id where applicable. If omitted, the current pilot default is ice-mp.
Endpoints
GET /healthzGET /healthPOST /mcpGET /oauth/authorizeGET /oauth/callbackPOST /oauth/tokenGET /.well-known/oauth-authorization-server
Use /health for Cloud Run external health checks. Cloud Run can reserve /healthz before requests reach the container, causing Google Frontend 404 responses even when the FastAPI app is healthy.
Local Development
Create a local environment and install dependencies:
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txtSet local environment variables. Do not commit real secrets. Local development may use memory; the pilot deployment uses Firestore.
export BASE_URL="http://localhost:8080"
export GOOGLE_OAUTH_CLIENT_ID="..."
export GOOGLE_OAUTH_CLIENT_SECRET="..."
export SESSION_SECRET="replace-with-random-value"
export ALLOWED_DOMAIN="impress.co.jp"
export DEFAULT_PROJECT_ID="ice-mp"
export ALLOWED_PROJECT_IDS=""
export ALLOWED_DATASET_IDS=""
export ALLOWED_USER_EMAILS="sinohara@impress.co.jp"
export MAXIMUM_BYTES_BILLED="1073741824"
export MAX_RESULTS="1000"
export QUERY_TIMEOUT_SECONDS="60"
export SESSION_TTL_SECONDS="86400"
export OAUTH_REFRESH_WINDOW_SECONDS="300"
export SESSION_STORE_BACKEND="memory"
export FIRESTORE_SESSION_COLLECTION="bigquery_mcp_sessions"ALLOWED_DATASET_IDS is a comma-separated list of project_id:dataset_id values, for example:
export ALLOWED_DATASET_IDS="ice-mp:example_datamart,ice-mp:example_source"Run the app:
uvicorn app.main:app --host 0.0.0.0 --port 8080 --reloadRun tests:
pytestOAuth Setup
Configure a Google OAuth Web application in ice-sh:
Consent screen: internal or equivalent organization-limited configuration
Authorized domain:
impress.co.jpRedirect URI:
https://<cloud-run-url>/oauth/callbackScopes:
openidemailprofilehttps://www.googleapis.com/auth/bigquery.readonly
Store these values in Secret Manager rather than the repository:
google-oauth-client-idgoogle-oauth-client-secretbigquery-mcp-session-secret
For MCP clients such as ChatGPT custom connectors, this service acts as the OAuth server and bridges to Google OAuth internally:
Authorization endpoint:
https://<cloud-run-url>/oauth/authorizeToken endpoint:
https://<cloud-run-url>/oauth/tokenMetadata endpoint:
https://<cloud-run-url>/.well-known/oauth-authorization-serverMCP endpoint:
https://<cloud-run-url>/mcp
The OAuth callback /oauth/callback is the Google OAuth redirect URI. MCP clients should be configured with their own callback URL in the client UI when prompted; the service preserves that callback through the OAuth state and returns an authorization code to the MCP client.
The service requests Google OAuth with access_type=offline, include_granted_scopes=true, and consent/account selection prompts so a refresh token can be captured for Firestore-backed sessions. If Google does not return a refresh token for an existing grant, revoke the app grant from the user's Google account or reconnect with an explicit consent flow, then retry the MCP connection.
Session Storage
The current pilot deployment uses Firestore-backed session storage with refresh-token backed access-token renewal:
SESSION_STORE_BACKEND=firestore
FIRESTORE_SESSION_COLLECTION=bigquery_mcp_sessions
SESSION_TTL_SECONDS=86400
OAUTH_REFRESH_WINDOW_SECONDS=300Firestore persistence keeps a post-login MCP session available across Cloud Run instance restarts, scale-out, and new revisions. This was initially validated on 2026-06-08: a session document was created in Firestore, the access token was stored only as encrypted ciphertext, and the same mcp_session cookie continued to work after new Cloud Run revisions and GitHub Actions deploys.
As of 2026-06-15, newly created Firestore sessions can also store a Google refresh token as encrypted ciphertext. When a stored access token is within OAUTH_REFRESH_WINDOW_SECONDS of expiry, the server exchanges the refresh token for a new access token and updates the Firestore document. This makes the MCP session TTL independent from the roughly 1-hour Google access-token lifetime, while keeping BigQuery calls bound to the logged-in user's IAM.
Security behavior:
Access tokens and refresh tokens are encrypted before being stored in Firestore.
Encryption uses AES-GCM with a key derived from
SESSION_SECRET.Refresh-token ciphertext is bound to both the session id and token purpose.
SESSION_SECRETrotation intentionally invalidates existing persisted sessions.Invalid, expired, or undecryptable session documents are deleted and treated as logged out.
If Google rejects a refresh-token exchange, the session is treated as logged out and the user must reconnect.
Firestore persistence does not grant BigQuery access. BigQuery calls still use the logged-in user's OAuth token and IAM permissions.
Operational notes:
Existing sessions created before refresh-token support may still expire at the previous access-token boundary. Reconnect once after deployment so the new session stores a refresh token.
The pilot
SESSION_TTL_SECONDSis 24 hours. Increase only after confirming risk acceptance for refresh-token storage and session revocation behavior.Short-lived OAuth authorization requests and authorization codes remain in memory, so a login flow that overlaps a revision restart may still need to be retried. This does not affect already-created Firestore MCP sessions.
Cloud Run Deployment
The GitHub Actions workflow expects these GitHub Secrets:
GCP_PROJECT_ID:ice-shGCP_WORKLOAD_IDENTITY_PROVIDERGCP_DEPLOY_SERVICE_ACCOUNTBASE_URL: deployed Cloud Run URL or custom domain
The deploy service account needs only deployment permissions, such as Artifact Registry write and Cloud Run deploy permissions. It is not used for BigQuery query execution.
Current pilot deployment settings:
DEFAULT_PROJECT_ID=ice-mpALLOWED_PROJECT_IDS=ALLOWED_DATASET_IDS=ALLOWED_USER_EMAILS=sinohara@impress.co.jpMAXIMUM_BYTES_BILLED=1073741824MAX_RESULTS=1000QUERY_TIMEOUT_SECONDS=60SESSION_STORE_BACKEND=firestoreFIRESTORE_SESSION_COLLECTION=bigquery_mcp_sessionsSESSION_TTL_SECONDS=86400OAUTH_REFRESH_WINDOW_SECONDS=300
Deployment is managed per GCP project. The Cloud Run deployment project is ice-sh; the current default BigQuery project is ice-mp, but project access is governed by the logged-in user's BigQuery IAM because ALLOWED_PROJECT_IDS is empty.
See docs/cloud-run.md for the full Phase 5 deployment procedure, including required APIs, Artifact Registry, Secret Manager, manual deploy, /health, Cloud Run session storage, and Cloud Logging checks.
See docs/github-actions-deploy.md for the preferred GitHub Actions deployment path using Workload Identity Federation.
See docs/phase-7-ice-sh-validation.md for the Phase 7 validation record.
See docs/rollout-policy.md for the Phase 8 rollout policy covering rollout patterns, allowlists, per-project deployment ownership, validation, audit retention, session persistence, and follow-up hardening.
See docs/all-project-iam-user-pilot.md for the approved pilot change that leaves project access to the named user's BigQuery IAM while keeping ALLOWED_USER_EMAILS=sinohara@impress.co.jp.
See docs/session-refresh-validation.md for the refresh-token session validation plan and Cloud Shell checks.
Initial Validation On ice-sh
Use a user account in the allowed domain with the required BigQuery IAM permissions, then verify:
MCP connects to
/mcpGoogle OAuth login succeeds
dataset list can be retrieved from
ice-shtable list can be retrieved
schema can be retrieved
dry run returns estimated bytes processed
SELECTexecutes within limitsDML and DDL are rejected before BigQuery execution
unauthorized projects return Access Denied
audit logs are emitted to Cloud Logging
Current status as of 2026-06-04: Phase 7 validation is complete. ice-sh readonly checks, SQL guard checks, Cloud Logging audit checks, and unauthorized-project rejection have been verified. The unauthorized-project check used bq-mcp-access-denied-test-2 with a project-level IAM Deny policy for sinohara@impress.co.jp; MCP dry_run_query returned HTTP 403 Forbidden.
Current Pilot
Current status as of 2026-06-15: pilot operation is enabled for sinohara@impress.co.jp only. ALLOWED_PROJECT_IDS is empty after security-owner approval, so the named user may use any BigQuery project readable through their own Google account IAM. DEFAULT_PROJECT_ID=ice-mp keeps ordinary use oriented to the pilot project. ALLOWED_DATASET_IDS is empty, so dataset access is governed by the logged-in user's BigQuery IAM. Firestore-backed MCP session persistence is enabled with refresh-token renewal and SESSION_TTL_SECONDS=86400.
Before expanding beyond the initial pilot user, repeat the rollout checklist in docs/rollout-policy.md and decide whether ALLOWED_PROJECT_IDS, ALLOWED_DATASET_IDS, or additional named-user restrictions are needed.
Audit Logging
Every tool call writes a single-line JSON audit event to stdout. Cloud Run ingests stdout into Cloud Logging, where the message, event_type, severity, and tool-specific fields can be filtered.
Cloud Logging is the required audit source. BigQuery audit dataset export has been evaluated as an optional Phase 8 P2 hardening path, recommended through a Cloud Logging Log Router sink when retention, reporting, or dashboard requirements justify it. Query history UI is deferred until exported audit data and administrator review requirements are confirmed.
Each audit event includes:
severitymessagetimestampevent_typeuser_emailtoolproject_iddatasettablebytes_processedsuccesserrorrejection_reasonfor failed or rejected calls
rejection_reason uses these categories:
project_not_allowed:project_idis outsideALLOWED_PROJECT_IDS.dataset_not_allowed:project_id:dataset_idis outsideALLOWED_DATASET_IDSfor dataset-targeting metadata tools.user_not_allowed: user email is outsideALLOWED_USER_EMAILS.sql_not_allowed: SQL guard rejected a non-readonly or unsafe query.bigquery_iam_denied: BigQuery returned a permission-denied response, including HTTP 403.bigquery_api_error: BigQuery or HTTP API failed for a non-403 API reason.execution_error: internal execution failed outside the known categories.
Some fields may be empty when the tool does not target a dataset or table, or when validation fails before a BigQuery job is created.
Example Cloud Logging filter:
jsonPayload.event_type="bigquery_mcp_tool_call"
jsonPayload.project_id="ice-mp"To inspect rejected calls by category:
jsonPayload.event_type="bigquery_mcp_tool_call"
jsonPayload.success=false
jsonPayload.rejection_reason="project_not_allowed"Current Phase Coverage
Phase 1: FastAPI app,
/healthz,/health,/mcp, OAuth authorize/callback skeleton, user email lookupPhase 2: six initial BigQuery tools
Phase 3: readonly SQL guard,
maximumBytesBilled,max_results, timeout, basic query error handlingPhase 4: structured JSON audit logs are emitted to stdout for Cloud Logging ingestion
Phase 5: Docker, env example, Secret Manager policy, Cloud Run deployment procedure,
/healthverification, and Firestore-backed session storage are complete forice-shPhase 6: GitHub Actions workflow, Workload Identity Federation, IAM, GitHub Secrets, deploy verification, and Firestore session env pinning are complete
Phase 7:
ice-shOAuth, MCP, BigQuery tools, readonly guard, unauthorized-project rejection, and audit log validation are completePhase 8:
ALLOWED_PROJECT_IDS,ALLOWED_DATASET_IDS,ALLOWED_USER_EMAILS, structured audit rejection categories, allow/reject tests, current pilot defaults, Firestore-backed session persistence, and refresh-token backed session renewal are implemented; project access is currently user-IAM-scoped forsinohara@impress.co.jp; BigQuery audit dataset export and query history UI are evaluated; rollout patterns, allowlist policy, per-project rollout record template, per-project validation, audit requirements, session policy, and follow-up backlog are documented indocs/rollout-policy.md
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Growth-Management/bigquery-readonly-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server