fxiaoke-mcp
by zs001122
README.md
# fxiaoke-mcp
纷享销客 OpenAPI v2 的 MCP 服务。已在 sandbox 租户全链路实测通过,目前提供 19 个工具:全部业务对象(含 `__c` 自定义对象)的增删改查、作废/恢复、变更负责人,公海、成交状态、查价、商机阶段推进等模块专属操作,以及客户 360° 视图、销售漏斗、报价比价等场景工具。
## 项目结构
```
src/
├── server.py # 入口:注册 core + 全部业务域
├── fxk_client.py # HTTP 客户端:token 缓存、统一请求、预设/自定义对象路由
├── core/
│ └── tools.py # 通用工具(10 个,所有对象共享,勿加模块专属逻辑)
└── modules/ # 按纷享文档业务域划分(与 developer.fxiaoke.com 层级一致)
├── __init__.py # 业务域注册表(DOMAINS)
├── base.py # 自定义功能接口桥(APL 控制器函数 /cgi/crm/v2/special/function)
├── customers_and_contacts/ # 客户与联系人:account.py(客户)、account_addr.py(客户地址)
├── opportunities_and_leads/ # 商机与线索:opportunity.py(商机2.0)、leads.py(线索)
├── commodities_and_products/ # 商品与产品:product.py(产品)
├── quotations_and_pricing/ # 报价与价目:quote.py(报价单)、price_book.py(价目表)
├── contract_management/ # 合同管理:contract.py(合同对象)
└── finance_and_payment/ # 财务与回款:account_fin.py(客户财务信息)
```
每个业务域包内,模块文件分两段:`register_native`(原生 OpenAPI 专属工具)、`register_custom`(平台自定义功能接口,经 `base.call_custom_function` 桥接)。新工具归属:对象通用能力 → `core/tools.py`;模块专属 → 对应业务域下模块文件。新增模块:在域包内建文件并登记到该域 `MODULES`;新增业务域:建域包并登记到 `modules/__init__.py` 的 `DOMAINS`。
## 工具清单
| 工具 | 说明 | 对应接口 |
|---|---|---|
| `fxk_query_data` | 条件查询列表(过滤 + 分页 + 字段投影) | `/cgi/crm/v2/data/query` |
| `fxk_get_record` | 按 ID 查单条详情 | `/cgi/crm/v2/data/get` |
| `fxk_create_record` | 创建(支持主从对象一起建) | `/cgi/crm/v2/data/create` |
| `fxk_update_record` | 修改字段 | `/cgi/crm/v2/data/update` |
| `fxk_invalid_record` | 作废 | `/cgi/crm/v2/data/invalid` |
| `fxk_recover_records` | 恢复已作废(批量) | `/cgi/crm/v2/data/recover` |
| `fxk_delete_records` | 删除已作废(批量,高危) | `/cgi/crm/v2/data/delete` |
| `fxk_change_owner` | 变更负责人 | `/cgi/crm/v2/data/changeOwner` |
| `fxk_describe_object` | 对象元数据(字段列表/必填/类型) | `/cgi/crm/v2/object/describe` |
| `fxk_list_objects` | 常用业务对象清单 | `/cgi/crm/v2/object/list` |
| `fxk_choose_from_pool` | 公海/线索池领取(客户+线索) | `/cgi/crm/v2/data/choose` |
| `fxk_return_to_pool` | 退回公海/线索池(客户+线索) | `/cgi/crm/v2/data/return` |
| `fxk_remove_from_pool` | 公海移除(仅客户) | `/cgi/crm/v2/data/remove` |
| `fxk_change_deal_status` | 修改客户成交状态 | `/cgi/crm/v2/special/AccountObj/action/ChangeDealStatus` |
| `fxk_customer_overview` | 客户 360° 视图(详情+联系人+地址+财务+商机+报价+合同一次聚合) | 编排 query/get |
| `fxk_opportunity_pipeline` | 销售漏斗(按阶段汇总数量/金额 + 商机清单含客户名) | 编排 query + 阶段实例 |
| `fxk_quote_price_check` | 报价比价(明细行报价 vs 价目表实际价格逐行比对) | 编排 query/get + getRealPrice |
| `fxk_get_real_price` | 查询产品价目表实际价格 | `/cgi/crm/v2/availableRange/getRealPrice` |
| `fxk_move_opportunity_stage` | 商机阶段推进/回退(两段式) | `getInstanceInfoByObjectId` + `stageMoveTo` |
| `fxk_customer_overview` | 客户 360° 聚合(详情+联系人+地址+财务+商机+报价+合同) | 编排多个查询接口 |
路由规则:`__c` 后缀的自定义对象自动走 `/cgi/crm/custom/v2/data/*`,预设对象走 `/cgi/crm/v2/data/*`。
## 环境准备
1. Python 3.11+,安装依赖:
```powershell
python -m venv .venv
.\.venv\Scripts\pip.exe install -e .
```
2. 复制 `.env.example` 为 `.env` 并填写:
| 变量 | 说明 |
|---|---|
| `FXK_APP_ID` / `FXK_APP_SECRET` / `FXK_PERMANENT_CODE` | 纷享后台自建应用凭证(管理后台 → 应用列表 → 开启开发模式) |
| `FXK_OPEN_USER_ID` | 默认调用身份的 OpenUserID(`FSUID_` 开头),用 `get_user_id.py` 通过手机号查询(见下)。仅 stdio 模式需要;HTTP 多用户模式身份逐请求由 `X-Fxk-Token` 解析,应留空 |
| `FXK_API_BASE_URL` | 默认 `https://open.fxiaoke.com`,多云租户按所在云修改 |
3. 确认本机出口 IP 已在纷享后台白名单内(能拿到 token 即说明白名单 OK)。
## 本地调试
### 方式一:测试脚本(最快,不依赖任何客户端)
前三个脚本在进程内直接调工具、不走 HTTP,因此需要显式指定调用身份
(服务端 `.env` 已不再提供共享默认身份):
```powershell
$env:FXK_OPEN_USER_ID='FSUID_xxx'
.\.venv\Scripts\python.exe tests\test_e2e.py # 4 个只读工具冒烟
.\.venv\Scripts\python.exe tests\test_p1.py # 销售订单全生命周期闭环(写操作)
.\.venv\Scripts\python.exe tests\test_identity.py # 双身份权限对比(改 FXK_OPEN_USER_ID 切身份)
```
`test_http.py` 走 HTTP,需先启动服务并携带令牌(监听地址/端口自动读 `.env`):
```powershell
$env:FXK_TEST_TOKEN='fxk-xxx'
.\.venv\Scripts\python.exe tests\test_http.py # HTTP 模式协议验证
```
### 方式二:MCP Inspector(可视化调单个工具)
```powershell
npx @modelcontextprotocol/inspector .\.venv\Scripts\python.exe -m src.server
```
浏览器打开 Inspector 地址 → 左侧选工具 → 填参数 → 直接看返回。
### 方式三:HTTP 集中部署(多用户推荐)
```powershell
$env:FXK_MCP_TRANSPORT='http'
.\.venv\Scripts\python.exe -m src.server # 监听 http://127.0.0.1:8765/mcp
```
可选环境变量:`FXK_MCP_HOST`(默认 127.0.0.1)、`FXK_MCP_PORT`(默认 8765)。
Linux 下可用项目级管理脚本(非 systemd,日志和 pid 文件在 `logs/`):
```bash
scripts/fxk-mcp.sh start # 后台启动
scripts/fxk-mcp.sh stop # 停止
scripts/fxk-mcp.sh restart # 重启(改配置/identity_map.json 后立即生效)
scripts/fxk-mcp.sh status # 运行状态 + 最近身份校验结果
scripts/fxk-mcp.sh logs # 实时日志
```
**身份头映射**:项目根目录 `identity_map.json`(已 gitignore,参考 `identity_map.example.json`)
配置后所有 HTTP 请求必须携带 `X-Fxk-Token`,服务端按令牌映射到对应员工的 FSUID,
未知/缺失令牌返回 401,每次调用写 `[fxk-audit]` 审计日志(谁、调了什么)。
未配置映射表时退化为共用 `.env` 默认身份(启动会打警告);若 `.env` 里也没有
`FXK_OPEN_USER_ID`,启动时会因身份校验失败而拒绝启动。
**身份校验**:映射表里的 FSUID 会在服务启动时逐个调 `/cgi/user/get` 校验是否为租户内
已注册且在职业用户,不合格的令牌直接置为停用(后续请求 403);若启动时没有任何可用令牌
则拒绝启动(fail-closed)。之后由后台线程按 `FXK_IDENTITY_CHECK_INTERVAL`(默认 86400 秒=一天)
定时复核,**不随每次请求校验**。每轮校验会重新加载 `identity_map.json`:新增/吊销令牌
在下一轮校验(最长一天)生效,重启服务可立即生效。
```json
{
"tokens": {
"fxk-随机字符串": {"fsuid": "FSUID_员工", "name": "姓名"}
}
}
```
用户侧 WorkBuddy 配置(每人一个令牌,由管理员分发):
```json
{
"mcpServers": {
"fxiaoke": {
"type": "streamable-http",
"url": "http://服务器IP:8765/mcp",
"headers": {"X-Fxk-Token": "fxk-发给我的令牌"}
}
}
}
```
局域网访问需设 `FXK_MCP_HOST=0.0.0.0`;令牌即凭证,按密钥管理(泄露=冒用身份)。
> 本机若配了 HTTP 代理,访问 127.0.0.1 可能被代理拦截返回 502,调试时绕开代理(httpx 用 `trust_env=False`)。
## 客户端接入
### WorkBuddy / Claude Desktop / Cherry Studio(stdio,推荐)
```json
{
"mcpServers": {
"fxiaoke": {
"command": "d:/0615/gvs_wsx/mcp开发/纷享销客/.venv/Scripts/python.exe",
"args": ["-m", "src.server"]
}
}
}
```
### HTTP 模式(需先按上面方式三启动服务)
```json
{
"mcpServers": {
"fxiaoke": {
"type": "streamable-http",
"url": "http://127.0.0.1:8765/mcp"
}
}
}
```
`.env` 从脚本所在目录加载,配置里不需要 `cwd`,也不含任何密钥。
## 接口台账(扩展开发用)
| 文件 | 说明 |
|---|---|
| 目录 | 说明 |
|---|---|
| `catalog/api-catalog-merged.json` | 唯一台账:1245 个文档条目,含接口路径、层级、实现/实测状态 |
| `catalog/api_tree.md` | 全量接口层级索引(模块 → 对象 → 操作 → 接口路径) |
| `catalog/api_pages.jsonl` | 爬虫原始数据(1186 页) |
| `catalog/fxiaoke-api-catalog-baseline.json` | 导航基线(1226 条目,合并的主键来源) |
| `docs/fxiaoke-mcp-usage-guide.md` | 使用说明:运维命令、令牌管理、员工接入、故障排查 |
| `docs/WorkBuddy接入说明.txt` | 员工版接入说明(可直接分发给员工) |
| `docs/` | 设计文档:总体规划、架构图、模块开发文档 |
```powershell
.\.venv\Scripts\python.exe scripts\crawl_docs.py # 重新全量爬取文档(约 3 分钟)
.\.venv\Scripts\python.exe scripts\merge_catalog.py # 刷新台账和覆盖率(改代码后跑)
.\.venv\Scripts\python.exe scripts\list_objects.py # 拉取租户内全部业务对象
.\.venv\Scripts\python.exe scripts\list_roles.py # 拉取租户全部角色
.\.venv\Scripts\python.exe scripts\get_user_id.py 13800138000 # 手机号查 FSUID + 角色权限
.\.venv\Scripts\python.exe scripts\get_user_id.py 13800138000 --set-env # 查到并直接写入 .env
.\.venv\Scripts\python.exe scripts\add_user.py 13800138000 # 查 FSUID + 生成令牌 + 写入 identity_map.json(--regen 换发)
.\.venv\Scripts\python.exe scripts\revoke_user.py fxk-xxx # 吊销令牌(也支持 FSUID/手机号,--list 查看当前映射)
.\.venv\Scripts\python.exe scripts\set_user_role.py 13800138000 add <roleCode> # 分配角色(userIds 格式未打通,暂不可用)
```
服务日志写入 `logs/` 目录(已 gitignore)。
## 实测踩坑备忘
- token 接口是 `/cgi/corpAccessToken/get/V2`(网上常见的 `/cgi/accessToken`、`/cgi/getAccessToken` 都是旧路径,404)
- 查询传参用**旧式** `filters[{field_name, field_values, operator}]` + `dataObjectApiName`;新式 conditions 不生效
- describe 的参数是 `apiName`,不是 `objectApiName`
- 创建/修改默认触发审批流,记录会被锁定(无法 update/invalid/delete);测试时传 `trigger_approval_flow=False`
- owner / changeOwner 的人员值是 **FSUID 列表**(`["FSUID_xxx"]`),不是员工工号
- 部分对象 name 是自动编号字段,传值会被忽略
- 主从结构对象(如销售订单)创建时必须带从对象明细(`details`)
- 商机阶段推进是两段式:`getInstanceInfoByObjectId`(参数 `{entityId, objectId}`)拿实例 ID → `stageMoveTo` 传 `{workflowInstanceId, stageId}`;文档里的 `moveNextSaleActionStage` 路由在租户上不存在,`backReason__o` 标注也是错的
- `data/remove`(公海移除)仅 AccountObj 可用,LeadsObj 路由不存在(文档与实测不符)
- 公海退回/领取依赖租户公海配置,无配置时报错文案可直接读("公海或销售线索不存在")
- 本租户报价单明细用的是**定制对象** `quote_product__c`(字段带 `__c` 后缀),标准 QuoteLinesObj 为空;查价/比价工具已做标准→定制自动回退,且 `__c` 对象查询必须走 `/cgi/crm/custom/v2/data/query` 路由
- `getRealPrice` 不传 `accountId` 会报参数错误;价格字段是 `selling_price`(不是任何含 price 的字段,文档未说明)
## 安全
### 身份机制(防注入失败越权)
身份解析优先级:**进程环境变量 > `~/.fxiaoke-mcp/.env` > 项目 `.env`**。
- 服务启动时强制校验身份(调用 `/cgi/user/get`),**身份非法立即拒绝启动**(fail-closed),
并在日志打印 `生效身份: 姓名 (FSUID) 来源=process_env/user_home/project_env`
- 身份来自项目 `.env` 默认值时会打印警告——多人分发时**必须删掉共享 `.env` 里的
`FXK_OPEN_USER_ID`**,这样 mcp.json env 注入失败的结果是"启动失败"(可见),
而不是"静默用管理员身份"(越权)
- 任何人可随时在对话中问"我当前是什么身份"(`fxk_whoami` 工具)核对
### 分发方式(防任务过程中代码被改)
| 方式 | 做法 | 适用 |
|---|---|---|
| **HTTP 集中部署(推荐)** | 代码只在管理员控制的一台机器上,其他人只配 URL + 身份头映射 | 正式多用户 |
| **wheel 安装包** | `python -m build` 打包后分发,用户 `pip install` 到本地环境,运行的是 site-packages 里的副本,源码被改不影响运行 | 单机 stdio |
| 源码直接分发 | 简单但 AI 任务可能改到代码,仅限开发调试 | 开发期 |
### 其他
- 凭证只放 `.env`(已 gitignore),不要提交到 git、不要截图外传
- `fxk_delete_records` 等高危工具:Agent 调用前应先向用户确认
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues