--- last_reviewed: 2026-10-19 --- # MCP 接口文档 本项目的**唯一业务入口**是 MCP 服务器:`python -m src.mcp.server`(stdio 传输),工具名在宿主里表现为 `mcp__arb__`。 没有 Web 界面,没有 CLI 产品入口,本项目也不调用任何大模型——**读图与综合判断由宿主 Agent 完成**, 本项目只提供确定性工具(渲染、金额匹配、规则校验、文档生成、浏览器填报)。 ## 注册到宿主 DSH(profile patch,`~/.dsh/profiles//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` 形如 `20261019-6月毕业采集`)。 **阶段完全由产物推导**,没有状态机文件,可随时断电重来、可人工改文件后继续。 ``` cases// ├── case.json 名称、类型、上传令牌(切勿提交) ├── config.json 可选,会话级凭据覆盖(白名单字段) ├── <材料>.pdf|jpg 发票、支付截图、申请单原件 ├── inbox/ 手机扫码上传落地点,case_add_files(source="inbox") 收取 ├── pages//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//` - 文本层与读图结论冲突时(尤其编号、卡号、金额位数)必须复核,不要默认信任某一路 ## 提交:不做,永久不做 **提交由人类在财务系统里亲自点。** 本项目不提供、也不计划提供 `submit_execute` 这类代提交工具 (用户明确否决)。填报完成就是 Agent 的终点:把 `browser_page_state` 的截图与已填内容报给用户, 由本人核对后自己提交。删除(`#delete`)、撤回同理,都在页面上手工做。 计划中的只有一个**只读**工具 `submit_preview`:把单据页上的实际内容读回来、与会话里的账目对账、 截图留证,帮人在点提交前发现问题。它不点任何按钮。目前尚未实现,不要调用、也不要承诺。 同样已下线的:Word 版易耗品出库单(模板自 2026-08 起不在仓库,历史上长期静默失败)。 --- # 工具参考 以下内容由运行中的服务器 schema 自动生成,与代码同源(`scripts/mcp_smoke.py` 打印同一份清单)。 ## 会话与材料 每个报销任务是一个会话目录 cases//,阶段由产物推导。 ### `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` 把单张票据的抽取结果写入会话缓存(按 .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//.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 | 是 | — | — |