"""MCP 服务器入口(本项目的唯一业务入口) 启动:python -m src.mcp.server (stdio 传输) 导入本模块必须零副作用:DSH 的 stdio 协商会先拉起一个探测进程再杀掉, 因此这里不启动浏览器、不开端口、不写状态文件,一切有状态动作发生在工具调用里。 """ from __future__ import annotations from collections.abc import Callable from typing import Any from mcp.server import MCPServer from .tools import analysis, documents, extraction, materials SERVER_NAME = "auto-reimbursement" INSTRUCTIONS = """财务报销自动化工具集(发票抽取交接 → 匹配 → 校验 → 文档 → 浏览器填报 → 提交)。 当前已实现(可直接调用): 1. mcp__arb__case_create 建会话;mcp__arb__case_add_files 拷入发票/支付截图/申请单 (路径一律用正斜杠,如 D:/Code/...;手机扫码上传的文件先 mcp__arb__case_add_files(source="inbox") 收取)。 2. mcp__arb__case_render_pages 渲染页面并取 PDF 文本层;text_chars>0 时优先 mcp__arb__case_read_text 读文本层, 否则读 pages 里的 PNG。文本层与读图结论冲突时(尤其编号、卡号、金额位数)必须复核,不要默认信任一路。 3. 按 mcp__arb__extraction_spec 的字段定义逐张产出 JSON,用 mcp__arb__case_save_extraction 落盘 (日期 YYYY-MM-DD、金额两位小数字符串;invoice_type 写错会被直接拒绝)。 4. mcp__arb__match_payments 做金额匹配;返回的 review_required 必须逐条处理(未匹配、发票总额小于支付 总额、一对多凑额),处理完再进下一步。贪心结果"成功"不等于"已核对"。 5. 依据 totals 与 suggested_invoice_type 综合缓存和匹配结果,产出 travel_info 或 normal_info, 用 mcp__arb__save_info 落盘(会自动跑规则校验);字段拿不准先 mcp__arb__rules_get 对照规则, 或用 mcp__arb__validate_info(draft=...) 不落盘反复试。不完整就向用户要补充材料,禁止自己宣布可提交。 拼 JSON 前先 mcp__arb__info_skeleton(info_type) 取骨架:required 为 true 的键必须存在(含值为空串的备注类 字段),骨架与 validation_rules.json 同源,别凭记忆造结构。 6. 普通发票用 mcp__arb__doc_requisition 生成 Excel 申购单(COM 自动化,需本机装有 Excel; exclude 可剔除不参与申购的物品;纯差旅没有申购明细会返回失败提示,不是错误)。 人工核对后要改单张票据的某个字段,用 mcp__arb__case_patch_extraction(只传要改的键,会回显 from/to)。 7. 每步之后用 mcp__arb__case_status 复查态势;阶段由产物推导,没有内部状态机。 mcp__arb__case_reset(scope) 可清派生产物重跑;mcp__arb__case_list 看全部会话。 尚未实现(**现在不要调用,也不要在回答里承诺**):Word 出库单与 Excel 申购单、浏览器填报、 提交预览与执行。需要这些步骤时告诉用户该环节还在改造中,可暂用旧的 Web 流程,不要自己拼 HTML 或猜接口。 硬约束:不得回显密码;不得臆造财务数据,拿不准的字段标出来问用户;产物只写 cases//。 """ TOOLS: list[Callable[..., dict[str, Any]]] = [ materials.case_create, materials.case_list, materials.case_status, materials.case_add_files, materials.case_inbox, materials.case_reset, extraction.case_render_pages, extraction.case_read_text, extraction.case_save_extraction, extraction.extraction_spec, extraction.case_patch_extraction, analysis.match_payments, analysis.save_info, analysis.validate_info, analysis.rules_get, analysis.info_skeleton, documents.doc_requisition, ] def build_server() -> MCPServer: """构造并注册全部工具(无副作用,可被探测进程安全导入)。""" server = MCPServer(name=SERVER_NAME, instructions=INSTRUCTIONS) for fn in TOOLS: server.tool()(fn) return server mcp = build_server() def main() -> None: """以 stdio 传输运行。""" mcp.run("stdio") if __name__ == "__main__": main()