GCP BigQuery MCP Server
GCP BigQuery MCP Server (FastMCP + Microsoft Entra ID SSO)
A production-ready, enterprise-grade Model Context Protocol (MCP) server built in Python using FastMCP, Streamable HTTP over /mcp, and Google Cloud BigQuery.
Architecture Overview
[ Developer / Claude Desktop / IDE / Inspector ]
│
│ 1. Client authenticates via Microsoft Entra ID (Azure AD SSO)
│ Sends Entra JWT: Authorization: Bearer <ACCESS_TOKEN>
▼
┌────────────────────────────────────────────────────────────────────────┐
│ GCP BigQuery MCP Server (FastMCP) │
│ - Streamable HTTP mounted at configured endpoint (default: /mcp) │
│ - Entra ID Auth Middleware: │
│ • Configurable toggle: security.enable_auth (true/false) │
│ • Fetches & caches Entra JWKS keys (login.microsoftonline.com) │
│ • Validates RS256 signature, audience, accepted issuers & expiration │
│ • Extracts user identity (upn / email / oid) │
│ - Dynamically exposes ONLY the tools toggled ON in config.yaml │
│ - Enforces Dual SQL Sanitizer (Regex keywords + AST syntax tree) │
│ - Connects to BigQuery directly via Service Account JSON key credentials│
│ - Binds queries to max_bytes_billed and safe max_results pagination │
│ - In-memory TTLCache with SHA-256 hashed keys for metadata queries │
└───────────────────────────────────┬────────────────────────────────────┘
│
│ 2. Direct Machine-to-Machine 2-Legged Auth
│ (bigquery.Client.from_service_account_json)
▼
┌────────────────────────────────────────────────────────────────────────┐
│ Google Cloud BigQuery │
│ - Enforces permissions via Service Account IAM & Dataset ACLs │
│ - Executes read queries strictly within max_bytes_billed limit │
└────────────────────────────────────────────────────────────────────────┘Key Features
Inbound SSO with Microsoft Entra ID (Azure AD):
Cryptographic verification against Microsoft's public JWKS endpoint.
Validates RS256 signature, audience (
<client_id>orapi://<client_id>), v1.0 and v2.0 token issuers, and expiration.Configurable Auth Toggle (
security.enable_auth): Easily toggle off auth (false) for local debugging and MCP Inspector testing, or enforce it in production (true).
Direct BigQuery Service Account Integration:
Zero interactive OAuth overhead; 2-legged server-to-server authentication directly initialized from the JSON service account key.
Dataset and column-level security enforced natively on Google Cloud.
Execution Guardrails & Cost Ceiling:
max_bytes_billedhard cost limit attached toQueryJobConfig(default: 10 GB).dry_run=Truesimulation mode calculating query cost and cache hits with zero billing.Container OOM protection by passing
max_resultsintoquery_job.result().
Dual SQL Sanitizer:
Enforces read-only query execution through both fast word-boundary regex filtering and deep BigQuery AST parsing (
sqlglot).Blocks
INSERT,UPDATE,DELETE,DROP,ALTER,TRUNCATE,CREATE,MERGE,GRANT,REVOKE,EXECUTE,CALL, multi-query chaining, and mutations nested inside CTEs/subqueries.
Dynamic Tool Enablement & Sanitized Error Shielding:
Each BigQuery tool (
bq_list_datasets,bq_list_tables,bq_table_metadata,bq_query_execution) can be selectively enabled or disabled viaconfig/config.yaml.All tool invocations are shielded with
try/exceptmapping Google API and internal errors to sanitizedfastmcp.exceptions.ToolErrorinstances, preventing internal GCP endpoints, tracebacks, credentials paths, and queries from leaking to clients.
Metadata Caching & Concurrency Hardening:
In-memory
TTLCachewith deterministic SHA-256 key hashing to minimize BigQuery API metadata calls.Thread-safe double-checked locking on
BigQueryClientManager.clienteliminates race conditions during lazy client initialization.
Sliding-Window Rate Limiting:
Built-in
RateLimitMiddlewareenforces sliding-window rate limits on the/mcptransport endpoint.Identifies callers by Microsoft Entra ID principal identity (
sub/oid/upn) or client IP, returning standard HTTP 429 andRetry-Afterheaders while exempting health checks.
File Layout
bq_mcp/
├── config/
│ ├── config.yaml # Centralized configuration (Entra ID, SA path, tools, rate limit)
│ └── settings.py # Pydantic Settings loader for config.yaml + .env
├── src/
│ ├── __init__.py
│ ├── entra_auth.py # Microsoft Entra ID JWT validation (JWKS + claims)
│ ├── cache.py # TTLCache manager with SHA-256 key hashing
│ ├── sanitizer.py # Configurable SQL Sanitizer (Regex + AST)
│ ├── client.py # Thread-safe BigQuery client manager & execution guardrails
│ ├── tools.py # Dynamic FastMCP tool definitions with error shielding
│ ├── rate_limiter.py # Sliding-window rate limiter & Starlette middleware
│ └── server.py # Entra ID middleware & Streamable HTTP ASGI app
├── tests/
│ ├── __init__.py
│ └── test_tools.py # Unit & integration tests (auth, concurrency, tools, rate limit)
├── service-account.json # BigQuery Service Account JSON key
├── .env.example # Optional environment overrides
├── Dockerfile # Hardened Python 3.12 non-root container
├── requirements.txt # Dependency manifest
├── run.py # Root CLI launcher with auto-reload and options
└── README.md # Entra ID setup, Inspector testing, and client configsConfiguration Specification
All operational parameters are defined in config/config.yaml and can be overridden via environment variables or .env:
# Server & HTTP Transport Settings
server:
host: "0.0.0.0"
port: 8000
endpoint_path: "/mcp"
log_level: "INFO"
# Inbound SSO Authentication: Microsoft Entra ID (Azure AD)
security:
enable_auth: true # If false, bypasses JWT validation (dev mode)
tenant_id: "your-azure-tenant-id" # Entra Directory (tenant) ID
client_id: "your-app-client-id" # Entra Application (client) ID / Audience (aud)
jwks_cache_ttl_seconds: 86400 # 24 hours cache for Microsoft public keys
accepted_issuers:
- "https://login.microsoftonline.com/{tenant_id}/v2.0"
- "https://sts.windows.net/{tenant_id}/"
# Google Cloud Service Account Authentication (Direct Backend Auth)
auth:
service_account_key_path: "service-account.json"
# BigQuery Execution Guardrails
bigquery:
project_id: null # null auto-detects from service-account.json
location: "us-east4"
max_rows_returned: 200 # Maximum rows serialized to prevent OOM
max_bytes_billed: 10737418240 # 10 GB hard cost-ceiling per query
query_timeout_seconds: 60
# SQL Sanitizer (Enforce Read-Only Queries)
sanitizer:
enabled: true # Enforce read-only checks
mode: "both" # Options: "regex", "ast", "both"
blocked_keywords:
- "INSERT"
- "UPDATE"
- "DELETE"
- "DROP"
- "ALTER"
- "TRUNCATE"
- "CREATE"
- "MERGE"
- "GRANT"
- "REVOKE"
- "EXECUTE"
- "CALL"
# Dynamic Tool Enablement Matrix
tools:
enable_bq_list_datasets: true
enable_bq_list_tables: true
enable_bq_table_metadata: true
enable_bq_query_execution: true
# In-Memory Metadata Caching (TTLCache)
cache:
enabled: true
metadata_ttl_seconds: 900 # 15 minutes
max_cache_entries: 1024
# MCP Endpoint Rate Limiting Guardrails
rate_limit:
enabled: true # Enable client rate limiting on MCP endpoint
requests_per_minute: 60 # Max requests per sliding window
window_seconds: 60 # Sliding window duration in secondsEnvironment Variable Overrides (.env)
Variable | Target Config | Description |
|
| Microsoft Entra Directory (tenant) ID |
|
| Entra Application (client) ID |
|
| Set to |
|
| Filepath to GCP credentials JSON |
|
| Target Google Cloud project ID |
|
| Geographic dataset location (e.g. |
|
| Host binding IP (default: |
|
| HTTP listening port (default: |
|
| MCP transport route (default: |
|
| Set to |
|
| Max requests per sliding window per client (default: |
|
| Rate limit sliding window duration in seconds (default: |
Setup & Prerequisites
1. Microsoft Entra ID (Azure AD) Setup
Log in to the Azure Portal and navigate to Microsoft Entra ID.
Go to App registrations > New registration.
Name:
GCP BigQuery MCP ServerSupported account types: Accounts in this organizational directory only
Note down the Application (client) ID and Directory (tenant) ID.
Go to Expose an API:
Set the Application ID URI to
api://<client_id>.Add a scope (e.g.,
BigQuery.Read).
In
config/config.yamlor.env, set:security: tenant_id: "<your-tenant-id>" client_id: "<your-client-id>"
2. Google Cloud BigQuery Setup
Open Google Cloud Console.
Navigate to IAM & Admin > Service Accounts > Create Service Account.
Grant the service account the required IAM roles:
BigQuery Data Viewer (
roles/bigquery.dataViewer): Read access to dataset tables and schemas.BigQuery Job User (
roles/bigquery.jobUser): Permission to submit query jobs.
Create and download a new JSON key.
Save the file as
service-account.jsonin the root of the project (or specify the path inconfig/config.yaml).
Installation & Local Execution
1. Create Virtual Environment and Install Dependencies
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt2. Run Test Suite
Run the full pytest suite covering Entra ID token validation, SQL AST sanitization, caching, and BigQuery execution:
pytest tests/ -v3. Start the Server
You can launch the server using the run.py launcher:
# Default launch (reads config/config.yaml and .env)
python run.py
# Optional CLI flags
python run.py --port 8000 --host 0.0.0.0 --reload --log-level debugOr via direct module invocation:
python -m src.serverThe server starts on http://0.0.0.0:8000 with the Streamable HTTP transport mounted at http://0.0.0.0:8000/mcp.
Docker Deployment (Hardened Non-Root Container)
The included Dockerfile builds a minimal, secure container running as a non-root system user (appuser:10001):
# Build Docker image
docker build -t bq-mcp-server:latest .
# Run container with volume mount for service account credentials
docker run -d \
--name bq-mcp-server \
-p 8000:8000 \
-v $(pwd)/service-account.json:/app/service-account.json:ro \
-e AZURE_TENANT_ID="your-tenant-id" \
-e AZURE_CLIENT_ID="your-client-id" \
-e SECURITY_ENABLE_AUTH="true" \
bq-mcp-server:latestCheck health status:
curl http://localhost:8000/healthClient Integration & Verification Guide
1. Test via MCP Inspector
For rapid local testing and schema inspection, you can test with or without auth.
A. Dev Mode (Auth Disabled)
In config/config.yaml, set security.enable_auth: false (or launch with SECURITY_ENABLE_AUTH=false), then run:
# Terminal 1: Run the MCP Server
python -m src.server
# Terminal 2: Launch MCP Inspector
npx @modelcontextprotocol/inspectorTransport:
Streamable HTTPURL:
http://localhost:8000/mcpClick Connect.
B. Production Mode (With Entra ID Bearer Token)
Transport:
Streamable HTTPURL:
http://localhost:8000/mcpCustom Headers:
{ "Authorization": "Bearer <YOUR_ENTRA_ACCESS_TOKEN>" }
2. Claude Desktop Integration
Add the server to your claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):
A. Local Development (Auth Disabled - Recommended for Quick Testing)
Set security.enable_auth: false in config/config.yaml (or SECURITY_ENABLE_AUTH=false in .env). The --header argument can be completely omitted:
{
"mcpServers": {
"bigquery": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://127.0.0.1:8000/mcp"
]
}
}
}B. Production / Authenticated Mode (With Microsoft Entra ID Token)
When security.enable_auth: true, obtain a token via the Azure CLI:
az login --tenant "<YOUR_AZURE_TENANT_ID>"
az account get-access-token --resource "<YOUR_AZURE_APP_CLIENT_ID>" --query accessToken -o tsvThen supply the Bearer token in the --header argument:
{
"mcpServers": {
"bigquery": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://127.0.0.1:8000/mcp",
"--header",
"Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
]
}
}
}3. IDE Integration (Cursor / VS Code / Windsurf)
In your workspace .cursor/mcp.json or IDE MCP settings:
A. Local Development (Auth Disabled)
{
"mcpServers": {
"bigquery": {
"url": "http://127.0.0.1:8000/mcp"
}
}
}B. Production / Authenticated Mode
{
"mcpServers": {
"bigquery": {
"url": "http://127.0.0.1:8000/mcp",
"headers": {
"Authorization": "Bearer eyJhbGciOiJSUzI1NiIs..."
}
}
}
}Exposed MCP Tools
Tool | Parameters | Description |
|
| Lists BigQuery datasets with identifiers and labels. Cached in TTLCache. |
|
| Lists tables, views, and materialized views. Cached in TTLCache. |
|
| Returns column schema, row counts, storage size, partition details, and clustering keys. Cached in TTLCache. |
|
| Executes read-only SQL queries with AST validation, |
Security Guardrails
AST Mutation Blocking: Uses
sqlglotto parse the BigQuery abstract syntax tree. Disallows query chaining (;), stored procedure executions (CALL), table drops (DROP), and data mutations (INSERT,UPDATE,DELETE,MERGE), including those obscured within CTEs or subqueries.Cost Ceilings (
maximum_bytes_billed): Protects against unexpected high-cost queries by enforcing a hard upper bound on bytes scanned.Container Memory Safeguards: Automatically sets
max_resultson BigQuery result iteration to prevent container memory exhaustion and out-of-memory crashes.Principle of Least Privilege: Inbound client auth verifies Entra ID identity, while BigQuery machine-to-machine auth is locked down via Google Cloud IAM.