@wukongcrm/mcp-server 0.2.4 → 0.2.6
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/README.md +68 -25
- package/dist/client.d.ts +35 -1
- package/dist/client.js +156 -29
- package/dist/nocode.js +75 -47
- package/dist/server.js +228 -51
- package/dist/tool-discovery.d.ts +18 -0
- package/dist/tool-discovery.js +186 -0
- package/dist/tools.js +492 -151
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -2,60 +2,184 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { CrmClient } from "./client.js";
|
|
4
4
|
import { createNocodeToolHandlers, nocodeToolDefinitions } from "./nocode.js";
|
|
5
|
+
import { getToolDiscovery, TOOL_DISCOVERY_META_KEY } from "./tool-discovery.js";
|
|
5
6
|
import { createToolHandlers } from "./tools.js";
|
|
6
7
|
export const CRM_READ_SCOPE = "crm.read";
|
|
7
8
|
export const CRM_WRITE_SCOPE = "crm.write";
|
|
8
9
|
export const OFFLINE_ACCESS_SCOPE = "offline_access";
|
|
9
10
|
export const MCP_RESOURCE_SCOPES = [CRM_READ_SCOPE, CRM_WRITE_SCOPE];
|
|
10
11
|
export const OAUTH_SCOPES = [...MCP_RESOURCE_SCOPES, OFFLINE_ACCESS_SCOPE];
|
|
12
|
+
// 工具名称不能可靠表达副作用(例如 workorder_update_record、favourOrCancel)。
|
|
13
|
+
// 这里以显式清单作为 access/annotations 的唯一运行时来源;未列出的主工具一律按只读处理。
|
|
14
|
+
const WRITE_TOOL_SAFETY = {
|
|
15
|
+
crm_toggle_address_book_attention: { destructive: true },
|
|
16
|
+
crm_write_hrm: { destructive: true },
|
|
17
|
+
crm_create_hrm_record: { destructive: false },
|
|
18
|
+
crm_update_hrm_record: { destructive: true },
|
|
19
|
+
crm_write_finance: { destructive: true },
|
|
20
|
+
crm_create_finance_voucher: { destructive: false },
|
|
21
|
+
crm_update_finance_voucher: { destructive: true },
|
|
22
|
+
crm_write_jxc: { destructive: true },
|
|
23
|
+
crm_create_jxc_record: { destructive: false },
|
|
24
|
+
crm_update_jxc_record: { destructive: true },
|
|
25
|
+
crm_create_oa_announcement: { destructive: false },
|
|
26
|
+
crm_update_oa_announcement: { destructive: true },
|
|
27
|
+
crm_delete_oa_announcement: { destructive: true },
|
|
28
|
+
crm_mark_oa_announcement_read: { destructive: false },
|
|
29
|
+
crm_create_record: { destructive: false },
|
|
30
|
+
crm_update_customer: { destructive: true },
|
|
31
|
+
crm_update_record_fields: { destructive: true },
|
|
32
|
+
crm_create_product: { destructive: false },
|
|
33
|
+
crm_update_product: { destructive: true },
|
|
34
|
+
crm_update_product_fields: { destructive: true },
|
|
35
|
+
crm_update_product_status: { destructive: true },
|
|
36
|
+
crm_transfer_products_owner: { destructive: true },
|
|
37
|
+
crm_delete_products: { destructive: true },
|
|
38
|
+
crm_create_contract: { destructive: false },
|
|
39
|
+
crm_update_contract: { destructive: true },
|
|
40
|
+
crm_create_receivables: { destructive: false },
|
|
41
|
+
crm_update_receivables: { destructive: true },
|
|
42
|
+
crm_create_receivables_plan: { destructive: false },
|
|
43
|
+
crm_update_receivables_plan: { destructive: true },
|
|
44
|
+
crm_create_invoice: { destructive: false },
|
|
45
|
+
crm_update_invoice: { destructive: true },
|
|
46
|
+
crm_create_quotation: { destructive: false },
|
|
47
|
+
crm_update_quotation: { destructive: true },
|
|
48
|
+
crm_create_calendar_event: { destructive: false },
|
|
49
|
+
crm_update_calendar_event: { destructive: true },
|
|
50
|
+
crm_create_oa_log: { destructive: false },
|
|
51
|
+
crm_update_oa_log: { destructive: true },
|
|
52
|
+
crm_delete_oa_log: { destructive: true },
|
|
53
|
+
crm_toggle_oa_log_favour: { destructive: true },
|
|
54
|
+
crm_create_oa_examine: { destructive: false },
|
|
55
|
+
crm_save_oa_examine_draft: { destructive: true },
|
|
56
|
+
crm_audit_oa_examine: { destructive: true },
|
|
57
|
+
crm_batch_audit_oa_examine: { destructive: true },
|
|
58
|
+
crm_create_oa_task: { destructive: false },
|
|
59
|
+
crm_update_oa_task_fields: { destructive: true },
|
|
60
|
+
crm_add_followup: { destructive: false },
|
|
61
|
+
crm_update_followup: { destructive: true },
|
|
62
|
+
workorder_create_record: { destructive: false },
|
|
63
|
+
workorder_update_record: { destructive: true }
|
|
64
|
+
};
|
|
11
65
|
const moduleSchema = z.union([z.string(), z.number()]);
|
|
12
|
-
const idSchema = z.
|
|
66
|
+
const idSchema = z.string()
|
|
67
|
+
.regex(/^\d+$/, "ID 必须是数字字符串。")
|
|
68
|
+
.describe("ID 必须以数字字符串传入,禁止使用 JSON number,避免 Snowflake ID 精度丢失。");
|
|
13
69
|
const passthroughSchema = z.object({}).passthrough();
|
|
14
70
|
const toolOutputSchema = z.object({ result: z.unknown() });
|
|
15
71
|
const searchFilterSchema = z.object({
|
|
16
|
-
fieldName: z.string().optional(),
|
|
17
|
-
name: z.string().optional(),
|
|
18
|
-
formType: z.string().optional(),
|
|
19
|
-
type: z.number().optional(),
|
|
20
|
-
searchEnum: z.union([
|
|
21
|
-
|
|
22
|
-
|
|
72
|
+
fieldName: z.string().optional().describe("后端字段名;与兼容字段 name 至少提供一个。"),
|
|
73
|
+
name: z.string().optional().describe("fieldName 的兼容别名。"),
|
|
74
|
+
formType: z.string().optional().describe("字段类型。人员用 user/single_user;日期用 date,日期时间用 datetime。"),
|
|
75
|
+
type: z.number().int().min(1).max(14).optional().describe("高级筛选操作码;与 searchEnum 同义,同时提供时必须一致。人员 ID 匹配用 3,日期范围用 14。"),
|
|
76
|
+
searchEnum: z.union([
|
|
77
|
+
z.number().int().min(1).max(14),
|
|
78
|
+
z.enum(["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14"])
|
|
79
|
+
]).optional().describe("高级筛选操作码;与 type 同义,同时提供时必须一致。"),
|
|
80
|
+
values: z.array(z.unknown()).optional().describe("筛选值数组。ID 必须使用数字字符串;除为空/不为空操作 5/6 外必须提供;datetime 范围 14 使用 [YYYY-MM-DD HH:mm:ss, YYYY-MM-DD HH:mm:ss]。"),
|
|
81
|
+
groupId: z.number().int().optional().describe("同组条件为 AND,不同组为 OR。")
|
|
23
82
|
}).passthrough();
|
|
24
83
|
const crmCreateRecordSchema = z.object({
|
|
25
84
|
module: moduleSchema,
|
|
26
|
-
entity: z.record(z.unknown()).optional().describe("CRM 实体字段;系统字段放在这里,例如 entity.customerName
|
|
85
|
+
entity: z.record(z.unknown()).optional().describe("CRM 实体字段;系统字段放在这里,例如 entity.customerName;所有 *Id/*Ids 字段使用数字字符串。"),
|
|
27
86
|
field: z.array(z.record(z.unknown())).optional().describe("CRM 自定义字段列表。"),
|
|
28
87
|
saveType: z.number().optional(),
|
|
29
88
|
fileId: idSchema.optional(),
|
|
30
89
|
aiAnalysis: z.number().optional(),
|
|
31
90
|
member: z.record(z.unknown()).optional(),
|
|
91
|
+
product: z.array(z.unknown()).optional().describe("商机等保存接口的关联产品列表;这是顶层字段,不放入 entity。"),
|
|
92
|
+
contactsId: idSchema.optional().describe("商机主联系人 ID;这是顶层字段,不放入 entity。"),
|
|
93
|
+
customerSuperior: z.record(z.unknown()).optional().describe("上级客户信息;这是顶层字段。"),
|
|
94
|
+
contacts: z.record(z.unknown()).optional().describe("企业微信联系人信息;这是顶层字段。"),
|
|
95
|
+
examineFlowData: z.record(z.unknown()).optional().describe("审批数据;这是顶层字段。"),
|
|
96
|
+
confirm: z.boolean().optional()
|
|
97
|
+
}).strict();
|
|
98
|
+
const followupActivityTypeSchema = z.union([
|
|
99
|
+
z.literal(1), z.literal(2), z.literal(3), z.literal(4), z.literal(5), z.literal(6),
|
|
100
|
+
z.literal(7), z.literal(14), z.literal(17), z.literal(18), z.literal(22)
|
|
101
|
+
]).describe("关联模块类型:1线索、2客户、3联系人、4产品、5商机、6合同、7回款、14回款计划、17回访、18发票、22报价单。");
|
|
102
|
+
const followupEntitySchema = z.object({
|
|
103
|
+
id: idSchema.optional().describe("更新跟进时的跟进记录 ID;必须使用数字字符串。"),
|
|
104
|
+
content: z.string().min(1).describe("跟进内容,不能为空。"),
|
|
105
|
+
category: z.string().min(1).describe("跟进方式/分类,不能为空。"),
|
|
106
|
+
activityType: followupActivityTypeSchema,
|
|
107
|
+
activityTypeId: idSchema.describe("被跟进 CRM 记录的 ID;必须使用数字字符串。"),
|
|
108
|
+
activityTypeName: z.string().optional(),
|
|
109
|
+
nextTime: z.string().optional(),
|
|
110
|
+
visitPlanId: idSchema.optional(),
|
|
111
|
+
remindTeamMembers: z.unknown().optional(),
|
|
112
|
+
atUserId: z.array(idSchema).optional(),
|
|
113
|
+
relateDataList: z.array(z.record(z.unknown())).optional()
|
|
114
|
+
}).passthrough();
|
|
115
|
+
const crmAddFollowupSchema = z.object({
|
|
116
|
+
entity: followupEntitySchema,
|
|
117
|
+
field: z.array(z.record(z.unknown())).optional(),
|
|
118
|
+
saveType: z.number().optional(),
|
|
119
|
+
fileId: idSchema.optional(),
|
|
120
|
+
aiAnalysis: z.number().optional(),
|
|
121
|
+
member: z.record(z.unknown()).optional(),
|
|
122
|
+
confirm: z.boolean().optional()
|
|
123
|
+
}).strict();
|
|
124
|
+
const crmUpdateFollowupSchema = z.object({
|
|
125
|
+
id: idSchema.optional(),
|
|
126
|
+
entity: followupEntitySchema.optional(),
|
|
127
|
+
field: z.array(z.record(z.unknown())).optional(),
|
|
128
|
+
saveType: z.number().optional(),
|
|
129
|
+
fileId: idSchema.optional(),
|
|
130
|
+
aiAnalysis: z.number().optional(),
|
|
131
|
+
member: z.record(z.unknown()).optional(),
|
|
132
|
+
useInformationUpdate: z.boolean().optional(),
|
|
133
|
+
list: z.array(z.record(z.unknown())).optional(),
|
|
134
|
+
fields: z.array(z.record(z.unknown())).optional(),
|
|
135
|
+
batchId: z.string().optional(),
|
|
32
136
|
confirm: z.boolean().optional()
|
|
33
137
|
}).strict();
|
|
34
138
|
const crmUserSearchShape = {
|
|
35
|
-
ownerUserId: idSchema.optional(),
|
|
36
|
-
ownerUserIds: z.array(idSchema).optional(),
|
|
37
|
-
ownerUserName: z.string().optional(),
|
|
38
|
-
ownerUserNames: z.array(z.string()).optional(),
|
|
39
|
-
createUserId: idSchema.optional(),
|
|
40
|
-
createUserIds: z.array(idSchema).optional(),
|
|
41
|
-
createUserName: z.string().optional(),
|
|
42
|
-
createUserNames: z.array(z.string()).optional(),
|
|
43
|
-
filters: z.array(searchFilterSchema).optional(),
|
|
44
|
-
searchList: z.array(searchFilterSchema).optional()
|
|
139
|
+
ownerUserId: idSchema.optional().describe("负责人用户 ID;MCP 转为后端支持的人员包含操作 3。"),
|
|
140
|
+
ownerUserIds: z.array(idSchema).optional().describe("多个负责人用户 ID。"),
|
|
141
|
+
ownerUserName: z.string().optional().describe("负责人唯一昵称;MCP 先解析为用户 ID,重名时报错。"),
|
|
142
|
+
ownerUserNames: z.array(z.string()).optional().describe("多个负责人唯一昵称。"),
|
|
143
|
+
createUserId: idSchema.optional().describe("创建人用户 ID;查询跟进记录时表示跟进人员。"),
|
|
144
|
+
createUserIds: z.array(idSchema).optional().describe("多个创建人用户 ID。"),
|
|
145
|
+
createUserName: z.string().optional().describe("创建人唯一昵称;查询跟进记录时表示跟进人员。"),
|
|
146
|
+
createUserNames: z.array(z.string()).optional().describe("多个创建人唯一昵称。"),
|
|
147
|
+
filters: z.array(searchFilterSchema).optional().describe("高级筛选列表;与 searchList 二选一。"),
|
|
148
|
+
searchList: z.array(searchFilterSchema).optional().describe("filters 的兼容别名;不能与 filters 同时提供。")
|
|
45
149
|
};
|
|
46
150
|
const toolDefinitions = [
|
|
47
151
|
["crm_auth_status", "使用 CRM_API_KEY 自动换取 Admin-Token 后检查当前登录态,异常时提示检查 API Key。", z.object({}).optional()],
|
|
48
152
|
["crm_list_modules", "列出当前 MCP 固定支持的 72CRM 模块、label、路径和中英文操作边界。", z.object({}).optional()],
|
|
49
153
|
["crm_list_customer_pools", "查询当前账号有权限访问的客户公海,并统计每个公海下面有多少客户;只读,不领取、不分配、不流转。", z.object({ includeSamples: z.boolean().optional(), sampleLimit: z.number().optional() }).passthrough()],
|
|
50
|
-
["crm_get_module_schema", "获取模块字段结构和列表头,供本地 AI 判断字段怎么填。", z.object({ module: moduleSchema, id:
|
|
154
|
+
["crm_get_module_schema", "获取模块字段结构和列表头,供本地 AI 判断字段怎么填。", z.object({ module: moduleSchema, id: idSchema.optional() }).passthrough()],
|
|
155
|
+
["crm_get_business_status_groups", "查询当前账号可用的商机状态组及阶段。新增商机前必须调用;entity.typeId 取 flowId,entity.statusId 取同一组 settingList 中的 settingId。存在多个状态组且用户未指定时必须询问,不能猜测。", z.object({ businessId: idSchema.optional() }).strict()],
|
|
51
156
|
["crm_validate_field", "调用固定字段校验接口,校验字段值或联系人查重。", passthroughSchema],
|
|
52
|
-
["crm_search_records", "
|
|
53
|
-
["crm_get_record", "按模块和 ID 查询单条 CRM
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
["
|
|
58
|
-
|
|
157
|
+
["crm_search_records", "查询模块分页列表。人员便捷参数和 user 筛选会解析为用户 ID,并使用后端支持的操作 3;日期/日期时间范围筛选必须使用操作 14。查询某人员某天的跟进记录时,module=activity,人员使用 createUserId(s)/createUserName(s),createTime 使用 formType=datetime 和当天 00:00:00 至 23:59:59 的范围。", z.object({ module: moduleSchema, page: z.number().optional(), limit: z.number().optional(), ...crmUserSearchShape }).passthrough()],
|
|
158
|
+
["crm_get_record", "按模块和 ID 查询单条 CRM 记录的完整对象;不返回详情页字段标签、记录动态、跟进列表或操作记录。", z.object({
|
|
159
|
+
module: moduleSchema.describe("CRM 模块 key、label 或别名;可先调用 crm_list_modules 获取。"),
|
|
160
|
+
id: idSchema.describe("目标 CRM 记录 ID;必须使用数字字符串。")
|
|
161
|
+
}).passthrough()],
|
|
162
|
+
["crm_get_record_information", "查询 CRM 详情页字段展示数据,返回字段中文名和值;不返回完整记录对象、记录动态、跟进列表或操作记录。", z.object({
|
|
163
|
+
module: moduleSchema.describe("CRM 模块 key、label 或别名。"),
|
|
164
|
+
id: idSchema.describe("目标 CRM 记录 ID;必须使用数字字符串。")
|
|
165
|
+
}).passthrough()],
|
|
166
|
+
["crm_get_related_records", "查询固定白名单内的 CRM 关联资料,例如商机产品、联系人商机、合同回款计划、产品上架列表;不查询记录动态、跟进记录或操作记录。", z.object({
|
|
167
|
+
module: moduleSchema.describe("CRM 模块 key、label 或别名。"),
|
|
168
|
+
id: idSchema.optional().describe("目标 CRM 记录 ID;具体 relation 不需要 ID 时可省略。"),
|
|
169
|
+
relation: z.string().describe("关联资料类型;必须是 MCP 固定白名单内的 relation。")
|
|
170
|
+
}).passthrough()],
|
|
171
|
+
["crm_get_record_timeline", "查询某条 CRM 记录的系统动态时间线(如创建记录、字段变化、阶段变化);固定调用 /crmActivity/queryActivityList,不返回跟进记录列表,也不是操作审计记录。", z.object({
|
|
172
|
+
module: moduleSchema.describe("CRM 模块 key、label 或别名,用于确定动态的 activityType。"),
|
|
173
|
+
id: idSchema.describe("目标 CRM 记录 ID;必须使用数字字符串。"),
|
|
174
|
+
page: z.number().int().positive().optional().describe("动态页码,默认 1。"),
|
|
175
|
+
limit: z.number().int().positive().max(200).optional().describe("每页动态条数,默认 15。"),
|
|
176
|
+
extra: z.record(z.unknown()).optional().describe("透传给动态查询接口的附加只读筛选参数。")
|
|
177
|
+
}).passthrough()],
|
|
178
|
+
["crm_get_action_records", "查询某条 CRM 记录的操作审计记录;不返回记录动态或跟进记录列表。", z.object({
|
|
179
|
+
module: moduleSchema.describe("CRM 模块 key、label 或别名。"),
|
|
180
|
+
id: idSchema.describe("目标 CRM 记录 ID;必须使用数字字符串。")
|
|
181
|
+
}).passthrough()],
|
|
182
|
+
["crm_list_files", "只读查询附件元数据,不上传、不删除、不代理下载流。", z.object({ module: moduleSchema, id: idSchema }).passthrough()],
|
|
59
183
|
["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()],
|
|
60
184
|
["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()],
|
|
61
185
|
["crm_get_product", "只读查询 CRM 产品详情;固定调用 /crmProduct/queryById/{productId}。", z.object({ productId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
|
|
@@ -120,8 +244,8 @@ const toolDefinitions = [
|
|
|
120
244
|
["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()],
|
|
121
245
|
["crm_create_jxc_record", "新增 JXC 产品、供应商或仓库主数据;固定调用对应 /add,必须 confirm=true。", z.object({ module: moduleSchema, confirm: z.boolean().optional() }).passthrough()],
|
|
122
246
|
["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()],
|
|
123
|
-
["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(
|
|
124
|
-
["crm_get_calendar_event", "查询 OA 日历单条日程详情;固定调用 /oaEvent/queryById。", z.object({ eventId:
|
|
247
|
+
["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(idSchema).optional(), userId: idSchema.optional() }).passthrough()],
|
|
248
|
+
["crm_get_calendar_event", "查询 OA 日历单条日程详情;固定调用 /oaEvent/queryById。", z.object({ eventId: idSchema.optional(), id: idSchema.optional(), startTime: z.union([z.string(), z.number()]).optional(), endTime: z.union([z.string(), z.number()]).optional() }).passthrough()],
|
|
125
249
|
["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()],
|
|
126
250
|
["crm_get_oa_announcement", "查询 OA 公告详情;固定调用 /oaAnnouncement/queryById/{announcementId}。", z.object({ announcementId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
|
|
127
251
|
["crm_create_oa_announcement", "新增 OA 公告;固定调用 /oaAnnouncement/addAnnouncement,必须 confirm=true。", z.object({ confirm: z.boolean().optional() }).passthrough()],
|
|
@@ -146,11 +270,11 @@ const toolDefinitions = [
|
|
|
146
270
|
["crm_list_oa_examine_record_logs", "只读查询 OA 审批流转日志;固定调用 /examineRecord/queryExamineRecordLog。", z.object({ recordId: idSchema.optional(), examineRecordId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
|
|
147
271
|
["crm_list_oa_examine_flow_records", "只读查询 OA 审批流程节点记录;固定调用 /examineSuperExamine/examine/record/list。", z.object({ recordId: idSchema.optional(), examineRecordId: idSchema.optional(), id: idSchema.optional() }).passthrough()],
|
|
148
272
|
["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()],
|
|
149
|
-
["crm_list_related_oa_tasks", "按关联业务查询 OA 任务;固定调用 /oaTask/queryTypeTaskList。", z.object({ type: z.union([z.string(), z.number()]), typeId:
|
|
273
|
+
["crm_list_related_oa_tasks", "按关联业务查询 OA 任务;固定调用 /oaTask/queryTypeTaskList。", z.object({ type: z.union([z.string(), z.number()]), typeId: idSchema }).passthrough()],
|
|
150
274
|
["crm_preview_write", "写入前预览和校验,不调用 72CRM 写接口。", passthroughSchema],
|
|
151
|
-
["crm_create_record", "新增线索、客户、联系人、产品、商机;系统字段放 entity,自定义字段放 field,不支持把 customerName
|
|
275
|
+
["crm_create_record", "新增线索、客户、联系人、产品、商机;系统字段放 entity,自定义字段放 field,不支持把 customerName 放在最外层。新增商机必须先用 crm_get_business_status_groups 获取合法的 entity.typeId/statusId,并提供 businessName/customerId;必须 confirm=true 才会写入。", crmCreateRecordSchema],
|
|
152
276
|
["crm_update_customer", "按客户 ID 或手机号/客户名唯一匹配后更新客户字段;会自动补全字段结构并写后校验,必须 confirm=true 才会写入。", z.object({ confirm: z.boolean().optional() }).passthrough()],
|
|
153
|
-
["crm_update_record_fields", "更新线索、客户、联系人、产品、商机基础字段;必须 confirm=true 才会写入。", z.object({ module: moduleSchema, id:
|
|
277
|
+
["crm_update_record_fields", "更新线索、客户、联系人、产品、商机基础字段;必须 confirm=true 才会写入。", z.object({ module: moduleSchema, id: idSchema, confirm: z.boolean().optional() }).passthrough()],
|
|
154
278
|
["crm_create_product", "新增 CRM 产品;固定调用 /crmProduct/add,可直接传 72CRM payload 或 entity/field,必须 confirm=true。", z.object({ confirm: z.boolean().optional() }).passthrough()],
|
|
155
279
|
["crm_update_product", "编辑 CRM 产品;固定调用 /crmProduct/update,必须传 productId/id,必须 confirm=true。", z.object({ productId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
|
|
156
280
|
["crm_update_product_fields", "更新 CRM 产品基础字段;固定调用 /crmProduct/updateInformation,会自动补字段结构,必须 confirm=true。", z.object({ productId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
|
|
@@ -168,7 +292,7 @@ const toolDefinitions = [
|
|
|
168
292
|
["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()],
|
|
169
293
|
["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()],
|
|
170
294
|
["crm_create_calendar_event", "新增 OA 日历日程;支持 72CRM SetEventBO 结构或顶层便捷字段,必须 confirm=true 才会写入。", z.object({ confirm: z.boolean().optional() }).passthrough()],
|
|
171
|
-
["crm_update_calendar_event", "修改 OA 日历日程;支持 72CRM SetEventBO 结构或顶层便捷字段,必须 confirm=true 才会写入。", z.object({ eventId:
|
|
295
|
+
["crm_update_calendar_event", "修改 OA 日历日程;支持 72CRM SetEventBO 结构或顶层便捷字段,必须 confirm=true 才会写入。", z.object({ eventId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
|
|
172
296
|
["crm_create_oa_log", "新增 OA 日志;固定调用 /oaLog/addOrUpdate,可直接传 payload,或传 categoryId+fields 自动组装日志模板字段,必须 confirm=true。", z.object({ categoryId: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
|
|
173
297
|
["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()],
|
|
174
298
|
["crm_delete_oa_log", "删除 OA 日志;固定调用 /oaLog/deleteById,logId 走查询参数,必须 confirm=true。", z.object({ logId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
|
|
@@ -178,9 +302,9 @@ const toolDefinitions = [
|
|
|
178
302
|
["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()],
|
|
179
303
|
["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()],
|
|
180
304
|
["crm_create_oa_task", "新增 OA 主任务;固定调用 /workTask/saveWorkTask,支持 WorkTask 结构或顶层便捷字段,必须 confirm=true 才会写入。", z.object({ confirm: z.boolean().optional() }).passthrough()],
|
|
181
|
-
["crm_update_oa_task_fields", "更新 OA 主任务字段;按字段映射到 /workTask 下的标题、描述、负责人、参与人、时间、标签、优先级、状态接口,必须 confirm=true 才会写入。", z.object({ taskId:
|
|
182
|
-
["crm_add_followup", "
|
|
183
|
-
["crm_update_followup", "
|
|
305
|
+
["crm_update_oa_task_fields", "更新 OA 主任务字段;按字段映射到 /workTask 下的标题、描述、负责人、参与人、时间、标签、优先级、状态接口,必须 confirm=true 才会写入。", z.object({ taskId: idSchema.optional(), id: idSchema.optional(), confirm: z.boolean().optional() }).passthrough()],
|
|
306
|
+
["crm_add_followup", "新增跟进记录;entity 必须提供 content、category、activityType 和 activityTypeId,MCP 会先回读关联记录再写入;必须 confirm=true 才会写入。", crmAddFollowupSchema],
|
|
307
|
+
["crm_update_followup", "更新跟进记录;完整更新必须提供 entity,字段更新使用 useInformationUpdate=true 和 list;必须 confirm=true 才会写入。", crmUpdateFollowupSchema],
|
|
184
308
|
["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()],
|
|
185
309
|
["workorder_get_record", "查询普通工单或线下工单详情及详情字段;不执行状态流转。", z.object({ scope: z.enum(["workorder", "offline"]).optional(), id: idSchema }).passthrough()],
|
|
186
310
|
["workorder_get_schema", "查询普通工单或线下工单字段结构,可传 id 获取编辑字段。", z.object({ scope: z.enum(["workorder", "offline"]).optional(), id: idSchema.optional() }).passthrough()],
|
|
@@ -192,9 +316,13 @@ export function createWukongMcpServer(config = {}) {
|
|
|
192
316
|
const { oauth, ...crmConfig } = config;
|
|
193
317
|
const server = new McpServer({
|
|
194
318
|
name: "wukong-mcp",
|
|
195
|
-
version: "0.2.
|
|
319
|
+
version: "0.2.6"
|
|
196
320
|
}, {
|
|
197
321
|
instructions: "先使用只读工具确认模块、记录和字段,再执行写入。所有写入必须显式传 confirm=true;不要尝试任意 URL 请求。"
|
|
322
|
+
+ "所有 ID 必须使用数字字符串,禁止把 Snowflake ID 作为 JSON number 传入。"
|
|
323
|
+
+ "写工具正常返回且 writeOutcome=confirmed 即表示成功;data=null 只表示旧 CRM 未返回业务数据。"
|
|
324
|
+
+ "工具返回错误时不得声称原操作成功,也不得未经用户明确同意改用 OA 任务等其他模块替代。"
|
|
325
|
+
+ "写入错误若标记 writeOutcome=unknown,必须先回读原目标确认是否落库,禁止直接重放。"
|
|
198
326
|
});
|
|
199
327
|
const sharedClient = new CrmClient(crmConfig);
|
|
200
328
|
const handlers = {
|
|
@@ -206,21 +334,23 @@ export function createWukongMcpServer(config = {}) {
|
|
|
206
334
|
name,
|
|
207
335
|
description,
|
|
208
336
|
schema,
|
|
209
|
-
access: toolAccess(name
|
|
210
|
-
destructive: isDestructiveTool(name)
|
|
337
|
+
access: toolAccess(name),
|
|
338
|
+
destructive: isDestructiveTool(name),
|
|
339
|
+
discovery: getToolDiscovery(name, description)
|
|
211
340
|
})),
|
|
212
341
|
...nocodeToolDefinitions.map((definition) => ({
|
|
213
342
|
...definition,
|
|
214
|
-
destructive: definition.destructive ?? false
|
|
343
|
+
destructive: definition.destructive ?? false,
|
|
344
|
+
discovery: getToolDiscovery(definition.name, definition.description)
|
|
215
345
|
}))
|
|
216
346
|
];
|
|
217
|
-
for (const { name, description, schema, access, destructive } of registeredTools) {
|
|
347
|
+
for (const { name, description, schema, access, destructive, discovery } of registeredTools) {
|
|
218
348
|
const requiredScopes = access === "write"
|
|
219
349
|
? [CRM_READ_SCOPE, CRM_WRITE_SCOPE]
|
|
220
350
|
: [CRM_READ_SCOPE];
|
|
221
351
|
const securitySchemes = [{ type: "oauth2", scopes: requiredScopes }];
|
|
222
352
|
server.registerTool(name, {
|
|
223
|
-
title:
|
|
353
|
+
title: discovery.title,
|
|
224
354
|
description,
|
|
225
355
|
inputSchema: schema,
|
|
226
356
|
outputSchema: toolOutputSchema,
|
|
@@ -231,8 +361,9 @@ export function createWukongMcpServer(config = {}) {
|
|
|
231
361
|
...(access === "read" ? { idempotentHint: true } : {})
|
|
232
362
|
},
|
|
233
363
|
_meta: {
|
|
234
|
-
//
|
|
235
|
-
securitySchemes
|
|
364
|
+
// securitySchemes 供通用 MCP 客户端鉴权;命名空间字段供宿主做确定性发现,不进入业务参数。
|
|
365
|
+
securitySchemes,
|
|
366
|
+
[TOOL_DISCOVERY_META_KEY]: discovery.meta
|
|
236
367
|
}
|
|
237
368
|
}, async (args) => {
|
|
238
369
|
const missingScopes = oauth
|
|
@@ -246,8 +377,9 @@ export function createWukongMcpServer(config = {}) {
|
|
|
246
377
|
if (!handler) {
|
|
247
378
|
throw new Error(`未找到工具实现:${name}`);
|
|
248
379
|
}
|
|
249
|
-
const
|
|
250
|
-
|
|
380
|
+
const normalizedArgs = normalizeToolArguments(args ?? {});
|
|
381
|
+
const result = await handler(normalizedArgs);
|
|
382
|
+
return toToolResult(access === "write" ? withSuccessfulWriteOutcome(result) : result);
|
|
251
383
|
}
|
|
252
384
|
catch (error) {
|
|
253
385
|
return {
|
|
@@ -275,14 +407,59 @@ function toToolResult(result) {
|
|
|
275
407
|
]
|
|
276
408
|
};
|
|
277
409
|
}
|
|
278
|
-
function
|
|
279
|
-
if (
|
|
280
|
-
return
|
|
410
|
+
function withSuccessfulWriteOutcome(result) {
|
|
411
|
+
if (isRecord(result) && result.preview === true) {
|
|
412
|
+
return result;
|
|
281
413
|
}
|
|
282
|
-
|
|
414
|
+
const details = isRecord(result) ? result : { data: result ?? null };
|
|
415
|
+
return {
|
|
416
|
+
...details,
|
|
417
|
+
success: true,
|
|
418
|
+
requestAccepted: true,
|
|
419
|
+
crmCode: 0,
|
|
420
|
+
writeOutcome: "confirmed"
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function normalizeToolArguments(value, path = "arguments") {
|
|
424
|
+
if (Array.isArray(value)) {
|
|
425
|
+
return value.map((item, index) => normalizeToolArguments(item, `${path}[${index}]`));
|
|
426
|
+
}
|
|
427
|
+
if (!isRecord(value)) {
|
|
428
|
+
return value;
|
|
429
|
+
}
|
|
430
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => {
|
|
431
|
+
const itemPath = `${path}.${key}`;
|
|
432
|
+
return [key, isIdFieldName(key)
|
|
433
|
+
? normalizeIdFieldValue(item, itemPath)
|
|
434
|
+
: normalizeToolArguments(item, itemPath)];
|
|
435
|
+
}));
|
|
436
|
+
}
|
|
437
|
+
function normalizeIdFieldValue(value, path) {
|
|
438
|
+
if (Array.isArray(value)) {
|
|
439
|
+
return value.map((item, index) => normalizeIdFieldValue(item, `${path}[${index}]`));
|
|
440
|
+
}
|
|
441
|
+
if (isRecord(value)) {
|
|
442
|
+
return normalizeToolArguments(value, path);
|
|
443
|
+
}
|
|
444
|
+
if (typeof value !== "number") {
|
|
445
|
+
return value;
|
|
446
|
+
}
|
|
447
|
+
if (!Number.isSafeInteger(value)) {
|
|
448
|
+
throw new Error(`${path} 使用了不安全的 JSON number,原始 ID 可能已丢失精度;请改用数字字符串。`);
|
|
449
|
+
}
|
|
450
|
+
return String(value);
|
|
451
|
+
}
|
|
452
|
+
function isIdFieldName(key) {
|
|
453
|
+
return key === "id" || key === "ids" || /Ids?$/.test(key) || /_ids?$/i.test(key);
|
|
454
|
+
}
|
|
455
|
+
function isRecord(value) {
|
|
456
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
457
|
+
}
|
|
458
|
+
function toolAccess(name) {
|
|
459
|
+
return Object.hasOwn(WRITE_TOOL_SAFETY, name) ? "write" : "read";
|
|
283
460
|
}
|
|
284
461
|
function isDestructiveTool(name) {
|
|
285
|
-
return
|
|
462
|
+
return WRITE_TOOL_SAFETY[name]?.destructive ?? false;
|
|
286
463
|
}
|
|
287
464
|
function insufficientScopeResult(requiredScopes, missingScopes, resourceMetadataUrl) {
|
|
288
465
|
const requiredScopeText = requiredScopes.join(" ");
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const TOOL_DISCOVERY_META_KEY = "com.wukongcrm/tool-discovery";
|
|
2
|
+
export interface ToolDiscoveryMeta {
|
|
3
|
+
version: 1;
|
|
4
|
+
domain: string;
|
|
5
|
+
module: string;
|
|
6
|
+
entities: string[];
|
|
7
|
+
actions: string[];
|
|
8
|
+
capability: string;
|
|
9
|
+
aliases: string[];
|
|
10
|
+
excludes?: string[];
|
|
11
|
+
requires?: string[];
|
|
12
|
+
verifiesWith?: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface ToolDiscoveryDefinition {
|
|
15
|
+
title: string;
|
|
16
|
+
meta: ToolDiscoveryMeta;
|
|
17
|
+
}
|
|
18
|
+
export declare function getToolDiscovery(name: string, _description: string): ToolDiscoveryDefinition;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { CRM_MODULES } from "./modules.js";
|
|
2
|
+
export const TOOL_DISCOVERY_META_KEY = "com.wukongcrm/tool-discovery";
|
|
3
|
+
const CRM_ENTITIES = CRM_MODULES.flatMap((definition) => [definition.key, definition.name]);
|
|
4
|
+
// 这批工具容易因“详情、动态、跟进、操作记录”等相邻概念误选,元数据按真实接口语义显式区分。
|
|
5
|
+
const CORE_DISCOVERY = {
|
|
6
|
+
crm_search_records: {
|
|
7
|
+
title: "搜索 CRM 记录",
|
|
8
|
+
domain: "crm",
|
|
9
|
+
module: "record",
|
|
10
|
+
entities: CRM_ENTITIES,
|
|
11
|
+
actions: ["search", "list", "查询", "搜索"],
|
|
12
|
+
capability: "crm.record.search",
|
|
13
|
+
aliases: ["查询客户", "搜索联系人", "商机列表", "按负责人查询", "查询跟进记录"],
|
|
14
|
+
excludes: ["单条详情", "记录动态", "操作记录"]
|
|
15
|
+
},
|
|
16
|
+
crm_get_record: {
|
|
17
|
+
title: "查询 CRM 单条记录",
|
|
18
|
+
domain: "crm",
|
|
19
|
+
module: "record",
|
|
20
|
+
entities: CRM_ENTITIES,
|
|
21
|
+
actions: ["get", "read", "查询", "查看"],
|
|
22
|
+
capability: "crm.record.detail.read",
|
|
23
|
+
aliases: ["记录详情", "客户详情", "联系人详情", "商机详情", "按 ID 查询记录", "确认目标记录"],
|
|
24
|
+
excludes: ["详情页字段", "记录动态", "跟进记录列表", "操作记录", "关联资料"]
|
|
25
|
+
},
|
|
26
|
+
crm_get_record_information: {
|
|
27
|
+
title: "查询 CRM 详情页字段",
|
|
28
|
+
domain: "crm",
|
|
29
|
+
module: "record-information",
|
|
30
|
+
entities: CRM_ENTITIES,
|
|
31
|
+
actions: ["get", "read", "查询", "查看"],
|
|
32
|
+
capability: "crm.record.field-information.read",
|
|
33
|
+
aliases: ["详情页信息", "字段中文名和值", "基本信息字段", "详情字段展示"],
|
|
34
|
+
excludes: ["完整记录对象", "记录动态", "跟进记录列表", "操作记录"]
|
|
35
|
+
},
|
|
36
|
+
crm_get_related_records: {
|
|
37
|
+
title: "查询 CRM 关联资料",
|
|
38
|
+
domain: "crm",
|
|
39
|
+
module: "record-relation",
|
|
40
|
+
entities: CRM_ENTITIES,
|
|
41
|
+
actions: ["get", "read", "查询"],
|
|
42
|
+
capability: "crm.record.relation.read",
|
|
43
|
+
aliases: ["关联资料", "关联数据", "相关产品", "相关联系人", "回款计划"],
|
|
44
|
+
excludes: ["记录动态", "跟进记录", "操作记录"]
|
|
45
|
+
},
|
|
46
|
+
crm_get_record_timeline: {
|
|
47
|
+
title: "查询 CRM 记录动态",
|
|
48
|
+
domain: "crm",
|
|
49
|
+
module: "record-dynamics",
|
|
50
|
+
entities: CRM_ENTITIES,
|
|
51
|
+
actions: ["get", "read", "查询"],
|
|
52
|
+
capability: "crm.record.dynamics.read",
|
|
53
|
+
aliases: ["记录动态", "动态时间线", "创建记录", "字段变化", "阶段变化", "系统动态"],
|
|
54
|
+
excludes: ["跟进记录列表", "操作审计记录", "市场活动", "关联资料"]
|
|
55
|
+
},
|
|
56
|
+
crm_get_action_records: {
|
|
57
|
+
title: "查询 CRM 操作记录",
|
|
58
|
+
domain: "crm",
|
|
59
|
+
module: "record-audit",
|
|
60
|
+
entities: CRM_ENTITIES,
|
|
61
|
+
actions: ["get", "read", "查询"],
|
|
62
|
+
capability: "crm.record.audit-log.read",
|
|
63
|
+
aliases: ["操作记录", "变更日志", "审计记录", "操作审计"],
|
|
64
|
+
excludes: ["记录动态", "跟进记录列表"]
|
|
65
|
+
},
|
|
66
|
+
crm_get_business_status_groups: {
|
|
67
|
+
title: "查询商机状态组",
|
|
68
|
+
domain: "crm",
|
|
69
|
+
module: "business",
|
|
70
|
+
entities: ["business", "商机", "商机状态组", "商机阶段"],
|
|
71
|
+
actions: ["get", "read", "查询"],
|
|
72
|
+
capability: "crm.business.status.read",
|
|
73
|
+
aliases: ["商机状态组", "商机阶段", "创建商机前置", "typeId", "statusId"],
|
|
74
|
+
excludes: ["创建商机记录"]
|
|
75
|
+
},
|
|
76
|
+
crm_create_record: {
|
|
77
|
+
title: "新建 CRM 记录",
|
|
78
|
+
domain: "crm",
|
|
79
|
+
module: "record",
|
|
80
|
+
entities: ["leads", "线索", "customer", "客户", "contacts", "联系人", "product", "产品", "business", "商机"],
|
|
81
|
+
actions: ["create", "add", "创建", "新增"],
|
|
82
|
+
capability: "crm.record.create",
|
|
83
|
+
aliases: ["创建客户", "新增联系人", "新建产品", "创建商机", "新增线索"],
|
|
84
|
+
// 聚合创建只在目标实体为商机时需要该前置能力;消费者应结合依赖工具的 entities 判断是否联动发现。
|
|
85
|
+
requires: ["crm.business.status.read"],
|
|
86
|
+
verifiesWith: ["crm.record.detail.read"]
|
|
87
|
+
},
|
|
88
|
+
crm_add_followup: {
|
|
89
|
+
title: "新增 CRM 跟进记录",
|
|
90
|
+
domain: "crm",
|
|
91
|
+
module: "followup",
|
|
92
|
+
entities: ["activity", "跟进记录", "线索", "客户", "联系人", "产品", "商机", "合同", "回款", "回款计划", "回访", "报价单"],
|
|
93
|
+
actions: ["create", "add", "创建", "新增"],
|
|
94
|
+
capability: "crm.followup.create",
|
|
95
|
+
aliases: ["创建跟进记录", "新增跟进", "写跟进内容", "记录拜访结果"],
|
|
96
|
+
excludes: ["查询跟进记录", "记录动态"],
|
|
97
|
+
verifiesWith: ["crm.record.detail.read"]
|
|
98
|
+
},
|
|
99
|
+
nocode_search_records: {
|
|
100
|
+
title: "搜索无代码记录",
|
|
101
|
+
domain: "nocode",
|
|
102
|
+
module: "record",
|
|
103
|
+
entities: ["nocode-record", "无代码记录", "应用记录", "模块记录"],
|
|
104
|
+
actions: ["search", "list", "查询", "搜索"],
|
|
105
|
+
capability: "nocode.record.search",
|
|
106
|
+
aliases: ["查询无代码模块记录", "应用数据列表", "搜索模块数据"]
|
|
107
|
+
},
|
|
108
|
+
nocode_write_record: {
|
|
109
|
+
title: "写入无代码记录",
|
|
110
|
+
domain: "nocode",
|
|
111
|
+
module: "record",
|
|
112
|
+
entities: ["nocode-record", "无代码记录", "应用记录", "模块记录"],
|
|
113
|
+
actions: ["create", "update", "delete", "创建", "修改", "删除"],
|
|
114
|
+
capability: "nocode.record.write",
|
|
115
|
+
aliases: ["新增无代码记录", "修改应用数据", "删除模块记录"],
|
|
116
|
+
verifiesWith: ["nocode.record.search"]
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const ACTION_BY_VERB = {
|
|
120
|
+
list: "list",
|
|
121
|
+
search: "search",
|
|
122
|
+
query: "query",
|
|
123
|
+
get: "get",
|
|
124
|
+
find: "search",
|
|
125
|
+
auth: "read",
|
|
126
|
+
validate: "validate",
|
|
127
|
+
preview: "preview",
|
|
128
|
+
calculate: "calculate",
|
|
129
|
+
check: "validate",
|
|
130
|
+
create: "create",
|
|
131
|
+
add: "create",
|
|
132
|
+
save: "create",
|
|
133
|
+
update: "update",
|
|
134
|
+
write: "write",
|
|
135
|
+
delete: "delete",
|
|
136
|
+
transfer: "transfer",
|
|
137
|
+
toggle: "update",
|
|
138
|
+
audit: "audit",
|
|
139
|
+
batch: "batch"
|
|
140
|
+
};
|
|
141
|
+
function inferDomainAndModule(name) {
|
|
142
|
+
if (name.startsWith("nocode_"))
|
|
143
|
+
return { domain: "nocode", module: "nocode" };
|
|
144
|
+
if (name.startsWith("workorder_"))
|
|
145
|
+
return { domain: "workorder", module: "workorder" };
|
|
146
|
+
if (name.startsWith("project_"))
|
|
147
|
+
return { domain: "project", module: "project" };
|
|
148
|
+
if (name.includes("_hrm_"))
|
|
149
|
+
return { domain: "hrm", module: "hrm" };
|
|
150
|
+
if (name.includes("_finance_"))
|
|
151
|
+
return { domain: "finance", module: "finance" };
|
|
152
|
+
if (name.includes("_jxc_"))
|
|
153
|
+
return { domain: "jxc", module: "jxc" };
|
|
154
|
+
if (name.includes("_oa_") || name.includes("_calendar_") || name.includes("_address_book_")) {
|
|
155
|
+
return { domain: "oa", module: "oa" };
|
|
156
|
+
}
|
|
157
|
+
return { domain: "crm", module: "crm" };
|
|
158
|
+
}
|
|
159
|
+
function inferAction(name) {
|
|
160
|
+
const prefix = name.startsWith("workorder_") ? "workorder_"
|
|
161
|
+
: name.startsWith("project_") ? "project_"
|
|
162
|
+
: name.startsWith("nocode_") ? "nocode_" : "crm_";
|
|
163
|
+
const verb = name.slice(prefix.length).split("_")[0] ?? "read";
|
|
164
|
+
return ACTION_BY_VERB[verb] ?? verb;
|
|
165
|
+
}
|
|
166
|
+
export function getToolDiscovery(name, _description) {
|
|
167
|
+
const inferred = inferDomainAndModule(name);
|
|
168
|
+
const override = CORE_DISCOVERY[name];
|
|
169
|
+
const action = inferAction(name);
|
|
170
|
+
return {
|
|
171
|
+
title: override?.title ?? name,
|
|
172
|
+
meta: {
|
|
173
|
+
version: 1,
|
|
174
|
+
domain: override?.domain ?? inferred.domain,
|
|
175
|
+
module: override?.module ?? inferred.module,
|
|
176
|
+
entities: override?.entities ?? [inferred.module],
|
|
177
|
+
actions: override?.actions ?? [action],
|
|
178
|
+
capability: override?.capability ?? `wukong.${name.replaceAll("_", ".")}`,
|
|
179
|
+
// 长尾工具先依靠标准 description/Schema 降级检索;未经源码确认的“同义词”不能伪装成 aliases。
|
|
180
|
+
aliases: override?.aliases ?? [],
|
|
181
|
+
...(override?.excludes ? { excludes: override.excludes } : {}),
|
|
182
|
+
...(override?.requires ? { requires: override.requires } : {}),
|
|
183
|
+
...(override?.verifiesWith ? { verifiesWith: override.verifiesWith } : {})
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|