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 .envEdite .env e preencha MCP_AUTH_TOKEN. Sem PLUGGY_* o servidor usa o gateway CSV lendo CSV_STATEMENTS_DIR (padrão ./statements).
pnpm install
pnpm devConfira:
curl http://localhost:3000/healthOutros 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 32Coloque 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 mcpPublica 127.0.0.1:3000. O SQLite fica no volume mcp-data (/data/financas.db) e ./statements é montado somente leitura em /statements.
Cloudflare Tunnel
No Cloudflare Zero Trust: Networks → Tunnels → Create a tunnel (tipo Cloudflared). Copie o token.
Em Public Hostname, aponte um subdomínio (ex.:
financas.seudominio.com) parahttp://mcp:3000.Coloque o token em
CLOUDFLARE_TUNNEL_TOKENno.env.Suba os dois serviços:
docker compose up -d --buildO 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>/mcpAutenticaçã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 |
| Status da fatura aberta: à vista, parcelas, recorrentes pendentes, folgas, dias até fechar |
| Comprometimento com parcelas mês a mês |
| Avalia a compra e sugere |
| Resumo das últimas faturas fechadas |
| Assinaturas esperadas no cartão |
| Compras planejadas |
| Tetos, dia de fechamento e vencimento |
Regras
Fatura fecha no
closingDay(padrão 2) e vence nodueDay(padrão 10). Compra entreclosingDay+1de M-1 eclosingDayde M entra na fatura M. Ambos configuráveis viaupdate_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 comparaamount/installmentscom 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 janeladentro 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:
Sinal e
typedas transações de cartão: assumidoDEBIT= compra eCREDIT= pagamento ("pagamento" na descrição) ou estorno. Valor usaMath.abs(amount).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, trocarpurchaseTotalparatotalAmount * totalInstallments.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
feeTypeajuda.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.Datas:
transaction.dateconvertida comtoISOString().slice(0, 10)(UTC). Se a Pluggy devolver meia-noite local, pode deslocar um dia; ajustar para o fuso emtoIsoDate.Bills (
fetchCreditCardBills): o mês da fatura é derivado dedueDate;billClosingDatepode virnull, então o fechamento cai no cálculo porclosingDay. Conferir se as datas batem com a fatura do app.Fatura aberta:
creditData.balanceCloseDateebalanceDueDateda conta são usados como fechamento e vencimento. Confirmar se referem-se à fatura aberta.Conta: usa a primeira conta
type === "CREDIT"do item; se houver mais de um cartão, definirPLUGGY_ACCOUNT_ID.Transações
PENDINGsão incluídas. Decidir se devem entrar no cálculo.Janela de busca: fatura aberta busca desde 5 dias antes do início do período; faturas fechadas buscam
limitmeses para trás numa só chamada (fetchAllTransactions). Avaliar volume e rate limit.