@wukongcrm/mcp-server 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -1,47 +1,241 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
+ import { createNocodeToolHandlers, nocodeToolDefinitions } from "./nocode.js";
3
4
  import { createToolHandlers } from "./tools.js";
5
+ export const CRM_READ_SCOPE = "crm.read";
6
+ export const CRM_WRITE_SCOPE = "crm.write";
7
+ export const OFFLINE_ACCESS_SCOPE = "offline_access";
8
+ export const MCP_RESOURCE_SCOPES = [CRM_READ_SCOPE, CRM_WRITE_SCOPE];
9
+ export const OAUTH_SCOPES = [...MCP_RESOURCE_SCOPES, OFFLINE_ACCESS_SCOPE];
4
10
  const moduleSchema = z.union([z.string(), z.number()]);
11
+ const idSchema = z.union([z.string(), z.number()]);
5
12
  const passthroughSchema = z.object({}).passthrough();
13
+ const toolOutputSchema = z.object({ result: z.unknown() });
14
+ const searchFilterSchema = z.object({
15
+ fieldName: z.string().optional(),
16
+ name: z.string().optional(),
17
+ formType: z.string().optional(),
18
+ type: z.number().optional(),
19
+ searchEnum: z.union([z.string(), z.number()]).optional(),
20
+ values: z.array(z.unknown()).optional(),
21
+ groupId: z.number().optional()
22
+ }).passthrough();
23
+ const crmUserSearchShape = {
24
+ ownerUserId: idSchema.optional(),
25
+ ownerUserIds: z.array(idSchema).optional(),
26
+ ownerUserName: z.string().optional(),
27
+ ownerUserNames: z.array(z.string()).optional(),
28
+ createUserId: idSchema.optional(),
29
+ createUserIds: z.array(idSchema).optional(),
30
+ createUserName: z.string().optional(),
31
+ createUserNames: z.array(z.string()).optional(),
32
+ filters: z.array(searchFilterSchema).optional(),
33
+ searchList: z.array(searchFilterSchema).optional()
34
+ };
6
35
  const toolDefinitions = [
7
36
  ["crm_auth_status", "使用 CRM_API_KEY 自动换取 Admin-Token 后检查当前登录态,异常时提示检查 API Key。", z.object({}).optional()],
8
37
  ["crm_list_modules", "列出当前 MCP 固定支持的 72CRM 模块、label、路径和中英文操作边界。", z.object({}).optional()],
9
38
  ["crm_list_customer_pools", "查询当前账号有权限访问的客户公海,并统计每个公海下面有多少客户;只读,不领取、不分配、不流转。", z.object({ includeSamples: z.boolean().optional(), sampleLimit: z.number().optional() }).passthrough()],
10
39
  ["crm_get_module_schema", "获取模块字段结构和列表头,供本地 AI 判断字段怎么填。", z.object({ module: moduleSchema, id: z.union([z.string(), z.number()]).optional() }).passthrough()],
11
40
  ["crm_validate_field", "调用固定字段校验接口,校验字段值或联系人查重。", passthroughSchema],
12
- ["crm_search_records", "查询模块分页列表;当传入 keyword/query/q/phone/mobile/name 等明显搜索语义时,会自动转换为 72CRM 高级筛选 searchList。", z.object({ module: moduleSchema, page: z.number().optional(), limit: z.number().optional() }).passthrough()],
41
+ ["crm_search_records", "查询模块分页列表;支持 ownerUserId(s)/createUserId(s) 按用户 ID 筛选,也支持 ownerUserName(s)/createUserName(s) 将唯一昵称解析为用户 ID;用户类型 filters 的昵称值同样会先解析为 ID。", z.object({ module: moduleSchema, page: z.number().optional(), limit: z.number().optional(), ...crmUserSearchShape }).passthrough()],
13
42
  ["crm_get_record", "按模块和 ID 查询单条 CRM 记录详情。", z.object({ module: moduleSchema, id: z.union([z.string(), z.number()]) }).passthrough()],
14
43
  ["crm_get_record_information", "查询详情页字段展示数据,包含字段中文名和值。", z.object({ module: moduleSchema, id: z.union([z.string(), z.number()]) }).passthrough()],
15
- ["crm_get_related_records", "查询固定白名单内的关联数据,例如商机产品、联系人商机、合同回款计划。", z.object({ module: moduleSchema, id: z.union([z.string(), z.number()]), relation: z.string() }).passthrough()],
44
+ ["crm_get_related_records", "查询固定白名单内的关联数据,例如商机产品、联系人商机、合同回款计划、产品上架列表。", z.object({ module: moduleSchema, id: z.union([z.string(), z.number()]).optional(), relation: z.string() }).passthrough()],
16
45
  ["crm_get_record_timeline", "查询某条记录关联的跟进/活动时间线。", z.object({ module: moduleSchema, id: z.union([z.string(), z.number()]) }).passthrough()],
17
46
  ["crm_get_action_records", "查询某条记录的操作记录。", z.object({ module: moduleSchema, id: z.union([z.string(), z.number()]) }).passthrough()],
18
47
  ["crm_list_files", "只读查询附件元数据,不上传、不删除、不代理下载流。", z.object({ module: moduleSchema, id: z.union([z.string(), z.number()]) }).passthrough()],
48
+ ["crm_search_products", "只读查询 CRM 产品列表;固定调用 /crmProduct/queryPageList,支持 keyword/categoryId/status 以及负责人、创建人的用户 ID 或唯一昵称筛选。", z.object({ page: z.number().optional(), limit: z.number().optional(), keyword: z.string().optional(), categoryId: idSchema.optional(), status: z.union([z.string(), z.number()]).optional(), ...crmUserSearchShape }).passthrough()],
49
+ ["crm_search_sale_products", "只读查询 CRM 上架产品列表;固定调用 /crmProduct/querySaleProductPageList,支持负责人、创建人的用户 ID 或唯一昵称筛选。", z.object({ page: z.number().optional(), limit: z.number().optional(), keyword: z.string().optional(), categoryId: idSchema.optional(), ...crmUserSearchShape }).passthrough()],
50
+ ["crm_get_product", "只读查询 CRM 产品详情;固定调用 /crmProduct/queryById/{productId}。", z.object({ productId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
51
+ ["crm_get_product_information", "只读查询 CRM 产品详情页字段;固定调用 /crmProduct/information/{productId}。", z.object({ productId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
52
+ ["crm_get_product_schema", "只读查询 CRM 产品字段结构;固定调用 /crmProduct/field 或 /crmProduct/field/{productId}。", z.object({ productId: idSchema.optional(), id: idSchema.optional(), type: z.string().optional() }).passthrough()],
53
+ ["crm_list_product_files", "只读查询 CRM 产品附件元数据;固定调用 /crmProduct/queryFileList,不上传、不删除、不代理下载流。", z.object({ productId: idSchema.optional(), id: idSchema.optional(), fileType: z.string().optional() }).passthrough()],
54
+ ["crm_get_product_num", "只读查询 CRM 产品详情页数量;固定调用 /crmProduct/num。", z.object({ productId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
55
+ ["crm_get_simple_products", "只读按产品 ID 列表查询简要产品对象;固定调用 /crmProduct/querySimpleEntity。", z.object({ productIds: z.array(idSchema).optional(), ids: z.array(idSchema).optional() }).passthrough()],
19
56
  ["crm_list_users", "查询员工列表。", passthroughSchema],
20
- ["crm_find_user_id", "按登录名或真实姓名查询员工 ID。", z.object({ userName: z.string().optional(), name: z.string().optional(), mode: z.enum(["username", "realname"]).optional() }).passthrough()],
57
+ ["crm_find_user_id", "按登录名或昵称查询员工 ID;昵称必须唯一,重名时会返回候选并要求明确用户 ID。", z.object({ userName: z.string().optional(), name: z.string().optional(), mode: z.enum(["username", "realname"]).optional() }).passthrough()],
21
58
  ["crm_list_departments", "查询部门树。", passthroughSchema],
22
59
  ["crm_find_dept_id", "按部门名查询部门 ID。", z.object({ deptName: z.string().optional(), name: z.string().optional() }).passthrough()],
60
+ ["crm_list_address_book_contacts", "只读查询 OA 通讯录联系人;固定调用 /adminUser/queryListName,支持 search/keyword/deptId/status/acronym/initial。", z.object({ page: z.number().optional(), limit: z.number().optional(), search: z.string().optional(), keyword: z.string().optional(), deptId: idSchema.optional(), status: z.union([z.string(), z.number()]).optional(), acronym: z.string().optional(), initial: z.union([z.string(), z.number()]).optional() }).passthrough()],
61
+ ["crm_get_address_book_contact", "只读查询通讯录联系人详情;固定调用 /adminUser/queryUserInfo,userId 走查询参数。", z.object({ userId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
62
+ ["crm_list_address_book_departments", "只读查询通讯录部门树;固定调用 /adminDept/queryDeptTree。", z.object({ type: z.string().optional(), name: z.string().optional(), deptName: z.string().optional() }).passthrough()],
63
+ ["crm_list_address_book_auth_departments", "只读查询当前权限范围内部门;固定调用 /adminDept/queryDeptByAuth。", passthroughSchema],
64
+ ["crm_list_address_book_dept_user_ids", "只读按部门 ID 查询通讯录用户 ID;固定调用 /adminUser/queryUserByDeptIds。", z.object({ deptIds: z.array(idSchema).optional(), ids: z.array(idSchema).optional(), deptId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
65
+ ["crm_list_address_book_dept_users", "只读按部门 ID 查询通讯录简要用户;固定调用 /adminUser/queryListByDeptIds。", z.object({ deptIds: z.array(idSchema).optional(), ids: z.array(idSchema).optional(), deptId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
66
+ ["crm_list_address_book_auth_users", "只读查询当前权限范围内用户;固定调用 /adminUser/queryAuthUserList。", z.object({}).optional()],
67
+ ["crm_get_address_book_user_dept_role_info", "只读按 userIds/deptIds/roleIds 查询通讯录用户、部门、角色聚合信息;固定调用 /adminUser/queryUserDeptOrRoleInfo。", z.object({ userIds: z.array(idSchema).optional(), deptIds: z.array(idSchema).optional(), roleIds: z.array(idSchema).optional() }).passthrough()],
68
+ ["crm_get_address_book_organization", "只读查询完整组织架构信息;固定调用 /adminUser/queryOrganizationInfo。", z.object({ companyId: idSchema.optional() }).passthrough()],
69
+ ["crm_get_address_book_user_count", "只读按 username 查询用户数量;固定调用 /adminUser/queryUserNumInfo。", z.object({ username: z.string().optional(), userName: z.string().optional() }).passthrough()],
70
+ ["crm_toggle_address_book_attention", "切换通讯录关注状态;固定调用 /adminUser/attention,必须 confirm=true 才会写入。", z.object({ userId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
71
+ ["crm_list_hrm_employees", "只读查询 HRM 员工分页列表;固定调用 /hrmEmployee/queryPageList。", z.object({ page: z.number().optional(), limit: z.number().optional(), employeeName: z.string().optional(), keyword: z.string().optional(), deptId: idSchema.optional(), status: z.number().optional() }).passthrough()],
72
+ ["crm_get_hrm_employee", "只读查询 HRM 员工详情;固定调用 /hrmEmployee/queryById/{employeeId}。", z.object({ employeeId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
73
+ ["crm_list_hrm_employee_fields", "只读查询 HRM 员工列表字段表头;固定调用 /hrmEmployeeField/queryListHeads。", z.object({}).optional()],
74
+ ["crm_list_hrm_departments", "只读查询 HRM 部门树;固定调用 /hrmDept/queryTreeList。", z.object({ name: z.string().optional(), deptName: z.string().optional(), type: z.string().optional(), id: idSchema.optional() }).passthrough()],
75
+ ["crm_get_hrm_department", "只读查询 HRM 部门详情;固定调用 /hrmDept/queryById/{deptId}。", z.object({ deptId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
76
+ ["crm_list_hrm_department_employees", "只读查询 HRM 部门员工分页列表;固定调用 /hrmDept/queryEmployeeByDeptId。", z.object({ deptId: idSchema.optional(), id: idSchema.optional(), page: z.number().optional(), limit: z.number().optional(), search: z.string().optional() }).passthrough()],
77
+ ["crm_get_hrm_employee_post", "只读查询 HRM 员工岗位信息;固定调用 /hrmEmployeePost/postInformation/{employeeId}。", z.object({ employeeId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
78
+ ["crm_list_hrm_attendance_month_records", "只读查询 HRM 员工月度考勤汇总;固定调用 /hrmAttendanceEmpMonthRecord/queryAttendanceEmpMonthRecordPageList。", z.object({ page: z.number().optional(), limit: z.number().optional(), search: z.string().optional(), employeeId: idSchema.optional(), deptIds: z.array(idSchema).optional(), times: z.array(z.string()).optional() }).passthrough()],
79
+ ["crm_get_hrm_attendance_daily_detail", "只读查询 HRM 员工每日打卡明细;固定调用 /hrmAttendanceClock/queryAttendanceDailyDetail。", z.object({ employeeId: idSchema.optional(), id: idSchema.optional(), currentDate: z.string().optional(), date: z.string().optional() }).passthrough()],
80
+ ["crm_list_hrm_leave_records", "只读查询 HRM 请假记录列表;固定调用 /hrmEmployeeLeaveRecord/queryLeaveRecordPageList。", z.object({ page: z.number().optional(), limit: z.number().optional(), employeeId: idSchema.optional(), deptIds: z.array(idSchema).optional(), times: z.array(z.string()).optional(), leaveTypes: z.array(z.string()).optional() }).passthrough()],
81
+ ["crm_list_hrm_leave_types", "只读查询 HRM 请假类型;固定调用 /hrmEmployeeLeaveRecord/queryLeaveTypeList。", z.object({}).optional()],
82
+ ["crm_write_hrm", "执行 HRM 域内固定路径写入;targetPath 必须以 /hrm 开头,或传 module+operation 组合,必须 confirm=true 才会写入。", z.object({ targetPath: z.string().optional(), path: z.string().optional(), endpoint: z.string().optional(), module: z.string().optional(), operation: z.string().optional(), confirm: z.boolean().optional() }).passthrough()],
83
+ ["crm_create_hrm_record", "便捷新增 HRM 记录;默认调用 /hrmEmployee/addEmployee,也可传 targetPath 或 module+operation 指定 HRM 新增接口,必须 confirm=true。", z.object({ targetPath: z.string().optional(), module: z.string().optional(), operation: z.string().optional(), confirm: z.boolean().optional() }).passthrough()],
84
+ ["crm_update_hrm_record", "便捷编辑 HRM 记录;默认调用 /hrmEmployee/updateInformation,也可传 targetPath 或 module+operation 指定 HRM 编辑接口,必须 confirm=true。", z.object({ targetPath: z.string().optional(), module: z.string().optional(), operation: z.string().optional(), confirm: z.boolean().optional() }).passthrough()],
85
+ ["crm_list_finance_modules", "列出当前 MCP 固定支持的独立财务 FM 模块;读查询有专用工具,写入可使用财务域写入工具。", z.object({}).optional()],
86
+ ["crm_search_finance_vouchers", "只读查询独立财务 FM 凭证列表;固定调用 /financeCertificate/queryPageList。", z.object({ page: z.number().optional(), limit: z.number().optional(), accountId: idSchema.optional(), keyword: z.string().optional(), search: z.string().optional() }).passthrough()],
87
+ ["crm_get_finance_voucher", "只读查询独立财务 FM 凭证详情;固定调用 /financeCertificate/queryById。", z.object({ certificateId: idSchema.optional(), id: idSchema.optional(), accountId: idSchema.optional() }).passthrough()],
88
+ ["crm_summarize_finance_vouchers", "只读查询独立财务 FM 凭证汇总;固定调用 /financeCertificate/summary。", z.object({ accountId: idSchema.optional(), startTime: z.string().optional(), endTime: z.string().optional(), voucherId: idSchema.optional() }).passthrough()],
89
+ ["crm_query_finance_ledger", "只读查询独立财务 FM 账簿;ledger 支持 detail/general/balance/multiColumn/itemsDetail/itemsBalance/amountDetail/amountGeneral。", z.object({ ledger: z.string(), accountId: idSchema.optional() }).passthrough()],
90
+ ["crm_query_finance_report", "只读查询独立财务 FM 报表;report 支持 balanceSheet/incomeStatement/cashFlow,可传 checkBalance=true 做平衡检查。", z.object({ report: z.string(), accountId: idSchema.optional(), checkBalance: z.boolean().optional(), balanceCheck: z.boolean().optional() }).passthrough()],
91
+ ["crm_list_finance_subjects", "只读查询独立财务 FM 科目列表;固定调用 /financeSubject/list。", passthroughSchema],
92
+ ["crm_list_finance_currencies", "只读查询独立财务 FM 币别列表;有 accountId 时调用 /financeCurrency/queryListByAccountId,否则调用 /financeCurrency/queryAllList。", z.object({ accountId: idSchema.optional() }).passthrough()],
93
+ ["crm_list_finance_voucher_words", "只读查询独立财务 FM 凭证字列表;固定调用 /financeVoucher/queryList。", passthroughSchema],
94
+ ["crm_query_finance_dashboard", "只读查询独立财务 FM 仪表盘;query 支持 incomeStatement/config/indicator/statistics/fieldLanguage。", z.object({ query: z.string(), accountId: idSchema.optional() }).passthrough()],
95
+ ["crm_write_finance", "执行独立财务 FM 域内固定路径写入;targetPath 必须以 /finance 开头,或传 module+operation 组合,必须 confirm=true 才会写入。", z.object({ targetPath: z.string().optional(), path: z.string().optional(), endpoint: z.string().optional(), module: z.string().optional(), operation: z.string().optional(), confirm: z.boolean().optional() }).passthrough()],
96
+ ["crm_create_finance_voucher", "新增独立财务 FM 凭证;固定调用 /financeCertificate/add,必须 confirm=true。", z.object({ confirm: z.boolean().optional() }).passthrough()],
97
+ ["crm_update_finance_voucher", "编辑独立财务 FM 凭证;固定调用 /financeCertificate/update,必须传 certificateId 或 id,必须 confirm=true。", z.object({ certificateId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
98
+ ["crm_list_jxc_modules", "列出当前 MCP 固定支持的 JXC 主数据模块;产品、供应商、仓库支持专用新增/编辑,其他 JXC 写接口可走域级写入工具。", z.object({}).optional()],
99
+ ["crm_search_jxc_records", "只读查询 JXC 主数据列表;固定白名单模块 product/supplier/warehouse,调用对应 /queryPageList。", z.object({ module: moduleSchema, page: z.number().optional(), limit: z.number().optional(), keyword: z.string().optional(), search: z.string().optional() }).passthrough()],
100
+ ["crm_get_jxc_record", "只读查询 JXC 主数据详情;固定调用模块 /queryById/{id}。", z.object({ module: moduleSchema, id: idSchema }).passthrough()],
101
+ ["crm_get_jxc_record_information", "只读查询 JXC 详情页字段展示数据;固定调用模块 /information/{id}。", z.object({ module: moduleSchema, id: idSchema }).passthrough()],
102
+ ["crm_get_jxc_module_schema", "只读查询 JXC 模块字段结构;固定调用模块 /field 或 /field/{id}。", z.object({ module: moduleSchema, id: idSchema.optional(), type: z.string().optional() }).passthrough()],
103
+ ["crm_list_jxc_files", "只读查询 JXC 附件元数据,不上传、不删除、不代理下载流。", z.object({ module: moduleSchema, id: idSchema, fileType: z.string().optional() }).passthrough()],
104
+ ["crm_list_jxc_stock_products", "只读查询 JXC 产品库存列表;固定调用 /jxcWarehouseProduct/queryPageList。", z.object({ page: z.number().optional(), limit: z.number().optional(), keyword: z.string().optional(), search: z.string().optional(), warehouseId: idSchema.optional(), productCode: z.string().optional() }).passthrough()],
105
+ ["crm_get_jxc_product_inventory", "只读查询单个 JXC 产品库存;固定调用 /jxcProduct/queryProductInventory/{productId}。", z.object({ productId: idSchema.optional(), id: idSchema.optional(), warehouseId: idSchema.optional() }).passthrough()],
106
+ ["crm_list_jxc_stock_movements", "只读查询 JXC 出入库明细;固定调用 /jxcDetailed/queryPageList。", z.object({ page: z.number().optional(), limit: z.number().optional(), search: z.string().optional(), searchList: z.array(z.object({}).passthrough()).optional() }).passthrough()],
107
+ ["crm_get_jxc_warehouse_product_stock", "只读批量查询仓库产品库存数量;固定调用 /jxcWarehouse/queryProductWarehouseNumBatch。", z.object({ warehouseProductIdsMap: z.record(z.array(idSchema)).optional() }).passthrough()],
108
+ ["crm_get_jxc_warehouse_names", "只读按仓库 ID 查询仓库名称;固定调用 /jxcWarehouse/queryWarehouseNameByIds。", z.object({ warehouseIds: z.array(idSchema).optional(), ids: z.array(idSchema).optional() }).passthrough()],
109
+ ["crm_write_jxc", "执行 JXC 域内固定路径写入;targetPath 必须以 /jxc 开头,或传 module+operation 组合,必须 confirm=true 才会写入。", z.object({ targetPath: z.string().optional(), path: z.string().optional(), endpoint: z.string().optional(), module: z.string().optional(), operation: z.string().optional(), confirm: z.boolean().optional() }).passthrough()],
110
+ ["crm_create_jxc_record", "新增 JXC 产品、供应商或仓库主数据;固定调用对应 /add,必须 confirm=true。", z.object({ module: moduleSchema, confirm: z.boolean().optional() }).passthrough()],
111
+ ["crm_update_jxc_record", "编辑 JXC 产品、供应商或仓库主数据;默认调用对应 /update,可传 useInformationUpdate=true 调用 /updateInformation,必须 confirm=true。", z.object({ module: moduleSchema, id: idSchema.optional(), confirm: z.boolean().optional(), useInformationUpdate: z.boolean().optional() }).passthrough()],
112
+ ["crm_list_calendar_events", "查询 OA 日历日程列表;固定调用 /oaEvent/queryList。startTime/endTime 使用 72CRM 接口要求的毫秒时间戳。", z.object({ startTime: z.union([z.string(), z.number()]), endTime: z.union([z.string(), z.number()]), typeIds: z.array(z.union([z.string(), z.number()])).optional(), userId: z.union([z.string(), z.number()]).optional() }).passthrough()],
113
+ ["crm_get_calendar_event", "查询 OA 日历单条日程详情;固定调用 /oaEvent/queryById。", z.object({ eventId: z.union([z.string(), z.number()]).optional(), id: z.union([z.string(), z.number()]).optional(), startTime: z.union([z.string(), z.number()]).optional(), endTime: z.union([z.string(), z.number()]).optional() }).passthrough()],
114
+ ["crm_list_oa_announcements", "查询 OA 公告列表;固定调用 /oaAnnouncement/queryList。", z.object({ page: z.number().optional(), limit: z.number().optional(), type: z.union([z.string(), z.number()]).optional() }).passthrough()],
115
+ ["crm_get_oa_announcement", "查询 OA 公告详情;固定调用 /oaAnnouncement/queryById/{announcementId}。", z.object({ announcementId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
116
+ ["crm_create_oa_announcement", "新增 OA 公告;固定调用 /oaAnnouncement/addAnnouncement,必须 confirm=true。", z.object({ confirm: z.boolean().optional() }).passthrough()],
117
+ ["crm_update_oa_announcement", "编辑 OA 公告;固定调用 /oaAnnouncement/setAnnouncement,必须传 announcementId/id 且 confirm=true。", z.object({ announcementId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
118
+ ["crm_delete_oa_announcement", "删除 OA 公告;固定调用 /oaAnnouncement/delete/{announcementId},必须 confirm=true。", z.object({ announcementId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
119
+ ["crm_mark_oa_announcement_read", "标记 OA 公告已读;固定调用 /oaAnnouncement/readAnnouncement,必须 confirm=true。", z.object({ announcementId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
120
+ ["crm_list_oa_logs", "查询 OA 日志列表;固定调用 /oaLog/queryList。", z.object({ page: z.number().optional(), limit: z.number().optional(), categoryId: idSchema.optional(), keyword: z.string().optional() }).passthrough()],
121
+ ["crm_get_oa_log", "查询 OA 日志详情;固定调用 /oaLog/queryById,logId 走查询参数。", z.object({ logId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
122
+ ["crm_get_oa_log_information", "查询 OA 日志详情页字段;固定调用 /oaLog/information/{logId}。", z.object({ logId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
123
+ ["crm_list_oa_log_templates", "查询可用 OA 日志模板;固定调用 /oaLogTemplate/queryPartList。", z.object({ page: z.number().optional(), limit: z.number().optional(), search: z.string().optional() }).passthrough()],
124
+ ["crm_get_oa_log_fields", "查询 OA 日志模板字段;新增调 /oaLogTemplateField/field,编辑调 /oaLogTemplateField/field/{logId}。", z.object({ configId: idSchema, logId: idSchema.optional(), id: idSchema.optional(), type: z.string().optional() }).passthrough()],
125
+ ["crm_get_oa_log_welcome", "只读获取 OA 日志欢迎语;固定调用 /oaLog/getLogWelcomeSpeech。", z.object({}).optional()],
126
+ ["crm_get_oa_log_bulletin", "只读查询 OA 日志概要统计;固定调用 /oaLog/queryLogBulletin。", z.object({}).optional()],
127
+ ["crm_get_oa_log_complete_stats", "只读查询 OA 日志完成统计;固定调用 /oaLog/queryCompleteStats。", z.object({ configId: idSchema }).passthrough()],
128
+ ["crm_list_oa_log_complete_records", "只读查询 OA 日志已完成人员/记录列表;固定调用 /oaLog/queryCompleteOaLogList。", z.object({ page: z.number().optional(), limit: z.number().optional(), configId: idSchema.optional() }).passthrough()],
129
+ ["crm_list_oa_log_incomplete_records", "只读查询 OA 日志未完成人员列表;固定调用 /oaLog/queryIncompleteOaLogList。", z.object({ page: z.number().optional(), limit: z.number().optional(), configId: idSchema.optional() }).passthrough()],
130
+ ["crm_list_oa_examine_categories", "只读查询 OA 审批类型;固定调用 /examines/queryPartList,默认 label=0、pageType=0。", z.object({ pageType: z.number().optional(), label: z.union([z.string(), z.number()]).optional() }).passthrough()],
131
+ ["crm_list_oa_examine_groups", "只读查询 OA 审批分组;固定调用 /examineSuperExamines/queryExamineGroup,默认 groupType=0。", z.object({ groupType: z.union([z.string(), z.number()]).optional() }).passthrough()],
132
+ ["crm_list_oa_examines", "只读查询 OA 审批列表;listType 支持 upcoming/track/archive/draft/all/legacyWaiting/legacyRecord/myInitiate/myOaExamine,可用 tab 选择子列表。", z.object({ listType: z.string().optional(), tab: z.union([z.string(), z.number()]).optional(), page: z.number().optional(), limit: z.number().optional(), search: z.string().optional() }).passthrough()],
133
+ ["crm_get_oa_examine", "只读查询 OA 审批详情;固定调用 /oaExamine/queryOaExamineInfo/{examineId}。", z.object({ examineId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
134
+ ["crm_get_oa_examine_fields", "只读查询 OA 审批字段;传 categoryId 默认调 /oaExamineField/queryField/{categoryId} 获取新建自定义字段,mode=list 时调 /queryFieldList/{categoryId},传 examineId/id 调 /oaExamine/getField。", z.object({ categoryId: idSchema.optional(), examineId: idSchema.optional(), id: idSchema.optional(), mode: z.string().optional() }).passthrough()],
135
+ ["crm_list_oa_examine_record_logs", "只读查询 OA 审批流转日志;固定调用 /examineRecord/queryExamineRecordLog。", z.object({ recordId: idSchema.optional(), examineRecordId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
136
+ ["crm_list_oa_examine_flow_records", "只读查询 OA 审批流程节点记录;固定调用 /examineSuperExamine/examine/record/list。", z.object({ recordId: idSchema.optional(), examineRecordId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
137
+ ["crm_list_oa_tasks", "查询 OA 任务列表;固定调用 /oaTask/queryTaskList。", z.object({ page: z.number().optional(), limit: z.number().optional(), type: z.number().optional(), status: z.number().optional(), priority: z.number().optional() }).passthrough()],
138
+ ["crm_list_related_oa_tasks", "按关联业务查询 OA 任务;固定调用 /oaTask/queryTypeTaskList。", z.object({ type: z.union([z.string(), z.number()]), typeId: z.union([z.string(), z.number()]) }).passthrough()],
23
139
  ["crm_preview_write", "写入前预览和校验,不调用 72CRM 写接口。", passthroughSchema],
24
- ["crm_create_record", "新增线索、客户、联系人、商机;必须 confirm=true 才会写入。", z.object({ module: moduleSchema, confirm: z.boolean().optional() }).passthrough()],
140
+ ["crm_create_record", "新增线索、客户、联系人、产品、商机;必须 confirm=true 才会写入。", z.object({ module: moduleSchema, confirm: z.boolean().optional() }).passthrough()],
25
141
  ["crm_update_customer", "按客户 ID 或手机号/客户名唯一匹配后更新客户字段;会自动补全字段结构并写后校验,必须 confirm=true 才会写入。", z.object({ confirm: z.boolean().optional() }).passthrough()],
26
- ["crm_update_record_fields", "更新线索、客户、联系人、商机基础字段;必须 confirm=true 才会写入。", z.object({ module: moduleSchema, id: z.union([z.string(), z.number()]), confirm: z.boolean().optional() }).passthrough()],
142
+ ["crm_update_record_fields", "更新线索、客户、联系人、产品、商机基础字段;必须 confirm=true 才会写入。", z.object({ module: moduleSchema, id: z.union([z.string(), z.number()]), confirm: z.boolean().optional() }).passthrough()],
143
+ ["crm_create_product", "新增 CRM 产品;固定调用 /crmProduct/add,可直接传 72CRM payload 或 entity/field,必须 confirm=true。", z.object({ confirm: z.boolean().optional() }).passthrough()],
144
+ ["crm_update_product", "编辑 CRM 产品;固定调用 /crmProduct/update,必须传 productId/id,必须 confirm=true。", z.object({ productId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
145
+ ["crm_update_product_fields", "更新 CRM 产品基础字段;固定调用 /crmProduct/updateInformation,会自动补字段结构,必须 confirm=true。", z.object({ productId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
146
+ ["crm_update_product_status", "CRM 产品上架/下架;固定调用 /crmProduct/updateStatus,必须传 ids/productIds 与 status,必须 confirm=true。", z.object({ ids: z.array(idSchema).optional(), productIds: z.array(idSchema).optional(), status: z.union([z.string(), z.number()]), confirm: z.boolean().optional() }).passthrough()],
147
+ ["crm_transfer_products_owner", "CRM 产品负责人转移;固定调用 /crmProduct/changeOwnerUser,必须传 ids/productIds 与 ownerUserId,必须 confirm=true。", z.object({ ids: z.array(idSchema).optional(), productIds: z.array(idSchema).optional(), ownerUserId: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
148
+ ["crm_delete_products", "删除 CRM 产品;固定调用 /crmProduct/deleteByIds,必须传 ids/productIds 且 confirm=true。", z.object({ ids: z.array(idSchema).optional(), productIds: z.array(idSchema).optional(), confirm: z.boolean().optional() }).passthrough()],
149
+ ["crm_create_contract", "新增合同;固定调用 /crmContract/add,可直接传 72CRM payload,或传 fields/updates 自动组装 entity/field/product/examineFlowData,必须 confirm=true。", z.object({ confirm: z.boolean().optional(), draft: z.boolean().optional() }).passthrough()],
150
+ ["crm_update_contract", "编辑合同;固定调用 /crmContract/update,必须传 contractId/id,可直接传 72CRM payload,或传 fields/updates 自动组装,必须 confirm=true。", z.object({ contractId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional(), draft: z.boolean().optional() }).passthrough()],
151
+ ["crm_create_receivables", "新增回款;固定调用 /crmReceivables/add,可直接传 72CRM payload,或传 fields/updates 自动组装,必须 confirm=true。", z.object({ confirm: z.boolean().optional(), draft: z.boolean().optional() }).passthrough()],
152
+ ["crm_update_receivables", "编辑回款;固定调用 /crmReceivables/update,必须传 receivablesId/id,可直接传 72CRM payload,或传 fields/updates 自动组装,必须 confirm=true。", z.object({ receivablesId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional(), draft: z.boolean().optional() }).passthrough()],
153
+ ["crm_create_receivables_plan", "新增回款计划;固定调用 /crmReceivablesPlan/add,可直接传 72CRM payload,或传 fields/updates 自动组装,必须 confirm=true。", z.object({ confirm: z.boolean().optional() }).passthrough()],
154
+ ["crm_update_receivables_plan", "编辑回款计划;固定调用 /crmReceivablesPlan/update,必须传 receivablesPlanId/id,可直接传 72CRM payload,或传 fields/updates 自动组装,必须 confirm=true。", z.object({ receivablesPlanId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
155
+ ["crm_create_invoice", "新增发票;固定调用 /crmInvoice/add,可直接传 72CRM payload,或传 fields/updates 自动组装,必须 confirm=true。", z.object({ confirm: z.boolean().optional(), draft: z.boolean().optional() }).passthrough()],
156
+ ["crm_update_invoice", "编辑发票;固定调用 /crmInvoice/update,必须传 invoiceId/id,可直接传 72CRM payload,或传 fields/updates 自动组装,必须 confirm=true。", z.object({ invoiceId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional(), draft: z.boolean().optional() }).passthrough()],
157
+ ["crm_create_quotation", "新增报价单;固定调用 /crmQuotation/add,可直接传 72CRM payload,或传 fields/updates 自动组装 entity/field/product/examineFlowData,必须 confirm=true。", z.object({ confirm: z.boolean().optional(), draft: z.boolean().optional() }).passthrough()],
158
+ ["crm_update_quotation", "编辑报价单;固定调用 /crmQuotation/update,必须传 quotationId/id,可直接传 72CRM payload,或传 fields/updates 自动组装,必须 confirm=true。", z.object({ quotationId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional(), draft: z.boolean().optional() }).passthrough()],
159
+ ["crm_create_calendar_event", "新增 OA 日历日程;支持 72CRM SetEventBO 结构或顶层便捷字段,必须 confirm=true 才会写入。", z.object({ confirm: z.boolean().optional() }).passthrough()],
160
+ ["crm_update_calendar_event", "修改 OA 日历日程;支持 72CRM SetEventBO 结构或顶层便捷字段,必须 confirm=true 才会写入。", z.object({ eventId: z.union([z.string(), z.number()]).optional(), id: z.union([z.string(), z.number()]).optional(), confirm: z.boolean().optional() }).passthrough()],
161
+ ["crm_create_oa_log", "新增 OA 日志;固定调用 /oaLog/addOrUpdate,可直接传 payload,或传 categoryId+fields 自动组装日志模板字段,必须 confirm=true。", z.object({ categoryId: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
162
+ ["crm_update_oa_log", "编辑 OA 日志;固定调用 /oaLog/addOrUpdate,必须传 logId/id,可直接传 payload 或自动组装字段,必须 confirm=true。", z.object({ logId: idSchema.optional(), id: idSchema.optional(), categoryId: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
163
+ ["crm_delete_oa_log", "删除 OA 日志;固定调用 /oaLog/deleteById,logId 走查询参数,必须 confirm=true。", z.object({ logId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
164
+ ["crm_toggle_oa_log_favour", "点赞或取消点赞 OA 日志;固定调用 /oaLog/favourOrCancel,logId/isFavour 走查询参数,必须 confirm=true。", z.object({ logId: idSchema.optional(), id: idSchema.optional(), isFavour: z.boolean().optional(), confirm: z.boolean().optional() }).passthrough()],
165
+ ["crm_create_oa_examine", "新增或提交 OA 审批;固定调用 /oaExamine/setOaExamine,可直接传 72CRM payload,或传 categoryId+fields/updates 自动组装自定义字段,必须 confirm=true。", z.object({ categoryId: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
166
+ ["crm_save_oa_examine_draft", "保存 OA 审批草稿;固定调用 /oaExamine/setOaExamineDraft,可直接传 72CRM payload,或传 categoryId+fields/updates 自动组装自定义字段,必须 confirm=true。", z.object({ categoryId: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
167
+ ["crm_audit_oa_examine", "处理单条 OA 审批;固定调用 /examineRecord/auditExamine,必须传 recordId/examineRecordId/id 且 confirm=true。", z.object({ recordId: idSchema.optional(), examineRecordId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
168
+ ["crm_batch_audit_oa_examine", "批量处理 OA 审批;固定调用 /examineRecord/batchAuditExamine,必须传 recordIds/examineRecordIds/ids 且 confirm=true。", z.object({ recordIds: z.array(idSchema).optional(), examineRecordIds: z.array(idSchema).optional(), ids: z.array(idSchema).optional(), confirm: z.boolean().optional() }).passthrough()],
169
+ ["crm_create_oa_task", "新增 OA 主任务;固定调用 /workTask/saveWorkTask,支持 WorkTask 结构或顶层便捷字段,必须 confirm=true 才会写入。", z.object({ confirm: z.boolean().optional() }).passthrough()],
170
+ ["crm_update_oa_task_fields", "更新 OA 主任务字段;按字段映射到 /workTask 下的标题、描述、负责人、参与人、时间、标签、优先级、状态接口,必须 confirm=true 才会写入。", z.object({ taskId: z.union([z.string(), z.number()]).optional(), id: z.union([z.string(), z.number()]).optional(), confirm: z.boolean().optional() }).passthrough()],
27
171
  ["crm_add_followup", "新增跟进记录;必须 confirm=true 才会写入。", z.object({ confirm: z.boolean().optional() }).passthrough()],
28
- ["crm_update_followup", "更新跟进记录;必须 confirm=true 才会写入。", z.object({ id: z.union([z.string(), z.number()]).optional(), confirm: z.boolean().optional() }).passthrough()]
172
+ ["crm_update_followup", "更新跟进记录;必须 confirm=true 才会写入。", z.object({ id: z.union([z.string(), z.number()]).optional(), confirm: z.boolean().optional() }).passthrough()],
173
+ ["workorder_search_records", "查询工单、工单池或线下工单;scope 可选 workorder、pool、offline。", z.object({ scope: z.enum(["workorder", "pool", "offline"]).optional(), page: z.number().optional(), limit: z.number().optional() }).passthrough()],
174
+ ["workorder_get_record", "查询普通工单或线下工单详情及详情字段;不执行状态流转。", z.object({ scope: z.enum(["workorder", "offline"]).optional(), id: idSchema }).passthrough()],
175
+ ["workorder_get_schema", "查询普通工单或线下工单字段结构,可传 id 获取编辑字段。", z.object({ scope: z.enum(["workorder", "offline"]).optional(), id: idSchema.optional() }).passthrough()],
176
+ ["workorder_preview_write", "预览工单新建或更新请求,不调用写接口。", z.object({ scope: z.enum(["workorder", "offline"]).optional() }).passthrough()],
177
+ ["workorder_create_record", "新建普通工单或线下工单;必须 confirm=true 才会写入,写入后自动回读。", z.object({ scope: z.enum(["workorder", "offline"]).optional(), confirm: z.boolean().optional() }).passthrough()],
178
+ ["workorder_update_record", "更新普通工单或线下工单;必须提供 id 且 confirm=true,写入后自动回读。", z.object({ scope: z.enum(["workorder", "offline"]).optional(), id: idSchema, confirm: z.boolean().optional() }).passthrough()]
29
179
  ];
30
180
  export function createWukongMcpServer(config = {}) {
181
+ const { oauth, ...crmConfig } = config;
31
182
  const server = new McpServer({
32
- name: "@wukongcrm/mcp-server",
33
- version: "0.1.3"
183
+ name: "wukong-mcp",
184
+ version: "0.2.0"
185
+ }, {
186
+ instructions: "先使用只读工具确认模块、记录和字段,再执行写入。所有写入必须显式传 confirm=true;不要尝试任意 URL 请求。"
34
187
  });
35
- const handlers = createToolHandlers(config);
36
- for (const [name, description, schema] of toolDefinitions) {
188
+ const handlers = {
189
+ ...createToolHandlers(crmConfig),
190
+ ...createNocodeToolHandlers(crmConfig)
191
+ };
192
+ const registeredTools = [
193
+ ...toolDefinitions.map(([name, description, schema]) => ({
194
+ name,
195
+ description,
196
+ schema,
197
+ access: toolAccess(name, description),
198
+ destructive: isDestructiveTool(name)
199
+ })),
200
+ ...nocodeToolDefinitions.map((definition) => ({
201
+ ...definition,
202
+ destructive: definition.destructive ?? false
203
+ }))
204
+ ];
205
+ for (const { name, description, schema, access, destructive } of registeredTools) {
206
+ const requiredScopes = access === "write"
207
+ ? [CRM_READ_SCOPE, CRM_WRITE_SCOPE]
208
+ : [CRM_READ_SCOPE];
209
+ const securitySchemes = [{ type: "oauth2", scopes: requiredScopes }];
37
210
  server.registerTool(name, {
38
211
  title: name,
39
212
  description,
40
- inputSchema: schema
213
+ inputSchema: schema,
214
+ outputSchema: toolOutputSchema,
215
+ annotations: {
216
+ readOnlyHint: access === "read",
217
+ openWorldHint: false,
218
+ destructiveHint: destructive,
219
+ ...(access === "read" ? { idempotentHint: true } : {})
220
+ },
221
+ _meta: {
222
+ // ChatGPT 在扫描工具时会读取这个兼容字段。
223
+ securitySchemes
224
+ }
41
225
  }, async (args) => {
226
+ const missingScopes = oauth
227
+ ? requiredScopes.filter((scope) => !oauth.scopes.includes(scope))
228
+ : [];
229
+ if (oauth && missingScopes.length > 0) {
230
+ return insufficientScopeResult(requiredScopes, missingScopes, oauth.resourceMetadataUrl);
231
+ }
42
232
  try {
43
- const result = await handlers[name](args ?? {});
44
- return toToolText(result);
233
+ const handler = handlers[name];
234
+ if (!handler) {
235
+ throw new Error(`未找到工具实现:${name}`);
236
+ }
237
+ const result = await handler(args ?? {});
238
+ return toToolResult(result);
45
239
  }
46
240
  catch (error) {
47
241
  return {
@@ -49,7 +243,7 @@ export function createWukongMcpServer(config = {}) {
49
243
  content: [
50
244
  {
51
245
  type: "text",
52
- text: sanitizeError(error)
246
+ text: sanitizeError(error, crmConfig.apiKey)
53
247
  }
54
248
  ]
55
249
  };
@@ -58,8 +252,9 @@ export function createWukongMcpServer(config = {}) {
58
252
  }
59
253
  return server;
60
254
  }
61
- function toToolText(result) {
255
+ function toToolResult(result) {
62
256
  return {
257
+ structuredContent: { result },
63
258
  content: [
64
259
  {
65
260
  type: "text",
@@ -68,11 +263,37 @@ function toToolText(result) {
68
263
  ]
69
264
  };
70
265
  }
71
- function sanitizeError(error) {
266
+ function toolAccess(name, description) {
267
+ if (name === "crm_preview_write") {
268
+ return "read";
269
+ }
270
+ return description.includes("confirm=true") ? "write" : "read";
271
+ }
272
+ function isDestructiveTool(name) {
273
+ return /^crm_(?:update|delete|transfer|toggle|audit|batch_audit|write|save)_/.test(name);
274
+ }
275
+ function insufficientScopeResult(requiredScopes, missingScopes, resourceMetadataUrl) {
276
+ const requiredScopeText = requiredScopes.join(" ");
277
+ const missingScopeText = missingScopes.join(" ");
278
+ const challenge = `Bearer resource_metadata="${resourceMetadataUrl}", error="insufficient_scope", scope="${requiredScopeText}", error_description="${missingScopeText} scope is required"`;
279
+ return {
280
+ isError: true,
281
+ content: [
282
+ {
283
+ type: "text",
284
+ text: `当前授权缺少 ${missingScopeText} 权限,请重新授权。`
285
+ }
286
+ ],
287
+ _meta: {
288
+ "mcp/www_authenticate": [challenge]
289
+ }
290
+ };
291
+ }
292
+ function sanitizeError(error, configuredApiKey) {
72
293
  let message = error instanceof Error ? error.message : String(error);
73
- const apiKey = process.env.CRM_API_KEY;
74
- if (apiKey && apiKey.length > 4) {
75
- message = message.replaceAll(apiKey, "***");
294
+ const secrets = [configuredApiKey, process.env.CRM_API_KEY].filter((value) => typeof value === "string" && value.length > 4);
295
+ for (const secret of secrets) {
296
+ message = message.replaceAll(secret, "***");
76
297
  }
77
298
  return message;
78
299
  }