kingdee_star
by adambbhe
README.md
# kingdee_star —— 金蝶云星辰 MCP Server
GC032 财务智能体 · 金蝶端(钉钉侧已由钉钉 MCP 打通,本仓库只做金蝶)。
对齐需求 **v2.1**:一期**只读** + 发票税额计算 + 报销单待录入报文生成。
> **v0.2.0 重要变更**:鉴权、端点、税额引擎均已按官方文档与沙箱实测重写。
> v0.1.0 的端点常量(`/finance/expense` 一类)在真实网关上并不存在,请勿沿用。
## 目录
```
kingdee_star/
├── kingdee_star/
│ ├── config.py # 环境/凭据(两层:ISV + 租户),只读/写开关
│ ├── signer.py # jdy 网关签名(X-Api-Signature / app_signature)
│ ├── models.py # Invoice / ExpenseDraft / ExpenseLine
│ ├── tax_engine.py # 发票税额决策树(专票/铁路/航空/旅客运输/公路水路/其他)
│ ├── guards.py # 只读守卫 + 受控写入守卫(白名单+默认拒绝)+ 审计
│ └── star_client.py # 鉴权 + 只读查询 + 报销报文生成
├── test_connection.py # 分层联调测试(配置→网络→鉴权→只读→dry-run)
├── run_test.bat # Windows 一键跑:装依赖 + 单测 + 联调
├── tests/ # pytest:税额引擎 + 签名算法
└── .env.example # 配置模板
```
## 鉴权(两层凭据,别搞混)
| 层 | 取值 | 用途 |
|---|---|---|
| ISV 应用 | `JDY_CLIENT_ID` / `JDY_CLIENT_SECRET` | `X-Api-ClientID` 头 + `X-Api-Signature` 的 HMAC 密钥 |
| 租户账套 | `JDY_APP_KEY` / `JDY_APP_SECRET` | 计算 `app_signature`,换 `app-token` |
链路:
```
POST /jdyconnector/app_management/push_app_authorize?outerInstanceId=...
→ data[0].appKey / appSecret
GET /jdyconnector/app_management/kingdee_auth_token?app_key=..&app_signature=..
→ data['app-token'](有效期约 2h)
GET /jdy/v2/{module}/{object}
→ 头带 app-token + X-Api-* 签名 + X-GW-Router-Addr
```
**三个容易踩的坑**(`signer.py` 已处理,改动前先看注释):
1. `hash_hmac(..., raw_output=false)` 返回 **hex 字符串**,base64 的输入是这个 hex,不是 raw digest。
2. 待签名串里的头名是**小写**,且 nonce 在前、timestamp 在后,与 `X-Api-SignHeaders` 声明的顺序相反;末尾还有一个换行。
3. `X-GW-Router-Addr`(取推送报文里的 `domain`,如 `https://tf.jdy.com`)是**全局必填头**,官方文档 370 个接口全部标注必填。
## 快速开始
```bash
pip install -r requirements.txt
cp .env.example .env # 填入凭据
pytest -q # 单测应全绿
python test_connection.py # 联调:配置→网络→鉴权→只读→dry-run
```
Windows 直接双击 `run_test.bat`(结果写入 `connection_test_result.txt`)。
## 作为 MCP Server 使用
```bash
python -m kingdee_star.server # stdio
```
把 `mcp.config.json` 里的 `kingdee-star` 合并进客户端 `mcpServers` 配置(改 `cwd` 与 `env`)。
兼容 `mcp` 1.x 与 2.x 两个大版本。
### 工具清单(17)
| 类别 | 工具 | 端点 |
|---|---|---|
| 元/鉴权 | `kdy_health` `kdy_auth_fetch_token` | — |
| 探针 | `kdy_current_user` | `sys/current_user_info` |
| 只读·科目 | `kdy_list_account` `kdy_list_account_type` | `fi/account` `fi/account_type` |
| 只读·凭证 | `kdy_list_voucher` `kdy_get_voucher` | `fi/voucher` `fi/voucher_detail` |
| 只读·发票 | `kdy_list_invoice` `kdy_get_invoice` | `fi/invoice_fp` `fi/invoice_detail` |
| 只读·收付 | `kdy_list_ar_receive` `kdy_list_ap_pay` | `arap/ar_credit` `arap/ap_credit` |
| 只读·往来 | `kdy_reconciliation` `kdy_customer_debt` | `arap/reconciliation_statement` `arap/customer_debt` |
| 只读·报销 | `kdy_get_reimb_detail` `kdy_list_expense` | `ebx/reimb_detail`(列表接口不存在) |
| 计算 | `kdy_calc_invoice_tax` | 本地,不调金蝶 |
| 报文生成 | `kdy_fill_reimbursement` | 仅 dry-run,见下 |
> 销项/进项发票不是两个端点,用 `fi/invoice_fp` 的 `bill_type` / `invoice_type` 过滤。
## ⚠ 报销写入的现实约束
官方开放平台共 370 个接口,**`ebx` 模块只有「报销单详情」一个 GET**,
既没有报销单列表,也没有任何报销单保存接口。
因此需求 v2.1 里的「发票代填报销单草稿」**无法通过开放 API 落库**:
- `kdy_fill_reimbursement` 只产出**已算好税额、已过合规校验的待录入报文**,
`dry_run=False` 会被显式拒绝。
- 要真正自动写入,只能走非开放-API 通道(RPA / 前端接口 / 找金蝶开定制接口)。
## 安全边界
- `JDY_READONLY=true` → 纯只读。
- 守卫层为**白名单 + 默认拒绝**:路径变形(尾斜杠、子路径、大小写)均无法绕过。
- 付款/凭证/科目写入、报销提交审批 → 永久禁止(`FORBID_ENDPOINTS`),人工执行。
- 全量审计:`kingdee_star.audit` 日志。
## 税额引擎
| 票种 | 口径 |
|---|---|
| 专票 | 取票面税额与不含税金额 |
| 旅客运输(列示税额) | 取票面税额;缺 `face_tax` 显式报错,不静默降档 |
| 铁路 | ÷1.09×9% |
| 航空 | (票价+燃油附加费)÷1.09×9%,校验用 `taxable_base()` 而非 `total` |
| 公路/水路 | ÷1.03×3% |
| 其他普票 | 不可抵扣;若票面已列税额会抛提示要求人工确认票种 |
旅客运输抵扣缺出行人 → 合规校验置为不可抵扣。负数(红字)发票直接拒绝。
TDQS
A3.5/5.0
Scored across 17 tools
Disambiguation5/5
每个工具针对明确的资源或操作:健康检查、令牌获取、用户、科目、凭证、发票、收付款、对账、余额、报销、税务计算和填写。list_与get_工具清晰区分实体层级,没有两个工具做同一件事。
Naming Consistency4/5
大多数工具采用 'kdy_动词_名词' 模式,如 list_account, get_voucher, calc_invoice_tax。少数如 reconciliation 和 customer_debt 省略动词,但仍是可读的名称,整体风格基本统一,仅有轻微不一致。
Tool Count4/5
17个工具对于财务查询域来说略多,但每个工具覆盖了必要的实体和操作,没有冗余。这个数量在合理范围内,略超出典型的最优区间但完全可接受。
Completeness4/5
覆盖了核心财务实体(科目、凭证、发票、收付款、对账、客户欠款、报销)的查询和详情,并提供了税务计算和受控写入凭证代填。缺少更新删除操作,但该API设计为只读为主,写入受控,基本满足需求。
Maintenance
ActivityMaintained
ResponsivenessNo issues