Pular para o conteúdo

Builder OS

Builder · OS
L05 · MCP com Postgres real
~18 MIN DE LEITURA

Lição 5 de 9: MCP com Postgres real

lição 5/9 do Módulo 4
AO FIM, VOCÊ VAI TER
  • Arquivo .mcp.example.json no repo (template público) + .mcp.json local (referencia ${POSTGRES_URL}, não comitado); credencial fica na env var POSTGRES_URL, fora dos dois arquivos
  • MCP do Postgres conectado e respondendo a queries do Claude
  • Pelo menos 1 relatório real salvo em docs/reports/<data>.md gerado via MCP
  • Commit feat(mcp): postgres mcp wired + first report

De psql pra "Claude responde em 30s"

Você vai plugar o Claude no banco de produção (via ) pra ele responder perguntas em pt-BR sobre dados reais. Uma pergunta tipo "quantos recibos foram criados na última semana?" passa de "vou ter que abrir o psql, escrever um SELECT, interpretar" pra "Claude responde em 30 segundos com dados reais."

Conectar o Claude ao Postgres muda a sua relação com o produto: você para de perguntar "como faço um SELECT pra X?" e passa a perguntar "X aconteceu na última semana?". A lição é cara em atenção (a configuração tem pegadinhas) e barata em código (10 linhas no total). O retorno compensa: depois disso, toda pergunta sobre dados reais vira uma pergunta ao Claude, não uma sessão no psql.

Vai passar por:

  1. Criar um usuário de banco que só lê (segurança primeiro): claude_readonly com GRANT SELECT
  2. Configurar o Claude pra usar esse usuário via .mcp.json (fora do git)
  3. Criar .mcp.example.json (template público) que entra no git
  4. Gerar seu primeiro relatório real via Claude, salvo em docs/reports/<data>.md

Pré-requisitos desta lição

  • Postgres de produção rodando. Provisionado na L02 (Vercel Postgres, Supabase, Neon, Railway, qualquer um). Se você usou SQLite, Vercel KV, ou outro storage que não é Postgres, a lição não se aplica direto: pula pro Build Diary e segue pra L06. Pra dado público em CSV/Parquet, veja logo abaixo o callout "Sem Postgres? Caminho equivalente com DuckDB".
  • Acesso de admin ao banco (superuser ou owner): pra rodar CREATE USER e GRANT. Se você usa Vercel Postgres / Supabase, o painel deles dá esse acesso pelo SQL Editor. Se for Railway/Neon, você tem a connection string com permissões altas no .env.local.
  • openssl no PATH: pra gerar a senha do usuário readonly.

Restrição de stack

Esta lição assume Postgres. Outros sistemas (MySQL, MongoDB, DynamoDB) têm próprios. A lógica é a mesma (user separado read-only, MCP server específico, .mcp.example.json no git), mas os comandos SQL e o nome do pacote npm mudam. Adapte conforme o seu caso.

Passo 1 — Decida read-only ou read-write

Pra esta lição: read-only sempre. Mesmo que você queira "Claude criando recibo de teste" no futuro, comece read-only. As razões:

  1. O ganho de read-only já é 80% do valor (relatórios, debug, análise)
  2. Read-write num produto público com tráfego real abre espaço pra mudança não-auditada: se o Claude faz um UPDATE errado, você só descobre depois
  3. Você pode adicionar write depois, com um segundo MCP de escopo limitado a uma tabela específica; não precisa ser tudo ou nada

Passo 2 — Crie um usuário read-only no Postgres

Não use o DATABASE_URL da aplicação, que tem permissões de write. Crie um usuário separado, só de leitura.

prompt · text
Conecta no Postgres de produção (use o connection string com permissões de superuser/owner — você tem isso no Vercel Postgres / Supabase / wherever). Executa:

CREATE USER claude_readonly WITH PASSWORD '<<gere-com-openssl-rand-hex-32>>';
GRANT CONNECT ON DATABASE <nome-do-db> TO claude_readonly;
GRANT USAGE ON SCHEMA public TO claude_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO claude_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO claude_readonly;

Mostre o SQL antes de executar. Quando passar, me retorna o connection string completo no formato: postgres://claude_readonly:<senha>@<host>:5432/<db>?sslmode=require — vou guardar como a variável de ambiente POSTGRES_URL.

Aprove. Anota a connection string num lugar seguro (gestor de senha); você não vai guardar no repo.

Passo 3 — Instale o MCP do Postgres

prompt · text
Verifica se o pacote `@modelcontextprotocol/server-postgres` (ou equivalente atual) está disponível via npx:

npx -y @modelcontextprotocol/server-postgres --help

Se rodar e mostrar usage, está OK. Se der erro de "not found", procura o nome atual do servidor MCP de Postgres mantido pela Anthropic ou comunidade — pode ter mudado. Não invente nome — checa primeiro.

Confirma que existe. Esse é o servidor MCP padrão pro Postgres.

Passo 4 — Crie .mcp.example.json (público) + .mcp.json (local)

prompt · text
Cria `.mcp.example.json` na raiz do projeto (entra no git) com:

{
"mcpServers": {
  "postgres": {
    "type": "stdio",
    "command": "npx",
    "args": [
      "-y",
      "@modelcontextprotocol/server-postgres",
      "${POSTGRES_URL}"
    ]
  }
}
}

A connection string NÃO entra no JSON literal — Claude Code expande `${POSTGRES_URL}` a partir do ambiente, então o segredo nunca fica hardcoded no arquivo. Por isso o `.mcp.example.json` (com a referência `${POSTGRES_URL}`) pode entrar no git sem vazar credencial.

Depois cria `.mcp.json` (NÃO entra no git — adiciona ao `.gitignore` se ainda não tiver `.mcp.json`) com o MESMO conteúdo. O valor real de `POSTGRES_URL` (o connection string do `claude_readonly`) você define como variável de ambiente — ex: no `.env.local` ou exportada no shell antes de abrir o Claude:

export POSTGRES_URL="postgres://claude_readonly:<senha>@<host>:5432/<db>?sslmode=require"

Mostre o diff antes de aplicar. Confirma:
1. `.mcp.example.json` está stageado pra commit
2. `.mcp.json` está no `.gitignore`
3. `.gitignore` tem entrada explícita: `.mcp.json` (não só `.mcp*` que poderia pegar `.mcp.example.json` por engano)

Aprove. Confirma com cat .gitignore | grep mcp.

Passo 5 — Recarregue o Claude Code e teste

O .mcp.json é lido na startup do Claude Code. Saia e entre de novo (/exit, depois claude no projeto). Quando o Claude inicia, ele detecta o MCP server e pede aprovação. Aprove.

Cola no Claude:

prompt · text
Use o MCP do Postgres pra responder: quantos registros tem na tabela [recurso-da-fatia-1] (ex: recibos)? Conta com timestamp de criação dos últimos 7 dias.

O Claude vai usar a ferramenta query do MCP (ou nome equivalente), rodar um SELECT COUNT(*) FROM [recurso] WHERE created_at > now() - interval '7 days' e responder. Se funcionar, o MCP está ligado.

Se falhar:

  • "Permission denied": o claude_readonly não tem GRANT na tabela. Roda GRANT SELECT ON [recurso] TO claude_readonly (a tabela pode ter sido criada depois do GRANT inicial).
  • "Connection refused": connection string errada, ou o Postgres não aceita conexão externa. Confirma o sslmode=require e que o host aceita IPs externos (Vercel Postgres aceita; alguns Supabase precisam de configuração).

Passo 6 — Gere o primeiro relatório real

Esta é a parte que paga a lição. Cola no Claude:

prompt · text
Use o MCP do Postgres pra gerar um relatório semanal da fatia 1. Pergunta a si mesmo, em sequência:

1. Quantos registros foram criados na última semana vs semana anterior?
2. Qual o valor médio (se aplicável)?
3. Tem alguma anomalia que vale anotar (pico em dia específico, queda, etc.)?

Salva o resultado em `docs/reports/<data-de-hoje-ISO>.md` com:
- Cabeçalho com data + período coberto
- 3 perguntas + respostas baseadas em dados reais
- 1 frase de "o que isso significa pro produto" (qualitativo)

Mostre o diff antes de criar o arquivo.

Aprove. O relatório vai pra docs/reports/2026-MM-DD.md. Esse arquivo é a primeira evidência tangível de que o MCP está funcionando. Você não rodou SQL, não exportou CSV, não abriu painel: só perguntou.

Passo 7 — Faça o commit

prompt · text
Faça commit de `.mcp.example.json` + `docs/reports/<data>.md` + ajustes em `.gitignore`. Mensagem: `feat(mcp): postgres mcp wired + first report`. **Confirma que `.mcp.json` não está sendo comitado** (pra ter certeza, mostra o `git diff --staged` antes do commit).

Aprove. Confira no GitHub que o .mcp.json não aparece, só o .mcp.example.json. Esse passo é crítico.

Build Diary — no CEAP, Claude rodou query iterativa pra refinar o detector de fraude

O CEAP não usa MCP (é um projeto Python de análise estática, não tem banco de produção). Mas tem o equivalente: o Claude conectado ao notebook Jupyter, rodando queries em DataFrames Polars de forma iterativa, até refinar a classificação de "transação suspeita". Trecho do INVESTIGATION.md v3.1:

v3.1 Mapping Update (2026-01-05)

Added to Category 1 (Office Maintenance) to remove false positives:

  • 84 - Public Administration (municipalities rent office space to deputies)
  • 94 - Associations/Unions (rent space to deputies)

False Positives Successfully Removed:

  • Municipalities (CNAE 84) - R$ 445k removed
  • Associations/unions (CNAE 94) - R$ 590k removed

A v3.1 nasceu do Claude rodando query iterativa: "lista os CNPJs flagrados que parecem prefeituras", "agrupa por CNAE", "calcula quanto saiu se eu adicionar o CNAE 84 à categoria de escritório". 3 rounds de query → mudança de 4 linhas no código de mapping → R$ 1.5M de falsos positivos removidos.

O que isso muda pra você: o MCP encurta o ciclo de descoberta. Sem MCP, "vou ver se essa hipótese é verdade" passa por abrir o psql, lembrar a sintaxe, escrever a query e interpretar o resultado: fricção que faz você deixar a pergunta de lado em vez de responder. Com MCP, a pergunta em pt-BR vira uma resposta com dados, e você acaba checando hipóteses que antes assumiria.

Takeaways

  • Sempre read-only pra produção. claude_readonly com GRANT SELECT no schema; nunca permissão de write.
  • .mcp.example.json no git (template), .mcp.json no .gitignore. A credencial fica na env var POSTGRES_URL; o JSON só referencia ${POSTGRES_URL}. Confirma com git diff --staged antes do commit.
  • O MCP encurta o ciclo de descoberta: a pergunta em pt-BR vira dado, sem psql, e você checa hipóteses que antes assumiria.
  • Fallback se o MCP não rola: script Node simples com pg.Pool. Resolve o mesmo problema sem depender de a configuração do MCP convergir.

Você terminou quando

Quatro coisas:

  1. .mcp.example.json no git, .mcp.json local (não comitado); a credencial mora na env var POSTGRES_URL, não dentro do JSON
  2. Usuário claude_readonly criado no Postgres de produção com permissão só SELECT
  3. Claude responde pelo menos 1 pergunta sobre dados reais usando MCP
  4. docs/reports/<data>.md commitado