server.js 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486
  1. #!/usr/bin/env node
  2. const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
  3. const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
  4. const { z } = require('zod');
  5. const { redactSecret } = require('./providers/fmode-wecom-gateway');
  6. const { ensureRelayDaemon } = require('./core/relay-daemon');
  7. const { qiweiProductModeStatus, qiweiProductModeSet } = require('./tools/qiwei-product-mode-run');
  8. const { searchQiweiApis, getQiweiApiDoc, callQiweiApi } = require('./tools/qiwei-api-catalog-run');
  9. const {
  10. qiweiLoginStatus,
  11. qiweiLoginStart,
  12. qiweiLoginCheck,
  13. qiweiLoginVerify
  14. } = require('./tools/qiwei-login-run');
  15. const {
  16. qiweiSubscriptionStatus,
  17. qiweiSubscribe,
  18. qiweiSubscriptionAutoRenew
  19. } = require('./tools/qiwei-subscription-run');
  20. const {
  21. qiweiOfficialStatus,
  22. qiweiOfficialPrepare,
  23. qiweiOfficialHelp,
  24. qiweiOfficialCall
  25. } = require('./tools/wecom-official-cli-run');
  26. const {
  27. qiweiBatchAddFriends,
  28. qiweiAutoCreateGroup,
  29. qiweiCheckFriendStatus,
  30. qiweiGetCustomerProfile
  31. } = require('./tools/qiwei-customer-ops-run');
  32. const {
  33. qiweiSyncExternalGroups,
  34. qiweiListExternalGroups,
  35. qiweiAnalyzeGroupMembers,
  36. qiweiConfirmExternalGroup,
  37. qiweiAddExternalGroup,
  38. qiweiConfigureGroupKeywords,
  39. qiweiRejectExternalGroup
  40. } = require('./tools/qiwei-group-management-run');
  41. const {
  42. qiweiGroupOpsGetContext,
  43. qiweiGroupOpsGeneratePlan,
  44. qiweiGroupOpsBatchGeneratePlans,
  45. qiweiGroupOpsGenerateCopy,
  46. qiweiGroupOpsReviewMessages,
  47. qiweiGroupOpsDailyBrief,
  48. qiweiGroupOpsManagePlaybook,
  49. qiweiGroupOpsExecuteItem,
  50. qiweiGroupOpsUpdateFinding,
  51. qiweiGroupOpsInsights,
  52. qiweiGroupOpsManageTask,
  53. qiweiGroupOpsAutomation,
  54. qiweiGroupOpsManageAccess
  55. } = require('./tools/qiwei-group-operations-run');
  56. const { qiweiBusinessDiagnosis, qiweiBusinessActionCandidate, qiweiBusinessActionFeedback } = require('./tools/qiwei-business-diagnosis-run');
  57. const {
  58. qiweiPrepareCustomerPortrait,
  59. qiweiUpdateCustomerPortrait,
  60. qiweiSaveCustomerPortrait,
  61. qiweiBatchUpdateCustomerPortrait,
  62. qiweiBatchSaveCustomerPortrait,
  63. qiweiExportCustomerPortraits,
  64. qiweiAddCustomerTags,
  65. qiweiRemoveCustomerTags,
  66. qiweiListCustomerTags,
  67. qiweiListAllTags,
  68. qiweiSyncPersonalLabels,
  69. qiweiCreatePersonalLabel,
  70. qiweiUpdatePersonalLabel,
  71. qiweiDeletePersonalLabel,
  72. qiweiApplyPersonalLabels
  73. } = require('./tools/qiwei-portrait-tags-run');
  74. const {
  75. qiweiDistillBroker,
  76. qiweiPrepareBrokerPlaybook,
  77. qiweiSaveBrokerPlaybook,
  78. qiweiBatchDistillBroker,
  79. qiweiBatchSaveBrokerPlaybook,
  80. qiweiExportBrokerPlaybooks,
  81. qiweiGetBrokerPlaybook
  82. } = require('./tools/qiwei-broker-playbook-run');
  83. const {
  84. qiweiPreviewTransferPackage,
  85. qiweiExecuteTransfer
  86. } = require('./tools/qiwei-customer-transfer-run');
  87. const {
  88. qiweiGoalCreatePlan,
  89. qiweiGoalGet,
  90. qiweiGoalUpdateTask,
  91. qiweiGoalImportMeetingActions
  92. } = require('./tools/qiwei-goal-management-run');
  93. const {
  94. qiweiTranscribeVoice,
  95. qiweiVoiceProfileStatus,
  96. qiweiEnrollVoice,
  97. qiweiSendClonedVoice
  98. } = require('./tools/qiwei-voice-run');
  99. const {
  100. qiweiWebhookStatus,
  101. qiweiWebhookDiscover,
  102. qiweiWebhookServerStart,
  103. qiweiWebhookServerStop,
  104. qiweiWebhookAutoSetup,
  105. qiweiWebhookSetup,
  106. qiweiRelayConfig,
  107. qiweiRelaySaveConfig,
  108. qiweiRelayRegister,
  109. qiweiRelayConnect,
  110. qiweiRelayStart
  111. } = require('./tools/qiwei-webhook-relay-run');
  112. const {
  113. qiweiAgentDashboardStart,
  114. qiweiAgentBindController,
  115. qiweiAgentStatus,
  116. qiweiAgentListConversations,
  117. qiweiAgentSessionGuide,
  118. qiweiAgentCustomerIntelligence,
  119. qiweiAgentInbox,
  120. qiweiAgentListener,
  121. qiweiAgentSetGlobal,
  122. qiweiAgentGenerateDraft
  123. } = require('./tools/qiwei-agent-control-run');
  124. const goalTaskSchema = z.object({
  125. title: z.string(),
  126. owner: z.string().optional(),
  127. deadline: z.string().optional(),
  128. priority: z.string().optional(),
  129. acceptance: z.string().optional(),
  130. status: z.enum(['planned', 'in_progress', 'blocked', 'done', 'cancelled']).optional(),
  131. note: z.string().optional(),
  132. blockedReason: z.string().optional()
  133. });
  134. const goalMilestoneSchema = z.object({
  135. title: z.string(),
  136. owner: z.string().optional(),
  137. deadline: z.string().optional(),
  138. acceptance: z.string().optional(),
  139. status: z.enum(['planned', 'in_progress', 'blocked', 'done', 'cancelled']).optional(),
  140. tasks: z.array(goalTaskSchema).optional()
  141. });
  142. function asToolResult(result) {
  143. const normalized = result && typeof result === 'object' ? result : { status: 'ok', assistantMessage: String(result) };
  144. return {
  145. content: [
  146. {
  147. type: 'text',
  148. text: normalized.assistantMessage || JSON.stringify(normalized, null, 2)
  149. }
  150. ],
  151. structuredContent: normalized,
  152. isError: normalized.status === 'error'
  153. };
  154. }
  155. function wrap(handler) {
  156. return async (input = {}) => {
  157. try {
  158. return asToolResult(await handler(input));
  159. } catch (error) {
  160. const safeMessage = redactSecret(error && error.message ? error.message : error);
  161. return asToolResult({
  162. status: 'error',
  163. assistantMessage: `工具执行异常:${safeMessage}`,
  164. errors: [{ message: safeMessage }]
  165. });
  166. }
  167. };
  168. }
  169. function createServer() {
  170. const server = new McpServer({
  171. name: 'enterprise-wechat-assistant',
  172. version: '0.5.2'
  173. });
  174. server.registerTool(
  175. 'qiwei_product_mode_status',
  176. {
  177. title: '查看企微个人版或企业版模式',
  178. description: '查看当前项目是个人版还是企业版、消息接收和存储方式、企业 Relay 是否就绪,以及企业版升级服务的当前报价占位。',
  179. inputSchema: {}
  180. },
  181. wrap(qiweiProductModeStatus)
  182. );
  183. server.registerTool(
  184. 'qiwei_product_mode_set',
  185. {
  186. title: '切换企微个人版或企业版模式',
  187. description: '切换项目运行模式。personal 使用本地主动监听和单设备本地数据;enterprise 使用服务端统一回调、多设备归集和企业隔离。切换后按返回步骤重启相关进程。',
  188. inputSchema: { mode: z.enum(['personal', 'enterprise']) }
  189. },
  190. wrap(qiweiProductModeSet)
  191. );
  192. server.registerTool(
  193. 'qiwei_api_search',
  194. {
  195. title: '检索企业微信开放接口清单',
  196. description: [
  197. '检索企业微信能力的全量接口清单(100+ 个接口:登录/设备、消息收发、群管理、联系人、朋友圈、标签、会话、文件等模块)。',
  198. '当需要对企业微信做任何操作(发消息、拉群、加好友、同步消息、发朋友圈等)时,先来这里查有哪些接口。',
  199. '入参 query(关键词,中英文均可)、module(模块 key:login/client/msg/room/contact/sns/label/session/user/cloud)、tag、limit 均可选;都不传返回全量清单。'
  200. ].join(' '),
  201. inputSchema: {
  202. query: z.string().optional(),
  203. module: z.string().optional(),
  204. tag: z.string().optional(),
  205. limit: z.number().int().min(1).max(200).optional()
  206. }
  207. },
  208. wrap(searchQiweiApis)
  209. );
  210. server.registerTool(
  211. 'qiwei_api_doc',
  212. {
  213. title: '读取企业微信接口的详细参数文档',
  214. description: '读取清单中某个企业微信接口的完整参数文档(参数表、必填项、返回字段说明、调用模板)。入参 id(如 msg.sendText,来自 qiwei_api_search)或 method 路径(如 /msg/sendText)。',
  215. inputSchema: {
  216. id: z.string().optional(),
  217. method: z.string().optional()
  218. }
  219. },
  220. wrap(getQiweiApiDoc)
  221. );
  222. server.registerTool(
  223. 'qiwei_api_call',
  224. {
  225. title: '调用企业微信开放接口',
  226. description: [
  227. '通过 Fmode 网关转发的企业微信接口执行请求,接口访问凭据和设备上下文由 Fmode 网关管理。',
  228. '推荐传 id(清单接口 id)+ params;未登记接口可传 rawMethod(如 /msg/sendText)+ params。',
  229. '客户端只需 Fmode 鉴权 token 和 uid,不接触企业微信接口访问凭据。'
  230. ].join(' '),
  231. inputSchema: {
  232. id: z.string().optional(),
  233. rawMethod: z.string().optional(),
  234. params: z.record(z.any()).optional(),
  235. filePath: z.string().optional(),
  236. uid: z.string().optional(),
  237. authToken: z.string().optional(),
  238. apiBase: z.string().optional()
  239. }
  240. },
  241. wrap(callQiweiApi)
  242. );
  243. server.registerTool(
  244. 'qiwei_login_status',
  245. {
  246. title: '查询企业微信登录/配置状态',
  247. description: '通过 Fmode 网关转发的企业微信接口查询当前 uid 对应的设备在线状态。技能包安装后、或任何接口报设备/登录错误时先调用。',
  248. inputSchema: {
  249. uid: z.string().optional(),
  250. authToken: z.string().optional(),
  251. apiBase: z.string().optional()
  252. }
  253. },
  254. wrap(qiweiLoginStatus)
  255. );
  256. server.registerTool(
  257. 'qiwei_login_start',
  258. {
  259. title: '开始企业微信扫码登录',
  260. description: [
  261. '登录两步流程入口:默认在本地端口(默认 4310)启动流程页服务并打开浏览器,页面自动推进:未开通订阅时先展示服务端当前套餐价格(飞马余额扣费),开通后自动生成二维码并每 3 秒轮询扫码状态,需要验证码时页面提示输入。',
  262. '传 flowUi=false 仅用于已开通订阅后的扫码回退模式;未开通时返回 needs_subscription,不生成包含请求凭据的静态付费页。',
  263. '需要有效企微包月订阅和可用席位;无需提供企业微信接口访问凭据、代理或设备参数。'
  264. ].join(' '),
  265. inputSchema: {
  266. uid: z.string().optional(),
  267. authToken: z.string().optional(),
  268. apiBase: z.string().optional(),
  269. openBrowser: z.boolean().optional(),
  270. flowUi: z.boolean().optional(),
  271. flowPort: z.number().int().optional()
  272. }
  273. },
  274. wrap(qiweiLoginStart)
  275. );
  276. server.registerTool(
  277. 'qiwei_login_check',
  278. {
  279. title: '检测扫码登录状态',
  280. description: '通过 Fmode 网关转发的企业微信接口轮询扫码登录状态(用户扫码后每 3-5 秒调一次)。状态 10 表示需要 6 位验证码;manual=true 时先尝试免扫码登录。',
  281. inputSchema: {
  282. manual: z.boolean().optional(),
  283. uid: z.string().optional(),
  284. authToken: z.string().optional(),
  285. apiBase: z.string().optional()
  286. }
  287. },
  288. wrap(qiweiLoginCheck)
  289. );
  290. server.registerTool(
  291. 'qiwei_login_verify',
  292. {
  293. title: '提交扫码登录的 6 位验证码',
  294. description: '当 qiwei_login_check 返回状态 10(需要验证码)时,向用户索要手机端显示的 6 位验证码并通过本工具提交,随后再调 qiwei_login_check 确认登录成功。',
  295. inputSchema: {
  296. code: z.string(),
  297. uid: z.string().optional(),
  298. authToken: z.string().optional(),
  299. apiBase: z.string().optional()
  300. }
  301. },
  302. wrap(qiweiLoginVerify)
  303. );
  304. server.registerTool(
  305. 'qiwei_subscription_status',
  306. {
  307. title: '查询企微包月订阅与席位状态',
  308. description: '查询企微包月订阅、到期时间、自动续费、已购/已用席位和飞马余额。首次登录或收到 402/403 时先调用。',
  309. inputSchema: {
  310. authToken: z.string().optional(),
  311. apiBase: z.string().optional()
  312. }
  313. },
  314. wrap(qiweiSubscriptionStatus)
  315. );
  316. server.registerTool(
  317. 'qiwei_subscribe',
  318. {
  319. title: '开通、续费或增购企微席位',
  320. description: '使用飞马余额一次开通或续费 1-12 个月企微订阅。1 个席位对应 1 个企业微信账号;seats 和 months 不传时均默认 1。',
  321. inputSchema: {
  322. seats: z.number().int().min(1).optional(),
  323. months: z.number().int().min(1).max(12).optional(),
  324. authToken: z.string().optional(),
  325. apiBase: z.string().optional()
  326. }
  327. },
  328. wrap(qiweiSubscribe)
  329. );
  330. server.registerTool(
  331. 'qiwei_subscription_auto_renew',
  332. {
  333. title: '设置企微订阅自动续费',
  334. description: '开启或关闭企微包月订阅到期自动续费。',
  335. inputSchema: {
  336. autoRenew: z.boolean(),
  337. authToken: z.string().optional(),
  338. apiBase: z.string().optional()
  339. }
  340. },
  341. wrap(qiweiSubscriptionAutoRenew)
  342. );
  343. server.registerTool(
  344. 'qiwei_official_status',
  345. {
  346. title: '检查企业微信官方 CLI 状态',
  347. description: '检查固定版本官方 CLI 是否已下载、版本是否正确,以及官方机器人扫码授权是否完成。此工具与 Fmode 网关登录状态完全独立。',
  348. inputSchema: {}
  349. },
  350. wrap(qiweiOfficialStatus)
  351. );
  352. server.registerTool(
  353. 'qiwei_official_prepare',
  354. {
  355. title: '下载企业微信官方 CLI',
  356. description: '将固定版本 @wecom/cli 下载到用户级缓存并校验。不会全局安装,不修改官方 CLI,也不会改动 Fmode 网关转发的企业微信接口通道。',
  357. inputSchema: {
  358. force: z.boolean().optional()
  359. }
  360. },
  361. wrap(qiweiOfficialPrepare)
  362. );
  363. server.registerTool(
  364. 'qiwei_official_help',
  365. {
  366. title: '读取企业微信官方 CLI 帮助',
  367. description: '读取官方 CLI 总帮助、category 工具列表或具体 method 的参数 schema。category/method 级帮助需要先完成官方 CLI 初始化。',
  368. inputSchema: {
  369. category: z.enum(['contact', 'doc', 'meeting', 'msg', 'schedule', 'todo']).optional(),
  370. method: z.string().regex(/^[a-z][a-z0-9_]*$/).optional()
  371. }
  372. },
  373. wrap(qiweiOfficialHelp)
  374. );
  375. server.registerTool(
  376. 'qiwei_official_call',
  377. {
  378. title: '调用企业微信官方 CLI 能力',
  379. description: '通过固定版本官方 CLI 调用通讯录、文档、会议、消息、日程或待办。参数以结构化 JSON 传递,不经过 shell,也不经过 Fmode 网关。',
  380. inputSchema: {
  381. category: z.enum(['contact', 'doc', 'meeting', 'msg', 'schedule', 'todo']),
  382. method: z.string().regex(/^[a-z][a-z0-9_]*$/),
  383. args: z.record(z.any()).optional(),
  384. timeoutMs: z.number().int().min(1000).max(300000).optional()
  385. }
  386. },
  387. wrap(qiweiOfficialCall)
  388. );
  389. server.registerTool(
  390. 'qiwei_batch_add_friends',
  391. {
  392. title: '批量搜索并添加企微好友',
  393. description: [
  394. '迁移自 Qiwei 项目的批量加好友能力。按手机号搜索联系人,依据 qiwei-endpoints.json 中的 /contact/searchContact、/contact/addSearchWxContact、/contact/addSearchWxWorkContact 调用 Fmode 网关。',
  395. '不修改 qiwei_login_* 和 qiwei_api_* 既有流程;需要已登录设备 guid。支持 customers、phones,或传入 .xlsx filePath 读取 Excel。'
  396. ].join(' '),
  397. inputSchema: {
  398. guid: z.string().optional(),
  399. customers: z.array(z.object({
  400. phone: z.string(),
  401. name: z.string().optional(),
  402. greeting: z.string().optional(),
  403. verifyText: z.string().optional(),
  404. groupName: z.string().optional(),
  405. supportMemberIds: z.array(z.string()).optional()
  406. })).optional(),
  407. phones: z.array(z.string()).optional(),
  408. filePath: z.string().optional(),
  409. greeting: z.string().optional(),
  410. defaultGreeting: z.string().optional(),
  411. rateLimitPerMinute: z.number().int().min(1).max(60).optional(),
  412. maxAttempts: z.number().int().min(1).max(5).optional(),
  413. addToAllowlist: z.boolean().optional().describe('仅添加测试好友时设为 true;成功后自动加入个人消息白名单并启用待审核监听'),
  414. uid: z.string().optional(),
  415. authToken: z.string().optional(),
  416. apiBase: z.string().optional()
  417. }
  418. },
  419. wrap(qiweiBatchAddFriends)
  420. );
  421. server.registerTool(
  422. 'qiwei_auto_create_group',
  423. {
  424. title: '自动创建企微服务群',
  425. description: [
  426. '迁移自 Qiwei 项目的自动建群能力。基于 qiwei-endpoints.json 中的 /room/createRoom、/room/modifyRoomName、/room/inviteRoomMember、/msg/sendText 执行建群、改群名、邀请协作成员和发送欢迎语。',
  427. '不修改目标包已有登录、订阅、通用 API 调用流程;需要已登录设备 guid。'
  428. ].join(' '),
  429. inputSchema: {
  430. guid: z.string().optional(),
  431. customerUserId: z.string().optional(),
  432. externalUserId: z.string().optional(),
  433. memberList: z.array(z.string()).optional(),
  434. supportMemberIds: z.array(z.string()).optional(),
  435. groupName: z.string().optional(),
  436. welcomeText: z.string().optional(),
  437. isOuterRoom: z.number().int().optional(),
  438. uid: z.string().optional(),
  439. authToken: z.string().optional(),
  440. apiBase: z.string().optional()
  441. }
  442. },
  443. wrap(qiweiAutoCreateGroup)
  444. );
  445. server.registerTool(
  446. 'qiwei_check_friend_status',
  447. {
  448. title: '检查好友申请状态',
  449. description: '按手机号批量检查联系人搜索状态,判断对方是否已是好友、已被其他人添加、未添加或未找到。需要已登录设备 guid。',
  450. inputSchema: {
  451. guid: z.string().optional(),
  452. customers: z.array(z.object({
  453. phone: z.string(),
  454. name: z.string().optional()
  455. })).optional(),
  456. phones: z.array(z.string()).optional(),
  457. externalUserIds: z.array(z.string()).optional(),
  458. uid: z.string().optional(),
  459. authToken: z.string().optional(),
  460. apiBase: z.string().optional()
  461. }
  462. },
  463. wrap(qiweiCheckFriendStatus)
  464. );
  465. server.registerTool(
  466. 'qiwei_get_customer_profile',
  467. {
  468. title: '查询客户档案',
  469. description: '按手机号或 externalUserId 查询客户档案:搜索联系人、拉取外部联系人列表、拉取群列表,并读取本地已保存的画像文件。需要已登录设备 guid。',
  470. inputSchema: {
  471. guid: z.string().optional(),
  472. phone: z.string().optional(),
  473. externalUserId: z.string().optional(),
  474. uid: z.string().optional(),
  475. authToken: z.string().optional(),
  476. apiBase: z.string().optional()
  477. }
  478. },
  479. wrap(qiweiGetCustomerProfile)
  480. );
  481. server.registerTool(
  482. 'qiwei_sync_external_groups',
  483. {
  484. title: '同步外部群列表',
  485. description: '从当前账号创建的群(self)、最近会话中的群(session)或两者(all)扫描群目录。支持按可配置关键词自动分类客户群,结果写入 outputs/groups/。',
  486. inputSchema: {
  487. guid: z.string().optional(),
  488. scope: z.enum(['self', 'session', 'all']).optional(),
  489. includeInternalGroups: z.boolean().optional(),
  490. customerKeywords: z.array(z.string()).optional(),
  491. matchMode: z.enum(['any', 'threshold']).optional(),
  492. threshold: z.number().int().min(1).optional(),
  493. highConfidenceTerms: z.array(z.string()).optional(),
  494. autoClassify: z.boolean().optional(),
  495. maxPages: z.number().int().min(1).max(300).optional(),
  496. uid: z.string().optional(),
  497. authToken: z.string().optional(),
  498. apiBase: z.string().optional()
  499. }
  500. },
  501. wrap(qiweiSyncExternalGroups)
  502. );
  503. server.registerTool(
  504. 'qiwei_list_external_groups',
  505. {
  506. title: '列出外部群',
  507. description: '读取最近一次同步的群列表和已确认/已拒绝映射,返回每个群的状态(IMPORTED / SUGGESTED / AUTO_CONFIRMED / CONFIRMED / REJECTED),支持按关键词、状态、来源过滤。',
  508. inputSchema: {
  509. keyword: z.string().optional(),
  510. status: z.enum(['IMPORTED', 'SUGGESTED', 'AUTO_CONFIRMED', 'CONFIRMED', 'REJECTED']).optional(),
  511. source: z.enum(['roomList', 'session', 'messages']).optional(),
  512. includeRejected: z.boolean().optional(),
  513. customerKeywords: z.array(z.string()).optional(),
  514. matchMode: z.enum(['any', 'threshold']).optional(),
  515. threshold: z.number().int().min(1).optional(),
  516. highConfidenceTerms: z.array(z.string()).optional()
  517. }
  518. },
  519. wrap(qiweiListExternalGroups)
  520. );
  521. server.registerTool(
  522. 'qiwei_analyze_group_members',
  523. {
  524. title: '分析群详情',
  525. description: '对指定的 roomId 列表调用 /room/batchGetRoomDetail,按群名关键词判断哪些可能是客户群。',
  526. inputSchema: {
  527. guid: z.string().optional(),
  528. roomIds: z.array(z.string()),
  529. customerKeywords: z.array(z.string()).optional(),
  530. matchMode: z.enum(['any', 'threshold']).optional(),
  531. threshold: z.number().int().min(1).optional(),
  532. highConfidenceTerms: z.array(z.string()).optional(),
  533. autoClassify: z.boolean().optional(),
  534. updateSnapshot: z.boolean().optional(),
  535. uid: z.string().optional(),
  536. authToken: z.string().optional(),
  537. apiBase: z.string().optional()
  538. }
  539. },
  540. wrap(qiweiAnalyzeGroupMembers)
  541. );
  542. server.registerTool(
  543. 'qiwei_confirm_external_group',
  544. {
  545. title: '确认外部群为客户群',
  546. description: '对最近一次同步列表中的某个 roomId 标记为 CONFIRMED,并关联 customerId / externalUserId。映射写入 outputs/groups/confirmed-mapping.json。',
  547. inputSchema: {
  548. roomId: z.string(),
  549. customerId: z.string().optional(),
  550. externalUserId: z.string().optional(),
  551. customerName: z.string().optional()
  552. }
  553. },
  554. wrap(qiweiConfirmExternalGroup)
  555. );
  556. server.registerTool(
  557. 'qiwei_add_external_group',
  558. {
  559. title: '手动添加外部群',
  560. description: '直接按 roomId 添加外部群并标记为 CONFIRMED,关联 customerId / externalUserId。适用于同步列表之外的群。',
  561. inputSchema: {
  562. roomId: z.string(),
  563. roomName: z.string().optional(),
  564. customerId: z.string().optional(),
  565. externalUserId: z.string().optional(),
  566. customerName: z.string().optional()
  567. }
  568. },
  569. wrap(qiweiAddExternalGroup)
  570. );
  571. server.registerTool(
  572. 'qiwei_configure_group_keywords',
  573. {
  574. title: '配置客户群关键词',
  575. description: '持久化配置客户群识别关键词、匹配模式(any/threshold)、阈值和高置信度词,写入 outputs/groups/customer-keywords.json。传 reset=true 可恢复默认。',
  576. inputSchema: {
  577. keywords: z.array(z.string()).optional(),
  578. highConfidenceTerms: z.array(z.string()).optional(),
  579. matchMode: z.enum(['any', 'threshold']).optional(),
  580. threshold: z.number().int().min(1).optional(),
  581. reset: z.boolean().optional()
  582. }
  583. },
  584. wrap(qiweiConfigureGroupKeywords)
  585. );
  586. server.registerTool(
  587. 'qiwei_reject_external_group',
  588. {
  589. title: '拒绝外部群',
  590. description: '将最近一次同步列表中的某个 roomId 标记为 REJECTED,写入 outputs/groups/rejected-mapping.json。',
  591. inputSchema: {
  592. roomId: z.string(),
  593. reason: z.string().optional()
  594. }
  595. },
  596. wrap(qiweiRejectExternalGroup)
  597. );
  598. server.registerTool('qiwei_group_ops_get_context', {
  599. title: '读取社群运营上下文',
  600. description: '读取当前账号的群运营总览,或指定群的主档、已发布 SOP、计划和质检风险。用于制定计划、写话术和生成管理简报前获取事实。',
  601. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), roomId: z.string().optional(), date: z.string().optional(), group: z.record(z.any()).optional() }
  602. }, wrap(qiweiGroupOpsGetContext));
  603. server.registerTool('qiwei_group_ops_generate_plan', {
  604. title: '生成客户群运营计划',
  605. description: '依据已发布 SOP 或显式计划项,为指定客户群生成每日运营计划草稿。结果只进入待审核状态,不自动发送。',
  606. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), roomId: z.string(), roomName: z.string().optional(), planDate: z.string().optional(), playbookId: z.string().optional(), items: z.array(z.record(z.any())).optional(), actor: z.string().optional() }
  607. }, wrap(qiweiGroupOpsGeneratePlan));
  608. server.registerTool('qiwei_group_ops_batch_generate_plans', {
  609. title: '批量生成客户群运营计划',
  610. description: '按门店或显式群列表批量生成指定日期的运营计划。每个群独立幂等,失败项不会影响成功项,全部结果进入待审核。',
  611. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), planDate: z.string().optional(), storeId: z.string().optional(), playbookId: z.string().optional(), groups: z.array(z.record(z.any())).optional(), actor: z.string().optional() }
  612. }, wrap(qiweiGroupOpsBatchGeneratePlans));
  613. server.registerTool('qiwei_group_ops_generate_copy', {
  614. title: '生成并检查群运营话术',
  615. description: '替换话术变量并执行缺失变量、敏感承诺等基础规则检查。仅返回可审核草稿。',
  616. inputSchema: { content: z.string().optional(), template: z.string().optional(), variables: z.record(z.any()).optional(), riskTerms: z.array(z.string()).optional() }
  617. }, wrap(qiweiGroupOpsGenerateCopy));
  618. server.registerTool('qiwei_group_ops_review_messages', {
  619. title: '质检客户群消息',
  620. description: '对指定客户群消息执行投诉风险和超时未回应检查,每个结论保留原消息证据。',
  621. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), roomId: z.string(), messages: z.array(z.record(z.any())).optional(), startAt: z.string().optional(), endAt: z.string().optional(), slaMinutes: z.number().int().positive().optional(), riskTerms: z.array(z.string()).optional(), actor: z.string().optional() }
  622. }, wrap(qiweiGroupOpsReviewMessages));
  623. server.registerTool('qiwei_group_ops_daily_brief', {
  624. title: '生成社群运营每日简报',
  625. description: '汇总今日应执行、已完成、待审核和高风险事项,并返回可下钻的计划与质检数据。',
  626. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), date: z.string().optional() }
  627. }, wrap(qiweiGroupOpsDailyBrief));
  628. server.registerTool('qiwei_group_ops_manage_playbook', {
  629. title: '管理群运营 SOP',
  630. description: '列出、创建、创建新版本、发布或回滚群运营 SOP。发布与回滚属于受控写操作,调用前必须向用户确认。',
  631. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), action: z.enum(['list','create','new_version','versions','preview','publish','rollback']), playbookId: z.string().optional(), name: z.string().optional(), groupType: z.string().optional(), content: z.record(z.any()).optional(), scope: z.record(z.any()).optional(), version: z.number().int().positive().optional(), changeNote: z.string().optional(), confirmed: z.boolean().optional(), actor: z.string().optional() }
  632. }, wrap(qiweiGroupOpsManagePlaybook));
  633. server.registerTool('qiwei_group_ops_execute_item', {
  634. title: '审核或记录群运营计划项',
  635. description: '批准、拒绝、跳过或记录计划项已由人工发送。真实自动发送在接口回执验证前保持关闭。',
  636. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), itemId: z.string(), action: z.enum(['approve','reject','skip','mark_sent','send']), content: z.string().optional(), reason: z.string().optional(), externalMessageId: z.string().optional(), actor: z.string().optional() }
  637. }, wrap(qiweiGroupOpsExecuteItem));
  638. server.registerTool('qiwei_group_ops_update_finding', {
  639. title: '处理群质检事项',
  640. description: '认领、确认、关闭或标记群质检误报,并保留处理审计。',
  641. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), findingId: z.string(), status: z.enum(['open','acknowledged','resolved','false_positive']).optional(), assignee: z.string().optional(), resolution: z.string().optional(), actor: z.string().optional() }
  642. }, wrap(qiweiGroupOpsUpdateFinding));
  643. server.registerTool('qiwei_group_ops_insights', {
  644. title: '分析社群运营与配置质检规则',
  645. description: '查看跨门店执行对比、群活跃趋势、生命周期建议、优秀话术和质检反馈;也可更新当前账号的 SLA、风险词和禁止承诺。',
  646. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), action: z.enum(['get','update_settings']).optional(), roomId: z.string().optional(), date: z.string().optional(), slaMinutes: z.number().int().positive().optional(), riskTerms: z.array(z.string()).optional(), forbiddenClaims: z.array(z.string()).optional(), actor: z.string().optional() }
  647. }, wrap(qiweiGroupOpsInsights));
  648. server.registerTool('qiwei_group_ops_manage_task', {
  649. title: '管理社群整改任务',
  650. description: '创建、查询、推进社群整改任务,并在负责人精确匹配且人工确认后同步为真实企微官方待办。',
  651. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), action: z.enum(['list','create','update','sync_official']), taskId: z.string().optional(), findingId: z.string().optional(), roomId: z.string().optional(), title: z.string().optional(), owner: z.string().optional(), dueAt: z.string().optional(), priority: z.enum(['low','medium','high','urgent']).optional(), status: z.enum(['open','in_progress','done','cancelled']).optional(), remindType: z.number().int().optional(), confirmed: z.boolean().optional(), actor: z.string().optional() }
  652. }, wrap(qiweiGroupOpsManageTask));
  653. server.registerTool('qiwei_group_ops_automation', {
  654. title: '管理社群受控自动化',
  655. description: '创建和激活自动化策略,配置白名单、允许类型、时间窗、频控、灰度与熔断,执行 dry-run 或在全部安全条件满足时受控发送。默认暂停且真实发送环境开关关闭。',
  656. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), action: z.enum(['status','create_policy','activate','pause','resume','reset_breaker','preview','run']), policyId: z.string().optional(), policy: z.record(z.any()).optional(), changeNote: z.string().optional(), itemIds: z.array(z.string()).optional(), limit: z.number().int().positive().max(500).optional(), at: z.string().optional(), execute: z.boolean().optional(), confirmed: z.boolean().optional(), actor: z.string().optional() }
  657. }, wrap(qiweiGroupOpsAutomation));
  658. server.registerTool('qiwei_group_ops_manage_access', {
  659. title: '管理社群运营角色权限',
  660. description: '配置或查看当前企微账号下的总部管理员、区域/门店经理、执行员工和审计者权限。首次初始化必须显式确认。',
  661. inputSchema: { accountKey: z.string().optional(), guid: z.string().optional(), requester: z.string().optional(), principal: z.string().optional(), actor: z.string().optional(), action: z.enum(['list','upsert']).optional(), role: z.enum(['headquarters_admin','regional_manager','store_manager','executor','auditor']).optional(), storeScope: z.array(z.string()).optional(), status: z.enum(['active','disabled']).optional(), bootstrapConfirmed: z.boolean().optional() }
  662. }, wrap(qiweiGroupOpsManageAccess));
  663. server.registerTool('qiwei_business_diagnosis', {
  664. title: '诊断企微经营问题',
  665. description: '只读汇总客户主档、真实会话和社群运营数据,针对经营问题返回结论、证据、数据缺口和可下钻动作。缺少订单或 GMV 数据时明确拒绝生成虚假交易结论。',
  666. inputSchema: {
  667. question: z.string().min(2),
  668. accountKey: z.string().optional(),
  669. guid: z.string().optional(),
  670. uid: z.string().optional(),
  671. date: z.string().optional()
  672. }
  673. }, wrap(qiweiBusinessDiagnosis));
  674. server.registerTool('qiwei_business_action_candidate', {
  675. title: '将经营建议加入行动候选',
  676. description: '把经营诊断中的一条建议加入统一任务中心候选池。只创建待人工确认候选,不会生成正式任务、同步企微待办或发送消息。',
  677. inputSchema: {
  678. question: z.string().min(2),
  679. diagnosisType: z.string().optional(),
  680. scope: z.record(z.any()).optional(),
  681. action: z.object({
  682. id: z.string().optional(),
  683. label: z.string().min(1),
  684. description: z.string().optional(),
  685. route: z.string().optional(),
  686. hash: z.string().optional(),
  687. priority: z.enum(['low', 'medium', 'high', 'urgent']).optional()
  688. }),
  689. evidence: z.array(z.union([z.string(), z.record(z.any())])).optional(),
  690. dataGaps: z.array(z.record(z.any())).optional()
  691. }
  692. }, wrap(qiweiBusinessActionCandidate));
  693. server.registerTool('qiwei_business_action_feedback', {
  694. title: '记录经营行动效果反馈',
  695. description: '为已完成的经营诊断来源内部任务记录有效、部分有效、无效或暂无法判断。反馈只用于内部复盘,不会发送客户消息或同步企微待办。',
  696. inputSchema: {
  697. taskId: z.string().min(1),
  698. outcome: z.enum(['effective', 'partially_effective', 'ineffective', 'unknown']),
  699. note: z.string().max(1000).optional(),
  700. actor: z.string().max(100).optional()
  701. }
  702. }, wrap(qiweiBusinessActionFeedback));
  703. server.registerTool(
  704. 'qiwei_prepare_customer_portrait',
  705. {
  706. title: '准备客户画像分析上下文',
  707. description: '从 outputs/messages/ 中收集指定 externalUserId 发送的消息,生成画像分析上下文并写入 outputs/portraits/,供 Agent 分析后保存。',
  708. inputSchema: {
  709. externalUserId: z.string(),
  710. roomIds: z.array(z.string()).optional()
  711. }
  712. },
  713. wrap(qiweiPrepareCustomerPortrait)
  714. );
  715. server.registerTool(
  716. 'qiwei_update_customer_portrait',
  717. {
  718. title: '更新客户画像',
  719. description: '更新客户画像。updateMode=incremental 仅分析新增消息并合并现有画像;updateMode=full 基于全部消息重新计算。传 aiMode=keyword 时使用关键词模式。',
  720. inputSchema: {
  721. externalUserId: z.string(),
  722. aiMode: z.enum(['keyword']).optional(),
  723. updateMode: z.enum(['incremental', 'full']).optional(),
  724. roomIds: z.array(z.string()).optional()
  725. }
  726. },
  727. wrap(qiweiUpdateCustomerPortrait)
  728. );
  729. server.registerTool(
  730. 'qiwei_save_customer_portrait',
  731. {
  732. title: '保存客户画像',
  733. description: '将 Agent 分析后的画像 JSON 保存到 outputs/portraits/<externalUserId>.json。',
  734. inputSchema: {
  735. externalUserId: z.string(),
  736. portrait: z.record(z.any()),
  737. source: z.string().optional(),
  738. messageCount: z.number().int().optional()
  739. }
  740. },
  741. wrap(qiweiSaveCustomerPortrait)
  742. );
  743. server.registerTool(
  744. 'qiwei_batch_update_customer_portrait',
  745. {
  746. title: '批量更新客户画像',
  747. description: '对多个 externalUserId 执行增量或全量画像更新。',
  748. inputSchema: {
  749. externalUserIds: z.array(z.string()),
  750. aiMode: z.enum(['keyword']).optional(),
  751. updateMode: z.enum(['incremental', 'full']).optional(),
  752. roomIds: z.array(z.string()).optional()
  753. }
  754. },
  755. wrap(qiweiBatchUpdateCustomerPortrait)
  756. );
  757. server.registerTool(
  758. 'qiwei_batch_save_customer_portrait',
  759. {
  760. title: '批量保存客户画像',
  761. description: '批量保存多个客户的画像。',
  762. inputSchema: {
  763. items: z.array(z.object({ externalUserId: z.string(), portrait: z.record(z.any()), source: z.string().optional() }))
  764. }
  765. },
  766. wrap(qiweiBatchSaveCustomerPortrait)
  767. );
  768. server.registerTool(
  769. 'qiwei_export_customer_portraits',
  770. {
  771. title: '导出客户画像',
  772. description: '将 outputs/portraits/ 中保存的画像导出为 Excel。未传 externalUserIds 时导出全部。',
  773. inputSchema: {
  774. externalUserIds: z.array(z.string()).optional()
  775. }
  776. },
  777. wrap(qiweiExportCustomerPortraits)
  778. );
  779. server.registerTool(
  780. 'qiwei_add_customer_tags',
  781. {
  782. title: '添加客户标签',
  783. description: '为 externalUserId 添加本地标签,去重保存到 outputs/tags/<externalUserId>.json。',
  784. inputSchema: {
  785. externalUserId: z.string(),
  786. tags: z.array(z.string())
  787. }
  788. },
  789. wrap(qiweiAddCustomerTags)
  790. );
  791. server.registerTool(
  792. 'qiwei_remove_customer_tags',
  793. {
  794. title: '移除客户标签',
  795. description: '为 externalUserId 移除本地标签。',
  796. inputSchema: {
  797. externalUserId: z.string(),
  798. tags: z.array(z.string())
  799. }
  800. },
  801. wrap(qiweiRemoveCustomerTags)
  802. );
  803. server.registerTool(
  804. 'qiwei_list_customer_tags',
  805. {
  806. title: '列出客户标签',
  807. description: '读取 outputs/tags/<externalUserId>.json 返回标签列表。',
  808. inputSchema: {
  809. externalUserId: z.string()
  810. }
  811. },
  812. wrap(qiweiListCustomerTags)
  813. );
  814. server.registerTool(
  815. 'qiwei_list_all_tags',
  816. {
  817. title: '列出所有本地标签',
  818. description: '汇总所有客户的本地标签并去重返回。',
  819. inputSchema: {}
  820. },
  821. wrap(qiweiListAllTags)
  822. );
  823. server.registerTool(
  824. 'qiwei_sync_personal_labels',
  825. {
  826. title: '同步企微个人标签',
  827. description: '调用 /label/syncLabelList 同步企微个人标签列表。',
  828. inputSchema: {
  829. guid: z.string().optional(),
  830. uid: z.string().optional(),
  831. authToken: z.string().optional(),
  832. apiBase: z.string().optional()
  833. }
  834. },
  835. wrap(qiweiSyncPersonalLabels)
  836. );
  837. server.registerTool(
  838. 'qiwei_create_personal_label',
  839. {
  840. title: '创建企微个人标签',
  841. description: '调用 /label/editLabel opType=1 创建个人标签。',
  842. inputSchema: {
  843. guid: z.string().optional(),
  844. labelName: z.string(),
  845. labelSuperId: z.string().optional(),
  846. uid: z.string().optional(),
  847. authToken: z.string().optional(),
  848. apiBase: z.string().optional()
  849. }
  850. },
  851. wrap(qiweiCreatePersonalLabel)
  852. );
  853. server.registerTool(
  854. 'qiwei_update_personal_label',
  855. {
  856. title: '更新企微个人标签',
  857. description: '调用 /label/editLabel opType=3 更新个人标签。',
  858. inputSchema: {
  859. guid: z.string().optional(),
  860. labelId: z.string(),
  861. labelName: z.string(),
  862. labelSuperId: z.string().optional(),
  863. uid: z.string().optional(),
  864. authToken: z.string().optional(),
  865. apiBase: z.string().optional()
  866. }
  867. },
  868. wrap(qiweiUpdatePersonalLabel)
  869. );
  870. server.registerTool(
  871. 'qiwei_delete_personal_label',
  872. {
  873. title: '删除企微个人标签',
  874. description: '调用 /label/editLabel opType=2 删除个人标签。',
  875. inputSchema: {
  876. guid: z.string().optional(),
  877. labelId: z.string(),
  878. uid: z.string().optional(),
  879. authToken: z.string().optional(),
  880. apiBase: z.string().optional()
  881. }
  882. },
  883. wrap(qiweiDeletePersonalLabel)
  884. );
  885. server.registerTool(
  886. 'qiwei_apply_personal_labels',
  887. {
  888. title: '应用企微个人标签到客户',
  889. description: '调用 /label/contactEditLabel 为客户添加/移除个人标签。',
  890. inputSchema: {
  891. guid: z.string().optional(),
  892. externalUserId: z.string(),
  893. addLabels: z.array(z.string()).optional(),
  894. removeLabels: z.array(z.string()).optional(),
  895. uid: z.string().optional(),
  896. authToken: z.string().optional(),
  897. apiBase: z.string().optional()
  898. }
  899. },
  900. wrap(qiweiApplyPersonalLabels)
  901. );
  902. server.registerTool(
  903. 'qiwei_prepare_broker_playbook',
  904. {
  905. title: '准备顾问 playbook 分析上下文',
  906. description: '从 outputs/messages/ 中收集指定顾问发送的消息,生成 playbook 分析上下文并写入 outputs/broker-playbooks/。',
  907. inputSchema: {
  908. brokerUserId: z.string(),
  909. roomIds: z.array(z.string()).optional()
  910. }
  911. },
  912. wrap(qiweiPrepareBrokerPlaybook)
  913. );
  914. server.registerTool(
  915. 'qiwei_distill_broker',
  916. {
  917. title: '蒸馏顾问 playbook',
  918. description: '默认 Agent 驱动:准备上下文并返回给调用方分析。传 aiMode=keyword 时直接基于关键词生成简版 playbook 并保存。',
  919. inputSchema: {
  920. brokerUserId: z.string(),
  921. aiMode: z.enum(['keyword']).optional(),
  922. roomIds: z.array(z.string()).optional()
  923. }
  924. },
  925. wrap(qiweiDistillBroker)
  926. );
  927. server.registerTool(
  928. 'qiwei_save_broker_playbook',
  929. {
  930. title: '保存顾问 playbook',
  931. description: '将 Agent 分析后的 playbook JSON 保存到 outputs/broker-playbooks/<brokerUserId>.json。',
  932. inputSchema: {
  933. brokerUserId: z.string(),
  934. playbook: z.record(z.any()),
  935. source: z.string().optional(),
  936. messageCount: z.number().int().optional()
  937. }
  938. },
  939. wrap(qiweiSaveBrokerPlaybook)
  940. );
  941. server.registerTool(
  942. 'qiwei_batch_distill_broker',
  943. {
  944. title: '批量蒸馏顾问 playbook',
  945. description: '对多个 brokerUserId 执行 qiwei_distill_broker。',
  946. inputSchema: {
  947. brokerUserIds: z.array(z.string()),
  948. aiMode: z.enum(['keyword']).optional(),
  949. roomIds: z.array(z.string()).optional()
  950. }
  951. },
  952. wrap(qiweiBatchDistillBroker)
  953. );
  954. server.registerTool(
  955. 'qiwei_batch_save_broker_playbook',
  956. {
  957. title: '批量保存顾问 playbook',
  958. description: '批量保存多个顾问的 playbook。',
  959. inputSchema: {
  960. items: z.array(z.object({ brokerUserId: z.string(), playbook: z.record(z.any()), source: z.string().optional() }))
  961. }
  962. },
  963. wrap(qiweiBatchSaveBrokerPlaybook)
  964. );
  965. server.registerTool(
  966. 'qiwei_export_broker_playbooks',
  967. {
  968. title: '导出顾问 playbook',
  969. description: '将 outputs/broker-playbooks/ 中保存的 playbook 导出为 Excel。未传 brokerUserIds 时导出全部。',
  970. inputSchema: {
  971. brokerUserIds: z.array(z.string()).optional()
  972. }
  973. },
  974. wrap(qiweiExportBrokerPlaybooks)
  975. );
  976. server.registerTool(
  977. 'qiwei_get_broker_playbook',
  978. {
  979. title: '读取顾问 playbook',
  980. description: '读取 outputs/broker-playbooks/<brokerUserId>.json 返回 playbook。',
  981. inputSchema: {
  982. brokerUserId: z.string()
  983. }
  984. },
  985. wrap(qiweiGetBrokerPlaybook)
  986. );
  987. server.registerTool(
  988. 'qiwei_preview_transfer_package',
  989. {
  990. title: '预览客户交接包',
  991. description: '根据 fromUserId/toUserId 和 roomIds/externalUserIds 生成交接包预览,写入 outputs/transfers/。',
  992. inputSchema: {
  993. fromUserId: z.string(),
  994. toUserId: z.string(),
  995. roomIds: z.array(z.string()).optional(),
  996. externalUserIds: z.array(z.string()).optional()
  997. }
  998. },
  999. wrap(qiweiPreviewTransferPackage)
  1000. );
  1001. server.registerTool(
  1002. 'qiwei_execute_transfer',
  1003. {
  1004. title: '执行客户交接',
  1005. description: '读取交接包预览,对每个 room 邀请新顾问入群,可选移除旧顾问并发送欢迎语。结果写入 outputs/transfers/。',
  1006. inputSchema: {
  1007. guid: z.string().optional(),
  1008. previewFile: z.string().optional(),
  1009. removeOldBroker: z.boolean().optional(),
  1010. welcomeText: z.string().optional(),
  1011. uid: z.string().optional(),
  1012. authToken: z.string().optional(),
  1013. apiBase: z.string().optional()
  1014. }
  1015. },
  1016. wrap(qiweiExecuteTransfer)
  1017. );
  1018. server.registerTool(
  1019. 'qiwei_transcribe_voice',
  1020. {
  1021. title: '语音转写',
  1022. description: '接收语音 URL、本地文件路径或 base64,保存到 outputs/voice/,可选解码 silk 并调用转写服务。',
  1023. inputSchema: {
  1024. voiceUrl: z.string().optional(),
  1025. filePath: z.string().optional(),
  1026. base64Audio: z.string().optional(),
  1027. transcribe: z.boolean().optional(),
  1028. uid: z.string().optional(),
  1029. authToken: z.string().optional(),
  1030. apiBase: z.string().optional()
  1031. }
  1032. },
  1033. wrap(qiweiTranscribeVoice)
  1034. );
  1035. server.registerTool(
  1036. 'qiwei_voice_profile_status',
  1037. {
  1038. title: '查询声音克隆状态',
  1039. description: '查询当前企微账号是否已配置云端 IndexTTS2,以及是否完成本人声音初始化。',
  1040. inputSchema: {
  1041. uid: z.string().optional(),
  1042. guid: z.string().optional(),
  1043. authToken: z.string().optional(),
  1044. apiBase: z.string().optional()
  1045. }
  1046. },
  1047. wrap(qiweiVoiceProfileStatus)
  1048. );
  1049. server.registerTool(
  1050. 'qiwei_enroll_voice',
  1051. {
  1052. title: '初始化本人声音',
  1053. description: '保存并校验 5~30 秒本人参考录音,转换为账号隔离的 16kHz 单声道声音档案。',
  1054. inputSchema: {
  1055. filePath: z.string().optional(),
  1056. base64Audio: z.string().optional(),
  1057. originalName: z.string().optional(),
  1058. mime: z.string().optional(),
  1059. uid: z.string().optional(),
  1060. guid: z.string().optional(),
  1061. authToken: z.string().optional(),
  1062. apiBase: z.string().optional()
  1063. }
  1064. },
  1065. wrap(qiweiEnrollVoice)
  1066. );
  1067. server.registerTool(
  1068. 'qiwei_send_cloned_voice',
  1069. {
  1070. title: '发送克隆企微语音',
  1071. description: '真实发送操作。合成本人音色、自动选择受控客服语气、编码 24kHz SILK 并发送给指定企微联系人。调用前必须获得人工确认。',
  1072. inputSchema: {
  1073. toId: z.string().min(1),
  1074. text: z.string().min(1).max(600),
  1075. context: z.string().optional(),
  1076. tone: z.enum(['auto', 'natural', 'friendly', 'apology', 'empathetic', 'reminder']).optional(),
  1077. confirmed: z.literal(true),
  1078. uid: z.string().optional(),
  1079. guid: z.string().optional(),
  1080. authToken: z.string().optional(),
  1081. apiBase: z.string().optional()
  1082. },
  1083. annotations: { destructiveHint: true, openWorldHint: true }
  1084. },
  1085. wrap(qiweiSendClonedVoice)
  1086. );
  1087. server.registerTool(
  1088. 'qiwei_webhook_status',
  1089. {
  1090. title: '查询 webhook 状态',
  1091. description: '查询当前个人版/企业版消息接收方式、本地 webhook、企业 Relay 和事件状态,不返回任何回调或租户密钥。',
  1092. inputSchema: {}
  1093. },
  1094. wrap(qiweiWebhookStatus)
  1095. );
  1096. server.registerTool(
  1097. 'qiwei_webhook_discover',
  1098. {
  1099. title: '获取个人版直连回调地址',
  1100. description: '个人版隔离部署专用:返回本地 webhook server 的回调 URL。企业版使用服务端统一回调。',
  1101. inputSchema: {}
  1102. },
  1103. wrap(qiweiWebhookDiscover)
  1104. );
  1105. server.registerTool(
  1106. 'qiwei_webhook_server_start',
  1107. {
  1108. title: '启动个人版本地 webhook server',
  1109. description: '个人版隔离部署专用:在本机启动 HTTP server,仍需独立公网入口才能接收企微回调。企业版不能调用。',
  1110. inputSchema: {
  1111. port: z.number().int().min(0).max(65535).optional()
  1112. }
  1113. },
  1114. wrap(qiweiWebhookServerStart)
  1115. );
  1116. server.registerTool(
  1117. 'qiwei_webhook_server_stop',
  1118. {
  1119. title: '停止本地 webhook server',
  1120. description: '停止正在运行的本地 webhook server。',
  1121. inputSchema: {}
  1122. },
  1123. wrap(qiweiWebhookServerStop)
  1124. );
  1125. server.registerTool(
  1126. 'qiwei_webhook_auto_setup',
  1127. {
  1128. title: '自动配置企业版 Relay 回调',
  1129. description: '企业版专用:注册设备到中央 Relay,并调用 Fmode 服务端专用接口连接 Token 级全局回调。个人版使用本地监听,无需调用。',
  1130. inputSchema: {
  1131. guid: z.string().optional(),
  1132. port: z.number().int().min(0).max(65535).optional(),
  1133. callbackUrl: z.string().optional(),
  1134. secret: z.string().optional(),
  1135. uid: z.string().optional(),
  1136. authToken: z.string().optional(),
  1137. apiBase: z.string().optional()
  1138. }
  1139. },
  1140. wrap(qiweiWebhookAutoSetup)
  1141. );
  1142. server.registerTool(
  1143. 'qiwei_webhook_setup',
  1144. {
  1145. title: '配置隔离部署的个人回调',
  1146. description: '仅限个人版独立服务 Token 和独立公网部署;需要显式开启直连回调,企业版不能调用。',
  1147. inputSchema: {
  1148. guid: z.string().optional(),
  1149. callbackUrl: z.string(),
  1150. secret: z.string().optional(),
  1151. uid: z.string().optional(),
  1152. authToken: z.string().optional(),
  1153. apiBase: z.string().optional()
  1154. }
  1155. },
  1156. wrap(qiweiWebhookSetup)
  1157. );
  1158. server.registerTool(
  1159. 'qiwei_relay_config',
  1160. {
  1161. title: '读取 relay 配置',
  1162. description: '读取 outputs/webhook/relay-config.json 中的中央 relay 配置。',
  1163. inputSchema: {}
  1164. },
  1165. wrap(qiweiRelayConfig)
  1166. );
  1167. server.registerTool(
  1168. 'qiwei_relay_save_config',
  1169. {
  1170. title: '保存 relay 配置',
  1171. description: '保存中央 relay 配置到 outputs/webhook/relay-config.json。',
  1172. inputSchema: {
  1173. relayBaseUrl: z.string(),
  1174. tenantId: z.string().optional(),
  1175. publicKey: z.string().optional()
  1176. }
  1177. },
  1178. wrap(qiweiRelaySaveConfig)
  1179. );
  1180. server.registerTool(
  1181. 'qiwei_relay_register',
  1182. {
  1183. title: '注册 Relay 租户',
  1184. description: '使用 Fmode token 在中央 Relay 服务端注册租户,自动保存 tenantId/apiKey/apiSecret/privateKey 到 .env.local 和 outputs/webhook/relay-config.json。',
  1185. inputSchema: {
  1186. relayBaseUrl: z.string().optional(),
  1187. description: z.string().optional(),
  1188. guid: z.string().optional(),
  1189. force: z.boolean().optional()
  1190. }
  1191. },
  1192. wrap(qiweiRelayRegister)
  1193. );
  1194. server.registerTool(
  1195. 'qiwei_relay_connect',
  1196. {
  1197. title: '连接中央 relay',
  1198. description: '企业版专用:检查 Relay 配置,注册设备并调用 Fmode `/relay/connect` 接入服务端统一回调。',
  1199. inputSchema: {
  1200. guid: z.string().optional(),
  1201. force: z.boolean().optional()
  1202. }
  1203. },
  1204. wrap(qiweiRelayConnect)
  1205. );
  1206. server.registerTool(
  1207. 'qiwei_agent_dashboard_start',
  1208. {
  1209. title: '启动企微智能工作台',
  1210. description: '启动并预览当前项目的企微 Dashboard(默认 4320 端口),同时返回 Fmode 鉴权、企微席位、账号在线、测试白名单和企业回调状态。Dashboard 仅用于管理,不承担企业版消息接收。',
  1211. inputSchema: {}
  1212. },
  1213. wrap(qiweiAgentDashboardStart)
  1214. );
  1215. server.registerTool(
  1216. 'qiwei_goal_create_plan',
  1217. {
  1218. title: '建立目标推进计划',
  1219. description: '把已确认的大目标、里程碑和任务保存为本地推进台账;不会自动创建企微待办。',
  1220. inputSchema: {
  1221. title: z.string(),
  1222. objective: z.string(),
  1223. owner: z.string().optional(),
  1224. deadline: z.string().optional(),
  1225. acceptance: z.string().optional(),
  1226. status: z.enum(['planned', 'in_progress', 'blocked', 'done', 'cancelled']).optional(),
  1227. milestones: z.array(goalMilestoneSchema).optional()
  1228. }
  1229. },
  1230. wrap(qiweiGoalCreatePlan)
  1231. );
  1232. server.registerTool(
  1233. 'qiwei_goal_get',
  1234. {
  1235. title: '查看目标与推进状态',
  1236. description: '列出目标计划,或读取单个计划的里程碑、任务、完成率、阻塞和逾期情况。',
  1237. inputSchema: { goalId: z.string().optional() }
  1238. },
  1239. wrap(qiweiGoalGet)
  1240. );
  1241. server.registerTool(
  1242. 'qiwei_goal_update_task',
  1243. {
  1244. title: '更新目标任务进度',
  1245. description: '更新目标任务的状态、负责人、截止时间、完成说明或阻塞原因。',
  1246. inputSchema: {
  1247. goalId: z.string(),
  1248. taskId: z.string(),
  1249. status: z.enum(['planned', 'in_progress', 'blocked', 'done', 'cancelled']).optional(),
  1250. owner: z.string().optional(),
  1251. deadline: z.string().optional(),
  1252. note: z.string().optional(),
  1253. blockedReason: z.string().optional()
  1254. }
  1255. },
  1256. wrap(qiweiGoalUpdateTask)
  1257. );
  1258. server.registerTool(
  1259. 'qiwei_goal_import_meeting_actions',
  1260. {
  1261. title: '把会议行动项写入目标计划',
  1262. description: '把已经过用户确认的会议行动项写入目标推进台账,并标记缺失的负责人或截止时间。',
  1263. inputSchema: {
  1264. goalId: z.string(),
  1265. meetingTitle: z.string(),
  1266. meetingDate: z.string().optional(),
  1267. sourceUrl: z.string().optional(),
  1268. actions: z.array(goalTaskSchema).min(1)
  1269. }
  1270. },
  1271. wrap(qiweiGoalImportMeetingActions)
  1272. );
  1273. server.registerTool(
  1274. 'qiwei_agent_bind_controller',
  1275. {
  1276. title: '绑定企微项目主控会话',
  1277. description: '把当前或指定 Claude Code Session 绑定为项目主控,并让每个客户专属 Session 记录该主控关联。通常由 qiwei_agent_dashboard_start 自动尝试。',
  1278. inputSchema: { sessionId: z.string().uuid().optional() }
  1279. },
  1280. wrap(qiweiAgentBindController)
  1281. );
  1282. server.registerTool(
  1283. 'qiwei_agent_status',
  1284. {
  1285. title: '查看企微 Agent 状态',
  1286. description: '读取账号在线状态、监听状态、全局策略、Claude Code/Fmode 模型和客户会话数量。',
  1287. inputSchema: {}
  1288. },
  1289. wrap(qiweiAgentStatus)
  1290. );
  1291. server.registerTool(
  1292. 'qiwei_agent_list_conversations',
  1293. {
  1294. title: '列出企微 Agent 客户会话',
  1295. description: '列出白名单客户会话、最新消息、会话模式和待审核草稿。每个客户由独立 Claude Code Session 处理。',
  1296. inputSchema: {}
  1297. },
  1298. wrap(qiweiAgentListConversations)
  1299. );
  1300. server.registerTool(
  1301. 'qiwei_agent_session_guide',
  1302. {
  1303. title: '查看客户 Claude Code 会话入口',
  1304. description: '当用户问某个企微客户由哪个 Claude Code Session 处理、会话在哪里或怎么打开时调用。返回客户可识别会话名和 Fmode Studio 安全打开命令,不暴露原始 Session ID。',
  1305. inputSchema: {
  1306. conversationId: z.string().optional(),
  1307. customerName: z.string().optional()
  1308. }
  1309. },
  1310. wrap(qiweiAgentSessionGuide)
  1311. );
  1312. server.registerTool(
  1313. 'qiwei_agent_customer_intelligence',
  1314. {
  1315. title: '查看客户画像、待办与预警',
  1316. description: '读取监听和 Agent 持续沉淀的客户画像、内部跟进待办和风险/高意向预警。用户问客户需求进展、下一步该做什么或有哪些风险时调用。',
  1317. inputSchema: {
  1318. conversationId: z.string().optional(),
  1319. customerName: z.string().optional()
  1320. }
  1321. },
  1322. wrap(qiweiAgentCustomerIntelligence)
  1323. );
  1324. server.registerTool(
  1325. 'qiwei_agent_inbox',
  1326. {
  1327. title: '读取项目主控事件箱',
  1328. description: '读取企微监听、Agent 决策、人工审核和发送动作的审计事件,供项目主控 Claude Code 会话掌握前端与客户会话状态。',
  1329. inputSchema: { limit: z.number().int().min(1).max(200).optional() }
  1330. },
  1331. wrap(qiweiAgentInbox)
  1332. );
  1333. server.registerTool(
  1334. 'qiwei_agent_listener',
  1335. {
  1336. title: '查看或控制企微消息接收',
  1337. description: '企业版公网回调自动常驻,不需要监听开关;个人版可用 running 控制本地主动监听。停止 Agent 处理请使用全局 paused,消息仍会接收和保存。',
  1338. inputSchema: { running: z.boolean() }
  1339. },
  1340. wrap(qiweiAgentListener)
  1341. );
  1342. server.registerTool(
  1343. 'qiwei_agent_set_global',
  1344. {
  1345. title: '设置企微 Agent 全局策略',
  1346. description: '设置 paused(全局暂停)、review(生成待审核草稿)、auto(仅高置信且无需人工时自动发送)、autopilot(全自动接管,非空回复直接发送)或 human(人工模式)。autopilot 必须同时传 confirmation=ENABLE_AUTOPILOT。',
  1347. inputSchema: {
  1348. mode: z.enum(['paused', 'review', 'auto', 'autopilot', 'human']),
  1349. confirmation: z.string().optional(),
  1350. }
  1351. },
  1352. wrap(qiweiAgentSetGlobal)
  1353. );
  1354. server.registerTool(
  1355. 'qiwei_agent_generate_draft',
  1356. {
  1357. title: '让 Claude Code 生成企微回复草稿',
  1358. description: '对指定客户会话的最新入站消息运行专用 Claude Code/Fmode Session,只生成待审核草稿,不发送给客户。',
  1359. inputSchema: { conversationId: z.string() }
  1360. },
  1361. wrap(qiweiAgentGenerateDraft)
  1362. );
  1363. return server;
  1364. }
  1365. async function main() {
  1366. const server = createServer();
  1367. const transport = new StdioServerTransport();
  1368. await server.connect(transport);
  1369. ensureRelayDaemon();
  1370. void (async () => {
  1371. const connected = await qiweiRelayConnect({});
  1372. if (connected.status !== 'ok') {
  1373. console.error(`[RelayBootstrap] ${connected.assistantMessage || '企业回调连接失败'}`);
  1374. return;
  1375. }
  1376. const started = await qiweiRelayStart({});
  1377. if (started.status !== 'ok') console.error(`[RelayBootstrap] ${started.assistantMessage || 'Relay 消费守护进程启动失败'}`);
  1378. })().catch(error => console.error('[RelayBootstrap] 企业回调启动异常:', error.message));
  1379. }
  1380. main().catch(error => {
  1381. console.error('enterprise-wechat-assistant mcp server failed:', error);
  1382. process.exit(1);
  1383. });