Files
Auto-Finance/docs/MCP.md
T
wandering 821822a408 docs: 全面体检并更新文档,工具参考改为从运行时 schema 生成
先体检再动手:44 个 Markdown,相对链接 0 处失效,逐份分辨「现状文档」与「历史记录」。
历史记录(error-experience/*、架构分析、已过时的全景图)保留原貌只加指路,不重写。

- 新增 scripts/gen_mcp_docs.py:起 stdio 子进程 list_tools 后整段重生成 docs/MCP.md 的「工具参考」
  (291 行 / 25 个工具,占这份文档四分之三,手工维护必漂)。--check 漂移退出码 1,--diff 打印差异;
  GROUPS 是刻意闸门——加了工具没登记就报错,防止「有工具没文档」。接进 make mcp-docs / docs-check
  与 tasks.py 同名任务
- 生成时抓到两处真实漂移:invoice_type 线上是 string/null 且默认 null,文档手写成 null/string 无默认;
  case_list 无参数在旧文档里写成一句散文。现已与线上一字不差
- 过期说法清理:src/README 的「browser_* 与 submit_* 尚未实现」(browser 早已联机验通、submit 定案不做);
  tests/README 的「当前 107 例」(现 148)与缺 test_mcp_browser.py 一行、「Playwright 填报不进单测」
  (现在选择器分支/状态判定/错误路径有单测,只有真实页面需要联机);.agents/README 的「Cursor Agent」;
  plans/README 里还写着「提交加确认 token」;scripts/data/README 表格有一行跑到正文后面
- 工程实践指南里把已删除的 CLI 入口当例子:mypy src/main.py、make run、python src/main.py 全部改为现状
  (mypy src / 唯一入口 python -m src.mcp.server),并补上「任务必须透传退出码」这条本轮踩到的坑
- README:「还没做完」重写为「边界与未完事项」——把刻意不做(提交、submit_* 整类、删除、撤回)与
  真的未完(科目/项目/人员固定取第 N 行,待业务口径)分开写;开发命令补 mcp-docs / docs-check / skill-install
- 操作指南:7.3 经济科目与项目从「要改就改索引」改成「已知限制,等口径,填报后务必人工核对」;
  相关命令补齐新目标;MCP.md 标准流程补上填报与「到此为止,提交由人」两步,并改掉与新纪律矛盾的
  「把截图报给用户」表述
- AGENTS:命令表加两行;测试段给出改工具后的固定顺序(mcp-smoke → mcp-docs → 重连宿主);提交纪律
  补「make check 之外还有 docs-check / skill-check 两道专项门禁」;截图产物改标注为排查证据
- install_skill.py:拿不到主目录时退出码改 4,别和「漂移」的 1 混成一类
- 已核对:submit_execute / src/main.py 在文档里的残留都是禁止性表述或「已删除」清单项,不是现状描述

验证:ruff / format --check / mypy src / deptry / pytest 148 passed / mcp-smoke 25 工具 /
docs-check 与 skill-check 均退出码 0。--no-verify 提交并手跑等价检查。
2026-10-20 23:12:46 +08:00

405 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
last_reviewed: 2026-10-19
---
# MCP 接口文档
本项目的**唯一业务入口**是 MCP 服务器:`python -m src.mcp.server`(stdio 传输),工具名在宿主里表现为 `mcp__arb__<tool>`。
没有 Web 界面,没有 CLI 产品入口,本项目也不调用任何大模型——**读图与综合判断由宿主 Agent 完成**,
本项目只提供确定性工具(渲染、金额匹配、规则校验、文档生成、浏览器填报)。
## 注册到宿主
DSH(profile patch,`~/.dsh/profiles/<profile>/cordis.patch.yml`):
```yaml
- insert:
- id: mcp-arb
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: arb
transport: stdio
command: 'D:/Code/AI Agent/自动报销系统/.venv/Scripts/python.exe'
args: ['-m', 'src.mcp.server']
cwd: 'D:/Code/AI Agent/自动报销系统'
```
> 注意:DSH 对 stdio 子进程做了环境变量清洗(抹掉 `*KEY|PASSWORD|SECRET|TOKEN` 与全部 `DSH_*`),
> 所以财务系统凭据只能来自 `scripts/data/config.json`,不能靠环境变量注入。
通用 MCP 客户端等价配置:
```json
{ "mcpServers": { "arb": { "command": "<项目根>/.venv/Scripts/python.exe", "args": ["-m", "src.mcp.server"], "cwd": "<项目根>" } } }
```
改完 Python 代码需要让宿主重连(DSH 会热插拔新工具条目,但**不会**重载已运行子进程里的代码;
杀掉 stdio 子进程即可自动重启并重同步工具)。
## 返回信封
所有工具返回统一信封,宿主判类型看 `ok`:
```json
{ "ok": true, "case_id": "...", "next_action": "..." }
{ "ok": false, "reason": "缺少 payment_records.csv", "next_action": "先 mcp__arb__match_payments 生成汇总" }
```
- **不抛异常穿透协议**:文件缺失、参数不合法、COM 失败都转成 `ok: false` + `reason` + `next_action`。
- `next_action` 是给 Agent 的下一步指令,通常是具体工具名,照做即可。
- 部分客户端会把结果包在 `content[0].text`(JSON 字符串)里,同时提供 `structuredContent`;两者内容一致。
## 会话目录
一个报销任务 = 一个 `cases/<case_id>/` 目录(`case_id` 形如 `20261019-6月毕业采集`)。
**阶段完全由产物推导**,没有状态机文件,可随时断电重来、可人工改文件后继续。
```
cases/<case_id>/
├── case.json 名称、类型、上传令牌(切勿提交)
├── config.json 可选,会话级凭据覆盖(白名单字段)
├── <材料>.pdf|jpg 发票、支付截图、申请单原件
├── inbox/ 手机扫码上传落地点,case_add_files(source="inbox") 收取
├── pages/<stem>/p01.png 渲染页面 + text.txt(PDF 文本层)+ meta.json(dpi/转正角/字符数)
├── .invoice_cache/*.json 单张票据抽取结果(宿主产出,本服务规范化后落盘)
├── match_result.json 匹配结果
├── invoice_groups.json 差旅/普通分组与计数
├── invoice_summary.csv 发票级汇总
├── payment_records.csv 支付记录汇总(申购单取数)
├── travel_applications.json 出差申请单
├── travel_info.json 差旅报销结构化数据
├── normal_info.json 普通报销结构化数据
├── artifacts/ 申购单等文档产物
├── screenshots/ (P3 落地后出现)填报过程截图
```
`cases/` 整个目录都在 `.gitignore` 里:发票含身份证号与卡号。
## 标准流程
1. `case_create` → 记下 `case_id` 与返回的 `upload_page`(手机扫码上传页)
2. `case_add_files`(本地路径用**正斜杠**)或 `case_add_files(source="inbox")` 收手机上传
3. `case_render_pages` → `text_chars>0` 先 `case_read_text`,否则读 `pages/**.png`
4. 逐张按 `extraction_spec` 出 JSON → `case_save_extraction`;核对后小改用 `case_patch_extraction`
5. `match_payments` → **逐条处理 `review_required`**(未匹配、发票总额小于支付总额、一对多凑额)
6. `info_skeleton` 取骨架 → 填值 → `save_info`(自动跑校验);`validate_info` 可反复试算不落盘
7. 普通发票再 `doc_requisition` 生成申购单
8. 每步之后 `case_status` 复查(`next_action` 与 `warnings` 最该看);`case_reset(scope)` 清派生产物重跑
9. 填报:`browser_open` → `browser_login`(返回 `need_human` 就请用户在**已打开的窗口**里完成再重调)→
`browser_new_form`(断线改用 `browser_open_form` 按**单据编号**找回原单续填,别重开新单)→
按返回的 `steps` 逐个 `browser_fill` → `browser_page_state` 复查 → `browser_close`
10. 到此为止:把单据编号与会话目录告诉用户,**提交由他本人在财务系统里做**
## 硬约束
- **不得回显密码**,不得把凭据写进产物或日志
- **不得臆造财务数据**:拿不准的字段标出来问用户,禁止自己宣布"可以提交"
- 产物只写 `cases/<case_id>/`
- 文本层与读图结论冲突时(尤其编号、卡号、金额位数)必须复核,不要默认信任某一路
## 提交:不做,永久不做
**提交由人类在财务系统里亲自点。** 本项目不提供、也不计划提供 `submit_execute` 这类代提交工具
(用户明确否决)。填报完成就是 Agent 的终点:把**单据编号**和会话目录(`case_status` 的 `case_dir`)告诉用户,
由他本人到财务系统里核对、提交、查状态。删除(`#delete`)、撤回同理,都在页面上手工做。
`submit_*` 整类(代提交、提交前预览 `submit_preview`)也一并不做:用户自己到财务系统里核对、提交、
查状态。Agent 到 `browser_fill` / `browser_page_state` 为止,把单据编号与会话目录告诉用户就够了;
截图留在 `cases/<id>/screenshots/` 供排查,不必逐张呈报。
同样已下线的:Word 版易耗品出库单(模板自 2026-08 起不在仓库,历史上长期静默失败)。
---
# 工具参考
<!-- 本节由 scripts/gen_mcp_docs.py 从运行时 schema 生成,别手改;改完工具跑 make mcp-docs -->
共 25 个工具,在宿主里都带 `mcp__arb__` 前缀。
## 会话与材料
每个报销任务是一个会话目录 cases/<case_id>/,阶段由产物推导。
### `mcp__arb__case_create`
新建报销会话目录,返回 case_id 与目录路径。
name 是本次报销的人类可读名称(如「6月毕业采集」);
invoice_type 可选 travel|normal,未定时留空,抽取后由发票分类决定。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `name` | string | 是 | — | — |
| `invoice_type` | string/null | 否 | null | — |
### `mcp__arb__case_list`
列出所有报销会话及其推导阶段、计数与待收文件数。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| (无参数) | — | — | — | — |
### `mcp__arb__case_status`
单个会话的完整态势:材料数、抽取完成/缺失/陈旧、匹配、信息、产物、收件箱、下一步建议。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
### `mcp__arb__case_add_files`
把材料拷入会话目录。
paths 传绝对路径列表(从手机下载目录、微信文件目录等任意位置取文件);
source='inbox' 表示收取手机扫码上传落在 inbox 的文件。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `paths` | array/null | 否 | null | — |
| `source` | string/null | 否 | null | — |
### `mcp__arb__case_inbox`
列出手机/PC 上传进来但尚未进入流程的文件。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
### `mcp__arb__case_reset`
清理派生产物以便重跑。scope=extractions|match|info|pages|artifacts|all。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `scope` | string | 否 | "extractions" | — |
## 抽取交接
渲染页面与读文本层由宿主 Agent 自己判断,本服务不调用任何模型。
### `mcp__arb__case_render_pages`
为材料准备可读取的页面图片与 PDF 文本层。
返回每项材料的页 PNG 路径、文本层字符数与自动转正角度。
有文本层(text_chars > 0)时应优先读文本层,比读图更准且省 token。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `files` | array/null | 否 | null | — |
| `dpi` | integer | 否 | 150 | — |
| `force` | boolean | 否 | false | — |
### `mcp__arb__case_read_text`
读取某材料渲染时提取的 PDF 文本层;无文本层时返回空。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `file` | string | 是 | — | — |
### `mcp__arb__extraction_spec`
返回抽取字段定义。kind 省略给全部文档类型;'info' 给 travel_info/normal_info 骨架。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `kind` | string/null | 否 | null | — |
### `mcp__arb__case_save_extraction`
把单张票据的抽取结果写入会话缓存(按 <stem><suffix>.json 约定)。
日期会规范成 YYYY-MM-DD、金额规范成两位小数字符串;
返回 warnings 提示必填缺失,但不阻断写入(完整性由 arb_validate_info 判定)。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `source_file` | string | 是 | — | — |
| `data` | object | 是 | — | — |
### `mcp__arb__case_patch_extraction`
局部修正已落盘的抽取结果(人工核对后改字段用)。
只合并传入的键,返回每个被改字段的 from/to,避免静默覆盖;
未在 schema 中出现的键会告警但仍写入(有些字段是给下游自定义用的)。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `source_file` | string | 是 | — | — |
| `patch` | object | 是 | — | — |
## 匹配与校验
金额匹配是确定性的;完整性的裁判是规则文件,不是宿主。
### `mcp__arb__match_payments`
按缓存做发票与支付记录的金额匹配,并写出 CSV 与 invoice_groups.json。
返回 matched / unmatched_invoices / unmatched_payments / review_required。
未匹配与一对多凑额项必须复核,不要盲信贪心结果。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `tolerance` | number/null | 否 | null | — |
### `mcp__arb__info_skeleton`
按校验规则生成「形状完整、值为空」的可填骨架。
规则里 required 为 true 的字段(含 remark 这类备注)要求键必须存在,
所以别凭记忆拼 JSON:先取骨架再逐个填值。骨架与 validation_rules.json 同源,不会漂移。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `info_type` | string | 是 | — | — |
### `mcp__arb__save_info`
写 travel_info 或 normal_info 到缓存,并立刻跑一次规则校验。
info_type 取 travel|normal。完整性以返回的 validation 为准,
不要自己在 data 里塞 can_submit。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `info_type` | string | 是 | — | — |
| `data` | object | 是 | — | — |
### `mcp__arb__validate_info`
校验报销信息完整性。省略 draft 时校验缓存里已存的 info。
也可传 draft 先试校验不落盘,适合改字段时反复迭代。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `info_type` | string/null | 否 | null | — |
| `draft` | object/null | 否 | null | — |
### `mcp__arb__rules_get`
返回声明式校验规则(config/validation_rules.json),供补字段前对照。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `info_type` | string/null | 否 | null | — |
## 文档产物
需要 Windows + Excel(COM)。Word 出库单已下线,只剩申购单。
### `mcp__arb__doc_requisition`
生成低值易耗品申购单(Excel,仅普通发票)。
exclude 传物品名关键词可剔除不参与申购的发票;project 覆盖「对应实验实训项目」,
默认取 normal_info 的报销事由。需要 Windows + 已安装 Microsoft Excel。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `exclude` | array/null | 否 | null | — |
| `project` | string/null | 否 | null | — |
## 浏览器分步填报
常驻会话加分步执行;提交由人在财务系统里做,工具不含提交动作。断线用 browser_open_form 按单据编号找回原单续填,别重开新单。
### `mcp__arb__browser_open`
打开(或复用)该会话的浏览器。
默认有头:提交不可逆,要看得见在点什么。同一会话重复调用直接复用现有浏览器。
被别的进程占用时返回 owned_by_other_process,别绕过去另开一个浏览器。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `headless` | boolean | 否 | false | — |
### `mcp__arb__browser_login`
登录信息门户。遇到扫码或验证码不要死等:返回 need_human,人工完成后再次调用。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `wait_seconds` | integer | 否 | 25 | — |
### `mcp__arb__browser_new_form`
进入报销系统并新建单据。kind 省略时按会话里已有的 info 推断。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `kind` | string/null | 否 | null | — |
### `mcp__arb__browser_open_form`
从列表页找回已存在的单据接着填(中途断了不必重来)。
match 必须在当前列表页唯一命中;命中多张直接失败,让宿主把关键词收紧——财务系统里
点错别人的单据是不能接受的后果。注意命中判据是**列表页可见文字**,长报销说明会被
CSS 截断(`元器件采购(P3 全 ...`),所以最稳的 match 是**单据编号**(不会被截断)。
preview_only=true 只搜索定位、不点开单据,用来先确认是哪一张。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `match` | string | 是 | — | — |
| `kind` | string/null | 否 | null | — |
| `preview_only` | boolean | 否 | false | — |
### `mcp__arb__browser_fill`
执行一步填报。step 按 browser_new_form 返回的 steps 顺序,不能跳步。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `step` | string | 是 | — | — |
### `mcp__arb__browser_page_state`
看当前页面:URL、标题、命中的表单标记、已完成步骤、最近截图。
at_portal 只表示在信息门户;进了报销系统要用 in_reimburse_system 与 on_form_page 判断。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `screenshot` | boolean | 否 | true | — |
### `mcp__arb__browser_steps`
列出该会话单据类型的步骤顺序与含义(不要求浏览器已打开)。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
### `mcp__arb__browser_close`
关闭浏览器并释放锁。不会提交也不会存草稿,填了一半的内容会丢。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |