Files
Auto-Finance/docs/MCP.md
T
wandering 2e2519632a docs+chore: 定案——提交永远由人类执行,删掉 submit_execute 的全部规划
用户明确否决代提交工具。这不是「还没做」,是能力边界,所以凡是把它写成「待实现」的地方都改了,
免得以后有人以为补个实现就齐活。

- server.py INSTRUCTIONS 改成硬约束:本项目不提供也不计划提供点提交的工具;删除(#delete)、
  撤回同理。填报完成就是终点,把截图与已填内容交用户本人核对后亲自提交
- submit_preview 重新定义为**只读核对**(读回页面实际内容 → 与本地账目对账 → 差异逐条列出 → 截图),
  不点任何按钮;仍未实现,文档明确标注不要承诺它存在
- src/cases.py 删掉 submitted 阶段与 meta.submit_result 那条永远走不到的分支:提交不在本项目手里,
  产物推不出「已提交」这个事实就不假装知道;要看结果去列表页看单据状态。previewed 保留
  (它对应真会落盘的只读产物 submit_preview.json)
- 文案:browser.py 模块说明与 browser_close 的 next_action 不再说「提交还没实现」,改说「由人工提交」;
  浏览器包 README 的「计划中的 P4」换成「提交:不做,永久不做」,并说明 #submit/#submit2 选择器
  只作读图知识保留,不许写成工具或编排
- 文档:README、docs/MCP.md(新增「提交:不做,永久不做」一节)、操作指南(数据流标注到此为止、
  提交小节标明工具不提供该动作)、SKILL 的提交段改为永久约束;方案文档 §16 记录定案与理由,
  并把早期把 submit_execute 列为待办的 4 处改为删除线 + 指向 §16

验证:pytest 148 passed;ruff / format --check / mypy src / deptry 全绿;mcp_smoke 25 工具,
INSTRUCTIONS 2224 字节。--no-verify 提交并手跑等价检查。
2026-10-20 21:00:48 +08:00

395 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 落地后出现)填报过程截图
└── submit_preview.json (只读核对落地后出现)页面回读快照与对账结果
```
`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` 复查;`case_reset(scope)` 清派生产物重跑
## 硬约束
- **不得回显密码**,不得把凭据写进产物或日志
- **不得臆造财务数据**:拿不准的字段标出来问用户,禁止自己宣布"可以提交"
- 产物只写 `cases/<case_id>/`
- 文本层与读图结论冲突时(尤其编号、卡号、金额位数)必须复核,不要默认信任某一路
## 提交:不做,永久不做
**提交由人类在财务系统里亲自点。** 本项目不提供、也不计划提供 `submit_execute` 这类代提交工具
(用户明确否决)。填报完成就是 Agent 的终点:把 `browser_page_state` 的截图与已填内容报给用户,
由本人核对后自己提交。删除(`#delete`)、撤回同理,都在页面上手工做。
计划中的只有一个**只读**工具 `submit_preview`:把单据页上的实际内容读回来、与会话里的账目对账、
截图留证,帮人在点提交前发现问题。它不点任何按钮。目前尚未实现,不要调用、也不要承诺。
同样已下线的:Word 版易耗品出库单(模板自 2026-08 起不在仓库,历史上长期静默失败)。
---
# 工具参考
以下内容由运行中的服务器 schema 自动生成,与代码同源(`scripts/mcp_smoke.py` 打印同一份清单)。
## 会话与材料
每个报销任务是一个会话目录 cases/<case_id>/,阶段由产物推导。
### `mcp__arb__case_create`
新建报销会话目录,返回 case_id 与目录路径。
name 是本次报销的人类可读名称(如「6月毕业采集」);
invoice_type 可选 travel|normal,未定时留空,抽取后由发票分类决定。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `name` | string | 是 | — | — |
| `invoice_type` | null/string | 否 | — | — |
### `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 | 否 | — | — |
| `source` | null/string | 否 | — | — |
### `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 | 否 | — | — |
| `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` | null/string | 否 | — | — |
### `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 | 是 | — | — |
## 匹配与校验
匹配与校验都是确定性代码;结论以 validate_info 为准,不要自己宣布可提交。
### `mcp__arb__match_payments`
按缓存做发票与支付记录的金额匹配,并写出 CSV 与 invoice_groups.json。
返回 matched / unmatched_invoices / unmatched_payments / review_required。
未匹配与一对多凑额项必须复核,不要盲信贪心结果。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `tolerance` | null/number | 否 | — | — |
### `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` | null/string | 否 | — | — |
| `draft` | null/object | 否 | — | — |
### `mcp__arb__rules_get`
返回声明式校验规则(config/validation_rules.json),供补字段前对照。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `info_type` | null/string | 否 | — | — |
## 文档产物
Excel COM,需本机装有 Microsoft Excel;仅普通发票需要。模板顺序:REQUISITION_TEMPLATE → templates/ → 项目根。
### `mcp__arb__doc_requisition`
生成低值易耗品申购单(Excel,仅普通发票)。
exclude 传物品名关键词可剔除不参与申购的发票;project 覆盖「对应实验实训项目」,
默认取 normal_info 的报销事由。需要 Windows + 已安装 Microsoft Excel。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `exclude` | array/null | 否 | — | — |
| `project` | null/string | 否 | — | — |
## 浏览器分步填报
需先 `uv run playwright install chromium`。默认**有头**;归属由 `cases/<id>/.browser.lock` 记录 (PID + 心跳,180 秒未心跳可被接管)。中途断了不必重来:用 `browser_open_form` 从列表页找回原单据 续填,**不要再 `browser_new_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` | null/string | 否 | — | — |
### `mcp__arb__browser_open_form`
从列表页找回已存在的单据接着填(中途断了不必重来)。
match 用报销说明里的关键词,必须在当前列表页唯一命中;命中多张直接失败,
让宿主把关键词收紧——财务系统里点错别人的单据是不能接受的后果。preview_only=true
只搜索定位、不点开单据,用来先确认是哪一张。
| 参数 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `case_id` | string | 是 | — | — |
| `match` | string | 是 | — | — |
| `kind` | null/string | 否 | — | — |
| `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 | 是 | — | — |