内容
<div align="center">
<img src=".github/assets/banner.png" alt="MCPForge" width="350"/>
**巴西个人财务MCP服务器.**


[](https://github.com/astral-sh/ruff)



一个带有OFX/CSV解析、PT-BR分类和CPF/CNPJ验证的巴西个人财务MCP服务器 - 无需银行API。
</div>
---
## 演示
MCP客户端中的两个链接工具调用:
```text
用户:我导入了伊塔乌银行2月份的OFX文件。这个月我花在食物和交通上的钱是多少?
克劳德:[parse_ofx("/tmp/itau_fev2025.ofx")]
→ 143笔交易,银行提示:"itau",0个错误
[categorize_transaction × 143]
→ 规则层:98笔交易 | llm层:45笔交易
食品 + 餐厅:R$ 1,247.30(34笔交易)
IFOOD*PEDIDO, PADARIA NOSSA SRA, MCDONALD'S SP...
交通:R$ 389.10(18笔交易)
UBER *TRIP, 99APP, METRÔ SP BILHETE...
本月总计:R$ 8,643.22,143笔交易。
```
每次`categorize_transaction`调用都会返回分类的层级:
```json
{
"category": "交通",
"confidence": 1.0,
"path": "rules"
}
```
`path` ∈ `{rules, embeddings, llm}` — 分类来源始终明确。规则命中置信度为1.0表示正则表达式匹配;LLM命中置信度为0.7表示模型不太确定。调用者决定如何处理该信号。
---
## 为什么存在
巴西银行的导出不标准化。伊塔乌银行将PIX交易分散在三行OFX中,必须通过FITID前缀合并。Inter以`cp1252`导出,但在文件上标注`ENCODING:1252` — 一个被`ofxparse`拒绝的值。 Nubank在描述字段中嵌入专有元数据。BB省略某些交易类型的付款人CNPJ。现有的工具要么完全跳过巴西格式,要么悄悄地掩盖这些怪癖,从而在交易计数不准确时不给出任何提示。
模型上下文协议(由Anthropic于2024年末发布)已成为连接LLM客户端(如Claude Desktop、Cursor、IDE代理)到外部工具和数据的标准。截至2025年中期,实际上没有针对巴西金融工作流的已发布MCP服务器。每个巴西开发人员在构建个人财务助手时都从头开始重新实现OFX解析。该项目解决了这个问题,并将解决方案作为可重用的MCP服务器发布。
---
## 主要功能
- **特定银行解析器**,带有文档记录的怪癖 — cp1252编码修复适用于伊塔乌/Inter,PIX分割检测适用于伊塔乌,CSV模式下适用于所有5家银行的头部指纹对齐检测
- **透明分类** — 每次响应均返回`path: rules | llm`;没有黑盒;客户端准确知道为什么分配了类别
- **LLM调用前PII屏蔽** — `utils/pii.py:mask_br_documents`在进程外部之前无条件地从描述文本中剥离CPF/CNPJ(符合LGPD)
- **所有地方都使用`Decimal`** — 货币金额永远不是`float`;通过构造无法实现下游聚合的舍入错误
- **离线安全核心** — `validate_cpf`、`validate_cnpj`、`parse_ofx`、`parse_csv`均不进行网络调用;只有`categorize_transaction`和`lookup_cnae`会访问网络
---
## 快速入门
```bash
git clone <此仓库> && cd mcp-forge
uv sync
export ANTHROPIC_API_KEY=sk-... # 仅在 categorize_transaction 时需要
uv run mcpforge # 在stdio上启动MCP服务器
uv run pytest # 198个测试
```
### 离线模式(无Anthropic密钥)
`categorize_transaction` 可以在本地Ollama服务器上完全离线运行,使用Phi-3.5。 当`ANTHROPIC_API_KEY`未设置或通过`MCPFORGE_LLM_BACKEND=ollama`显式设置时自动选择。
```bash
ollama pull phi3.5
uv sync --extra ollama
unset ANTHROPIC_API_KEY
uv run mcpforge
```
使用`MCPFORGE_OLLAMA_HOST`和`MCPFORGE_OLLAMA_MODEL`覆盖Ollama主机/模型。 启动和调用超时:`MCPFORGE_OLLAMA_HEALTH_TIMEOUT_S`(默认2.0),`MCPFORGE_OLLAMA_CALL_TIMEOUT_S`(默认30.0)。
### OpenTelemetry指标(可选)
当配置了OTel端点时,除了JSONL跟踪日志外,还会发出两个指标:
- `mcpforge_tool_calls_total{tool, success}` — 每个工具调用的计数器
- `mcpforge_tool_latency_ms{tool}` — 工具延迟的直方图
```bash
uv sync --extra otel
export OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector
export OTEL_SERVICE_NAME=mcpforge # 默认:mcpforge
uv run mcpforge
```
如果没有环境变量或额外功能,指标发出是静默的noop;`logs/traces.jsonl`下的JSONL跟踪仍然是默认的可观察性表面。
### 客户端配置
将以下内容添加到您的MCP客户端配置中。适用于Claude Desktop的示例配置(`claude_desktop_config.json`)- Cursor、Zed和Windsurf遵循相同的`command` + `args` + `cwd`模式:
```json
{
"mcpServers": {
"mcpforge": {
"command": "uv",
"args": ["run", "mcpforge"],
"cwd": "/path/to/mcp-forge"
}
}
}
```
### 可用工具
| 工具 | 功能 | 网络? |
| --- | --- | --- |
| `validate_cpf(cpf)` | Receita Federal mod-11校验 | 否 |
| `validate_cnpj(cnpj)` | Receita Federal mod-11校验 | 否 |
| `parse_ofx(file_path)` | 解析巴西银行OFX;返回标准化交易 + `bank_hint` + 每笔交易错误列表 | 否 |
| `parse_csv(file_path, bank_hint?)` | 解析巴西银行CSV,自动对齐检测 | 否 |
| `lookup_cnae(cnpj)` | 通过BrasilAPI进行主要CNAE分类;缓存24小时 | 是 — BrasilAPI |
| `categorize_transaction(description, amount?)` | 规则 → LLM级联;返回 `{category, confidence, path}` | 条件 — Anthropic |
所有输入均使用Pydantic v2验证。错误返回MCP兼容的错误对象,而不是Python回溯。
---
## 架构
```mermaid
flowchart LR
A["MCP客户端\nClaude Desktop · Cursor · Zed · Windsurf"]
A <-->|stdio| B["FastMCP 服务器"]
B --> P["解析器/\nofx · csv"]
B --> V["验证器/\ncpf · cnpj · cnae"]
B --> C["分类器/\n级联"]
P --> Banks["银行适配器\n伊塔乌 · 巴西adesco · Nubank · Inter · BB"]
V --> BrasilAPI["BrasilAPI\n缓存24小时"]
C --> Rules["规则层\n零API成本"]
C -.->|"路线图"| Emb["嵌入层\nBGE-m3"]
C --> PII["pii.mask_br_documents"]
PII --> Haiku["Anthropic Haiku 4.5"]
```
单个进程Python服务器通过stdio传输 - 与任何标准MCP客户端(Claude Desktop、Cursor、Zed、Windsurf等)兼容,无需代理。解析器是无状态的。 CNAE查找带有内存中24小时TTL缓存。每个工具调用都会将结构化跟踪追加到`logs/traces.jsonl`,并带有延迟、token计数和美元成本。
---
## 差异化
### 1. 巴西垂直关注,不是翻译层
银行特定的OFX/CSV怪癖是一流的,而不是事后才考虑:
- [`parsers/ofx.py:_fix_encoding_header`](src/mcpforge/parsers/ofx.py) — 将非标准的`ENCODING:1252`声明(由伊塔乌、Inter等生成)映射到`USASCII`,然后由`ofxparse`解码内容,然后以`cp1252`解码内容。 如果没有此修复,这些文件会在解析过程中抛出`UnboundLocalError` — 只有在用户报告缺少交易时才会发现这种失败。
- [`parsers/itau.py`](src/mcpforge/parsers/itau.py) — 通过FITID前缀模式检测PIX交易;当有真正的多行OFX样本时,合并逻辑存根(欢迎贡献)。
- [`validators/cpf.py`](src/mcpforge/validators/cpf.py),[`validators/cnpj.py`](src/mcpforge/validators/cnpj.py) — Receita Federal mod-11校验,纯Python,毫秒级,无外部服务。
<details>
<summary>完整的25类别PT-BR分类法</summary>
`alimentacao` · `restaurantes` · `transporte` · `combustivel` · `moradia` · `utilities` · `telecom` · `saude` · `farmacia` · `educacao` · `lazer` · `streaming` · `vestuario` · `viagens` · `beleza` · `pets` · `impostos` · `tarifas_bancarias` · `salario` · `investimentos` · `transferencias` · `saques` · `doacoes` · `presentes` · `outros` · `uncategorized`
在[`classifiers/taxonomy.py`](src/mcpforge/classifiers/taxonomy.py)中定义。 v1中分类法是可配置的 - 固定分类法以便在调整级联之前标记黄金数据集。
</details>
### 2. 显示其工作的分类
大多数交易分类器返回一个标签 — 你信任它或不信任它。 MCPForge在每次调用时返回`path`,因此调用者有足够的信息来决定如何处理不确定性:
```
path: "rules" → 正则表达式匹配;置信度为1.0;零API成本
path: "llm" → 规则未命中;调用了Haiku;置信度反映模型确定性
path: "embeddings" → (路线图)最近邻在标记集上;无LLM成本
```
代理可以显示`llm/0.6`命中以供用户确认,同时静默接受`rules/1.0`命中。 分类源透明,因为区分很重要:错误类别来自规则错失与LLM没有良好匹配是不同类型的错误。
当前状态:级联是两层(规则 → LLM)。 嵌入层在[`cascade.py:42`](src/mcpforge/classifiers/cascade.py)中被stubbed — 它可以插槽而无需更改工具签名或`path`语义。
### 3. 诚实的评估,而不是标题数字
每类别的准确性比整体准确性更重要。 一个系统可能有87%的顶级准确率,但在BR工作流中可能有42%的F1分数(DARF、IPTU和IPVA付款的常见问题),而整体准确性完全掩盖了它。
计划:检查CI中运行的AI,使用500交易标记的黄金集(在所有5家银行和所有25个类别中平衡),并在每个类别中发布F1分数,以及整体准确率,当PR门控阻止>2pp的回归时。 黄金集将被提交到仓库中,而不是单独存储。
**当前状态:评估框架尚未构建。** 数字在基准测试中是SPEC目标,而不是测量值。 当评估框架落地时,它们将被替换为测量结果 — 包括任何未达到目标的类别。
---
## 基准测试/评估
### 目标(来自[SPEC.md](./SPEC.md) — 尚未测量)
| 指标 | 目标 |
| --- | --- |
| 顶级分类准确率 | ≥ 85% |
| `parse_ofx` 1 MB文件p95 | < 1.5 s |
| `categorize_transaction` p95 | < 400 ms |
| 每笔交易的LLM成本 | < $0.0005 |
| OFX覆盖率 | 5/5家银行,≥ 95%交易解析 |
_这些是校准估计值,而不是测量值。 评估框架正在路线图中。_
### 今日状态
| 子系统 | 状态 |
| --- | --- |
| 单元测试 | **181通过** (`uv run pytest`) |
| CSV解析 | 5家银行,头部指纹对齐检测; `tests/parsers/fixtures/`中的fixtures |
| OFX解析 | 通用ofxparse + cp1252怪癖修复;伊塔乌PIX分割存根;银行提示检测 |
| 分类器评估 | 还没有 — 正在路线图中构建Inspect AI + 500交易黄金集 |
| 延迟/成本基准 | 还没有 — 每笔调用的跟踪记录在`logs/traces.jsonl`中;没有聚合报告 |
目标将被替换为测量数字 — 包括任何类别未达到目标时的测量结果 — 当评估框架落地时。
---
## 技术栈
| 选择 | 为什么 |
| --- | --- |
| Python 3.11+ | `mcp` SDK是Python-first; `StrEnum`用于分类法 |
| `uv` | 可重复的快速安装;所有开发命令都是`uv run …` |
| FastMCP(`mcp ≥ 1.9`) | 官方SDK;stdio传输 = 与任何标准MCP客户端兼容 |
| Pydantic v2 | 每个工具输入/输出都是一个模型 — MCP规范合规性和输入验证在一层 |
| `ofxparse` + 编码修复 | 比自定义SGML解析器更便宜; cp1252怪癖修复是10行补丁 |
| `chardet` | OFX文件扩展名撒谎编码;内容被嗅探 |
| Anthropic Haiku 4.5 | 便宜且快速用于短PT-BR文本分类; ~$0.0005/tx目标 |
| `Decimal`(stdlib) | 钱永远不是`float` |
| `ruff` | lint + 格式在一个工具中 |
| `pytest` + `pytest-asyncio` | 今日181个测试 |
---
## 路线图
v1完成:
- Inspect AI评估框架(500交易黄金集,CI回归门控,模拟LLM运行)
- 三层级联 — 规则 → 嵌入(BGE-m3 k-NN)→ LLM
- Ollama离线后端(Phi-3.5)并带有有界超时和启动健康检查
- 无状态每文件重复注释(`Transaction.duplicate_of`)
- 可选OpenTelemetry指标(`mcpforge_tool_calls_total`,`mcpforge_tool_latency_ms`)
- [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) 带有完整的模块映射和设计原理
v1.x开放:
- **真实LLM评估基准** — 当前`evals/baseline_run.json`中的100%是对`evals/_mock_llm.py`中的确定性模拟进行的评估,而非实时Anthropic调用。 运行一次对抗Haiku 4.5(成本~$0.20–0.30用于整个500交易集)是验证SPEC ≥85%目标的过程。 过程在[`evals/README.md`](./evals/README.md#running-against-the-real-anthropic-api)中记录。
- **每银行后处理模块**适用于Bradesco, Nubank, Inter, BB *(被样本阻塞 — 见贡献)— 今天通用OFX/CSV解析器通过头部指纹检测处理所有五家银行,但银行特定怪癖(PIX拆分,嵌入式元数据,编码边缘情况)需要真实的导出以提取和测试它们。
- **伊塔乌PIX分割合并** *(被样本阻塞)* — [`parsers/itau.py`](src/mcpforge/parsers/itau.py)中的存根,待有真正的多行OFX样本。
不在v1范围内(v2候选人)- 可配置的分类法
- 交叉导入重复检测(有状态)
- 投资账户类型(CDB, FIIs, Tesouro Direto)
- `.xlsx` / PDF导入
- CNAE LLM生成的摘要在BrasilAPI之上
---
## 已知限制
- **银行特定后处理模块**适用于Bradesco, Nubank, Inter, BB *(被样本阻塞)* — 今天的通用OFX/CSV解析器处理所有五家银行,通过头部指纹检测识别它们,但没有运行银行特定的后处理 — 只要有匿名的陈述样本(请参阅贡献)。伊塔乌的PIX分割合并也是一个通道存根。
- **评估准确率仅为mock-LLM**。 `evals/baseline_run.json`中的100%基准线是对`evals/_mock_llm.py`中的确定性关键词模拟器进行的评估,而不是实时Anthropic调用。 需要实际LLM运行,然后再声明SPEC ≥85%目标。
- **单进程,单用户,本地**。stdio传输;不支持多租户或托管部署。
- **没有Open Finance / 直接银行API**;伊塔乌/巴西adesco API访问需要机构注册和开放金融合规 — 不在范围内。
- **没有PDF或.xlsx**。仅支持OFX和CSV。
- **银行特定怪癖修复仅适用于伊塔乌**。其他银行通过通用OFX/CSV流水线解析,但没有银行特定的后处理 — 只要有匿名的陈述样本(见贡献)。伊塔乌的PIX分割合并也是一个通道存根。
- **评估准确性仅基于模拟LLM**。 `evals/baseline_run.json`中的100%基准线是对`evals/_mock_llm.py`中的确定性关键词模拟器进行的评估,而不是实时Anthropic调用。 需要实际LLM运行,然后再声明SPEC ≥85%目标。
- **单进程,单用户,本地**。stdio传输;不支持多租户或托管部署。
- **BrasilAPI没有付费备用方案**。 如果不可用,`lookup_cnae`返回`cnae_available: false`并不崩溃 — 但没有替代数据源。
---
## 贡献
打开一个问题或PR。在提交之前,`uv run pytest && uv run ruff check && uv run ruff format --check`必须干净。
**最有价值的贡献:匿名OFX/CSV报表样本**来自五家支持的银行中的任何一家。具体需要:
| 银行 | 解锁什么 |
| --- | --- |
| 伊塔乌 | 真实的OFX与PIX → 在`parsers/itau.py`中实现多行合并逻辑 |
| 巴西德斯科 | 任意OFX/CSV → 创建`parsers/bradesco.py`如果怪癖证明需要的话 |
| 努布克 | 带专有描述字段的CSV → `parsers/nubank.py` |
| Inter | 带有cp1252/ISO-8859边缘情况的OFX → 确认编码修复覆盖范围 |
| BB | 某些交易类型上缺少付款人CNPJ的OFX → `parsers/bb.py` |
用`mask_br_documents`屏蔽CPF/CNPJ(如果有用),替换金额为合成值,并清除个人姓名/地址。结构和字段形状对于基于fixture的测试至关重要。
---
## 许可
MIT — 参见 [LICENSE](./LICENSE).