💰 Finanpy — MCP Server de Finanças Pessoais
Finanpy é um servidor MCP (Model Context Protocol) para análise e gestão de finanças pessoais. Ele conecta um banco de dados SQLite a qualquer cliente MCP compatível, permitindo registrar, consultar e analisar transações financeiras através de linguagem natural.
---
📁 Estrutura do Projeto
.
├── finance.db # Banco de dados SQLite com as transações
├── database.py # Camada de acesso ao banco de dados
├── mcp_server.py # Servidor MCP com as ferramentas expostas
├── import_csv.py # Script para importar extratos do Nubank (CSV)
└── sample_agent.py # Agente de exemplo com LangChain + LangGraph
---
🗄️ Banco de Dados
O projeto utiliza SQLite com uma única tabela principal:
Tabela transactions
| Coluna | Tipo | Descrição | |---------------|---------|--------------------------------------------------------| | id | INTEGER | Chave primária, autoincremento | | identifier | TEXT | Identificador único da transação (evita duplicatas) | | type | TEXT | Tipo da transação: receita ou despesa | | amount | REAL | Valor da transação (negativo para despesas) | | category | TEXT | Categoria da transação (padrão: sem categoria) | | description | TEXT | Descrição ou histórico da transação | | date | TEXT | Data da transação em ISO 8601: YYYY-MM-DD |
Timezone: todas as datas são armazenadas em horário local (America/Sao_Paulo). Não há offset UTC explícito no banco. Arredondamento: valores monetários são armazenados como
REALcom 2 casas decimais, usando arredondamento half-up no momento da inserção.
---
⚙️ Instalação
Pré-requisitos
- Python 3.10+
- uv
Dependências
uv add fastmcp
Para utilizar o agente de exemplo (sample_agent.py), instale também:
uv add langchain-openai langchain-mcp-adapters langgraph python-dotenv
Configuração
- Clone o repositório.
- Inicialize o banco de dados:
python database.py
- (Opcional) Importe um extrato do Nubank:
# Coloque o arquivo CSV exportado do Nubank na raiz do projeto:
# extrato_nubank_05_2026.csv
python import_csv.py
---
🚀 Executando o Servidor MCP
python mcp_server.py
Transporte STDIO
O Finanpy utiliza transporte STDIO: o cliente MCP inicia o servidor como um processo filho e se comunica com ele via stdin/stdout usando o protocolo MCP. Não há porta de rede envolvida.
Fluxo de inicialização:
- O client executa
python mcp_server.pycomo subprocesso. - O servidor anuncia suas capacidades (
tools,resources) via protocolo MCP pelostdout. - O client recebe o manifesto e passa a chamar ferramentas ou consultar recursos conforme necessário.
- O processo do servidor vive enquanto o client estiver ativo e é encerrado junto com ele.
Quando o client decide chamar cada ferramenta/recurso:
- Consulta
resumo_financeiro_atual(recurso) para orientar o raciocínio antes de responder perguntas sobre saldo ou categorias. - Chama
registrar_transacaoquando o usuário pede para registrar, lançar ou salvar uma transação. - Chama
listar_transacoesquando o usuário pede consultas, históricos ou totais por período/categoria.
---
🔧 Ferramentas e Recursos MCP
Ferramenta 1 — registrar_transacao
Registra uma nova transação financeira no banco de dados de forma idempotente.
Entradas:
| Parâmetro | Tipo | Obrigatório | Descrição | |---------------|--------|-------------|---------------------------------------------------------------------------| | type | string | ✅ | "receita" ou "despesa" | | amount | float | ✅ | Valor absoluto da transação (sempre positivo; o sinal é inferido do type) | | category | string | ✅ | Categoria (ex: "alimentacao", "transporte", "moradia") | | description | string | ✅ | Texto livre descrevendo a transação | | date | string | ❌ | Data em ISO 8601 YYYY-MM-DD (padrão: data atual) | | identifier | string | ❌ | UUID ou chave externa para idempotência (ex: ID do extrato bancário) |
Saída (sucesso):
{
"sucesso": true,
"id": 42,
"normalizado": {
"amount": 73.90,
"date": "2026-06-23",
"type": "despesa"
},
"avisos": []
}
Saída (duplicata detectada):
{
"sucesso": false,
"code": "DUPLICATE",
"id_existente": 17,
"mensagem": "Transação com este identifier já existe."
}
Erros e limites:
| Código | Causa | O que o client deve fazer | |------------------|-----------------------------------------------------------|----------------------------------------------------------------| | INVALID_DATE | Data fora do formato ISO 8601 ou data futura | Corrigir o formato para YYYY-MM-DD antes de reenviar | | DUPLICATE | identifier já existe no banco | Informar o usuário; não reenviar | | BAD_TYPE | type diferente de "receita" ou "despesa" | Normalizar para um dos dois valores antes de reenviar | | INVALID_AMOUNT | amount negativo, zero ou não numérico | Solicitar o valor correto ao usuário; nunca inferir | | DB_BUSY | SQLite travado por operação concorrente | Retry com backoff exponencial: 200ms, 400ms, 800ms (máx. 3x) | | MISSING_FIELD | Campo obrigatório ausente | Solicitar o campo faltante ao usuário antes de reenviar |
Atenção — sinal vs. tipo: nunca envie
amountnegativo comtype="despesa". O servidor armazena despesas com sinal negativo internamente; a entrada esperada é sempre o valor absoluto. Se o client receberamountnegativo do usuário junto comtype="despesa", deve aplicarabs(amount)antes de chamar a ferramenta.
---
Ferramenta 2 — listar_transacoes
Lista e filtra transações financeiras armazenadas no banco.
Entradas:
| Parâmetro | Tipo | Obrigatório | Descrição | |------------|--------|-------------|-----------------------------------------------------| | period | string | ❌ | Período no formato YYYY-MM (ex: "2026-05") | | category | string | ❌ | Filtra pelo nome exato da categoria | | type | string | ❌ | "receita" ou "despesa" |
Saída (sucesso):
[
{
"id": 1,
"identifier": "69fa0c39-ca20-4683-849a-85519872249a",
"type": "receita",
"amount": 50.0,
"category": "sem categoria",
"description": "Resgate RDB",
"date": "2026-05-05"
}
]
Retorna uma lista vazia [] quando nenhum registro corresponde aos filtros (não é um erro).
Erros e limites:
| Código | Causa | O que o client deve fazer | |------------------|-----------------------------------------------------------|----------------------------------------------------------------| | INVALID_PERIOD | Formato de período inválido (ex: "05-2026") | Corrigir para YYYY-MM antes de reenviar | | BAD_TYPE | type diferente de "receita" ou "despesa" | Normalizar antes de reenviar | | DB_BUSY | SQLite travado por operação concorrente | Retry com backoff exponencial: 200ms, 400ms, 800ms (máx. 3x) |
Janela recomendada: evite consultar períodos superiores a 12 meses sem filtro de categoria, pois o retorno pode ser extenso. Prefira decompor em múltiplas chamadas mensais se necessário.
---
Recurso MCP — resumo_financeiro_atual
URI: finance://resumo_financeiro_atual
Recurso somente-leitura que retorna um snapshot consolidado das finanças do mês corrente. Diferente das ferramentas (que executam ações), este recurso é consultado pelo agente para orientar seu raciocínio antes de responder perguntas sobre saldo, categorias e situação financeira geral — evitando chamadas desnecessárias a listar_transacoes.
Saída:
{
"period": "2026-06",
"total_income": 9947.94,
"total_expense": 9947.94,
"balance": 0.0,
"expenses_by_category": {
"sem categoria": 9947.94
}
}
Quando o client deve consultá-lo:
- Antes de responder qualquer pergunta sobre saldo atual, total gasto ou total recebido no mês.
- Para verificar se há dados suficientes antes de sugerir categorias ao usuário.
- Como ponto de partida para decidir se é necessário chamar
listar_transacoescom filtros mais específicos.
---
📥 Importação de Extrato do Nubank
O script import_csv.py importa automaticamente transações a partir do CSV exportado pelo aplicativo do Nubank.
Formato esperado do CSV:
| Data | Valor | Identificador | Descrição | |------------|---------|---------------|--------------------| | 05/05/2026 | -50.00 | 69fa0c7e-... | Transferência Pix | | 06/05/2026 | 188.03 | 69fbee60-... | Resgate RDB |
O tipo da transação é derivado automaticamente pelo sinal do valor:
- Valor negativo →
despesa - Valor positivo →
receita
Transações com identifier já existente são ignoradas automaticamente (idempotência via INSERT OR IGNORE).
Categorias: todas as transações importadas via CSV chegam como
"sem categoria". Use o agente para categorizá-las após a importação.
---
🔄 Cenário de Uso Ponta a Ponta
Mensagem do usuário: > "Quanto gastei em alimentação em maio de 2026? E registra aí um Uber de R$ 73,90 que peguei hoje."
Raciocínio do agente:
- A pergunta envolve o mês atual — consultar o recurso
resumo_financeiro_atualpara ter contexto geral antes de filtrar. - Para o total de alimentação em maio, chamar
listar_transacoescom filtros de período e categoria. - Para registrar o Uber, chamar
registrar_transacaocom os dados fornecidos.
---
Passo 1 — Consulta ao recurso resumo_financeiro_atual
GET finance://resumo_financeiro_atual
Resposta do servidor:
{
"period": "2026-06",
"total_income": 1835.29,
"total_expense": 462.75,
"balance": 1372.54,
"expenses_by_category": {
"sem categoria": 462.75
}
}
O agente identifica que o mês atual é junho — a pergunta é sobre maio, então precisa de uma chamada específica.
---
Passo 2 — Chamada à ferramenta listar_transacoes
{
"period": "2026-05",
"category": "alimentacao",
"type": "despesa"
}
Resposta do servidor:
[
{
"id": 8,
"identifier": "nubank-2026-05-12-ifood",
"type": "despesa",
"amount": -49.90,
"category": "alimentacao",
"description": "iFood - Pizza",
"date": "2026-05-12"
},
{
"id": 15,
"identifier": "nubank-2026-05-22-mercado",
"type": "despesa",
"amount": -312.40,
"category": "alimentacao",
"description": "Mercado Extra",
"date": "2026-05-22"
}
]
Total calculado pelo agente: R$ 362,30.
---
Passo 3 — Chamada à ferramenta registrar_transacao
{
"type": "despesa",
"amount": 73.90,
"category": "transporte",
"description": "Uber",
"date": "2026-06-23",
"identifier": "manual-2026-06-23-uber-001"
}
Resposta do servidor:
{
"sucesso": true,
"id": 27,
"normalizado": {
"amount": 73.90,
"date": "2026-06-23",
"type": "despesa"
},
"avisos": []
}
---
Resposta final ao usuário:
"Em maio de 2026 você gastou R$ 362,30 em alimentação (iFood R$ 49,90 + Mercado Extra R$ 312,40). Sua despesa de Uber de R$ 73,90 foi registrada em transporte para hoje, 23/06/2026. ✅"
---
🤖 Agente de Exemplo
O arquivo sample_agent.py demonstra como integrar o servidor MCP com um agente LangChain + LangGraph usando GPT-4o-mini como modelo de linguagem, via transporte STDIO.
Configuração
Crie um arquivo .env na raiz do projeto:
OPENAI_API_KEY=sua_chave_aqui
Executando o agente
python sample_agent.py
Exemplo de interação real:
Finanpy - Assistente Financeiro Pessoal
Digite 'sair' para encerrar.
Você: Poderia categorizar as minhas transações realizadas? E também fazer uma somatória transacionada para cada categoria?
Finanpy: processando...
Finanpy: Aqui está a categorização das suas transações e a somatória para cada categoria:
### Receitas
1. Resgate RDB: R$ 50,00
2. Resgate RDB: R$ 188,03
...
**Total de Receitas**: R$ 9.882,74
### Despesas
1. Transferência enviada pelo Pix: R$ -50,00
...
**Total de Despesas**: R$ -10.188,84
---
🧩 Integração com Claude Desktop
Para usar o Finanpy diretamente no Claude Desktop, adicione ao arquivo de configuração do MCP (claude_desktop_config.json):
{
"mcpServers": {
"finanpy": {
"command": "python",
"args": ["/caminho/absoluto/para/mcp_server.py"]
}
}
}
O campo
"transport"pode ser omitido — o Claude Desktop usa STDIO por padrão. Use o caminho absoluto para omcp_server.pypara evitar erros de working directory.
Após reiniciar o Claude Desktop, as ferramentas e recursos do Finanpy estarão disponíveis automaticamente.
---
🔒 Segurança e Privacidade
- Dados sensíveis (PII): o banco contém informações financeiras pessoais, incluindo descrições de transações que podem conter nomes de pessoas (ex: transferências Pix). Não compartilhe o arquivo
finance.dbpublicamente. - Logs: evite ativar logging detalhado em produção, pois descrições de transações podem aparecer nos logs. Em desenvolvimento, use nível
WARNINGou superior. - Acesso ao banco: o SQLite não tem autenticação nativa. Garanta que o arquivo
finance.dbtenha permissões restritas ao usuário do sistema que executa o servidor (chmod 600 finance.db). - Variáveis de ambiente: nunca comite o arquivo
.envcom chaves de API no repositório. Adicione-o ao.gitignore.
---
📌 Observações Técnicas
- Datas: o campo
dateé armazenado como texto em formatoYYYY-MM-DD(ISO 8601). Entradas no formatoDD/MM/YYYY(como as do CSV do Nubank) são convertidas internamente. O client deve sempre enviar datas em ISO 8601. - Categorias: não há validação de categoria no servidor — qualquer string é aceita. Para padronizar, consulte o recurso
resumo_financeiro_atualpara ver as categorias já existentes antes de registrar uma nova transação. - Idempotência: o campo
identifiergarante que a mesma transação não seja inserida duas vezes (INSERT OR IGNORE). Ao importar extratos, use sempre o ID original do banco comoidentifier. - Concorrência: SQLite não suporta múltiplos escritores simultâneos. Se múltiplos agentes estiverem ativos, implemente retry com backoff exponencial em caso de
DB_BUSY. - Banco de dados: o arquivo
finance.dbé criado automaticamente na raiz do projeto ao executardatabase.pyoumcp_server.py.






