Skip to main content
Glama
adambbhe
by adambbhe

sanxiao-mcp — Kingdee Cloud·Xingchen "Sanxiao Project Management" MCP Server

GC032 Financial Agent · Sanxiao endpoint. App identifier bdi_projectmanagement, subscription URL https://cloud.kingdee.com/kae/#/market/detail?sid=1285.

Implemented per the official 《Sanxiao Project Management API_2025》 documentation + 《Kingdee Sanxiao Project Management API Development Framework》. Phase 1 read-only: all query operations enabled; write operations are in place in code but rejected by guards by default.

The shape of the Sanxiao API (understand this first, the rest is easy)

Sanxiao is not "one endpoint per business" — it is generic Bill CRUD: all bills — project profiles, loans, reimbursements, payments, working-hour entries, purchase requests — go through the same set of interfaces, driven by formId + field identifiers.

POST https://bj1-api.kingdee.com/bdiprojectapi/common/{action}
后端  openapi/ierp/kapi/app/bdi_projectmanagement/{action}

action ∈ { listQuery, getById, saveOrUpdate, submit, unSubmit,
           audit, unAudit, delete, push, operation }

So this service's structure is also "one gateway + two layers of wrapping", rather than dozens of endpoint constants.

Related MCP server: mcp-timely

Table of Contents

sanxiao/
├── config.py    环境与鉴权四要素;url() / headers() 在这里定形
├── forms.py     formId 登记表 + 中文别名解析(项目档案 → bdi_projectfile)
├── query.py     qParams 结构化查询 DSL:构造、校验、还原成类 SQL 可读串
├── models.py    saveOrUpdate 字段模型(8 种 fType + 分录 + 下推 + 附件)
├── guards.py    白名单 + 默认拒绝 + 审计
├── client.py    唯一 HTTP 出口 _post(),读写方法都从这里过
└── server.py    28 个 MCP 工具(通用层 + 语义层)
test_connection.py   L1 配置 → L2 网络 → L3 鉴权 → L4 只读 → L5 守卫
tests/               pytest:query / models / guards / client

Authentication: four request headers, no signature

Unlike kingdee-star-mcp (jdy open gateway) — Sanxiao requires no HMAC signature, just four headers, all obtained in advance via the standard Xingchen API:

Header

Source of value

Token

Product account-set-level token, obtained via Xingchen standard API authentication

X-GW-Router-Addr

IDC domain = the domain field in the 【Real-time Receiving Authorization】push message

groupname

Authorization info, pushed by the open platform to the sandbox message receiving address

accountid

Same as above

Pitfall warning: The official docs explicitly state "when debugging in the cloud platform API marketplace, X-GW-Router-Addr can be ignored". So many people get it working in the marketplace, then hit 404 as soon as they write code — because code calls must include this header. config.headers() already handles this; don't delete it.

Once you have the four elements, fill them into .env (template in .env.example).

Quick Start

pip install -r requirements.txt
cp .env.example .env        # 填入四要素
pytest -q                   # 69 项单测应全绿
python test_connection.py   # 分层联调,结果写入 connection_test_result.txt

On Windows, just double-click run_test.bat.

MCP Tools (28)

Meta Information

Tool

Purpose

sx_health

Gateway address, read-only switch, readiness status of the four elements and what's missing

sx_list_forms

Registered formIds, Chinese names, default fields

sx_capabilities

Available actions, read-only operation whitelist, currently permitted write actions

Generic Layer — Full Mapping of Official Interfaces

Tool

Official Interface

sx_list_query

listQuery

sx_get_by_id

getById

sx_operation

operation (constrained by the read-only whitelist)

sx_build_query

Local tool: translates simplified conditions into qParams, sends no request

Semantic Layer — No Need to Memorize formIds

sx_list_projects / sx_list_reimbursements / sx_list_loans / sx_list_payments / sx_list_working_hours / sx_get_bill sx_query_cost_budget / sx_query_material_budget / sx_query_working_hour_budget sx_get_user_permission / sx_get_form_config / sx_workflow_status / sx_get_app_parameter

Write Layer — Rejected by Default

sx_build_bill_payload (only assembles, does not send; usable in phase 1 for manual review of the payload) sx_save_or_update / sx_submit / sx_un_submit / sx_audit / sx_un_audit / sx_delete / sx_push

How to Write Query Conditions

The official qParams is an array of conditions: top-level items are joined by and, and within a condition group items are joined by joinKey.

[
  { "childGroup": false, "qKey": "number", "qCp": "like", "qValue": "ew" },
  { "childGroup": true,  "joinKey": "or", "childCondition": [
      { "childGroup": false, "qKey": "number", "qCp": "=", "qValue": "new5" },
      { "childGroup": false, "qKey": "number", "qCp": "=", "qValue": "New" }
  ]}
]
// 等价于 number like '%ew%' and (number='new5' or number='New')

Comparison operators: = > >= < <= != like likeLeft. There is no in — use query.any_of() or the or-group in sx_build_query to simulate it. Entry fields are written as "entry identifier.field identifier", e.g. projectfileteam.teamstaff.

Security Model

The guard is a whitelist + default-deny three-layer design:

  1. Action classificationlistQuery/getById/operation are reads; the other seven are writes.

  2. operationKey whitelistoperation looks like a read interface, but operationKey is a free-form string, so the nine methods in doc sections 10.1–10.9 get a second whitelist pass.

  3. Funds-related operations permanently write-blocked — writes and workflow actions on payment bills (bdi_ex_pay*), even SX_ALLOW_WRITE_ACTIONS=* will not bypass this.

Order for enabling writes in phase 2: SX_READONLY=false → add actions one by one in SX_ALLOW_WRITE_ACTIONS for gray rollout. Do not jump straight to *.

All calls and blocks are logged to the sanxiao.audit logger.

Real Payloads Overturn What the Docs Suggest

The official 《Sanxiao-API Reference Code》 is a complete bdi_projectfile bill payload (saved as tests/fixtures/projectfile_reference.json). It is more trustworthy than the doc examples, and overturns four assumptions taken for granted — each one is pinned down in tests/test_reference_payload.py, and reverting any of them will fail immediately:

What the doc examples suggest

What the real payload shows

Every field has fValue

Empty-value fields omit the fValue key entirely, not fValue:""

enum must come with fValueText

status/enable/enablecostamtctl all have only fValue

fValue is always a string

Native true / false / 0 appear, mixed with string "10"

fValueText is exclusive to enums

bd also carries it, holding the display name of the base data

The third one is the most dangerous: an earlier str(d["fValue"]) in field_from_dict, when hitting {"fType":"enum","fValue":true}, would produce Python-style "True" — a string with a capital T, which the server does not recognize, and the error message will not tell you this is the cause. Now fValue is passed through as-is.

The benefit is that getById responses can be fed straight back into saveOrUpdate (change one or two fields and save again), lossless round-trip, and this path is guarded by test_roundtrip_is_lossless.

Additionally, eight base-data formIds were extracted from the bd field of the payload and registered in forms.py: bd_employee, bd_department, bd_customer, bdi_bd_customer_fork, bdi_projecttypes, bdi_projectarea, bdi_projectstauts, bdi_projectroles.

bdi_projectstauts is not a typo — the vendor spelled status as stauts, and both the formId and field names use that spelling. Don't "fix it" out of habit.

Where Field Identifiers Come From

Don't guess. The authoritative method is in the Xingchen UI: Bill list → More → Import Data → Template Management → New Template.

Once running, you can also look it up in reverse: sx_get_form_config(form_id) calls operation.getUserconfig and returns that bill's field configuration.

forms.py registers 15 formIds: seven from the official docs (bdi_projectfile, bdi_ex_loan, bdi_ex_bx, bdi_ex_pay, bdi_fillinworkinghours, pur_bill_request, bd_auxinfo), and eight base-data references from the reference-code payload. For other bills, just pass the formId directly to sx_list_query — no need to register first.

Same for field names — only bdi_projectfile's fields have been verified against the real payload (note it uses status/enable, not billstatus; that belongs to business bills). The default fields for other bills are still inferred by convention; after getting them working, please verify with sx_get_form_config.

Relationship with kingdee-star-mcp

The two are two open capabilities of the same Xingchen account set, each deployed independently:

kingdee-star-mcp

sanxiao-mcp

Gateway

api.kingdee.com jdy gateway

bj1-api.kingdee.com Sanxiao gateway

Auth

HMAC signature + app-token two-layer credentials

Four headers, no signature

Endpoints

/jdy/v2/{module}/{object} hundreds

common/{action} ten

Coverage

Finance (vouchers, reimbursements, receivables/payables)

Project management (projects, working hours, budgets, reimbursements)

Sanxiao's Token must first be obtained via the Xingchen standard API — if you've already gotten the authorization chain working in kingdee-star-mcp, you can fill the token you obtained along with the domain/groupname/accountid from the authorization push directly into this project's .env.

Known Gaps

  • The official docs do not provide a unified response body schema; client._unwrap() does a loose unwrap: if it can recognize errcode/success/data it normalizes, otherwise it returns as-is — better to return extra data than to swallow data by guessing the structure wrong. It can be tightened once real responses are available.

  • Bill status codes are not fully documented. In the reference code, project profile status="A", enable="1", but what A/B/C each mean, and the value domain of billstatus for business bills, still lack authoritative explanation. The semantic layer's status parameter currently passes through the raw identifier.

  • In the reference code, a few fields have contradictory fType values — phaseplanenddate, phaseenddate are declared as num yet are clearly dates. This is an inconsistency in the vendor's own payload; the model layer accepts it as-is without correction, so as not to "help in the wrong direction".

  • Attachment upload goes through base64; large files need evaluation against the gateway size limit, which the docs do not specify.

F
license - not found
C
quality
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    A
    quality
    B
    maintenance
    Read-only MCP connector for querying the Protheus (TOTVS) system, exposing 10 GET endpoints as MCP tools with OAuth2 authentication and friendly error handling.
    10
  • A
    license
    B
    quality
    C
    maintenance
    MCP server for Kingdee Cloud (K3Cloud) ERP that enables AI assistants to query and operate ERP data through natural language, supporting bills, metadata, and read/write operations.
    8
    1
    Apache 2.0

View all related MCP servers

Related MCP Connectors

  • A paid remote MCP for AI SDK data query MCP, built to return verdicts, receipts, usage logs, and aud

  • Log, query, and edit expenses, budgets, and accounts in Ledgy from any MCP-compatible AI assistant.

  • Read-only MCP server for ClassQuill, a tutoring-business-management platform.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/adambbhe/kingdee-sanxiao-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server