Finanpy

Gahbrielms/exercicio_mcp
0 starsCommunity

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

MCP server for personal finance management that connects a SQLite database to any MCP client, allowing users to register, query, and analyze financial transactions through natural language.

README.md

💰 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 REAL com 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

  1. Clone o repositório.
  2. Inicialize o banco de dados:
python database.py
  1. (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:

  1. O client executa python mcp_server.py como subprocesso.
  2. O servidor anuncia suas capacidades (tools, resources) via protocolo MCP pelo stdout.
  3. O client recebe o manifesto e passa a chamar ferramentas ou consultar recursos conforme necessário.
  4. 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_transacao quando o usuário pede para registrar, lançar ou salvar uma transação.
  • Chama listar_transacoes quando 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 amount negativo com type="despesa". O servidor armazena despesas com sinal negativo internamente; a entrada esperada é sempre o valor absoluto. Se o client receber amount negativo do usuário junto com type="despesa", deve aplicar abs(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_transacoes com 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 negativodespesa
  • Valor positivoreceita

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:

  1. A pergunta envolve o mês atual — consultar o recurso resumo_financeiro_atual para ter contexto geral antes de filtrar.
  2. Para o total de alimentação em maio, chamar listar_transacoes com filtros de período e categoria.
  3. Para registrar o Uber, chamar registrar_transacao com 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 o mcp_server.py para 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.db publicamente.
  • Logs: evite ativar logging detalhado em produção, pois descrições de transações podem aparecer nos logs. Em desenvolvimento, use nível WARNING ou superior.
  • Acesso ao banco: o SQLite não tem autenticação nativa. Garanta que o arquivo finance.db tenha permissões restritas ao usuário do sistema que executa o servidor (chmod 600 finance.db).
  • Variáveis de ambiente: nunca comite o arquivo .env com chaves de API no repositório. Adicione-o ao .gitignore.

---

📌 Observações Técnicas

  • Datas: o campo date é armazenado como texto em formato YYYY-MM-DD (ISO 8601). Entradas no formato DD/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_atual para ver as categorias já existentes antes de registrar uma nova transação.
  • Idempotência: o campo identifier garante que a mesma transação não seja inserida duas vezes (INSERT OR IGNORE). Ao importar extratos, use sempre o ID original do banco como identifier.
  • 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 executar database.py ou mcp_server.py.

Related MCP servers

Browse all →