weflow-cli 1.5.0 → 1.6.1

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.
Files changed (332) hide show
  1. package/ARCHITECTURE.md +105 -0
  2. package/CHANGELOG.md +121 -0
  3. package/CONTRIBUTING.md +45 -0
  4. package/LICENSE +21 -21
  5. package/OPERATIONS.md +272 -0
  6. package/README.en.md +281 -0
  7. package/README.md +334 -149
  8. package/SECURITY.md +47 -0
  9. package/bin/weflow-cli.ts +4050 -581
  10. package/dist/bin/weflow-cli.js +4310 -588
  11. package/dist/bin/weflow-cli.js.map +1 -1
  12. package/dist/mcp-server/index.js +178 -236
  13. package/dist/mcp-server/index.js.map +1 -1
  14. package/dist/src/core/dbPathService.d.ts +15 -1
  15. package/dist/src/core/dbPathService.d.ts.map +1 -1
  16. package/dist/src/core/dbPathService.js +177 -19
  17. package/dist/src/core/dbPathService.js.map +1 -1
  18. package/dist/src/core/keyService.d.ts +8 -0
  19. package/dist/src/core/keyService.d.ts.map +1 -1
  20. package/dist/src/core/keyService.js +127 -22
  21. package/dist/src/core/keyService.js.map +1 -1
  22. package/dist/src/core/ntCore.d.ts +59 -1
  23. package/dist/src/core/ntCore.d.ts.map +1 -1
  24. package/dist/src/core/ntCore.js +201 -65
  25. package/dist/src/core/ntCore.js.map +1 -1
  26. package/dist/src/core/sqlcipherCore.d.ts.map +1 -1
  27. package/dist/src/core/sqlcipherCore.js +18 -1
  28. package/dist/src/core/sqlcipherCore.js.map +1 -1
  29. package/dist/src/core/wcdbCore.d.ts.map +1 -1
  30. package/dist/src/core/wcdbCore.js +3 -6
  31. package/dist/src/core/wcdbCore.js.map +1 -1
  32. package/dist/src/services/assistantDaemon.d.ts +19 -0
  33. package/dist/src/services/assistantDaemon.d.ts.map +1 -0
  34. package/dist/src/services/assistantDaemon.js +117 -0
  35. package/dist/src/services/assistantDaemon.js.map +1 -0
  36. package/dist/src/services/assistantMemory.d.ts +36 -0
  37. package/dist/src/services/assistantMemory.d.ts.map +1 -0
  38. package/dist/src/services/assistantMemory.js +182 -0
  39. package/dist/src/services/assistantMemory.js.map +1 -0
  40. package/dist/src/services/assistantPrivacy.d.ts +25 -0
  41. package/dist/src/services/assistantPrivacy.d.ts.map +1 -0
  42. package/dist/src/services/assistantPrivacy.js +77 -0
  43. package/dist/src/services/assistantPrivacy.js.map +1 -0
  44. package/dist/src/services/assistantRouting.d.ts +18 -0
  45. package/dist/src/services/assistantRouting.d.ts.map +1 -0
  46. package/dist/src/services/assistantRouting.js +68 -0
  47. package/dist/src/services/assistantRouting.js.map +1 -0
  48. package/dist/src/services/assistantService.d.ts +37 -0
  49. package/dist/src/services/assistantService.d.ts.map +1 -0
  50. package/dist/src/services/assistantService.js +284 -0
  51. package/dist/src/services/assistantService.js.map +1 -0
  52. package/dist/src/services/assistantTools.d.ts +24 -0
  53. package/dist/src/services/assistantTools.d.ts.map +1 -0
  54. package/dist/src/services/assistantTools.js +582 -0
  55. package/dist/src/services/assistantTools.js.map +1 -0
  56. package/dist/src/services/chatService.d.ts +44 -0
  57. package/dist/src/services/chatService.d.ts.map +1 -1
  58. package/dist/src/services/chatService.js +105 -4
  59. package/dist/src/services/chatService.js.map +1 -1
  60. package/dist/src/services/configService.d.ts +55 -0
  61. package/dist/src/services/configService.d.ts.map +1 -1
  62. package/dist/src/services/configService.js +147 -6
  63. package/dist/src/services/configService.js.map +1 -1
  64. package/dist/src/services/evidenceService.d.ts +30 -0
  65. package/dist/src/services/evidenceService.d.ts.map +1 -0
  66. package/dist/src/services/evidenceService.js +98 -0
  67. package/dist/src/services/evidenceService.js.map +1 -0
  68. package/dist/src/services/exportService.d.ts +13 -20
  69. package/dist/src/services/exportService.d.ts.map +1 -1
  70. package/dist/src/services/exportService.js +113 -47
  71. package/dist/src/services/exportService.js.map +1 -1
  72. package/dist/src/services/initKeyService.d.ts +19 -0
  73. package/dist/src/services/initKeyService.d.ts.map +1 -0
  74. package/dist/src/services/initKeyService.js +80 -0
  75. package/dist/src/services/initKeyService.js.map +1 -0
  76. package/dist/src/services/messageContract.d.ts +23 -0
  77. package/dist/src/services/messageContract.d.ts.map +1 -0
  78. package/dist/src/services/messageContract.js +49 -0
  79. package/dist/src/services/messageContract.js.map +1 -0
  80. package/dist/src/services/messageQuery.d.ts +7 -0
  81. package/dist/src/services/messageQuery.d.ts.map +1 -0
  82. package/dist/src/services/messageQuery.js +30 -0
  83. package/dist/src/services/messageQuery.js.map +1 -0
  84. package/dist/src/services/wechat-formatter.js +14 -14
  85. package/dist/src/services/wechatMessageService.d.ts.map +1 -1
  86. package/dist/src/services/wechatMessageService.js +31 -3
  87. package/dist/src/services/wechatMessageService.js.map +1 -1
  88. package/dist/src/services/whitelistService.d.ts +52 -4
  89. package/dist/src/services/whitelistService.d.ts.map +1 -1
  90. package/dist/src/services/whitelistService.js +183 -17
  91. package/dist/src/services/whitelistService.js.map +1 -1
  92. package/dist/src/types.d.ts +20 -0
  93. package/dist/src/types.d.ts.map +1 -1
  94. package/dist/src/utils/dateRange.d.ts +15 -0
  95. package/dist/src/utils/dateRange.d.ts.map +1 -0
  96. package/dist/src/utils/dateRange.js +48 -0
  97. package/dist/src/utils/dateRange.js.map +1 -0
  98. package/dist/src/utils/mcpSecurity.d.ts +5 -0
  99. package/dist/src/utils/mcpSecurity.d.ts.map +1 -0
  100. package/dist/src/utils/mcpSecurity.js +39 -0
  101. package/dist/src/utils/mcpSecurity.js.map +1 -0
  102. package/dist/src/utils/packageRoot.d.ts +2 -0
  103. package/dist/src/utils/packageRoot.d.ts.map +1 -0
  104. package/dist/src/utils/packageRoot.js +16 -0
  105. package/dist/src/utils/packageRoot.js.map +1 -0
  106. package/dist/src/utils/python.d.ts +2 -0
  107. package/dist/src/utils/python.d.ts.map +1 -0
  108. package/dist/src/utils/python.js +72 -0
  109. package/dist/src/utils/python.js.map +1 -0
  110. package/dist/src/utils/pythonProcessEnv.d.ts +3 -0
  111. package/dist/src/utils/pythonProcessEnv.d.ts.map +1 -0
  112. package/dist/src/utils/pythonProcessEnv.js +46 -0
  113. package/dist/src/utils/pythonProcessEnv.js.map +1 -0
  114. package/dist/src/utils/talkerUtils.d.ts +4 -1
  115. package/dist/src/utils/talkerUtils.d.ts.map +1 -1
  116. package/dist/src/utils/talkerUtils.js +9 -9
  117. package/dist/src/utils/talkerUtils.js.map +1 -1
  118. package/dist/src/utils/wechatEmoji.d.ts +5 -0
  119. package/dist/src/utils/wechatEmoji.d.ts.map +1 -0
  120. package/dist/src/utils/wechatEmoji.js +35 -0
  121. package/dist/src/utils/wechatEmoji.js.map +1 -0
  122. package/docs/AI_INTERFACE.md +189 -0
  123. package/docs/BRANCHES.md +87 -0
  124. package/docs/DATA_CONTRACT.md +74 -0
  125. package/docs/DECISIONS.md +270 -0
  126. package/docs/DEEPSEEK_V4_FLASH/346/226/275/345/267/245/346/226/207/346/241/243.md +234 -0
  127. package/docs/EVIDENCE_GUIDE.md +96 -0
  128. package/docs/MCP.md +95 -0
  129. package/docs/PARTNERS.md +41 -0
  130. package/docs/PROJECT_STATE.md +96 -0
  131. package/docs/RELEASING.md +124 -0
  132. package/docs/ROADMAP.md +191 -0
  133. package/docs/SETUP.md +103 -0
  134. package/docs/images/weflow-architecture-gpt-image-2.png +0 -0
  135. package/docs/images/weflow-architecture.png +0 -0
  136. package/docs/images/weflow-architecture.svg +65 -0
  137. package/mcp-server/index.ts +169 -269
  138. package/package.json +81 -72
  139. package/requirements-3x.txt +4 -0
  140. package/requirements-voice.txt +15 -0
  141. package/requirements.txt +9 -0
  142. package/resources/js/marked.min.js +69 -0
  143. package/resources/wechat-emoji/666.png +0 -0
  144. package/resources/wechat-emoji/Awesome.png +0 -0
  145. package/resources/wechat-emoji/Concerned.png +0 -0
  146. package/resources/wechat-emoji/Cry.png +0 -0
  147. package/resources/wechat-emoji/Emm.png +0 -0
  148. package/resources/wechat-emoji/Facepalm.png +0 -0
  149. package/resources/wechat-emoji/Grin.png +0 -0
  150. package/resources/wechat-emoji/OK.png +0 -0
  151. package/resources/wechat-emoji/Respect.png +0 -0
  152. package/resources/wechat-emoji/Sick.png +0 -0
  153. package/resources/wechat-emoji/Sleep.png +0 -0
  154. package/resources/wechat-emoji/Smile.png +0 -0
  155. package/resources/wechat-emoji//344/272/262/344/272/262.png +0 -0
  156. package/resources/wechat-emoji//344/276/277/344/276/277.png +0 -0
  157. package/resources/wechat-emoji//345/201/267/347/254/221.png +0 -0
  158. package/resources/wechat-emoji//345/202/262/346/205/242.png +0 -0
  159. package/resources/wechat-emoji//345/206/215/350/247/201.png +0 -0
  160. package/resources/wechat-emoji//345/207/213/350/260/242.png +0 -0
  161. package/resources/wechat-emoji//345/212/240/346/262/271.png +0 -0
  162. package/resources/wechat-emoji//345/213/276/345/274/225.png +0 -0
  163. package/resources/wechat-emoji//345/217/221/345/221/206.png +0 -0
  164. package/resources/wechat-emoji//345/217/221/346/200/222.png +0 -0
  165. package/resources/wechat-emoji//345/217/221/346/212/226.png +0 -0
  166. package/resources/wechat-emoji//345/217/257/346/200/234.png +0 -0
  167. package/resources/wechat-emoji//345/217/263/345/223/274/345/223/274.png +0 -0
  168. package/resources/wechat-emoji//345/217/271/346/260/224.png +0 -0
  169. package/resources/wechat-emoji//345/220/203/347/223/234.png +0 -0
  170. package/resources/wechat-emoji//345/220/210/345/215/201.png +0 -0
  171. package/resources/wechat-emoji//345/220/220.png +0 -0
  172. package/resources/wechat-emoji//345/221/262/347/211/231.png +0 -0
  173. package/resources/wechat-emoji//345/222/222/351/252/202.png +0 -0
  174. package/resources/wechat-emoji//345/222/226/345/225/241.png +0 -0
  175. package/resources/wechat-emoji//345/223/207.png +0 -0
  176. package/resources/wechat-emoji//345/225/244/351/205/222.png +0 -0
  177. package/resources/wechat-emoji//345/230/230.png +0 -0
  178. package/resources/wechat-emoji//345/230/264/345/224/207.png +0 -0
  179. package/resources/wechat-emoji//345/230/277/345/223/210.png +0 -0
  180. package/resources/wechat-emoji//345/233/247.png +0 -0
  181. package/resources/wechat-emoji//345/233/260.png +0 -0
  182. package/resources/wechat-emoji//345/235/217/347/254/221.png +0 -0
  183. package/resources/wechat-emoji//345/244/247/345/223/255.png +0 -0
  184. package/resources/wechat-emoji//345/244/251/345/225/212.png +0 -0
  185. package/resources/wechat-emoji//345/244/252/351/230/263.png +0 -0
  186. package/resources/wechat-emoji//345/244/261/346/234/233.png +0 -0
  187. package/resources/wechat-emoji//345/245/270/347/254/221.png +0 -0
  188. package/resources/wechat-emoji//345/245/275/347/232/204.png +0 -0
  189. package/resources/wechat-emoji//345/247/224/345/261/210.png +0 -0
  190. package/resources/wechat-emoji//345/256/263/347/276/236.png +0 -0
  191. package/resources/wechat-emoji//345/260/264/345/260/254.png +0 -0
  192. package/resources/wechat-emoji//345/272/206/347/245/235.png +0 -0
  193. package/resources/wechat-emoji//345/274/261.png +0 -0
  194. package/resources/wechat-emoji//345/274/272.png +0 -0
  195. package/resources/wechat-emoji//345/276/227/346/204/217.png +0 -0
  196. package/resources/wechat-emoji//345/276/256/347/254/221.png +0 -0
  197. package/resources/wechat-emoji//345/277/203/347/242/216.png +0 -0
  198. package/resources/wechat-emoji//345/277/253/345/223/255/344/272/206.png +0 -0
  199. package/resources/wechat-emoji//346/201/220/346/203/247.png +0 -0
  200. package/resources/wechat-emoji//346/202/240/351/227/262.png +0 -0
  201. package/resources/wechat-emoji//346/203/212/346/201/220.png +0 -0
  202. package/resources/wechat-emoji//346/203/212/350/256/266.png +0 -0
  203. package/resources/wechat-emoji//346/204/211/345/277/253.png +0 -0
  204. package/resources/wechat-emoji//346/206/250/347/254/221.png +0 -0
  205. package/resources/wechat-emoji//346/211/223/350/204/270.png +0 -0
  206. package/resources/wechat-emoji//346/212/223/347/213/202.png +0 -0
  207. package/resources/wechat-emoji//346/212/240/351/274/273.png +0 -0
  208. package/resources/wechat-emoji//346/212/261/346/213/263.png +0 -0
  209. package/resources/wechat-emoji//346/213/245/346/212/261.png +0 -0
  210. package/resources/wechat-emoji//346/213/263/345/244/264.png +0 -0
  211. package/resources/wechat-emoji//346/215/202/350/204/270.png +0 -0
  212. package/resources/wechat-emoji//346/217/241/346/211/213.png +0 -0
  213. package/resources/wechat-emoji//346/222/207/345/230/264.png +0 -0
  214. package/resources/wechat-emoji//346/223/246/346/261/227.png +0 -0
  215. package/resources/wechat-emoji//346/225/262/346/211/223.png +0 -0
  216. package/resources/wechat-emoji//346/227/240/350/257/255.png +0 -0
  217. package/resources/wechat-emoji//346/227/272/346/237/264.png +0 -0
  218. package/resources/wechat-emoji//346/231/225.png +0 -0
  219. package/resources/wechat-emoji//346/234/210/344/272/256.png +0 -0
  220. package/resources/wechat-emoji//346/234/272/346/231/272.png +0 -0
  221. package/resources/wechat-emoji//346/261/227.png +0 -0
  222. package/resources/wechat-emoji//346/265/201/346/263/252.png +0 -0
  223. package/resources/wechat-emoji//347/202/270/345/274/271.png +0 -0
  224. package/resources/wechat-emoji//347/203/237/350/212/261.png +0 -0
  225. package/resources/wechat-emoji//347/210/206/347/253/271.png +0 -0
  226. package/resources/wechat-emoji//347/210/261/345/277/203.png +0 -0
  227. package/resources/wechat-emoji//347/214/252/345/244/264.png +0 -0
  228. package/resources/wechat-emoji//347/216/253/347/221/260.png +0 -0
  229. package/resources/wechat-emoji//347/224/237/347/227/205.png +0 -0
  230. package/resources/wechat-emoji//347/226/221/351/227/256.png +0 -0
  231. package/resources/wechat-emoji//347/231/274.png +0 -0
  232. package/resources/wechat-emoji//347/231/275/347/234/274.png +0 -0
  233. package/resources/wechat-emoji//347/232/261/347/234/211.png +0 -0
  234. package/resources/wechat-emoji//347/235/241.png +0 -0
  235. package/resources/wechat-emoji//347/240/264/346/266/225/344/270/272/347/254/221.png +0 -0
  236. package/resources/wechat-emoji//347/244/274/347/211/251.png +0 -0
  237. package/resources/wechat-emoji//347/244/276/344/274/232/347/244/276/344/274/232.png +0 -0
  238. package/resources/wechat-emoji//347/246/217.png +0 -0
  239. package/resources/wechat-emoji//347/254/221/350/204/270.png +0 -0
  240. package/resources/wechat-emoji//347/272/242/345/214/205.png +0 -0
  241. package/resources/wechat-emoji//347/277/273/347/231/275/347/234/274.png +0 -0
  242. package/resources/wechat-emoji//350/200/266.png +0 -0
  243. package/resources/wechat-emoji//350/203/234/345/210/251.png +0 -0
  244. package/resources/wechat-emoji//350/204/270/347/272/242.png +0 -0
  245. package/resources/wechat-emoji//350/211/262.png +0 -0
  246. package/resources/wechat-emoji//350/213/246/346/266/251.png +0 -0
  247. package/resources/wechat-emoji//350/217/234/345/210/200.png +0 -0
  248. package/resources/wechat-emoji//350/233/213/347/263/225.png +0 -0
  249. package/resources/wechat-emoji//350/241/260.png +0 -0
  250. package/resources/wechat-emoji//350/243/202/345/274/200.png +0 -0
  251. package/resources/wechat-emoji//350/256/251/346/210/221/347/234/213/347/234/213.png +0 -0
  252. package/resources/wechat-emoji//350/260/203/347/232/256.png +0 -0
  253. package/resources/wechat-emoji//350/267/263/350/267/263.png +0 -0
  254. package/resources/wechat-emoji//350/275/254/345/234/210.png +0 -0
  255. package/resources/wechat-emoji//351/204/231/350/247/206.png +0 -0
  256. package/resources/wechat-emoji//351/227/255/345/230/264.png +0 -0
  257. package/resources/wechat-emoji//351/230/264/351/231/251.png +0 -0
  258. package/resources/wechat-emoji//351/232/276/350/277/207.png +0 -0
  259. package/resources/wechat-emoji//351/252/267/351/253/205.png +0 -0
  260. package/resources/wechat-emoji//351/274/223/346/216/214.png +0 -0
  261. package/scripts/_batch_link_sources.py +73 -73
  262. package/scripts/_utils.py +29 -0
  263. package/scripts/annual_report.py +796 -796
  264. package/scripts/auto_tag.py +128 -128
  265. package/scripts/biz_daily.py +210 -53
  266. package/scripts/chat_report.py +10 -0
  267. package/scripts/chat_stats.py +702 -702
  268. package/scripts/classify_daily.py +17 -11
  269. package/scripts/create_reading_notes.py +370 -370
  270. package/scripts/daily_stats.py +132 -0
  271. package/scripts/enrich_backlinks.py +163 -163
  272. package/scripts/export_chat_html.py +1704 -107
  273. package/scripts/extract_todos.py +445 -404
  274. package/scripts/fav_server.py +119 -12
  275. package/scripts/fix_topics.py +111 -111
  276. package/scripts/generate_ai_report.py +1 -1
  277. package/scripts/generate_html.py +597 -277
  278. package/scripts/inspection_records.py +354 -0
  279. package/scripts/mcp_bridge.py +398 -398
  280. package/scripts/nt_decrypt.py +702 -48
  281. package/scripts/pipeline.py +27 -19
  282. package/scripts/promote_all.py +312 -310
  283. package/scripts/promote_ideas.py +299 -297
  284. package/scripts/rag_chat.py +194 -191
  285. package/scripts/semantic_search.py +435 -431
  286. package/scripts/sync_fav.py +201 -146
  287. package/scripts/sync_weread.py +192 -192
  288. package/scripts/vault_rag.py +140 -139
  289. package/scripts/vault_search.py +141 -141
  290. package/scripts/wechat_emoji.py +132 -0
  291. package/scripts/wechat_emoticon.py +433 -0
  292. package/scripts/wechat_image.py +258 -0
  293. package/scripts/wechat_voice.py +327 -0
  294. package/src/core/dbPathService.ts +163 -18
  295. package/src/core/keyService.ts +137 -25
  296. package/src/core/ntCore.ts +215 -62
  297. package/src/core/sqlcipherCore.ts +17 -1
  298. package/src/core/wcdbCore.ts +3 -7
  299. package/src/services/assistantDaemon.ts +111 -0
  300. package/src/services/assistantMemory.ts +193 -0
  301. package/src/services/assistantPrivacy.ts +87 -0
  302. package/src/services/assistantRouting.ts +92 -0
  303. package/src/services/assistantService.ts +295 -0
  304. package/src/services/assistantTools.ts +549 -0
  305. package/src/services/chatService.ts +133 -4
  306. package/src/services/configService.ts +194 -6
  307. package/src/services/evidenceService.ts +129 -0
  308. package/src/services/exportService.ts +113 -48
  309. package/src/services/initKeyService.ts +83 -0
  310. package/src/services/messageContract.ts +77 -0
  311. package/src/services/messageQuery.ts +38 -0
  312. package/src/services/wechat-formatter.ts +348 -348
  313. package/src/services/wechatMessageService.ts +25 -3
  314. package/src/services/wereadService.ts +308 -308
  315. package/src/services/whitelistService.ts +209 -22
  316. package/src/types.ts +20 -0
  317. package/src/utils/dateRange.ts +58 -0
  318. package/src/utils/mcpSecurity.ts +39 -0
  319. package/src/utils/packageRoot.ts +14 -0
  320. package/src/utils/python.ts +73 -0
  321. package/src/utils/pythonProcessEnv.ts +45 -0
  322. package/src/utils/talkerUtils.ts +14 -2
  323. package/dist/src/utils/errors.d.ts +0 -25
  324. package/dist/src/utils/errors.d.ts.map +0 -1
  325. package/dist/src/utils/errors.js +0 -43
  326. package/dist/src/utils/errors.js.map +0 -1
  327. package/dist/src/utils/pythonRunner.d.ts +0 -27
  328. package/dist/src/utils/pythonRunner.d.ts.map +0 -1
  329. package/dist/src/utils/pythonRunner.js +0 -62
  330. package/dist/src/utils/pythonRunner.js.map +0 -1
  331. package/src/utils/errors.ts +0 -42
  332. package/src/utils/pythonRunner.ts +0 -81
@@ -8,22 +8,63 @@
8
8
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
9
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
10
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
11
- import { readFileSync, readdirSync, existsSync, statSync, writeFileSync, mkdirSync } from 'fs';
12
- import { join, resolve } from 'path';
13
- import { deflateSync } from 'zlib';
11
+ import { readFileSync, readdirSync, existsSync, statSync } from 'fs';
12
+ import { join } from 'path';
13
+ import { isAllowedWeChatArticleUrl, safeChildPath, safeDate } from '../src/utils/mcpSecurity.js';
14
+ import { resolvePackageRoot } from '../src/utils/packageRoot.js';
15
+ import { chatService } from '../src/services/chatService.js';
16
+ import { createWeFlowEnvelope } from '../src/services/messageContract.js';
14
17
  // ---- 微信公众号文章抓取 ----
18
+ async function readResponseTextLimited(response, maximumBytes) {
19
+ const contentLength = Number(response.headers.get('content-length') || 0);
20
+ if (contentLength > maximumBytes)
21
+ throw new Error('RESPONSE_TOO_LARGE');
22
+ if (!response.body)
23
+ return '';
24
+ const reader = response.body.getReader();
25
+ const chunks = [];
26
+ let size = 0;
27
+ while (true) {
28
+ const { done, value } = await reader.read();
29
+ if (done)
30
+ break;
31
+ size += value.byteLength;
32
+ if (size > maximumBytes) {
33
+ await reader.cancel();
34
+ throw new Error('RESPONSE_TOO_LARGE');
35
+ }
36
+ chunks.push(value);
37
+ }
38
+ return Buffer.concat(chunks).toString('utf8');
39
+ }
15
40
  async function fetchWeChatArticle(url) {
16
- const resp = await fetch(url, {
17
- headers: {
18
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
19
- 'Accept': 'text/html,application/xhtml+xml',
20
- 'Accept-Language': 'zh-CN,zh;q=0.9',
21
- },
22
- redirect: 'follow',
23
- });
41
+ let currentUrl = url;
42
+ let resp = null;
43
+ for (let redirectCount = 0; redirectCount <= 3; redirectCount++) {
44
+ if (!isAllowedWeChatArticleUrl(currentUrl))
45
+ throw new Error('URL_NOT_ALLOWED');
46
+ resp = await fetch(currentUrl, {
47
+ headers: {
48
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
49
+ 'Accept': 'text/html,application/xhtml+xml',
50
+ 'Accept-Language': 'zh-CN,zh;q=0.9',
51
+ },
52
+ redirect: 'manual',
53
+ signal: AbortSignal.timeout(20_000),
54
+ });
55
+ if (resp.status < 300 || resp.status >= 400)
56
+ break;
57
+ const location = resp.headers.get('location');
58
+ if (!location)
59
+ throw new Error('REDIRECT_WITHOUT_LOCATION');
60
+ currentUrl = new URL(location, currentUrl).toString();
61
+ resp = null;
62
+ }
63
+ if (!resp)
64
+ throw new Error('TOO_MANY_REDIRECTS');
24
65
  if (!resp.ok)
25
66
  throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
26
- const html = await resp.text();
67
+ const html = await readResponseTextLimited(resp, 5 * 1024 * 1024);
27
68
  // 提取 meta 信息
28
69
  const title = html.match(/<meta[^>]*property="og:title"[^>]*content="([^"]+)"/i)?.[1]
29
70
  || html.match(/<title>([^<]+)<\/title>/i)?.[1]?.trim()
@@ -94,75 +135,74 @@ function htmlToMarkdown(html) {
94
135
  return md;
95
136
  }
96
137
  import { formatWeChatArticle, listThemes } from '../src/services/wechat-formatter.js';
97
- // ---- 微信公众号 API 辅助 ----
98
- const WECHAT_APP_ID = process.env.WECHAT_APPID || '';
99
- const WECHAT_APP_SECRET = process.env.WECHAT_APPSECRET || '';
100
- let _tokenCache = null;
101
- async function getWeChatToken() {
102
- if (_tokenCache && Date.now() < _tokenCache.expiresAt - 60000) {
103
- return _tokenCache.token;
104
- }
105
- if (!WECHAT_APP_ID || !WECHAT_APP_SECRET) {
106
- throw new Error('请设置环境变量 WECHAT_APPID 和 WECHAT_APPSECRET');
107
- }
108
- const resp = await fetch(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${WECHAT_APP_ID}&secret=${WECHAT_APP_SECRET}`);
109
- const data = await resp.json();
110
- if (data.errcode) {
111
- throw new Error(`微信 Token 获取失败: ${data.errmsg} (${data.errcode})`);
138
+ import { boundedToolInteger, MCP_READ_ONLY_TOOL_DEFS, executeTool } from '../src/services/assistantTools.js';
139
+ import { AssistantMemory } from '../src/services/assistantMemory.js';
140
+ // ---- 本地微信数据工具 (复用 assistant 工具层, 微信 bot / MCP server 同源) ----
141
+ // MCP 无微信用户身份, 记忆固定挂 'mcp' 用户下; 与 assistant 守护进程共用同一持久化文件
142
+ const mcpMemory = new AssistantMemory();
143
+ const MCP_TOOL_CTX = { userId: 'mcp', memory: mcpMemory };
144
+ /** assistant 工具 (OpenAI function 格式) → MCP 工具格式; get_stats 并入现有 wechat.get_stats */
145
+ const ASSISTANT_TOOLS = MCP_READ_ONLY_TOOL_DEFS
146
+ .filter(t => t.function.name !== 'get_stats')
147
+ .map(t => ({
148
+ name: `wechat.${t.function.name}`,
149
+ description: t.function.description,
150
+ inputSchema: t.function.parameters,
151
+ }));
152
+ const PKG_ROOT = resolvePackageRoot(import.meta.url);
153
+ const BIZ_DAILY = join(PKG_ROOT, 'output', 'biz-daily');
154
+ const VAULT_WIKI = join(PKG_ROOT, 'output', 'wechat-vault', 'Wiki', 'Concepts');
155
+ const VAULT_INDEX = join(PKG_ROOT, 'output', 'wechat-vault', 'Wiki', '00-Overview.md');
156
+ const REVIEWS = join(PKG_ROOT, 'output', 'reviews', 'Daily');
157
+ const MCP_MESSAGE_LIMIT_DEFAULT = 100;
158
+ const MCP_MESSAGE_LIMIT_MAX = 1000;
159
+ function parseMessageDate(value, label) {
160
+ if (value === undefined || value === null || value === '')
161
+ return undefined;
162
+ const date = safeDate(value);
163
+ if (!date)
164
+ throw new Error(`${label} 必须是有效的 YYYY-MM-DD 日期`);
165
+ const [year, month, day] = date.split('-').map(Number);
166
+ const start = new Date(year, month - 1, day);
167
+ if (start.getFullYear() !== year || start.getMonth() !== month - 1 || start.getDate() !== day) {
168
+ throw new Error(`${label} 必须是有效的日历日期`);
112
169
  }
113
- _tokenCache = {
114
- token: data.access_token,
115
- expiresAt: Date.now() + (data.expires_in - 60) * 1000,
116
- };
117
- return _tokenCache.token;
170
+ return Math.floor(start.getTime() / 1000);
118
171
  }
119
- async function uploadWeChatImage(token, filePath) {
120
- const buf = readFileSync(filePath);
121
- const boundary = `----WeFlow${Date.now()}`;
122
- const filename = filePath.replace(/^.*[\\/]/, '');
123
- const ext = filename.split('.').pop()?.toLowerCase() || 'png';
124
- const mimeType = ext === 'jpg' || ext === 'jpeg' ? 'image/jpeg' : 'image/png';
125
- const header = [
126
- `--${boundary}`,
127
- `Content-Disposition: form-data; name="media"; filename="${filename}"`,
128
- `Content-Type: ${mimeType}`,
129
- '', '',
130
- ].join('\r\n');
131
- const trailer = `\r\n--${boundary}--\r\n`;
132
- const headerBytes = new TextEncoder().encode(header);
133
- const trailerBytes = new TextEncoder().encode(trailer);
134
- const body = new Uint8Array(headerBytes.length + buf.length + trailerBytes.length);
135
- body.set(headerBytes, 0);
136
- body.set(buf, headerBytes.length);
137
- body.set(trailerBytes, headerBytes.length + buf.length);
138
- const resp = await fetch(`https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=${token}&type=image`, {
139
- method: 'POST',
140
- headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}` },
141
- body,
142
- });
143
- const data = await resp.json();
144
- if (data.errcode) {
145
- throw new Error(`上传封面图失败: ${data.errmsg} (${data.errcode})`);
146
- }
147
- return data.media_id;
172
+ async function resolveMcpTalker(input) {
173
+ const contact = String(input || '').trim();
174
+ if (!contact)
175
+ throw new Error('contact 不能为空');
176
+ if (contact.startsWith('wxid_') || contact.includes('@chatroom') || contact.includes('@openim'))
177
+ return contact;
178
+ const sessions = await chatService.listSessions(contact, 20);
179
+ const exact = sessions.filter(session => session.username === contact || session.displayName === contact);
180
+ if (exact.length === 1)
181
+ return exact[0].username;
182
+ if (exact.length > 1 || sessions.length > 1)
183
+ throw new Error('contact 匹配到多个会话,请使用会话 ID');
184
+ if (sessions.length === 1)
185
+ return sessions[0].username;
186
+ throw new Error('未找到对应会话,请使用会话 ID 或准确的会话名称');
148
187
  }
149
- async function createWeChatDraft(token, articles) {
150
- const resp = await fetch(`https://api.weixin.qq.com/cgi-bin/draft/add?access_token=${token}`, {
151
- method: 'POST',
152
- headers: { 'Content-Type': 'application/json' },
153
- body: JSON.stringify({ articles }),
154
- });
155
- const data = await resp.json();
156
- if (data.errcode) {
157
- throw new Error(`创建草稿失败: ${data.errmsg} (${data.errcode})`);
188
+ async function exportMessagesForMcp(args) {
189
+ const limit = args.limit === undefined ? MCP_MESSAGE_LIMIT_DEFAULT : Number(args.limit);
190
+ if (!Number.isInteger(limit) || limit < 1 || limit > MCP_MESSAGE_LIMIT_MAX) {
191
+ throw new Error(`limit 必须是 1-${MCP_MESSAGE_LIMIT_MAX} 的整数`);
158
192
  }
159
- return data.media_id;
193
+ const from = parseMessageDate(args.from, 'from');
194
+ const toStart = parseMessageDate(args.to, 'to');
195
+ const to = toStart === undefined ? undefined : toStart + 24 * 60 * 60 - 1;
196
+ if (from !== undefined && to !== undefined && from > to)
197
+ throw new Error('from 不能晚于 to');
198
+ const talker = await resolveMcpTalker(args.contact);
199
+ const messages = await chatService.getMessagesInRange(talker, limit, from, to);
200
+ return JSON.stringify(createWeFlowEnvelope(messages, new Date().toISOString(), {
201
+ requestedFrom: from,
202
+ requestedTo: to,
203
+ requestedLimit: limit,
204
+ }), null, 2);
160
205
  }
161
- const PKG_ROOT = resolve(join(import.meta.dirname || '.', '..'));
162
- const BIZ_DAILY = join(PKG_ROOT, 'output', 'biz-daily');
163
- const VAULT_WIKI = join(PKG_ROOT, 'output', 'wechat-vault', 'Wiki', 'Concepts');
164
- const VAULT_INDEX = join(PKG_ROOT, 'output', 'wechat-vault', 'Wiki', '00-Overview.md');
165
- const REVIEWS = join(PKG_ROOT, 'output', 'reviews', 'Daily');
166
206
  function parseFrontmatter(text) {
167
207
  if (!text.startsWith('---'))
168
208
  return {};
@@ -229,70 +269,12 @@ function searchArticles(args) {
229
269
  if (args.date) {
230
270
  articles = articles.filter(a => a.dateDir === args.date);
231
271
  }
232
- const limit = Number(args.limit) || 20;
272
+ const limit = boundedToolInteger(args.limit, 20, 100);
233
273
  const top = articles.slice(0, limit);
234
274
  if (!top.length)
235
275
  return '未找到匹配文章';
236
276
  return top.map(a => `- [${a.date}] **${a.title || '(无标题)'}** — ${a.source || ''} [${a.topic || ''}] [${(a.tags || []).join(', ')}]\n ${(a.description || '').slice(0, 120)}`).join('\n\n');
237
277
  }
238
- /** 自动生成封面图(纯色主题渐变 PNG,无外部依赖) */
239
- function generateCoverImage(title, theme) {
240
- const colors = {
241
- warm: [230, 126, 34],
242
- default: [52, 152, 219],
243
- minimal: [51, 51, 51],
244
- green: [39, 174, 96],
245
- };
246
- const [r, g, b] = colors[theme] || colors.default;
247
- const w = 900, h = 500;
248
- function crc32(buf) {
249
- let c = 0xffffffff;
250
- for (let i = 0; i < buf.length; i++) {
251
- c ^= buf[i];
252
- for (let j = 0; j < 8; j++)
253
- c = (c >>> 1) ^ (c & 1 ? 0xedb88320 : 0);
254
- }
255
- return (c ^ 0xffffffff) >>> 0;
256
- }
257
- function chunk(type, data) {
258
- const head = Buffer.alloc(8);
259
- head.writeUInt32BE(data.length, 0);
260
- head.write(type, 4);
261
- const crcBuf = Buffer.alloc(4);
262
- crcBuf.writeUInt32BE(crc32(Buffer.concat([head.slice(4, 8), data])), 0);
263
- return Buffer.concat([head, data, crcBuf]);
264
- }
265
- const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
266
- const ihdr = Buffer.alloc(13);
267
- ihdr.writeUInt32BE(w, 0);
268
- ihdr.writeUInt32BE(h, 4);
269
- ihdr[8] = 8;
270
- ihdr[9] = 2;
271
- const raw = Buffer.alloc(h * (1 + w * 3));
272
- for (let y = 0; y < h; y++) {
273
- const offset = y * (1 + w * 3);
274
- raw[offset] = 0;
275
- for (let x = 0; x < w; x++) {
276
- const px = offset + 1 + x * 3;
277
- const darken = 0.9 + 0.1 * (y / h);
278
- raw[px] = Math.floor(r * darken);
279
- raw[px + 1] = Math.floor(g * darken);
280
- raw[px + 2] = Math.floor(b * darken);
281
- }
282
- }
283
- const png = Buffer.concat([
284
- signature,
285
- chunk('IHDR', ihdr),
286
- chunk('IDAT', deflateSync(raw)),
287
- chunk('IEND', Buffer.alloc(0)),
288
- ]);
289
- const tmpDir = join(PKG_ROOT, 'output', '.tmp');
290
- if (!existsSync(tmpDir))
291
- mkdirSync(tmpDir, { recursive: true });
292
- const path = join(tmpDir, `cover-${Date.now()}.png`);
293
- writeFileSync(path, png);
294
- return path;
295
- }
296
278
  async function main() {
297
279
  const server = new Server({ name: 'weflow-mcp', version: '1.0.0' }, { capabilities: { tools: {} } });
298
280
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -348,7 +330,7 @@ async function main() {
348
330
  },
349
331
  {
350
332
  name: 'wechat.get_stats',
351
- description: '知识库统计概览',
333
+ description: '统计概览: 知识库文章/概念页 + 本地微信数据(会话数/收藏总数)',
352
334
  inputSchema: { type: 'object', properties: {} },
353
335
  },
354
336
  {
@@ -363,22 +345,6 @@ async function main() {
363
345
  required: ['content'],
364
346
  },
365
347
  },
366
- {
367
- name: 'wechat.publish_article',
368
- description: '排版并发布 Markdown 文章到微信公众号草稿箱。需要设置环境变量 WECHAT_APPID 和 WECHAT_APPSECRET',
369
- inputSchema: {
370
- type: 'object',
371
- properties: {
372
- title: { type: 'string', description: '文章标题(≤64字符)' },
373
- content: { type: 'string', description: 'Markdown 格式的文章内容' },
374
- author: { type: 'string', description: '作者名(≤8字符),默认 "AI Assistant"' },
375
- theme: { type: 'string', description: '排版主题: default | warm | minimal | green,默认 default' },
376
- cover_image: { type: 'string', description: '封面图本地路径(可选)' },
377
- preview_only: { type: 'boolean', description: '仅排版预览不发布草稿,默认 false' },
378
- },
379
- required: ['title', 'content'],
380
- },
381
- },
382
348
  {
383
349
  name: 'wechat.list_themes',
384
350
  description: '列出微信公众号排版可用的所有主题',
@@ -407,6 +373,22 @@ async function main() {
407
373
  required: ['url'],
408
374
  },
409
375
  },
376
+ {
377
+ name: 'wechat.export_messages',
378
+ description: '以 weflow-message/v1 JSON 返回指定会话的本地消息。只读,不返回数据库路径、密钥或配置。',
379
+ inputSchema: {
380
+ type: 'object',
381
+ properties: {
382
+ contact: { type: 'string', description: '会话 ID,或唯一匹配的会话名称' },
383
+ limit: { type: 'number', description: '返回上限,默认 100,最大 1000' },
384
+ from: { type: 'string', description: '起始日期 YYYY-MM-DD(含当天)' },
385
+ to: { type: 'string', description: '结束日期 YYYY-MM-DD(含当天)' },
386
+ },
387
+ required: ['contact'],
388
+ },
389
+ },
390
+ // ---- 本地微信数据 (与 weflow-cli assistant / 微信 bot 共享工具层) ----
391
+ ...ASSISTANT_TOOLS,
410
392
  ],
411
393
  }));
412
394
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -417,9 +399,11 @@ async function main() {
417
399
  return { content: [{ type: 'text', text: searchArticles(args) }] };
418
400
  case 'wechat.get_daily': {
419
401
  const dates = existsSync(BIZ_DAILY) ? readdirSync(BIZ_DAILY).sort().reverse() : [];
420
- const date = String(args.date || dates[0] || '');
421
- const readme = join(BIZ_DAILY, date, 'README.md');
422
- if (!existsSync(readme))
402
+ if (args.date && !safeDate(args.date))
403
+ return { content: [{ type: 'text', text: '日期无效,请使用 YYYY-MM-DD' }] };
404
+ const date = safeDate(args.date) || dates.find(d => /^\d{4}-\d{2}-\d{2}$/.test(d)) || '';
405
+ const readme = safeChildPath(BIZ_DAILY, join(date, 'README.md'));
406
+ if (!readme || !existsSync(readme))
423
407
  return { content: [{ type: 'text', text: `未找到 ${date} 的日报` }] };
424
408
  return { content: [{ type: 'text', text: readFileSync(readme, 'utf-8') }] };
425
409
  }
@@ -430,9 +414,12 @@ async function main() {
430
414
  }
431
415
  case 'wechat.get_concept': {
432
416
  const name = String(args.name);
417
+ if (!name || name.includes('/') || name.includes('\\') || name.includes('..')) {
418
+ return { content: [{ type: 'text', text: '概念名无效' }] };
419
+ }
433
420
  for (const ext of ['', '.md']) {
434
- const path = join(VAULT_WIKI, name + ext);
435
- if (existsSync(path)) {
421
+ const path = safeChildPath(VAULT_WIKI, name + ext);
422
+ if (path && existsSync(path)) {
436
423
  return { content: [{ type: 'text', text: readFileSync(path, 'utf-8') }] };
437
424
  }
438
425
  }
@@ -440,7 +427,9 @@ async function main() {
440
427
  if (existsSync(VAULT_WIKI)) {
441
428
  for (const f of readdirSync(VAULT_WIKI)) {
442
429
  if (f.includes(name)) {
443
- return { content: [{ type: 'text', text: readFileSync(join(VAULT_WIKI, f), 'utf-8') }] };
430
+ const path = safeChildPath(VAULT_WIKI, f);
431
+ if (path)
432
+ return { content: [{ type: 'text', text: readFileSync(path, 'utf-8') }] };
444
433
  }
445
434
  }
446
435
  }
@@ -451,7 +440,7 @@ async function main() {
451
440
  const date = String(args.date || '');
452
441
  let path;
453
442
  if (date) {
454
- path = join(REVIEWS, `Daily-${date}.md`);
443
+ path = safeDate(date) ? join(REVIEWS, `Daily-${date}.md`) : '';
455
444
  }
456
445
  else {
457
446
  path = files.length ? join(REVIEWS, files[0]) : '';
@@ -489,72 +478,6 @@ async function main() {
489
478
  }]
490
479
  };
491
480
  }
492
- case 'wechat.publish_article': {
493
- const title = String(args.title || '').slice(0, 64);
494
- const content = String(args.content || '');
495
- const author = String(args.author || 'AI Assistant').slice(0, 8);
496
- const theme = String(args.theme || 'default');
497
- const coverImage = args.cover_image ? String(args.cover_image) : '';
498
- const previewOnly = args.preview_only === true || args.preview_only === 'true';
499
- if (!title || !content) {
500
- return { content: [{ type: 'text', text: '错误: title 和 content 不能为空' }] };
501
- }
502
- // 1. 排版
503
- const html = formatWeChatArticle(content, { theme: theme });
504
- const wordCount = html.replace(/<[^>]*>/g, '').replace(/\s+/g, '').length;
505
- if (previewOnly) {
506
- return {
507
- content: [{
508
- type: 'text',
509
- text: [
510
- `[仅预览] 标题: ${title}`,
511
- `作者: ${author} | 主题: ${theme} | 字数: ${wordCount}`,
512
- `---`,
513
- html,
514
- ].join('\n')
515
- }]
516
- };
517
- }
518
- // 2. 发布到草稿箱
519
- try {
520
- const token = await getWeChatToken();
521
- // 上传封面图(如果提供,否则自动生成)
522
- let thumbMediaId = '';
523
- if (coverImage && existsSync(coverImage)) {
524
- thumbMediaId = await uploadWeChatImage(token, coverImage);
525
- }
526
- else {
527
- // 自动生成封面图
528
- const autoCover = generateCoverImage(title, theme);
529
- thumbMediaId = await uploadWeChatImage(token, autoCover);
530
- }
531
- // 创建草稿
532
- const mediaId = await createWeChatDraft(token, [{
533
- title,
534
- author,
535
- digest: html.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').slice(0, 120),
536
- content: html,
537
- thumb_media_id: thumbMediaId || undefined,
538
- }]);
539
- return {
540
- content: [{
541
- type: 'text',
542
- text: [
543
- `✅ 已发布到公众号草稿箱`,
544
- `media_id: ${mediaId}`,
545
- `标题: ${title}`,
546
- `作者: ${author} | 主题: ${theme} | 字数: ${wordCount}`,
547
- `封面图: ${thumbMediaId ? '已上传' : '未提供(使用默认)'}`,
548
- '',
549
- `请登录 mp.weixin.qq.com 草稿箱查看和正式发布。`,
550
- ].join('\n')
551
- }]
552
- };
553
- }
554
- catch (e) {
555
- return { content: [{ type: 'text', text: `发布失败: ${e.message}\n\n——以下为排版预览——\n\n${html}` }] };
556
- }
557
- }
558
481
  case 'wechat.get_stats': {
559
482
  const articles = scanArticles();
560
483
  const dates = [...new Set(articles.map(a => a.dateDir))].sort();
@@ -570,13 +493,22 @@ async function main() {
570
493
  if (m)
571
494
  stats += `- 概念页: ${m[1]} 个\n`;
572
495
  }
496
+ // 追加本地微信数据统计 (会话/收藏; 数据库未配置时返回提示)
497
+ const chatStats = await executeTool('get_stats', {}, MCP_TOOL_CTX);
498
+ stats += `\n## 本地微信数据\n\n${chatStats}\n`;
573
499
  return { content: [{ type: 'text', text: stats }] };
574
500
  }
575
501
  case 'wechat.search_public': {
576
502
  const keyword = String(args.keyword || '').trim();
577
503
  if (!keyword)
578
504
  return { content: [{ type: 'text', text: '错误: keyword 不能为空' }] };
579
- const limit = Number(args.limit) || 10;
505
+ let limit = 10;
506
+ try {
507
+ limit = boundedToolInteger(args.limit, 10, 20);
508
+ }
509
+ catch {
510
+ return { content: [{ type: 'text', text: '错误: limit 必须是 1-20 的整数' }] };
511
+ }
580
512
  try {
581
513
  // 使用搜狗微信搜索
582
514
  const resp = await fetch(`https://weixin.sogou.com/weixin?type=2&query=${encodeURIComponent(keyword)}`, {
@@ -586,8 +518,9 @@ async function main() {
586
518
  'Accept': 'text/html',
587
519
  'Accept-Language': 'zh-CN,zh;q=0.9',
588
520
  },
521
+ signal: AbortSignal.timeout(20_000),
589
522
  });
590
- const html = await resp.text();
523
+ const html = await readResponseTextLimited(resp, 2 * 1024 * 1024);
591
524
  // 从搜狗结果中提取标题、公众号名、摘要
592
525
  // 搜狗将结果放在 li 标签中,每篇文章包含标题链接 + 摘要
593
526
  const items = [];
@@ -645,16 +578,16 @@ async function main() {
645
578
  }]
646
579
  };
647
580
  }
648
- catch (e) {
649
- return { content: [{ type: 'text', text: `搜索失败: ${e.message}` }] };
581
+ catch {
582
+ return { content: [{ type: 'text', text: '搜索失败,请检查网络连接后重试' }] };
650
583
  }
651
584
  }
652
585
  case 'wechat.fetch_article': {
653
586
  const url = String(args.url || '').trim();
654
587
  if (!url)
655
588
  return { content: [{ type: 'text', text: '错误: url 不能为空' }] };
656
- if (!url.includes('mp.weixin.qq.com')) {
657
- return { content: [{ type: 'text', text: '错误: 仅支持微信公众号文章链接 (mp.weixin.qq.com)' }] };
589
+ if (!isAllowedWeChatArticleUrl(url)) {
590
+ return { content: [{ type: 'text', text: '错误: 仅支持 HTTPS 微信公众号文章链接 (mp.weixin.qq.com)' }] };
658
591
  }
659
592
  try {
660
593
  const article = await fetchWeChatArticle(url);
@@ -671,16 +604,25 @@ async function main() {
671
604
  }]
672
605
  };
673
606
  }
674
- catch (e) {
675
- return { content: [{ type: 'text', text: `抓取失败: ${e.message}\n\n提示: 部分文章需要微信客户端环境才能访问,可尝试在微信中打开后复制链接。` }] };
607
+ catch {
608
+ return { content: [{ type: 'text', text: '抓取失败,请检查链接和网络连接。部分文章需要微信客户端环境才能访问。' }] };
676
609
  }
677
610
  }
678
- default:
611
+ case 'wechat.export_messages': {
612
+ return { content: [{ type: 'text', text: await exportMessagesForMcp(args) }] };
613
+ }
614
+ default: {
615
+ // assistant 工具层统一分发 (wechat.list_sessions / wechat.get_messages / ...)
616
+ if (name.startsWith('wechat.')) {
617
+ const text = await executeTool(name.slice('wechat.'.length), args, MCP_TOOL_CTX);
618
+ return { content: [{ type: 'text', text }] };
619
+ }
679
620
  return { content: [{ type: 'text', text: `未知工具: ${name}` }] };
621
+ }
680
622
  }
681
623
  }
682
- catch (e) {
683
- return { content: [{ type: 'text', text: `错误: ${e.message}` }] };
624
+ catch {
625
+ return { content: [{ type: 'text', text: '工具执行失败,请检查输入、本地配置或运行状态' }] };
684
626
  }
685
627
  });
686
628
  const transport = new StdioServerTransport();