Skip to main content
Glama
raphaelcarreiro

financas-mcp

financas-mcp

Servidor MCP (Streamable HTTP) que responde "posso comprar X em N parcelas?" com base na fatura real do cartão Nubank (Pluggy / Open Finance ou CSV) e nos tetos de gastos guardados em SQLite. Feito para ser conectado ao claude.ai como connector remoto via Cloudflare Tunnel.

Rodar local

Requisitos: Node 22, pnpm 10 (via corepack enable pnpm).

cp .env.example .env

Edite .env e preencha MCP_AUTH_TOKEN. Sem PLUGGY_* o servidor usa o gateway CSV lendo CSV_STATEMENTS_DIR (padrão ./statements).

pnpm install
pnpm dev

Confira:

curl http://localhost:3000/health

Outros comandos: pnpm test, pnpm typecheck, pnpm lint, pnpm build, pnpm db:generate (nova migration após mudar src/infrastructure/persistence/schema.ts).

As migrations em drizzle/ rodam no boot. Na primeira execução o seed insere tetos, gastos fixos, reserva anual e recorrentes esperados. Depois disso tudo é alterado pelas tools.

CSV do Nubank

Exporte a fatura pelo app (colunas date,title,amount) e salve em statements/YYYY-MM.csv, onde YYYY-MM é o mês da fatura (a que vence em YYYY-MM-10). Sem mês no nome, o gateway deduz pelo dia de fechamento. Exemplo em test/fixtures/.

Criar o token

openssl rand -hex 32

Coloque o valor em MCP_AUTH_TOKEN. Toda requisição a /mcp precisa de Authorization: Bearer <token>; GET /health é aberto.

Docker (local, sem túnel)

docker compose -f docker-compose.yml -f docker/compose.local.yml up --build mcp

Publica 127.0.0.1:3000. O SQLite fica no volume mcp-data (/data/financas.db) e ./statements é montado somente leitura em /statements.

Cloudflare Tunnel

  1. No Cloudflare Zero Trust: Networks → Tunnels → Create a tunnel (tipo Cloudflared). Copie o token.

  2. Em Public Hostname, aponte um subdomínio (ex.: financas.seudominio.com) para http://mcp:3000.

  3. Coloque o token em CLOUDFLARE_TUNNEL_TOKEN no .env.

  4. Suba os dois serviços:

docker compose up -d --build

O serviço mcp não publica porta no host; só o cloudflared fala com ele pela rede do compose. Teste com curl https://financas.seudominio.com/health.

Opcional: adicione uma Access Policy no Cloudflare para restringir ainda mais o hostname.

Conectar no claude.ai

Settings → Connectors → Add custom connector:

  • URL: https://<seu-dominio>/mcp

  • Autenticação: Bearer token, valor de MCP_AUTH_TOKEN

Depois, em uma conversa, ative o connector e pergunte, por exemplo: "posso comprar um notebook de 4500 em 6x?".

Tools

Tool

Uso

get_open_invoice

Status da fatura aberta: à vista, parcelas, recorrentes pendentes, folgas, dias até fechar

get_installment_schedule(months=12)

Comprometimento com parcelas mês a mês

can_i_buy(amount, installments=1, startMonth?)

Avalia a compra e sugere bestMonth

get_closed_invoices(n=6)

Resumo das últimas faturas fechadas

list/upsert/remove_recurring_expected

Assinaturas esperadas no cartão

list/upsert/remove_planned_purchase

Compras planejadas

get/update_budget_params

Tetos, dia de fechamento e vencimento

Regras

  • Fatura fecha no closingDay (padrão 2) e vence no dueDay (padrão 10). Compra entre closingDay+1 de M-1 e closingDay de M entra na fatura M. Ambos configuráveis via update_budget_params.

  • À vista: sem parcela (IOF de ida conta). Parcela nova: 1/N. Parcela antiga: k/N com k > 1. Pagamentos, estornos e IOF de volta não contam.

  • Cronograma: parcela k/N na fatura aberta segue ativa por mais N-k meses; compras planejadas com mês definido entram como amount/installments.

  • can_i_buy à vista compara com a folga de à vista da fatura aberta (teto - gasto - recorrentes pendentes). Parcelado compara amount/installments com a menor folga entre o mês inicial e o último mês.

  • Total previsto do mês = cartão previsto + gastos fixos + aporte da reserva anual (amount / meses da janela dentro da janela).

Pendências de validação com o payload real da Pluggy

Tudo isso está isolado em src/infrastructure/pluggy/pluggy-mapper.ts e pluggy-card-statement-gateway.ts:

  1. Sinal e type das transações de cartão: assumido DEBIT = compra e CREDIT = pagamento ("pagamento" na descrição) ou estorno. Valor usa Math.abs(amount).

  2. creditCardMetadata.totalAmount: assumido como valor total da compra parcelada. O tipo do SDK descreve como "amount of the installment"; se for o valor da parcela, trocar purchaseTotal para totalAmount * totalInstallments.

  3. IOF: detectado por prefixo "IOF" na descrição (e "IOF de volta" para devolução). Conferir como o Nubank via Open Finance nomeia esses lançamentos e se feeType ajuda.

  4. Agrupamento por fatura: usa creditCardMetadata.billForecastDate (YYYY-MM) quando presente; senão cai na regra de calendário por data. Validar se o Nubank preenche esse campo e se o mês bate com o vencimento.

  5. Datas: transaction.date convertida com toISOString().slice(0, 10) (UTC). Se a Pluggy devolver meia-noite local, pode deslocar um dia; ajustar para o fuso em toIsoDate.

  6. Bills (fetchCreditCardBills): o mês da fatura é derivado de dueDate; billClosingDate pode vir null, então o fechamento cai no cálculo por closingDay. Conferir se as datas batem com a fatura do app.

  7. Fatura aberta: creditData.balanceCloseDate e balanceDueDate da conta são usados como fechamento e vencimento. Confirmar se referem-se à fatura aberta.

  8. Conta: usa a primeira conta type === "CREDIT" do item; se houver mais de um cartão, definir PLUGGY_ACCOUNT_ID.

  9. Transações PENDING são incluídas. Decidir se devem entrar no cálculo.

  10. Janela de busca: fatura aberta busca desde 5 dias antes do início do período; faturas fechadas buscam limit meses para trás numa só chamada (fetchAllTransactions). Avaliar volume e rate limit.