BigQuery-Read-Only-MCP-Server

Um servidor MCP (Model Context Protocol) seguro e auto-hospedado para Google BigQuery. Listas de permissão de tabelas rigorosas, limites de varredura por consulta, limitação de taxa integrada e custos previsíveis no Cloud Run. Funciona com Claude, ChatGPT, Cursor, Gemini e qualquer agente de IA compatível com MCP.

Documentação

Servidor MCP Somente Leitura para BigQuery

Um servidor Model Context Protocol (MCP) para Google BigQuery seguro e auto-hospedado. Listas de permissão rígidas de tabelas, tetos de varredura por consulta, limitação de taxa integrada e custos previsíveis no Cloud Run. Funciona com Claude, ChatGPT, Cursor, Gemini e qualquer agente de IA compatível com MCP.

License: MIT Python 3.11+ Cloud Run MCP Claude Desktop


📚 Artigos relacionados

Publicações longas sobre este projeto em hugonissar.github.io:

Ou leia a visão geral do projeto.

📖 O que é isto

Um servidor MCP para BigQuery pronto para produção que você implanta no seu próprio projeto GCP. Agentes de IA se conectam via HTTPS e podem fazer exatamente duas coisas: ler os esquemas das tabelas que você explicitamente permite na lista, e executar consultas SELECT contra elas — sujeitas a um orçamento de varredura configurável, um limite de linhas de resultado e uma limitação de taxa por token-bucket. Nada mais. Sem DROP, sem INSERT, sem descoberta de esquema além do que você permite, sem cobranças surpresa.

Ele existe porque as alternativas — incluindo o servidor MCP oficial do Google para BigQuery — expõem todas as tabelas que a conta de serviço subjacente consegue acessar. Esse é o padrão correto para analistas internos confiáveis. É o padrão errado para um agente autônomo que pode ser invocado por um chatbot voltado ao cliente, uma ferramenta de terceiros ou um documento com injeção de prompt.

💬 Caso de uso

Você: Me dê a campanha com maior conversão nos últimos 30 dias
LLM: consulta seus dados e retorna o resultado

Conecte uma vez, consulte tudo. Aponte para sua exportação do Google Analytics 4, ou importe Google Ads, Meta e TikTok para o BigQuery para analisar todas as campanhas em todos os canais a partir de um único agente.

🏆 Por que escolher isto em vez do servidor MCP oficial do Google para BigQuery

  1. Lista de permissão rígida de tabelas aplicada no código, não apenas no IAM. Você lista pares (dataset, table) em variáveis de ambiente. Qualquer coisa fora da lista de permissão é rejeitada no parser SQL antes que um job seja sequer enviado — incluindo referências qualificadas como wrong_dataset.allowed_table. O servidor do Google permite que o agente veja todas as tabelas que o IAM da conta de serviço permite.

  2. Teto rígido de varredura por consulta (MAX_SCAN_MB). Toda consulta passa primeiro por um dry-run. Se a varredura estimada exceder o teto, a consulta é rejeitada — nenhum job do BigQuery é criado, nenhum byte é cobrado. Isso elimina de vez o conhecido problema "a IA acabou de rodar uma consulta de US$ 2.000". O servidor do Google não tem teto de varredura integrado — você precisa aplicar isso via papéis IAM personalizados ou cotas no nível do BigQuery.

  3. Limitação de taxa integrada com token-bucket + burst. A documentação do Google é explícita: "O servidor MCP do BigQuery não tem cotas próprias. Não há limite no número de chamadas que podem ser feitas ao servidor MCP." Este servidor vem com RATE_LIMIT_QPM e RATE_LIMIT_BURST configuráveis de fábrica, além de semáforos de concorrência separados para consultas vs. chamadas de metadados.

  4. Somente leitura aplicado pelo parser SQL, não apenas pelo IAM. DDL, DML, scripting, corpos com múltiplas instruções e construções procedurais são rejeitados na camada de aplicação, além do que o IAM já oferece. Defesa em profundidade — uma concessão de IAM mal configurada não pode habilitar escritas acidentalmente.

  5. Limite de linhas de resultado (MAX_RESULT_ROWS). Uma consulta que retorna um milhão de linhas é truncada no servidor antes de tocar a janela de contexto de um LLM. Economiza tokens, economiza dinheiro e evita exfiltração acidental de PII por varreduras amplas.

  6. Cache de dry-run. Dry-runs repetidos do mesmo SQL passam por um cache LRU + TTL, então um agente que tenta novamente ou itera não gera chamadas redundantes à API do BigQuery. Consultas de esquema também são armazenadas em cache com TTL.

  7. Custo previsível, quase zero em inatividade. O Cloud Run reduz a escala para zero. Você paga praticamente nada em inatividade e alguns dólares por mês sob carga moderada. Compare com um endpoint gerenciado onde você fica preso ao modelo de preços que o fornecedor adotar após o GA.

  8. Multi-tenant via variáveis de ambiente, sem arquivo de configuração. Listas separadas por vírgula de BQ_DATASET_ID e BQ_ALLOWED_TABLE são pareadas posicionalmente — analytics.events, reporting.daily, etc. Adicione ou remova tabelas com um único gcloud run deploy --update-env-vars. Sem arquivo de configuração para gravar na imagem, sem reimplantações de um serviço de configuração separado.

  9. Superfície de ferramentas mínima. Apenas duas ferramentas expostas: get_table_schema e query_assessments. Superfície de ataque menor, mais fácil de auditar, menos espaço para o agente usar indevidamente. O servidor do Google expõe um execute_sql genérico além de ferramentas de descoberta de metadados que percorrem todo o grafo do projeto.

  10. Um único arquivo, licença MIT, ~1400 linhas. Leia o código-fonte. Faça um fork. Adicione um validador personalizado. Troque o esquema de autenticação. Você não consegue fazer nada disso com um servidor gerenciado de código fechado.

⚖️ Tabela comparativa

Este servidorServidor MCP oficial do Google para BigQuery
Modelo de implantaçãoAuto-hospedado no Cloud RunEndpoint remoto gerenciado
AutenticaçãoChave de API + chave de administradorGoogle IAM / OAuth
Controle de acesso a tabelasLista de permissão rígida de pares (dataset, table), aplicada no códigoSomente IAM — qualquer tabela acessível
Teto de varredura por consultaSim (MAX_SCAN_MB, aplicado via dry-run)Não (depende de cotas IAM / BQ)
Limitação de taxaToken bucket integrado + burstNenhuma (segundo a documentação do Google)
Limite de linhas de resultadoSim (MAX_RESULT_ROWS)Não
Cache de dry-run / esquemaSim (LRU + TTL)Não
Aplicação de somente leituraParser SQL + IAMSomente IAM
Ferramentas expostas2 (esquema, consulta)5+ incluindo execute_sql, previsão, listagem de datasets/tabelas
Previsão / auxiliares de MLNão (escreva seu próprio SQL)Sim (forecast integrado)
Varredura de injeção de promptNãoSim (via Model Armor, complemento pago)
Registro de auditoriaCloud Logging + logs de auditoria do BQCloud Audit Logs
Código-fonteAberto (MIT)Fechado
Custo em inatividade~US$ 0 (reduz escala para zero)N/A (gerenciado)
Fork e modificaçãoSimNão
Região fixaEscolha qualquer região do Cloud RunSomente regiões do Google

Quando escolher o servidor do Google: se você quiser previsão e ARIMA prontos para uso, se precisar do Model Armor para varredura de injeção de prompt, ou se estiver confortável em deixar o agente ver tudo o que o IAM da conta de serviço alcança e não precisar de um teto rígido de varredura.

Quando escolher este servidor: qualquer outro caso — especialmente agentes de produção, implantações voltadas ao cliente, ambientes regulados, cenários multi-tenant e qualquer lugar onde as palavras "orçamento de varredura" ou "limite de taxa" importem.

🏗️ Arquitetura em resumo

┌─────────────┐       HTTPS + X-API-KEY       ┌──────────────────────┐         IAM        ┌───────────┐
│ MCP client  │ ────────────────────────────▶ │  Cloud Run service   │ ─────────────────▶ │ BigQuery  │
│ (Claude /   │            API KEY            │  bigquery-readonly-  │   service account  │  datasets │
│  Cursor /   │                               │       mcp-server     │                    │  + tables │
│  ChatGPT)   │                               │                      │                    │           │
└─────────────┘                               │  • SQL allowlist     │                    └───────────┘
                                              │  • Dry-run scan cap  │
                                              │  • Rate limiter      │                    ┌───────────┐
                                              │  • Schema cache      │ ◀───── secrets ──  │  Secret   │
                                              └──────────────────────┘                    │  Manager  │
                                                                                          └───────────┘

☁️ Serviços GCP necessários

Ative estas APIs no seu projeto:

ServiçoFinalidadeNecessário
Cloud Run (run.googleapis.com)Hospeda o servidor MCPSim
BigQuery (bigquery.googleapis.com)O data warehouse que você está consultandoSim
Secret Manager (secretmanager.googleapis.com)Armazena MCP_API_KEY e MCP_ADMIN_KEYSim
Artifact Registry (artifactregistry.googleapis.com)Hospeda a imagem do contêinerSim
IAM (iam.googleapis.com)Conta de serviço + vínculos de papéisSim
Cloud Build (cloudbuild.googleapis.com)Se você for compilar a imagem no GCPOpcional
Cloud LoggingCaptura logs estruturados do serviçoAutomático
Cloud MonitoringCaptura métricas + alertasAutomático

Ativação rápida:

gcloud services enable \
  run.googleapis.com \
  bigquery.googleapis.com \
  secretmanager.googleapis.com \
  artifactregistry.googleapis.com \
  iam.googleapis.com \
  cloudbuild.googleapis.com

🔐 Permissões da conta de serviço (menor privilégio)

Crie uma conta de serviço dedicada — não reutilize uma existente. O serviço precisa do mínimo absoluto: acesso de leitura a tabelas específicas do BigQuery, capacidade de executar jobs de consulta e acesso de leitura a dois segredos.

PROJECT_ID="your-project"
SA_NAME="bigquery-readonly-mcp"
SA_EMAIL="${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud iam service-accounts create $SA_NAME \
  --display-name="BigQuery Read-Only MCP Server"

🔑 Papéis IAM necessários

PapelEscopoPor quê
roles/bigquery.jobUserProjetoPermite que a conta de serviço envie jobs de consulta (não concede acesso a dados)
roles/bigquery.dataViewerDataset (por dataset na lista de permissão)Ler esquemas + linhas de tabelas
roles/bigquery.metadataViewerDataset (por dataset na lista de permissão)Ler esquemas sem acesso de leitura — opcional, apenas se você tiver datasets onde deseja visibilidade de esquema sem acesso a linhas
roles/secretmanager.secretAccessorSegredo (por segredo)Ler chave de API + chave de administrador

Conceder bigquery.jobUser no projeto

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:${SA_EMAIL}" \
  --role="roles/bigquery.jobUser"

Conceder bigquery.dataViewer no dataset específico (preferível em vez de no projeto inteiro)

# For each dataset in BQ_DATASET_ID:
DATASET="your_dataset"
bq add-iam-policy-binding \
  --member="serviceAccount:${SA_EMAIL}" \
  --role="roles/bigquery.dataViewer" \
  "${PROJECT_ID}:${DATASET}"

Para controle mais rígido, conceda acesso no nível da tabela em vez do dataset:

DATASET="your_dataset"
TABLE="your_table"
bq add-iam-policy-binding \
  --member="serviceAccount:${SA_EMAIL}" \
  --role="roles/bigquery.dataViewer" \
  "${PROJECT_ID}:${DATASET}.${TABLE}"

Conceder secretAccessor nos dois segredos

gcloud secrets add-iam-policy-binding mcp-api-key \
  --member="serviceAccount:${SA_EMAIL}" \
  --role="roles/secretmanager.secretAccessor"

gcloud secrets add-iam-policy-binding mcp-admin-key \
  --member="serviceAccount:${SA_EMAIL}" \
  --role="roles/secretmanager.secretAccessor"

Essa é toda a superfície de IAM. Não conceda bigquery.user, bigquery.admin, editor ou owner — nenhum deles é necessário e todos concedem estritamente mais do que o necessário.

🚀 Início rápido

1. Gerar e armazenar chaves de API

# MCP API key (clients use this in X-API-KEY)
openssl rand -hex 32 | gcloud secrets create mcp-api-key --data-file=-

# Admin key (for the /admin endpoint — schema cache invalidation)
openssl rand -hex 32 | gcloud secrets create mcp-admin-key --data-file=-

2. Compilar e enviar a imagem

REGION="europe-north2"
PROJECT_ID="your-project"
REPO="bigquery-readonly-mcp"
IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO}/server:latest"

gcloud artifacts repositories create $REPO \
  --repository-format=docker \
  --location=$REGION

gcloud builds submit --tag $IMAGE

3. Implantar no Cloud Run

gcloud run deploy bigquery-readonly-mcp \
  --image="$IMAGE" \
  --region="$REGION" \
  --service-account="bigquery-readonly-mcp@${PROJECT_ID}.iam.gserviceaccount.com" \
  --set-secrets="MCP_API_KEY=mcp-api-key:latest,MCP_ADMIN_KEY=mcp-admin-key:latest" \
  --set-env-vars="\
GCP_PROJECT_ID=${PROJECT_ID},\
BQ_DATASET_ID=your_dataset,\
BQ_ALLOWED_TABLE=your_table,\
MAX_SCAN_MB=100,\
MAX_RESULT_ROWS=2000,\
BQ_JOB_TIMEOUT_SECS=60,\
MAX_SQL_LENGTH=2000,\
SCHEMA_TTL_SECS=300,\
DRY_RUN_CACHE_TTL_SECS=60,\
DRY_RUN_CACHE_MAX_ENTRIES=1000,\
RATE_LIMIT_QPM=20,\
RATE_LIMIT_BURST=5,\
QUERY_CONCURRENCY=10,\
META_CONCURRENCY=3,\
ADMIN_RATE_LIMIT_QPM=10,\
MAX_REQUEST_BODY_BYTES=65536" \
  --allow-unauthenticated \
  --port 8080

Para vários datasets/tabelas, o gcloud precisa de um delimitador alternativo para que vírgulas dentro dos valores não sejam divididas como variáveis separadas:

gcloud run deploy bigquery-readonly-mcp \
  --image="$IMAGE" \
  ...
  --set-env-vars="^@^\
GCP_PROJECT_ID=${PROJECT_ID}@\
BQ_DATASET_ID=analytics,reporting,raw@\
BQ_ALLOWED_TABLE=events,daily_summary,events@\
MAX_SCAN_MB=100"

Datasets e tabelas são pareados posicionalmente: o índice 0 é pareado com o índice 0. O exemplo acima permite analytics.events, reporting.daily_summary e raw.events. Os tamanhos das listas devem ser iguais.

4. Conectar a partir de um cliente MCP

// claude_desktop_config.json or equivalent
{
  "mcpServers": {
    "bigquery": {
      "command": "/home/yourusername/.local/bin/uvx",
      "args": [
        "mcp-proxy",
        "--transport", "streamablehttp",
        "-H", "x-api-key", "YOUR_MCP_API_KEY",
        "https://bigquery-readonly-mcp-XXXX.a.run.app/mcp"
      ]
    }
  }
}

⚙️ Referência de configuração

Toda a configuração é feita por variáveis de ambiente. Variáveis obrigatórias abortam a inicialização se ausentes; todo o resto tem um valor padrão.

VariávelObrigatóriaPadrãoDescrição
GCP_PROJECT_IDSimO projeto onde os jobs do BigQuery são executados
BQ_DATASET_IDSimLista de datasets separada por vírgulas
BQ_ALLOWED_TABLESimLista de tabelas separada por vírgulas; pareada posicionalmente com BQ_DATASET_ID
MCP_API_KEYSimChave de API que os clientes devem apresentar
MCP_ADMIN_KEYNãoChave de API para o endpoint /admin; se vazia, usa o padrão MCP_API_KEY
MAX_SCAN_MBNão100Teto rígido de bytes varridos por consulta (MB)
MAX_RESULT_ROWSNão2000Linhas de resultado são truncadas acima deste valor
BQ_JOB_TIMEOUT_SECSNão30Timeout do BigQuery por consulta
MAX_SQL_LENGTHNão2000Rejeita SQL mais longo que isto
MAX_REQUEST_BODY_BYTESNão65536Rejeita corpos HTTP maiores que isto
SCHEMA_TTL_SECSNão300TTL do cache de esquema
DRY_RUN_CACHE_TTL_SECSNão60TTL do cache de dry-run
DRY_RUN_CACHE_MAX_ENTRIESNão1000Tamanho do LRU para o cache de dry-run
RATE_LIMIT_QPMNão20Taxa de reabastecimento do token-bucket (consultas por minuto)
RATE_LIMIT_BURSTNão5Capacidade de burst do token-bucket
QUERY_CONCURRENCYNão10Máximo de jobs de consulta BQ simultâneos
META_CONCURRENCYNão3Máximo de chamadas de metadados simultâneas
ADMIN_RATE_LIMIT_QPMNão10Limite de taxa separado para o endpoint de administrador

🛡️ Modelo de segurança

O modelo de ameaça é: um agente de IA é parcial ou totalmente não confiável e pode ser invocado com entrada adversária. Os controles são em camadas.

  1. Camada de rede. O Cloud Run oferece terminação HTTPS, com opção de Cloud Armor na frente para allowlist de IP ou regras de WAF.
  2. Camada de autenticação. Cada requisição exige um cabeçalho x-api-key: <key> comparado com MCP_API_KEY em tempo constante. O endpoint administrativo exige um cabeçalho x-admin-key separado.
  3. Limite de tamanho do corpo. Requisições acima de MAX_REQUEST_BODY_BYTES são rejeitadas antes do parsing.
  4. Limite de taxa. Token-bucket, por instância. Clientes com mau comportamento recebem 429s.
  5. Camada de parser SQL. sqlparse decompõe cada consulta. Declarações não-SELECT, corpos com múltiplas declarações, DDL, DML, scripting e construções procedurais são rejeitados.
  6. Aplicação da allowlist. Cada tabela referenciada em FROM / JOIN / junção por vírgula é validada contra a allowlist (dataset, table) nos seus dois últimos segmentos. Referências entre datasets como wrong_ds.allowed_table são rejeitadas. Nomes de tabela nus que existem em múltiplos datasets permitidos são rejeitados como ambíguos (o agente deve qualificar).
  7. Consciência de CTE. Nomes de Common Table Expression são isentos — não são tabelas.
  8. Teto de varredura dry-run. O dry-run do BigQuery estima bytes varridos. Consultas que excedem MAX_SCAN_MB são rejeitadas antes da execução real do job.
  9. Camada IAM. Mesmo que todas as verificações acima fossem contornadas, a conta de serviço só tem dataViewer em datasets / tabelas específicos.
  10. Truncamento de resultados. A saída é limitada a MAX_RESULT_ROWS para que uma única resposta não possa exfiltrar uma tabela inteira.

O que o servidor não faz (e você deve saber):

  • Sem varredura de injeção de prompt. Se precisar disso, implante atrás de um gateway de segurança de modelo (ex.: Model Armor do Google, Lakera Guard, NeMo Guardrails) ou use o servidor MCP oficial do Google, que tem integração com Model Armor.
  • Sem mascaramento em nível de coluna. A allowlist é na granularidade de tabela. Se suas tabelas contêm colunas de PII que você não quer expor, crie uma visão autorizada do BigQuery que projete apenas colunas seguras e coloque a visão na allowlist.
  • Sem atribuição por usuário. A chave de API é compartilhada entre clientes. Para trilhas de auditoria por usuário, coloque um proxy ciente de identidade na frente, ou faça um fork e adicione OAuth.
  • Sem suporte a escrita, nunca. Isso é intencional. Se precisar de escrita, use outro servidor.

💻 Desenvolvimento local

git clone https://github.com/hugonissar/bigquery-readonly-mcp-server.git
cd bigquery-readonly-mcp-server

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

# Authenticate to GCP
gcloud auth application-default login

# Set required env vars
export GCP_PROJECT_ID=your-project
export BQ_DATASET_ID=your_dataset
export BQ_ALLOWED_TABLE=your_table
export MCP_API_KEY=$(openssl rand -hex 32)

# Run
uvicorn main:app --host 0.0.0.0 --port 8080 --reload

O endpoint MCP estará em http://localhost:8080/mcp e /health retorna o status do serviço.

🛠️ Operações

🔄 Invalidar o cache de esquema após uma mudança de esquema

curl -X POST "https://your-service.run.app/admin/invalidate-cache" \
  -H "x-admin-key: $MCP_ADMIN_KEY"

📜 Acompanhar logs

gcloud run services logs tail bigquery-readonly-mcp --region=$REGION

🔍 Consultar a trilha de auditoria

Cada job do BigQuery é registrado no Cloud Audit Logs. Para ver quais consultas o servidor MCP executou:

SELECT
  protopayload_auditlog.authenticationInfo.principalEmail AS sa,
  protopayload_auditlog.servicedata_v1_bigquery.jobCompletedEvent.job.jobConfiguration.query.query AS sql,
  protopayload_auditlog.servicedata_v1_bigquery.jobCompletedEvent.job.jobStatistics.totalBilledBytes AS bytes,
  timestamp
FROM `your-project.cloudaudit_logs.cloudaudit_googleapis_com_data_access`
WHERE protopayload_auditlog.authenticationInfo.principalEmail = 'bigquery-readonly-mcp@your-project.iam.gserviceaccount.com'
ORDER BY timestamp DESC
LIMIT 100;

⚠️ Limitações

  • Uma região por implantação. O Cloud Run é regional. Para failover multi-região, implante múltiplas instâncias atrás de um balanceador de carga global.
  • Cold starts. Escala para zero significa que a primeira requisição após ociosidade leva alguns segundos. Defina --min-instances=1 para eliminar isso ao custo de alguns dólares por mês.
  • Esquema de autenticação único. Apenas chave de API no cabeçalho x-api-key. Sem OAuth, sem mTLS nativo (ambos são alcançáveis via autenticação baseada em IAM do Cloud Run + um proxy ciente de identidade — veja Operações).
  • Cache de esquema é por instância. Cada instância do Cloud Run mantém o seu próprio. O endpoint administrativo invalida o cache na instância que recebe a chamada; sob carga com múltiplas instâncias, talvez seja melhor lançar uma nova revisão.
  • Sem resultados em streaming. As consultas são materializadas completamente no servidor antes do truncamento. Não aumente MAX_RESULT_ROWS além de alguns milhares sem considerar memória.

❓Perguntas frequentes

Funciona com o Claude Desktop? Sim, via a configuração url + headers acima. Também funciona com Cursor, Windsurf, Claude Code, a API Responses da OpenAI e qualquer outra coisa que fale MCP streamable-HTTP.

Posso usar isso com uma VPC privada / conector VPC serverless? Sim. Adicione --vpc-connector e --ingress=internal ao comando de deploy do Cloud Run. Depois anexe um balanceador de carga interno para acesso do cliente.

Como isso se compara ao MCP Toolbox for Databases? O MCP Toolbox é genérico para muitos bancos de dados e configurado via YAML. Este servidor é específico para BigQuery e configurado via variáveis de ambiente. Escolha Toolbox se precisar de multi-banco; escolha este se quiser guardrails específicos do BigQuery (limites de varredura, allowlists) embutidos.

Posso adicionar ferramentas personalizadas? Sim — é um único arquivo Python. Adicione um novo @mcp.tool e aplique o mesmo padrão de validação.

O formato de resposta do esquema é estável? get_table_schema retorna {"tables": [{dataset, table, partition_field, clustering_fields, schema}, ...]}. A forma é estável entre configurações de tabela única e múltiplas tabelas.

Ele suporta SQL legado bq? Não. Apenas SQL padrão (GoogleSQL).

🤝 Contribuição

PRs são bem-vindos. Issues com reproduções têm prioridade.

📄 Licença

MIT. Veja LICENSE.


Palavras-chave: BigQuery MCP server, Model Context Protocol BigQuery, Claude BigQuery integration, secure BigQuery MCP, self-hosted BigQuery MCP, Cloud Run MCP server, BigQuery AI agent, read-only BigQuery, BigQuery LLM, MCP server Cloud Run, BigQuery rate limiting, BigQuery cost control AI.