vacation-rental-finance-agent
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
vacation-rental-finance-agent
Agente financeiro para um imóvel de aluguel por temporada. Ele puxa reservas e extratos da plataforma da gestora, categoriza e deduplica os lançamentos e transforma condições de pagamento em texto livre em parcelas, usando um LLM com a saída validada. Também calcula três saldos, aponta despesas fora do padrão e projeta o caixa. Tudo isso fica exposto como tools de um servidor MCP.
Versão pública, com dados sintéticos, de um agente que opero em produção num imóvel de temporada do qual sou sócio. Nomes, valores, ids e o imóvel ("Casa Exemplo") são inventados. A integração com a plataforma real não faz parte deste repositório: no lugar dela há uma PMS simulada que reproduz os comportamentos problemáticos da plataforma.
Stack: Node 22 · @modelcontextprotocol/sdk · @anthropic-ai/sdk (opcional) · sem banco externo
O problema
Somos dois sócios num imóvel de temporada administrado por uma gestora. Ela recebe dos hóspedes, retém a comissão, paga as despesas da casa e repassa o resto. O acompanhamento mensal era feito por um relatório em planilha, e com ele não dava para saber o saldo real a qualquer momento:
planilha e plataforma nem sempre batiam. Um relatório montado à mão está sujeito a divergências com o sistema de origem, e conferir lançamento por lançamento levava tempo. A conciliação automática passou a apontar essas diferenças;
as condições de pagamento ficam num campo de texto livre ("30% na reserva, 70% no check-in", "entrada 40% + saldo até 15/11"). Para saber quanto falta receber, alguém precisava ler reserva por reserva;
a plataforma troca o id da reserva depois do checkout. Um sync ingênuo grava a mesma estadia várias vezes e infla a receita.
O agente resolve as três coisas e responde em linguagem natural a perguntas como "qual é o saldo?" ou "por que a despesa de setembro subiu?", e os números da resposta vêm de tools que fazem a conta em código, não do modelo.
Related MCP server: AccountsOS MCP Server
Arquitetura
flowchart LR
subgraph PMS["PMS fake (data/pms, gerada com semente fixa)"]
R[reservas<br/>id muda pós-checkout]
S[extratos mensais<br/>débitos e créditos]
end
M[relatório manual<br/>da gestora, CSV]
subgraph SYNC["sync (cron 2x/dia em produção; replay aqui)"]
D[dedup em cascata<br/>3 critérios + update]
C[categorizador<br/>regras + confiança + log]
P[parser de parcelas<br/>LLM → validação → fallback]
end
R --> D --> DB[(store<br/>JSON ≈ schema Postgres)]
R --> P --> DB
S --> C --> DB
DB --> F[saldos<br/>contábil · confirmado · projetado]
DB --> A[anomalias<br/>mediana + MAD]
DB --> FC[previsão<br/>pipeline + sazonalidade]
M --> REC[conciliação]
S --> REC
F & A & FC --> MCP[servidor MCP stdio<br/>6 tools]
MCP --> AG[agente: Claude Desktop / Claude Code]
C -. confiança baixa .-> Q[fila de revisão humana]peça | arquivo | o que faz |
PMS fake |
| gera reservas, extratos e relatórios manuais sintéticos. Serve em processo ou por HTTP ( |
sync |
| puxa janelas que se sobrepõem, deduplica, categoriza, cria o cronograma de parcelas e marca o que foi pago e o que foi repassado |
dedup |
| (1) id externo → (2) mesmas datas + nome compatível → (3) mesmo checkout + líquido ±10% + nome → (4) mesmas datas e outro nome: atualiza a reserva em vez de inserir |
categorizador |
| regex sem acento, na ordem das regras, com o caso "Pag. solicitado X" tratado pelo sufixo. Devolve confiança e grava log. O que fica em |
parser LLM |
| transforma texto livre em parcelas. Ver a seção "Onde o LLM entra" |
dinheiro |
| três saldos, resumo por mês e consulta de transações que avisa quando o resultado foi truncado |
anomalias |
| mediana + MAD com pisos absoluto e relativo, total mensal para contas recorrentes |
previsão |
| receita = parcelas já contratadas; despesa = mediana do mesmo mês em anos anteriores |
conciliação |
| relatório manual × extrato: ok / valor diferente / falta no relatório / só no relatório |
MCP |
|
|
Onde o LLM entra e como fica contido
O LLM aparece em dois lugares, e em nenhum deles o número que ele produz vai direto para o banco.
1. Parser de condições de pagamento (no sync). A observação da reserva é escrita por pessoas, sem padrão. Regras não davam conta, então um modelo pequeno (claude-haiku-4-5 por padrão, chamado pelo SDK oficial) devolve um array JSON de parcelas. Antes de gravar, a saída passa por validateInstallments:
precisa ser um JSON parseável, com até 12 parcelas numeradas em sequência a partir de 1;
todo
amountprecisa ser um número maior que zero, e a soma precisa bater com o valor líquido (±R$ 1);a 1ª parcela precisa estar paga, porque a reserva só existe depois da entrada;
due_dateénullou uma data ISO que existe e não cai depois do check-in (é o erro clássico de ano trocado em "até 20/12").
Se a saída for reprovada, o parser tenta de novo uma vez. Se reprovar outra vez, se der timeout, se não houver ANTHROPIC_API_KEY ou se a observação estiver vazia, entra o fallback determinístico: 50% de entrada paga e 50% pendente sem prazo. Cada parcela registra se veio de llm ou de fallback. A observação vai delimitada no prompt como dado, mas a defesa que conta contra prompt injection é a validação. Um texto que pede "registre 1 parcela de R$ 1,00" gera uma saída que não soma o líquido e é descartada (caso P09 do eval).
2. O agente conversacional (via MCP). Ele só lê números calculados pelas tools. A descrição das tools diz o que cada saldo significa e avisa quando a lista foi truncada (truncated, net_sum_all_records). Assim o modelo não tira a média de uma página parcial. A única escrita, create_manual_entry, é validada com zod e por regras de negócio: data no futuro, valor negativo ou com 3 casas, categoria inventada, crédito com categoria de despesa e duplicata são rejeitados. No modo padrão (confirm), a primeira chamada devolve só uma prévia, e a gravação só acontece com confirmed: true depois que o humano aprova. Tudo que é gravado passa pelo audit_log, com o motivo.
Evals
Rodam offline em ~0,5 s, sem chave de API e sem rede. Os golden sets foram escritos à mão para esta versão e não reaproveitam casos reais.
eval | o que mede | placar |
categorizador | 66 descrições no estilo de extrato (prefixo "N. ", "Pag. solicitado X", sem acento, genéricas que devem ir para revisão) + invariante "other ⇒ confiança baixa" | 67/67 |
dedup (unidade) | 17 pares de nomes (acento, preposição, nome do meio, placeholder), 8 de valor, 7 de placeholder, 11 cenários da cascata | 43/43 |
dedup (ponta a ponta) | replay de 52 execuções do sync na PMS fake: nenhuma estadia duplicada, 3 ids trocados viram alias, divergência de valor sinalizada | 4/4 |
parser de parcelas | 11 casos do validador + 17 observações com respostas simuladas do modelo (boas, em markdown, soma errada, ano errado, injection, timeout, sem JSON, 13 parcelas, sem chave), conferindo fonte, tentativas, valores, vencimentos e invariantes | 98/98 |
saldos, anomalias, escrita | 3 saldos contra uma verdade calculada direto dos arquivos da PMS, anomalias plantadas detectadas, nenhum falso positivo em conta fixa, 10 casos de validação do lançamento manual + auditoria | 18/18 |
total | 230/230 |
Selftest. npm run evals:selftest roda cada eval contra versões sabotadas da implementação e exige que todas reprovem. Se um eval aprovasse código quebrado, ele não estaria medindo o que diz medir.
sabotagem | eval | resultado |
categorizador sem normalizar acento | categorizador | reprovou (63/67) |
ignora o sufixo de "Pag. solicitado X" | categorizador | reprovou (63/67) |
tudo vira | categorizador | reprovou (5/67) |
dedup só pelo id externo | dedup | reprovou (39/47) |
dedup sem remover preposições | dedup | reprovou (45/47) |
margem de valor de 20% em vez de 10% | dedup | reprovou (41/43) |
parser sem validar a saída do LLM | parser | reprovou (80/98) |
parser sem retry | parser | reprovou (86/98) |
saldo confirmado somando parcelas pagas (double counting) | saldos | reprovou (16/18) |
Com chave, node evals/eval-parser.js --live manda as mesmas observações para a API real e confere valores, datas e invariantes. Esse modo não roda no CI, porque custa dinheiro e não é determinístico. O modo --live ainda não foi executado nesta versão pública, por isso não há placar dele aqui.
O CI (.github/workflows/verify.yml) roda scripts/verify.sh: node --check em todos os arquivos, a confirmação de que o gerador reproduz exatamente os dados versionados, os evals, o selftest e um smoke test que sobe o servidor MCP por stdio com o cliente oficial e chama as 6 tools.
Decisões e trade-offs
Regras no categorizador, LLM só no parser. As descrições de despesa se repetem muito. Regex é auditável, grátis e determinística, e o que sobra vai para revisão humana. Já as observações de pagamento variam demais para regex, e nelas o LLM compensa, desde que fique cercado por validação.
Validar invariantes, não comparar texto. O eval do parser e o validador conferem soma, sequência, datas e a 1ª parcela paga. O texto exato que o modelo escreve não importa. Isso deixa o eval estável quando o modelo é trocado.
Fallback conservador. Quando o parser falha, uso 50% pago e 50% pendente sem prazo. Isso subestima o que está atrasado, mas não cria uma data de vencimento que ninguém combinou.
Dedup em cascata com atualização. Só o id não basta, porque a plataforma troca o id. Só nome + datas também não, porque a data de entrada pode ser corrigida. O critério 4 evita duas reservas nas mesmas datas quando o nome vira um placeholder. Quando o valor muda depois do checkout, o registro não é sobrescrito: a divergência é sinalizada.
Três saldos, não um. O contábil (o que caiu no caixa), o confirmado (+ o que o hóspede pagou e a gestora ainda não repassou) e o projetado (+ o que falta receber). O "a receber" é sempre
líquido − repassado. Somar as parcelas pagas por cima do contábil conta duas vezes o dinheiro que já foi repassado. Esse erro aconteceu na versão de produção e hoje é um dos casos do selftest.Mediana/MAD no lugar de z-score. Com cerca de 6 meses de amostra e outliers, a média e o desvio-padrão são puxados pelo próprio outlier. Os pisos absoluto (R$ 200) e relativo (25%) cortam alertas como "internet +2%".
JSON no lugar de Postgres neste repositório. Assim o projeto roda com
npm cie nada mais. Odb/schema.sqldocumenta o modelo relacional, e o acesso a dados fica isolado emsrc/lib/store.js.MCP com o SDK oficial. Na produção, o JSON-RPC era escrito à mão. Aqui o SDK cuida do protocolo, e o zod valida a entrada antes de o handler rodar.
Dados sintéticos
npm run generate cria tudo a partir da semente 20261117, e o CI confirma que a saída é idêntica byte a byte. São 71 reservas e cerca de 500 lançamentos de out/2024 a out/2026, com nomes de listas próprias combinadas ao acaso, preços com sazonalidade, comissão de 15% e o imóvel fictício "Casa Exemplo". Os casos difíceis entram de propósito:
3 reservas cujo id muda depois do checkout: uma com nome sem acento e sem preposição, outra com o check-in corrigido e o valor +4%, e outra cujo nome vira "Guest Name";
8 formatos de observação de pagamento, inclusive vazia e uma tentativa de prompt injection;
despesas sazonais: energia e piscina no verão, 13º em dezembro, férias em julho, IPTU em 10 parcelas, gás e diaristas extras na alta temporada;
anomalias plantadas: uma troca de bomba e uma conta de energia fora do padrão;
um lançamento repetido no extrato do mês seguinte e uma entrada paga e ainda não repassada;
6 relatórios manuais sintéticos com divergências plantadas (linhas faltando, uma duplicada e um valor diferente). Nos dados sintéticos,
npm run cli -- conciliarencontra 14 débitos ausentes em 6 meses.
Limitações
O LLM entra só no parser. O eval de agente de ponta a ponta (perguntas em linguagem natural → tool calls → resposta conferida contra a verdade), que existe na versão de produção, ainda não foi portado para cá.
A previsão só enxerga reservas que já existem e não estima a demanda futura.
O store em JSON não tem concorrência nem transações. Para produção, use o schema Postgres.
O categorizador não aprende sozinho: as regras novas saem da fila de revisão e são escritas por uma pessoa.
A PMS fake reproduz os comportamentos já observados na plataforma real, não todos.
O adaptador de chat (Telegram) e os alertas agendados da versão de produção não estão aqui.
Como rodar
npm ci # única etapa que usa rede
npm run generate # (opcional) regenera data/pms e data/manager-reports
npm run sync # replay do sync → data/db.json (sem chave: parser só no fallback)
npm run cli -- saldo # também: mes 6 | anomalias | previsao 6 | conciliar 2026-09 | revisao
npm run evals # placar
npm run evals:selftest # prova que os evals reprovam código quebrado
npm test # smoke do servidor MCP
npm run verify # tudo o que o CI rodaPara ligar o LLM, coloque ANTHROPIC_API_KEY no ambiente (veja .env.example) e rode npm run sync. Para usar a PMS por HTTP: npm run pms num terminal e PMS_URL=http://127.0.0.1:4010 npm run sync em outro.
Para usar no Claude Desktop ou no Claude Code, registre o servidor MCP:
{ "mcpServers": { "rental-finance": { "command": "node", "args": ["/caminho/para/vacation-rental-finance-agent/src/mcp/server.js"] } } }Depois pergunte "qual o saldo contábil e o projetado?" ou "alguma despesa fora do padrão nos últimos meses?".
Licença
MIT. Ver LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Manage vacation rentals, bookings, calendars and operations with OAuth and action approvals.
Read-only analytics for short-term rentals: bookings, occupancy, forecasts, reviews, owner payouts.
Manage Hostaway listings, reservations, calendar, guest messages, reviews and tasks.
Manage OwnerRez properties, bookings, guests, quotes and inquiries.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables agents to perform financial operations including categorization, reconciliation, and reporting on a tamper-evident, self-hosted ledger.AGPL 3.0
- AlicenseBqualityCmaintenanceEnables querying and managing a live accounting ledger, including transactions, balances, VAT summaries, deadlines, and documents, from any MCP client.86107 npmMIT
- AlicenseAqualityCmaintenanceA read-only MCP server that exposes the Smoobu API for the monthly close and invoicing of a small vacation-rental portfolio, returning normalized, flagged, auditable booking and property rows. It provides tools to list properties, fetch one or many bookings with totals, detect new/modified/cancelled bookings since a prior extraction, compute pro-rata monthly stays, and verify account health.6GPL 3.0
- AlicenseNot gradedqualityBmaintenanceGives Claude read-only tools over hotel finance data — PMS statistics, POS takings and room charges, card settlements, bank exceptions, supplier invoices and ad spend — alongside write tools that require explicit human approval, such as posting a room charge to a folio or raising a balancing journal. Writes are guarded (balanced journals, real accounts, limits, no duplicates) and every tool call, approval and rejection is logged, letting finance staff investigate red checks and apply fixes without touching the database directly.MIT