@andre.li/memoark 0.3.1 → 0.3.2

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 (497) hide show
  1. package/README.en.md +388 -69
  2. package/README.md +674 -309
  3. package/README.zh-CN.md +816 -0
  4. package/bin/memoark.mjs +7 -2
  5. package/dist/adapters/store.d.ts.map +1 -1
  6. package/dist/adapters/store.js +6 -5
  7. package/dist/adapters/store.js.map +1 -1
  8. package/dist/cli-helpers.d.ts +12 -0
  9. package/dist/cli-helpers.d.ts.map +1 -0
  10. package/dist/cli-helpers.js +11 -0
  11. package/dist/cli-helpers.js.map +1 -0
  12. package/dist/cli.js +682 -126
  13. package/dist/cli.js.map +1 -1
  14. package/dist/collectors/feishu/chat-name-resolver.d.ts +33 -0
  15. package/dist/collectors/feishu/chat-name-resolver.d.ts.map +1 -0
  16. package/dist/collectors/feishu/chat-name-resolver.js +63 -0
  17. package/dist/collectors/feishu/chat-name-resolver.js.map +1 -0
  18. package/dist/collectors/feishu/collector.d.ts +4 -0
  19. package/dist/collectors/feishu/collector.d.ts.map +1 -1
  20. package/dist/collectors/feishu/collector.js +27 -4
  21. package/dist/collectors/feishu/collector.js.map +1 -1
  22. package/dist/collectors/feishu/docs/blocks.d.ts +25 -0
  23. package/dist/collectors/feishu/docs/blocks.d.ts.map +1 -0
  24. package/dist/collectors/feishu/docs/blocks.js +34 -0
  25. package/dist/collectors/feishu/docs/blocks.js.map +1 -0
  26. package/dist/collectors/feishu/docs/candidate.d.ts +17 -0
  27. package/dist/collectors/feishu/docs/candidate.d.ts.map +1 -0
  28. package/dist/collectors/feishu/docs/candidate.js +36 -0
  29. package/dist/collectors/feishu/docs/candidate.js.map +1 -0
  30. package/dist/collectors/feishu/docs/config.d.ts +41 -0
  31. package/dist/collectors/feishu/docs/config.d.ts.map +1 -0
  32. package/dist/collectors/feishu/docs/config.js +30 -0
  33. package/dist/collectors/feishu/docs/config.js.map +1 -0
  34. package/dist/collectors/feishu/docs/decision.d.ts +14 -0
  35. package/dist/collectors/feishu/docs/decision.d.ts.map +1 -0
  36. package/dist/collectors/feishu/docs/decision.js +43 -0
  37. package/dist/collectors/feishu/docs/decision.js.map +1 -0
  38. package/dist/collectors/feishu/docs/full-builder.d.ts +17 -0
  39. package/dist/collectors/feishu/docs/full-builder.d.ts.map +1 -0
  40. package/dist/collectors/feishu/docs/full-builder.js +136 -0
  41. package/dist/collectors/feishu/docs/full-builder.js.map +1 -0
  42. package/dist/collectors/feishu/docs/hash.d.ts +8 -0
  43. package/dist/collectors/feishu/docs/hash.d.ts.map +1 -0
  44. package/dist/collectors/feishu/docs/hash.js +15 -0
  45. package/dist/collectors/feishu/docs/hash.js.map +1 -0
  46. package/dist/collectors/feishu/docs/ingest.d.ts +54 -0
  47. package/dist/collectors/feishu/docs/ingest.d.ts.map +1 -0
  48. package/dist/collectors/feishu/docs/ingest.js +120 -0
  49. package/dist/collectors/feishu/docs/ingest.js.map +1 -0
  50. package/dist/collectors/feishu/docs/llm-json.d.ts +13 -0
  51. package/dist/collectors/feishu/docs/llm-json.d.ts.map +1 -0
  52. package/dist/collectors/feishu/docs/llm-json.js +40 -0
  53. package/dist/collectors/feishu/docs/llm-json.js.map +1 -0
  54. package/dist/collectors/feishu/docs/pointer-builder.d.ts +7 -0
  55. package/dist/collectors/feishu/docs/pointer-builder.d.ts.map +1 -0
  56. package/dist/collectors/feishu/docs/pointer-builder.js +11 -0
  57. package/dist/collectors/feishu/docs/pointer-builder.js.map +1 -0
  58. package/dist/collectors/feishu/docs/render.d.ts +8 -0
  59. package/dist/collectors/feishu/docs/render.d.ts.map +1 -0
  60. package/dist/collectors/feishu/docs/render.js +105 -0
  61. package/dist/collectors/feishu/docs/render.js.map +1 -0
  62. package/dist/collectors/feishu/docs/run.d.ts +37 -0
  63. package/dist/collectors/feishu/docs/run.d.ts.map +1 -0
  64. package/dist/collectors/feishu/docs/run.js +143 -0
  65. package/dist/collectors/feishu/docs/run.js.map +1 -0
  66. package/dist/collectors/feishu/docs/status.d.ts +16 -0
  67. package/dist/collectors/feishu/docs/status.d.ts.map +1 -0
  68. package/dist/collectors/feishu/docs/status.js +24 -0
  69. package/dist/collectors/feishu/docs/status.js.map +1 -0
  70. package/dist/collectors/feishu/docs/store-writer.d.ts +32 -0
  71. package/dist/collectors/feishu/docs/store-writer.d.ts.map +1 -0
  72. package/dist/collectors/feishu/docs/store-writer.js +71 -0
  73. package/dist/collectors/feishu/docs/store-writer.js.map +1 -0
  74. package/dist/collectors/feishu/docs/toc.d.ts +3 -0
  75. package/dist/collectors/feishu/docs/toc.d.ts.map +1 -0
  76. package/dist/collectors/feishu/docs/toc.js +19 -0
  77. package/dist/collectors/feishu/docs/toc.js.map +1 -0
  78. package/dist/collectors/feishu/docs/triggers.d.ts +7 -0
  79. package/dist/collectors/feishu/docs/triggers.d.ts.map +1 -0
  80. package/dist/collectors/feishu/docs/triggers.js +31 -0
  81. package/dist/collectors/feishu/docs/triggers.js.map +1 -0
  82. package/dist/collectors/feishu/docs/types.d.ts +109 -0
  83. package/dist/collectors/feishu/docs/types.d.ts.map +1 -0
  84. package/dist/collectors/feishu/docs/types.js +2 -0
  85. package/dist/collectors/feishu/docs/types.js.map +1 -0
  86. package/dist/collectors/feishu/docs/upgrade-queue.d.ts +19 -0
  87. package/dist/collectors/feishu/docs/upgrade-queue.d.ts.map +1 -0
  88. package/dist/collectors/feishu/docs/upgrade-queue.js +36 -0
  89. package/dist/collectors/feishu/docs/upgrade-queue.js.map +1 -0
  90. package/dist/collectors/feishu/docs/url-parser.d.ts +3 -0
  91. package/dist/collectors/feishu/docs/url-parser.d.ts.map +1 -0
  92. package/dist/collectors/feishu/docs/url-parser.js +39 -0
  93. package/dist/collectors/feishu/docs/url-parser.js.map +1 -0
  94. package/dist/collectors/feishu/docs/walkers.d.ts +24 -0
  95. package/dist/collectors/feishu/docs/walkers.d.ts.map +1 -0
  96. package/dist/collectors/feishu/docs/walkers.js +90 -0
  97. package/dist/collectors/feishu/docs/walkers.js.map +1 -0
  98. package/dist/collectors/feishu/docs/wiki-resolver.d.ts +15 -0
  99. package/dist/collectors/feishu/docs/wiki-resolver.d.ts.map +1 -0
  100. package/dist/collectors/feishu/docs/wiki-resolver.js +24 -0
  101. package/dist/collectors/feishu/docs/wiki-resolver.js.map +1 -0
  102. package/dist/collectors/feishu/lark-cli-client.d.ts +12 -0
  103. package/dist/collectors/feishu/lark-cli-client.d.ts.map +1 -1
  104. package/dist/collectors/feishu/lark-cli-client.js +37 -2
  105. package/dist/collectors/feishu/lark-cli-client.js.map +1 -1
  106. package/dist/collectors/feishu/lark-cli-identity-backend.d.ts +28 -0
  107. package/dist/collectors/feishu/lark-cli-identity-backend.d.ts.map +1 -0
  108. package/dist/collectors/feishu/lark-cli-identity-backend.js +110 -0
  109. package/dist/collectors/feishu/lark-cli-identity-backend.js.map +1 -0
  110. package/dist/collectors/feishu/self-open-id.d.ts +20 -0
  111. package/dist/collectors/feishu/self-open-id.d.ts.map +1 -0
  112. package/dist/collectors/feishu/self-open-id.js +31 -0
  113. package/dist/collectors/feishu/self-open-id.js.map +1 -0
  114. package/dist/collectors/feishu/sources/dm.d.ts.map +1 -1
  115. package/dist/collectors/feishu/sources/dm.js +4 -0
  116. package/dist/collectors/feishu/sources/dm.js.map +1 -1
  117. package/dist/collectors/feishu/sources/mail.d.ts.map +1 -1
  118. package/dist/collectors/feishu/sources/mail.js +18 -20
  119. package/dist/collectors/feishu/sources/mail.js.map +1 -1
  120. package/dist/collectors/feishu/sources/messages.d.ts +2 -0
  121. package/dist/collectors/feishu/sources/messages.d.ts.map +1 -1
  122. package/dist/collectors/feishu/sources/messages.js +28 -1
  123. package/dist/collectors/feishu/sources/messages.js.map +1 -1
  124. package/dist/collectors/feishu/types.d.ts +37 -3
  125. package/dist/collectors/feishu/types.d.ts.map +1 -1
  126. package/dist/collectors/feishu/types.js.map +1 -1
  127. package/dist/config-center/connection-checks.d.ts.map +1 -1
  128. package/dist/config-center/connection-checks.js +1 -1
  129. package/dist/config-center/connection-checks.js.map +1 -1
  130. package/dist/config-center/schema.d.ts.map +1 -1
  131. package/dist/config-center/schema.js +77 -0
  132. package/dist/config-center/schema.js.map +1 -1
  133. package/dist/consolidator/consolidator.d.ts +13 -1
  134. package/dist/consolidator/consolidator.d.ts.map +1 -1
  135. package/dist/consolidator/consolidator.js +12 -2
  136. package/dist/consolidator/consolidator.js.map +1 -1
  137. package/dist/core/canonicalize.js +5 -1
  138. package/dist/core/canonicalize.js.map +1 -1
  139. package/dist/core/config.d.ts +52 -9
  140. package/dist/core/config.d.ts.map +1 -1
  141. package/dist/core/config.js +86 -24
  142. package/dist/core/config.js.map +1 -1
  143. package/dist/core/env-validation.d.ts +9 -0
  144. package/dist/core/env-validation.d.ts.map +1 -0
  145. package/dist/core/env-validation.js +94 -0
  146. package/dist/core/env-validation.js.map +1 -0
  147. package/dist/core/identity-resolver.d.ts +24 -3
  148. package/dist/core/identity-resolver.d.ts.map +1 -1
  149. package/dist/core/identity-resolver.js +147 -2
  150. package/dist/core/identity-resolver.js.map +1 -1
  151. package/dist/core/person-identity.d.ts +124 -0
  152. package/dist/core/person-identity.d.ts.map +1 -0
  153. package/dist/core/person-identity.js +342 -0
  154. package/dist/core/person-identity.js.map +1 -0
  155. package/dist/core/person-slug.d.ts +21 -0
  156. package/dist/core/person-slug.d.ts.map +1 -0
  157. package/dist/core/person-slug.js +93 -0
  158. package/dist/core/person-slug.js.map +1 -0
  159. package/dist/core/pipeline-factory.d.ts +1 -1
  160. package/dist/core/pipeline-factory.d.ts.map +1 -1
  161. package/dist/core/pipeline-factory.js +8 -3
  162. package/dist/core/pipeline-factory.js.map +1 -1
  163. package/dist/core/pipeline.d.ts +16 -0
  164. package/dist/core/pipeline.d.ts.map +1 -1
  165. package/dist/core/pipeline.js +49 -1
  166. package/dist/core/pipeline.js.map +1 -1
  167. package/dist/core/resource-loader.d.ts +2 -0
  168. package/dist/core/resource-loader.d.ts.map +1 -0
  169. package/dist/core/resource-loader.js +12 -0
  170. package/dist/core/resource-loader.js.map +1 -0
  171. package/dist/core/schemas.d.ts +2981 -309
  172. package/dist/core/schemas.d.ts.map +1 -1
  173. package/dist/core/schemas.js +27 -0
  174. package/dist/core/schemas.js.map +1 -1
  175. package/dist/core/signal-scoring.d.ts.map +1 -1
  176. package/dist/core/signal-scoring.js +5 -1
  177. package/dist/core/signal-scoring.js.map +1 -1
  178. package/dist/core/source-ref.d.ts +4 -0
  179. package/dist/core/source-ref.d.ts.map +1 -0
  180. package/dist/core/source-ref.js +24 -0
  181. package/dist/core/source-ref.js.map +1 -0
  182. package/dist/core/state.d.ts +1 -1
  183. package/dist/core/state.d.ts.map +1 -1
  184. package/dist/core/state.js +3 -2
  185. package/dist/core/state.js.map +1 -1
  186. package/dist/core/types.d.ts +34 -6
  187. package/dist/core/types.d.ts.map +1 -1
  188. package/dist/daemon/reload-manager.d.ts +27 -0
  189. package/dist/daemon/reload-manager.d.ts.map +1 -0
  190. package/dist/daemon/reload-manager.js +67 -0
  191. package/dist/daemon/reload-manager.js.map +1 -0
  192. package/dist/daemon/scheduler.d.ts +9 -1
  193. package/dist/daemon/scheduler.d.ts.map +1 -1
  194. package/dist/daemon/scheduler.js +113 -38
  195. package/dist/daemon/scheduler.js.map +1 -1
  196. package/dist/daemon/serve-runtime.d.ts +31 -0
  197. package/dist/daemon/serve-runtime.d.ts.map +1 -0
  198. package/dist/daemon/serve-runtime.js +230 -0
  199. package/dist/daemon/serve-runtime.js.map +1 -0
  200. package/dist/embedded-assets.generated.d.ts.map +1 -1
  201. package/dist/embedded-assets.generated.js +3 -3
  202. package/dist/embedded-assets.generated.js.map +1 -1
  203. package/dist/extractors/playbook-extractor.d.ts +35 -0
  204. package/dist/extractors/playbook-extractor.d.ts.map +1 -0
  205. package/dist/extractors/playbook-extractor.js +98 -0
  206. package/dist/extractors/playbook-extractor.js.map +1 -0
  207. package/dist/extractors/prompts/examples/agent-session.md +257 -0
  208. package/dist/extractors/prompts/signal-extract.md +199 -0
  209. package/dist/extractors/prompts/system.md +45 -0
  210. package/dist/extractors/signal-extractor.d.ts +2 -1
  211. package/dist/extractors/signal-extractor.d.ts.map +1 -1
  212. package/dist/extractors/signal-extractor.js +99 -7
  213. package/dist/extractors/signal-extractor.js.map +1 -1
  214. package/dist/hooks/handlers.d.ts +21 -0
  215. package/dist/hooks/handlers.d.ts.map +1 -0
  216. package/dist/hooks/handlers.js +22 -0
  217. package/dist/hooks/handlers.js.map +1 -0
  218. package/dist/hooks/inject.d.ts +7 -0
  219. package/dist/hooks/inject.d.ts.map +1 -0
  220. package/dist/hooks/inject.js +18 -0
  221. package/dist/hooks/inject.js.map +1 -0
  222. package/dist/hooks/install.d.ts +14 -0
  223. package/dist/hooks/install.d.ts.map +1 -0
  224. package/dist/hooks/install.js +34 -0
  225. package/dist/hooks/install.js.map +1 -0
  226. package/dist/hooks/output.d.ts +19 -0
  227. package/dist/hooks/output.d.ts.map +1 -0
  228. package/dist/hooks/output.js +13 -0
  229. package/dist/hooks/output.js.map +1 -0
  230. package/dist/hooks/recall-client.d.ts +25 -0
  231. package/dist/hooks/recall-client.d.ts.map +1 -0
  232. package/dist/hooks/recall-client.js +56 -0
  233. package/dist/hooks/recall-client.js.map +1 -0
  234. package/dist/hooks/run-event.d.ts +13 -0
  235. package/dist/hooks/run-event.d.ts.map +1 -0
  236. package/dist/hooks/run-event.js +20 -0
  237. package/dist/hooks/run-event.js.map +1 -0
  238. package/dist/hooks/settings-edit.d.ts +10 -0
  239. package/dist/hooks/settings-edit.d.ts.map +1 -0
  240. package/dist/hooks/settings-edit.js +44 -0
  241. package/dist/hooks/settings-edit.js.map +1 -0
  242. package/dist/hooks/writeback.d.ts +12 -0
  243. package/dist/hooks/writeback.d.ts.map +1 -0
  244. package/dist/hooks/writeback.js +48 -0
  245. package/dist/hooks/writeback.js.map +1 -0
  246. package/dist/install/clients/claude-code.d.ts +3 -0
  247. package/dist/install/clients/claude-code.d.ts.map +1 -0
  248. package/dist/install/clients/claude-code.js +30 -0
  249. package/dist/install/clients/claude-code.js.map +1 -0
  250. package/dist/install/clients/claude-desktop.d.ts +3 -0
  251. package/dist/install/clients/claude-desktop.d.ts.map +1 -0
  252. package/dist/install/clients/claude-desktop.js +31 -0
  253. package/dist/install/clients/claude-desktop.js.map +1 -0
  254. package/dist/install/clients/codex.d.ts +3 -0
  255. package/dist/install/clients/codex.d.ts.map +1 -0
  256. package/dist/install/clients/codex.js +32 -0
  257. package/dist/install/clients/codex.js.map +1 -0
  258. package/dist/install/clients/cursor.d.ts +3 -0
  259. package/dist/install/clients/cursor.d.ts.map +1 -0
  260. package/dist/install/clients/cursor.js +35 -0
  261. package/dist/install/clients/cursor.js.map +1 -0
  262. package/dist/install/clients/hermes.d.ts +3 -0
  263. package/dist/install/clients/hermes.d.ts.map +1 -0
  264. package/dist/install/clients/hermes.js +35 -0
  265. package/dist/install/clients/hermes.js.map +1 -0
  266. package/dist/install/clients/index.d.ts +4 -0
  267. package/dist/install/clients/index.d.ts.map +1 -0
  268. package/dist/install/clients/index.js +18 -0
  269. package/dist/install/clients/index.js.map +1 -0
  270. package/dist/install/clients/windsurf.d.ts +3 -0
  271. package/dist/install/clients/windsurf.d.ts.map +1 -0
  272. package/dist/install/clients/windsurf.js +32 -0
  273. package/dist/install/clients/windsurf.js.map +1 -0
  274. package/dist/install/command.d.ts +14 -0
  275. package/dist/install/command.d.ts.map +1 -0
  276. package/dist/install/command.js +34 -0
  277. package/dist/install/command.js.map +1 -0
  278. package/dist/install/directive.d.ts +7 -0
  279. package/dist/install/directive.d.ts.map +1 -0
  280. package/dist/install/directive.js +31 -0
  281. package/dist/install/directive.js.map +1 -0
  282. package/dist/install/index.d.ts +26 -0
  283. package/dist/install/index.d.ts.map +1 -0
  284. package/dist/install/index.js +117 -0
  285. package/dist/install/index.js.map +1 -0
  286. package/dist/install/json-config.d.ts +11 -0
  287. package/dist/install/json-config.d.ts.map +1 -0
  288. package/dist/install/json-config.js +39 -0
  289. package/dist/install/json-config.js.map +1 -0
  290. package/dist/install/marked-block.d.ts +11 -0
  291. package/dist/install/marked-block.d.ts.map +1 -0
  292. package/dist/install/marked-block.js +34 -0
  293. package/dist/install/marked-block.js.map +1 -0
  294. package/dist/install/skill.d.ts +4 -0
  295. package/dist/install/skill.d.ts.map +1 -0
  296. package/dist/install/skill.js +61 -0
  297. package/dist/install/skill.js.map +1 -0
  298. package/dist/install/toml-config.d.ts +4 -0
  299. package/dist/install/toml-config.d.ts.map +1 -0
  300. package/dist/install/toml-config.js +51 -0
  301. package/dist/install/toml-config.js.map +1 -0
  302. package/dist/install/types.d.ts +37 -0
  303. package/dist/install/types.d.ts.map +1 -0
  304. package/dist/install/types.js +4 -0
  305. package/dist/install/types.js.map +1 -0
  306. package/dist/install/yaml-config.d.ts +4 -0
  307. package/dist/install/yaml-config.d.ts.map +1 -0
  308. package/dist/install/yaml-config.js +18 -0
  309. package/dist/install/yaml-config.js.map +1 -0
  310. package/dist/processors/privacy.d.ts +4 -1
  311. package/dist/processors/privacy.d.ts.map +1 -1
  312. package/dist/processors/privacy.js +5 -3
  313. package/dist/processors/privacy.js.map +1 -1
  314. package/dist/profile/accumulate.d.ts +28 -0
  315. package/dist/profile/accumulate.d.ts.map +1 -0
  316. package/dist/profile/accumulate.js +48 -0
  317. package/dist/profile/accumulate.js.map +1 -0
  318. package/dist/profile/behavior.d.ts +32 -0
  319. package/dist/profile/behavior.d.ts.map +1 -0
  320. package/dist/profile/behavior.js +122 -0
  321. package/dist/profile/behavior.js.map +1 -0
  322. package/dist/profile/four-color.d.ts +12 -0
  323. package/dist/profile/four-color.d.ts.map +1 -0
  324. package/dist/profile/four-color.js +39 -0
  325. package/dist/profile/four-color.js.map +1 -0
  326. package/dist/profile/profile-synth.d.ts +27 -0
  327. package/dist/profile/profile-synth.d.ts.map +1 -0
  328. package/dist/profile/profile-synth.js +174 -0
  329. package/dist/profile/profile-synth.js.map +1 -0
  330. package/dist/profile/types.d.ts +84 -0
  331. package/dist/profile/types.d.ts.map +1 -0
  332. package/dist/profile/types.js +11 -0
  333. package/dist/profile/types.js.map +1 -0
  334. package/dist/server/api.d.ts +8 -2
  335. package/dist/server/api.d.ts.map +1 -1
  336. package/dist/server/api.js +69 -34
  337. package/dist/server/api.js.map +1 -1
  338. package/dist/server/backfill-routes.d.ts.map +1 -1
  339. package/dist/server/backfill-routes.js +14 -1
  340. package/dist/server/backfill-routes.js.map +1 -1
  341. package/dist/server/chat-name-refresh-job.d.ts +33 -0
  342. package/dist/server/chat-name-refresh-job.d.ts.map +1 -0
  343. package/dist/server/chat-name-refresh-job.js +120 -0
  344. package/dist/server/chat-name-refresh-job.js.map +1 -0
  345. package/dist/server/chat-name-routes.d.ts +13 -0
  346. package/dist/server/chat-name-routes.d.ts.map +1 -0
  347. package/dist/server/chat-name-routes.js +81 -0
  348. package/dist/server/chat-name-routes.js.map +1 -0
  349. package/dist/server/config-routes.d.ts +2 -0
  350. package/dist/server/config-routes.d.ts.map +1 -1
  351. package/dist/server/config-routes.js +2 -1
  352. package/dist/server/config-routes.js.map +1 -1
  353. package/dist/server/mcp-http.d.ts +26 -0
  354. package/dist/server/mcp-http.d.ts.map +1 -0
  355. package/dist/server/mcp-http.js +102 -0
  356. package/dist/server/mcp-http.js.map +1 -0
  357. package/dist/server/mcp.d.ts +191 -25
  358. package/dist/server/mcp.d.ts.map +1 -1
  359. package/dist/server/mcp.js +1111 -68
  360. package/dist/server/mcp.js.map +1 -1
  361. package/dist/server/open-browser.d.ts +3 -0
  362. package/dist/server/open-browser.d.ts.map +1 -0
  363. package/dist/server/open-browser.js +12 -0
  364. package/dist/server/open-browser.js.map +1 -0
  365. package/dist/server/runtime.d.ts +13 -0
  366. package/dist/server/runtime.d.ts.map +1 -0
  367. package/dist/server/runtime.js +27 -0
  368. package/dist/server/runtime.js.map +1 -0
  369. package/dist/server/setup-server.d.ts.map +1 -1
  370. package/dist/server/setup-server.js +8 -11
  371. package/dist/server/setup-server.js.map +1 -1
  372. package/dist/setup/connection-tests.d.ts +1 -1
  373. package/dist/setup/connection-tests.d.ts.map +1 -1
  374. package/dist/setup/connection-tests.js +4 -2
  375. package/dist/setup/connection-tests.js.map +1 -1
  376. package/dist/setup/generate-config.d.ts.map +1 -1
  377. package/dist/setup/generate-config.js +31 -1
  378. package/dist/setup/generate-config.js.map +1 -1
  379. package/dist/setup/init-wizard.d.ts +1 -0
  380. package/dist/setup/init-wizard.d.ts.map +1 -1
  381. package/dist/setup/init-wizard.js +38 -21
  382. package/dist/setup/init-wizard.js.map +1 -1
  383. package/dist/setup/validate-config.d.ts +5 -1
  384. package/dist/setup/validate-config.d.ts.map +1 -1
  385. package/dist/setup/validate-config.js +18 -0
  386. package/dist/setup/validate-config.js.map +1 -1
  387. package/dist/store/data-dir-lock.d.ts +15 -0
  388. package/dist/store/data-dir-lock.d.ts.map +1 -0
  389. package/dist/store/data-dir-lock.js +96 -0
  390. package/dist/store/data-dir-lock.js.map +1 -0
  391. package/dist/store/database.d.ts +2 -0
  392. package/dist/store/database.d.ts.map +1 -1
  393. package/dist/store/database.js +23 -10
  394. package/dist/store/database.js.map +1 -1
  395. package/dist/store/graph.d.ts +22 -0
  396. package/dist/store/graph.d.ts.map +1 -1
  397. package/dist/store/graph.js +97 -7
  398. package/dist/store/graph.js.map +1 -1
  399. package/dist/store/migrations/index.d.ts.map +1 -1
  400. package/dist/store/migrations/index.js +52 -0
  401. package/dist/store/migrations/index.js.map +1 -1
  402. package/dist/store/pages.d.ts +7 -0
  403. package/dist/store/pages.d.ts.map +1 -1
  404. package/dist/store/pages.js +56 -1
  405. package/dist/store/pages.js.map +1 -1
  406. package/dist/store/person-behavior.d.ts +28 -0
  407. package/dist/store/person-behavior.d.ts.map +1 -0
  408. package/dist/store/person-behavior.js +127 -0
  409. package/dist/store/person-behavior.js.map +1 -0
  410. package/dist/store/pglite-assets.d.ts +15 -0
  411. package/dist/store/pglite-assets.d.ts.map +1 -0
  412. package/dist/store/pglite-assets.js +47 -0
  413. package/dist/store/pglite-assets.js.map +1 -0
  414. package/dist/store/query-rewrite.d.ts +28 -0
  415. package/dist/store/query-rewrite.d.ts.map +1 -0
  416. package/dist/store/query-rewrite.js +97 -0
  417. package/dist/store/query-rewrite.js.map +1 -0
  418. package/dist/store/schema.sql +107 -0
  419. package/dist/store/search.d.ts +36 -8
  420. package/dist/store/search.d.ts.map +1 -1
  421. package/dist/store/search.js +188 -76
  422. package/dist/store/search.js.map +1 -1
  423. package/dist/store/tags.d.ts.map +1 -1
  424. package/dist/store/tags.js +6 -2
  425. package/dist/store/tags.js.map +1 -1
  426. package/dist/store/timeline.d.ts +13 -1
  427. package/dist/store/timeline.d.ts.map +1 -1
  428. package/dist/store/timeline.js +134 -4
  429. package/dist/store/timeline.js.map +1 -1
  430. package/dist/store/trgm-search.d.ts +13 -0
  431. package/dist/store/trgm-search.d.ts.map +1 -0
  432. package/dist/store/trgm-search.js +53 -0
  433. package/dist/store/trgm-search.js.map +1 -0
  434. package/dist/store/wikilink.d.ts +17 -0
  435. package/dist/store/wikilink.d.ts.map +1 -0
  436. package/dist/store/wikilink.js +58 -0
  437. package/dist/store/wikilink.js.map +1 -0
  438. package/dist/sync/obsidian.d.ts.map +1 -1
  439. package/dist/sync/obsidian.js +5 -1
  440. package/dist/sync/obsidian.js.map +1 -1
  441. package/dist/synth/cache.d.ts +17 -0
  442. package/dist/synth/cache.d.ts.map +1 -0
  443. package/dist/synth/cache.js +67 -0
  444. package/dist/synth/cache.js.map +1 -0
  445. package/dist/synth/citations.d.ts +12 -0
  446. package/dist/synth/citations.d.ts.map +1 -0
  447. package/dist/synth/citations.js +28 -0
  448. package/dist/synth/citations.js.map +1 -0
  449. package/dist/synth/context.d.ts +10 -0
  450. package/dist/synth/context.d.ts.map +1 -0
  451. package/dist/synth/context.js +67 -0
  452. package/dist/synth/context.js.map +1 -0
  453. package/dist/synth/engine.d.ts +16 -0
  454. package/dist/synth/engine.d.ts.map +1 -0
  455. package/dist/synth/engine.js +111 -0
  456. package/dist/synth/engine.js.map +1 -0
  457. package/dist/synth/gaps.d.ts +17 -0
  458. package/dist/synth/gaps.d.ts.map +1 -0
  459. package/dist/synth/gaps.js +57 -0
  460. package/dist/synth/gaps.js.map +1 -0
  461. package/dist/synth/index.d.ts +6 -0
  462. package/dist/synth/index.d.ts.map +1 -0
  463. package/dist/synth/index.js +6 -0
  464. package/dist/synth/index.js.map +1 -0
  465. package/dist/synth/intent.d.ts +6 -0
  466. package/dist/synth/intent.d.ts.map +1 -0
  467. package/dist/synth/intent.js +13 -0
  468. package/dist/synth/intent.js.map +1 -0
  469. package/dist/synth/intents/daily-report.d.ts +7 -0
  470. package/dist/synth/intents/daily-report.d.ts.map +1 -0
  471. package/dist/synth/intents/daily-report.js +37 -0
  472. package/dist/synth/intents/daily-report.js.map +1 -0
  473. package/dist/synth/intents/index.d.ts +2 -0
  474. package/dist/synth/intents/index.d.ts.map +1 -0
  475. package/dist/synth/intents/index.js +12 -0
  476. package/dist/synth/intents/index.js.map +1 -0
  477. package/dist/synth/intents/person-strategy.d.ts +40 -0
  478. package/dist/synth/intents/person-strategy.d.ts.map +1 -0
  479. package/dist/synth/intents/person-strategy.js +66 -0
  480. package/dist/synth/intents/person-strategy.js.map +1 -0
  481. package/dist/synth/intents/recall.d.ts +8 -0
  482. package/dist/synth/intents/recall.d.ts.map +1 -0
  483. package/dist/synth/intents/recall.js +26 -0
  484. package/dist/synth/intents/recall.js.map +1 -0
  485. package/dist/synth/intents/troubleshoot.d.ts +11 -0
  486. package/dist/synth/intents/troubleshoot.d.ts.map +1 -0
  487. package/dist/synth/intents/troubleshoot.js +66 -0
  488. package/dist/synth/intents/troubleshoot.js.map +1 -0
  489. package/dist/synth/scope.d.ts +13 -0
  490. package/dist/synth/scope.d.ts.map +1 -0
  491. package/dist/synth/scope.js +139 -0
  492. package/dist/synth/scope.js.map +1 -0
  493. package/dist/synth/types.d.ts +117 -0
  494. package/dist/synth/types.d.ts.map +1 -0
  495. package/dist/synth/types.js +2 -0
  496. package/dist/synth/types.js.map +1 -0
  497. package/package.json +18 -6
@@ -1,44 +1,344 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1
+ import { createHash } from "node:crypto";
2
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2
3
  import { z } from "zod";
4
+ // Static JSON import: bun's bundler inlines this into the --compile binary
5
+ // (a runtime require("../../package.json") is not resolvable inside $bunfs).
6
+ import pkg from "../../package.json";
7
+ import { ingestFeishuDoc } from "../collectors/feishu/docs/ingest.js";
8
+ import { PersonIdentityStore, } from "../core/person-identity.js";
9
+ import { SourceRefSchema } from "../core/schemas.js";
10
+ import { DIRECTIVE_L2 } from "../install/directive.js";
11
+ import { PersonBehaviorStore } from "../store/person-behavior.js";
12
+ import { synthesize } from "../synth/index.js";
13
+ import { dailyReportIntent } from "../synth/intents/daily-report.js";
14
+ import { troubleshootIntent } from "../synth/intents/troubleshoot.js";
3
15
  import { getSessionContext } from "./context.js";
4
16
  import { getEntityProfile, listSignalsByEntity } from "./entity.js";
5
- export function createMcpToolHandlers(stores) {
17
+ const packageVersion = pkg.version;
18
+ const SEARCH_DEFAULT_LIMIT = 20;
19
+ const SEARCH_MAX_LIMIT = 50;
20
+ const LIST_DEFAULT_LIMIT = 20;
21
+ const LIST_MAX_LIMIT = 100;
22
+ const GRAPH_DEFAULT_DEPTH = 2;
23
+ const GRAPH_MAX_DEPTH = 5;
24
+ const MCP_CONTRACT_VERSION = "2026-06-04";
25
+ function structuredError(code, message, suggestion) {
26
+ return { error: { code, message, suggestion } };
27
+ }
28
+ function isToolError(value) {
29
+ return Boolean(value &&
30
+ typeof value === "object" &&
31
+ "error" in value &&
32
+ typeof value.error === "object");
33
+ }
34
+ function clampLimit(limit, max, defaultLimit) {
35
+ if (!Number.isFinite(limit) || (limit ?? 0) <= 0)
36
+ return defaultLimit;
37
+ return Math.min(Math.floor(limit), max);
38
+ }
39
+ function clampDepth(depth) {
40
+ if (!Number.isFinite(depth) || (depth ?? 0) <= 0)
41
+ return GRAPH_DEFAULT_DEPTH;
42
+ return Math.min(Math.floor(depth), GRAPH_MAX_DEPTH);
43
+ }
44
+ function isValidDate(value) {
45
+ if (!value)
46
+ return true;
47
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value))
48
+ return !Number.isNaN(Date.parse(`${value}T00:00:00Z`));
49
+ return /^\d{4}-\d{2}-\d{2}T/.test(value) && !Number.isNaN(Date.parse(value));
50
+ }
51
+ function validateDates(filter) {
52
+ if (!isValidDate(filter.from) || !isValidDate(filter.to)) {
53
+ return structuredError("INVALID_DATE", "from/to must be ISO dates or datetimes", "Retry with values such as `2026-06-04` or `2026-06-04T10:00:00.000Z`.");
54
+ }
55
+ return undefined;
56
+ }
57
+ function validateTimelineDate(date) {
58
+ if (isValidDate(date))
59
+ return undefined;
60
+ return structuredError("INVALID_DATE", "date must be an ISO date or datetime", "Retry with a value such as `2026-06-04` or `2026-06-04T10:00:00.000Z`.");
61
+ }
62
+ function normalizeSearchFilter(args) {
63
+ const dateError = validateDates(args);
64
+ if (dateError)
65
+ return dateError;
6
66
  return {
7
- query: ({ query, limit }) => stores.search.query(query, { limit }),
8
- search: ({ query, limit }) => stores.search.search(query, { limit }),
9
- get_page: ({ slug }) => stores.pages.getPage(slug),
67
+ ...args,
68
+ limit: clampLimit(args.limit, SEARCH_MAX_LIMIT, SEARCH_DEFAULT_LIMIT),
69
+ };
70
+ }
71
+ function normalizeListFilter(args) {
72
+ const dateError = validateDates(args);
73
+ if (dateError)
74
+ return dateError;
75
+ return {
76
+ ...args,
77
+ limit: clampLimit(args.limit, LIST_MAX_LIMIT, LIST_DEFAULT_LIMIT),
78
+ };
79
+ }
80
+ function validSlug(slug) {
81
+ return Boolean(slug && slug.trim() === slug && !/\s/.test(slug) && !slug.includes(".."));
82
+ }
83
+ function invalidSlugError() {
84
+ return structuredError("INVALID_ARGUMENT", "slug must be a non-empty stable page identifier", "Use a slug such as `projects/memoark` or `people/alice`.");
85
+ }
86
+ function notFound(slug) {
87
+ return structuredError("NOT_FOUND", `Page not found: ${slug}`, "Call `query` or `search` first to find the correct page slug.");
88
+ }
89
+ function normalizeOptionalSourceRef(provenance) {
90
+ if (!provenance)
91
+ return undefined;
92
+ const parsed = SourceRefSchema.safeParse(provenance);
93
+ if (parsed.success)
94
+ return parsed.data;
95
+ return structuredError("INVALID_ARGUMENT", "provenance must be a valid SourceRef object", "Provide at least platform and channel; timestamp, raw_hash, and quote are filled with safe defaults when omitted.");
96
+ }
97
+ function hashContent(content) {
98
+ return createHash("sha256").update(content).digest("hex");
99
+ }
100
+ async function ensurePage(stores, slug) {
101
+ if (!validSlug(slug))
102
+ return invalidSlugError();
103
+ const page = await stores.pages.getPage(slug);
104
+ return page ?? notFound(slug);
105
+ }
106
+ const memoryFilterInputSchema = {
107
+ platform: z
108
+ .union([z.string(), z.array(z.string())])
109
+ .optional()
110
+ .describe("Limit results to one or more source platforms, for example `wechat` or `feishu`."),
111
+ source_type: z
112
+ .union([z.string(), z.array(z.string())])
113
+ .optional()
114
+ .describe("Limit results to one or more source types, for example `dm`, `group`, or `document`."),
115
+ channel: z
116
+ .string()
117
+ .optional()
118
+ .describe("Limit results to a stable source channel id, for example `dm/wechat/wxid_123`."),
119
+ channel_name: z
120
+ .string()
121
+ .optional()
122
+ .describe("Limit results to a human-readable channel name, for example `产品评审群`."),
123
+ participant: z
124
+ .string()
125
+ .optional()
126
+ .describe("Limit results to memories involving this exact participant display name."),
127
+ from: z.string().optional().describe("Inclusive lower time bound as an ISO date or datetime."),
128
+ to: z.string().optional().describe("Inclusive upper time bound as an ISO date or datetime."),
129
+ type: z
130
+ .array(z.string())
131
+ .optional()
132
+ .describe("Limit results to page types such as `decision`, `task`, or `person`."),
133
+ exclude_types: z.array(z.string()).optional().describe("Exclude these page types from results."),
134
+ limit: z
135
+ .number()
136
+ .optional()
137
+ .describe("Maximum number of results. Search tools default to 20 and clamp to 50."),
138
+ };
139
+ function description(name, body) {
140
+ return `## ${name}
141
+
142
+ ${body}`;
143
+ }
144
+ function jsonResource(uri, value) {
145
+ return {
146
+ contents: [
147
+ {
148
+ uri,
149
+ mimeType: "application/json",
150
+ text: JSON.stringify(value, null, 2),
151
+ },
152
+ ],
153
+ };
154
+ }
155
+ function decodeSlug(value) {
156
+ return decodeURIComponent(String(value ?? ""));
157
+ }
158
+ export function createMcpToolHandlers(stores, options = {}) {
159
+ const identity = new PersonIdentityStore(stores.db.pg, { pages: stores.pages }, { behavior: new PersonBehaviorStore(stores.db.pg) });
160
+ return {
161
+ query: async (args) => {
162
+ const filter = normalizeSearchFilter(args);
163
+ if (isToolError(filter))
164
+ return filter;
165
+ return stores.search.query(args.query, filter);
166
+ },
167
+ search: async (args) => {
168
+ const filter = normalizeSearchFilter(args);
169
+ if (isToolError(filter))
170
+ return filter;
171
+ return stores.search.search(args.query, filter);
172
+ },
173
+ get_page_context: async ({ slug, include, limit, }) => {
174
+ const page = await ensurePage(stores, slug);
175
+ if (isToolError(page))
176
+ return page;
177
+ const boundedLimit = clampLimit(limit, LIST_MAX_LIMIT, LIST_DEFAULT_LIMIT);
178
+ const includeLinks = include?.links ?? true;
179
+ const includeBacklinks = include?.backlinks ?? true;
180
+ const includeTimeline = include?.timeline ?? true;
181
+ const includeChunks = include?.chunks ?? false;
182
+ return {
183
+ page,
184
+ tags: await stores.tags.getTags(slug),
185
+ links: includeLinks
186
+ ? (await stores.graph.getLinksEnriched(slug)).slice(0, boundedLimit)
187
+ : [],
188
+ backlinks: includeBacklinks
189
+ ? (await stores.graph.getBacklinksEnriched(slug)).slice(0, boundedLimit)
190
+ : [],
191
+ timeline: includeTimeline
192
+ ? (await stores.timeline.getTimeline(slug)).slice(0, boundedLimit)
193
+ : [],
194
+ chunks: includeChunks
195
+ ? (await stores.chunks.getChunks(slug)).slice(0, boundedLimit)
196
+ : undefined,
197
+ provenance: page.frontmatter.source ??
198
+ page.frontmatter.first_seen,
199
+ };
200
+ },
201
+ timeline_feed: async (args) => {
202
+ const filter = normalizeListFilter(args);
203
+ if (isToolError(filter))
204
+ return filter;
205
+ return stores.timeline.feed({ ...filter, query: args.query });
206
+ },
207
+ explore_graph: async ({ slug, depth, direction, }) => {
208
+ const page = await ensurePage(stores, slug);
209
+ if (isToolError(page))
210
+ return page;
211
+ return stores.graph.traverse(slug, {
212
+ depth: clampDepth(depth),
213
+ direction: direction ?? "both",
214
+ });
215
+ },
216
+ get_page: ({ slug }) => ensurePage(stores, slug),
10
217
  put_page: async ({ slug, content }) => {
218
+ if (!validSlug(slug))
219
+ return invalidSlugError();
220
+ if (!content.trim()) {
221
+ return structuredError("INVALID_ARGUMENT", "content must be non-empty markdown", "Provide page content, usually with YAML frontmatter and a markdown body.");
222
+ }
223
+ const contentHash = hashContent(content);
224
+ const existing = await stores.pages.getPage(slug);
225
+ if (existing?.content_hash === contentHash) {
226
+ return {
227
+ ok: true,
228
+ slug,
229
+ changed: false,
230
+ content_hash: contentHash,
231
+ previous_hash: existing.content_hash,
232
+ updated_at: existing.updated_at,
233
+ };
234
+ }
11
235
  const page = await stores.pages.putPage(slug, content);
12
236
  await stores.chunks.rechunk(page.id, page.compiled_truth);
13
- return page;
237
+ return {
238
+ ok: true,
239
+ slug,
240
+ changed: true,
241
+ content_hash: page.content_hash,
242
+ previous_hash: existing?.content_hash,
243
+ updated_at: page.updated_at,
244
+ };
14
245
  },
15
- list_pages: (opts) => stores.pages.listPages(opts),
16
- get_chunks: ({ slug }) => stores.chunks.getChunks(slug),
17
- add_link: async ({ from, to, type, context, }) => {
18
- await stores.graph.addLink(from, to, type ?? "", context);
19
- return { ok: true };
246
+ list_pages: (opts) => stores.pages.listPages({
247
+ ...opts,
248
+ limit: clampLimit(opts?.limit, LIST_MAX_LIMIT, LIST_DEFAULT_LIMIT),
249
+ }),
250
+ get_chunks: async ({ slug, limit }) => {
251
+ const page = await ensurePage(stores, slug);
252
+ if (isToolError(page))
253
+ return page;
254
+ return (await stores.chunks.getChunks(slug)).slice(0, clampLimit(limit, LIST_MAX_LIMIT, LIST_DEFAULT_LIMIT));
20
255
  },
21
- remove_link: async ({ from, to }) => {
22
- await stores.graph.removeLink(from, to);
23
- return { ok: true };
256
+ manage_links: async ({ action, from, to, type, context, provenance, }) => {
257
+ const fromPage = await ensurePage(stores, from);
258
+ if (isToolError(fromPage))
259
+ return fromPage;
260
+ const toPage = await ensurePage(stores, to);
261
+ if (isToolError(toPage))
262
+ return toPage;
263
+ if (action === "add") {
264
+ const sourceRef = normalizeOptionalSourceRef(provenance);
265
+ if (isToolError(sourceRef))
266
+ return sourceRef;
267
+ await stores.graph.addLink(from, to, type ?? "mentions", context, sourceRef, sourceRef?.raw_hash);
268
+ }
269
+ else {
270
+ await stores.graph.removeLink(from, to);
271
+ }
272
+ return { ok: true, action, from, to };
24
273
  },
25
- get_links: ({ slug }) => stores.graph.getLinks(slug),
26
- get_backlinks: ({ slug }) => stores.graph.getBacklinks(slug),
27
- traverse_graph: ({ slug, depth, direction, }) => stores.graph.traverse(slug, { depth, direction }),
28
- add_tag: async ({ slug, tag }) => {
29
- await stores.tags.addTag(slug, tag);
30
- return { ok: true };
274
+ add_link: async ({ from, to, type, context, }) => createMcpToolHandlers(stores, options).manage_links({
275
+ action: "add",
276
+ from,
277
+ to,
278
+ type,
279
+ context,
280
+ }),
281
+ remove_link: async ({ from, to }) => createMcpToolHandlers(stores, options).manage_links({ action: "remove", from, to }),
282
+ get_links: async ({ slug, limit }) => {
283
+ const page = await ensurePage(stores, slug);
284
+ if (isToolError(page))
285
+ return page;
286
+ return (await stores.graph.getLinks(slug)).slice(0, clampLimit(limit, 200, 50));
31
287
  },
32
- remove_tag: async ({ slug, tag }) => {
33
- await stores.tags.removeTag(slug, tag);
34
- return { ok: true };
288
+ get_backlinks: async ({ slug, limit }) => {
289
+ const page = await ensurePage(stores, slug);
290
+ if (isToolError(page))
291
+ return page;
292
+ return (await stores.graph.getBacklinks(slug)).slice(0, clampLimit(limit, 200, 50));
293
+ },
294
+ traverse_graph: ({ slug, depth, direction, }) => stores.graph.traverse(slug, { depth: clampDepth(depth), direction }),
295
+ manage_tags: async ({ action, slug, tags, }) => {
296
+ const page = await ensurePage(stores, slug);
297
+ if (isToolError(page))
298
+ return page;
299
+ if (!tags.length) {
300
+ return structuredError("INVALID_ARGUMENT", "tags must contain at least one tag", "Retry with one or more short tag strings.");
301
+ }
302
+ for (const tag of tags) {
303
+ if (action === "add") {
304
+ await stores.tags.addTag(slug, tag);
305
+ }
306
+ else {
307
+ await stores.tags.removeTag(slug, tag);
308
+ }
309
+ }
310
+ return { ok: true, action, slug, tags };
311
+ },
312
+ add_tag: async ({ slug, tag }) => createMcpToolHandlers(stores, options).manage_tags({ action: "add", slug, tags: [tag] }),
313
+ remove_tag: async ({ slug, tag }) => createMcpToolHandlers(stores, options).manage_tags({ action: "remove", slug, tags: [tag] }),
314
+ get_tags: async ({ slug }) => {
315
+ const page = await ensurePage(stores, slug);
316
+ if (isToolError(page))
317
+ return page;
318
+ return stores.tags.getTags(slug);
35
319
  },
36
- get_tags: ({ slug }) => stores.tags.getTags(slug),
37
320
  add_timeline_entry: async (entry) => {
38
- await stores.timeline.addEntry(entry.slug, entry);
39
- return { ok: true };
321
+ const dateError = validateTimelineDate(entry.date);
322
+ if (dateError)
323
+ return dateError;
324
+ const page = await ensurePage(stores, entry.slug);
325
+ if (isToolError(page))
326
+ return page;
327
+ const provenance = normalizeOptionalSourceRef(entry.provenance);
328
+ if (isToolError(provenance))
329
+ return provenance;
330
+ await stores.timeline.addEntry(entry.slug, {
331
+ ...entry,
332
+ provenance,
333
+ });
334
+ return { ok: true, slug: entry.slug, date: entry.date, summary: entry.summary };
335
+ },
336
+ get_timeline: async ({ slug, limit }) => {
337
+ const page = await ensurePage(stores, slug);
338
+ if (isToolError(page))
339
+ return page;
340
+ return (await stores.timeline.getTimeline(slug)).slice(0, clampLimit(limit, LIST_MAX_LIMIT, LIST_DEFAULT_LIMIT));
40
341
  },
41
- get_timeline: ({ slug }) => stores.timeline.getTimeline(slug),
42
342
  get_health: async () => {
43
343
  const pages = await stores.db.pg.query("SELECT COUNT(*) AS c FROM pages");
44
344
  const chunks = await stores.db.pg.query("SELECT COUNT(*) AS c FROM content_chunks");
@@ -46,58 +346,801 @@ export function createMcpToolHandlers(stores) {
46
346
  status: "ok",
47
347
  pages: Number(pages.rows[0].c),
48
348
  chunks: Number(chunks.rows[0].c),
349
+ mcp_version: options.version ?? packageVersion,
350
+ mcp_contract_version: MCP_CONTRACT_VERSION,
351
+ legacy_tools_exposed: options.exposeLegacyTools ?? false,
352
+ read_only: options.readOnly ?? false,
353
+ capabilities: {
354
+ tools: true,
355
+ resources: true,
356
+ prompts: true,
357
+ structured_output: true,
358
+ streamable_http: true,
359
+ },
49
360
  };
50
361
  },
51
362
  get_session_context: ({ days }) => getSessionContext(stores, days ?? 7),
52
363
  list_signals_by_entity: ({ entity_slug, signal_types, limit, }) => listSignalsByEntity(stores, entity_slug, signal_types, limit ?? 20),
53
364
  get_entity_profile: ({ entity_slug }) => getEntityProfile(stores, entity_slug),
365
+ // ── Person identity (Layer 1: aliases / merge / rename) ──────────────
366
+ link_person_alias: async ({ canonical_slug, kind, value, strength, }) => {
367
+ await identity.addAlias(canonical_slug, kind, value, strength);
368
+ return { ok: true, canonical_slug, handles: await identity.listHandles(canonical_slug) };
369
+ },
370
+ list_person_handles: ({ canonical_slug }) => identity.listHandles(canonical_slug),
371
+ remove_person_alias: async ({ kind, value }) => {
372
+ await identity.removeHandle(kind, value);
373
+ return { ok: true };
374
+ },
375
+ merge_persons: async ({ from, into }) => {
376
+ await identity.merge(from, into);
377
+ return { ok: true, merged: from, into, handles: await identity.listHandles(into) };
378
+ },
379
+ recanonicalize_person: async ({ from, to }) => {
380
+ await identity.recanonicalize(from, to);
381
+ return { ok: true, from, to, handles: await identity.listHandles(to) };
382
+ },
383
+ // ── Synthesis (Spec 7): synthesize + recall ──────────────────────────
384
+ synthesize: async ({ intent, scope }) => {
385
+ if (!options.provider) {
386
+ return structuredError("INVALID_ARGUMENT", "No LLM provider configured for synthesis.", "Configure an LLM provider (llm config) to use synthesize/recall.");
387
+ }
388
+ try {
389
+ return await synthesize(intent, scope ?? {}, {
390
+ stores: stores,
391
+ provider: options.provider,
392
+ model: options.synthModel,
393
+ });
394
+ }
395
+ catch (e) {
396
+ return structuredError("INTERNAL_ERROR", `synthesis failed: ${e instanceof Error ? e.message : String(e)}`);
397
+ }
398
+ },
399
+ recall: async ({ entity, query, time, }) => {
400
+ if (!options.provider) {
401
+ return structuredError("INVALID_ARGUMENT", "No LLM provider configured for synthesis.", "Configure an LLM provider (llm config) to use synthesize/recall.");
402
+ }
403
+ const scope = { entity, query, time, limit: 30 };
404
+ try {
405
+ return await synthesize("recall", scope, {
406
+ stores: stores,
407
+ provider: options.provider,
408
+ model: options.synthModel,
409
+ });
410
+ }
411
+ catch (e) {
412
+ return structuredError("INTERNAL_ERROR", `synthesis failed: ${e instanceof Error ? e.message : String(e)}`);
413
+ }
414
+ },
415
+ // ── Spec 8: prep_for_person — communication strategy for a person ─────
416
+ prep_for_person: async ({ person, goal }) => {
417
+ if (!options.provider) {
418
+ return structuredError("INVALID_ARGUMENT", "No LLM provider configured for synthesis.", "Configure an LLM provider (llm config) to use prep_for_person.");
419
+ }
420
+ try {
421
+ return await synthesize("person_strategy", { entity: person }, {
422
+ stores: stores,
423
+ provider: options.provider,
424
+ model: options.synthModel,
425
+ }, { extra: goal ? { goal } : undefined });
426
+ }
427
+ catch (e) {
428
+ return structuredError("INTERNAL_ERROR", `synthesis failed: ${e instanceof Error ? e.message : String(e)}`);
429
+ }
430
+ },
431
+ // ── Spec 9: daily_report — cross-channel daily report (7 sections) ─────
432
+ daily_report: async ({ date }) => {
433
+ if (!options.provider) {
434
+ return structuredError("INVALID_ARGUMENT", "No LLM provider configured for synthesis.", "Configure an LLM provider (llm config) to use daily_report.");
435
+ }
436
+ try {
437
+ return await synthesize("daily_report", dailyReportIntent.buildScope({ date }), {
438
+ stores: stores,
439
+ provider: options.provider,
440
+ model: options.synthModel,
441
+ });
442
+ }
443
+ catch (e) {
444
+ return structuredError("INTERNAL_ERROR", `synthesis failed: ${e instanceof Error ? e.message : String(e)}`);
445
+ }
446
+ },
447
+ // ── Spec 11: troubleshoot — one-shot playbook troubleshooting ──────────
448
+ troubleshoot: async ({ query }) => {
449
+ if (!options.provider) {
450
+ return structuredError("INVALID_ARGUMENT", "No LLM provider configured for synthesis.", "Configure an LLM provider (llm config) to use troubleshoot.");
451
+ }
452
+ try {
453
+ return await synthesize("troubleshoot", troubleshootIntent.buildScope({ query }), {
454
+ stores: stores,
455
+ provider: options.provider,
456
+ model: options.synthModel,
457
+ });
458
+ }
459
+ catch (e) {
460
+ return structuredError("INTERNAL_ERROR", `synthesis failed: ${e instanceof Error ? e.message : String(e)}`);
461
+ }
462
+ },
54
463
  };
55
464
  }
56
- export function createMcpServer(stores) {
57
- const server = new McpServer({ name: "memoark", version: "1.0.0" });
58
- const tools = createMcpToolHandlers(stores);
59
- const text = (value) => ({
465
+ function text(value) {
466
+ return {
60
467
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
468
+ isError: isToolError(value) || undefined,
469
+ };
470
+ }
471
+ function structuredText(value, structuredContent) {
472
+ if (isToolError(value))
473
+ return text(value);
474
+ return {
475
+ ...text(value),
476
+ structuredContent,
477
+ };
478
+ }
479
+ const outputSchemas = {
480
+ results: {
481
+ results: z.array(z.record(z.unknown())).describe("Ranked memory results."),
482
+ },
483
+ timelineFeed: {
484
+ entries: z.array(z.record(z.unknown())).describe("Timeline feed entries."),
485
+ },
486
+ pageContext: {
487
+ page: z.record(z.unknown()).describe("Memoark page."),
488
+ tags: z.array(z.string()).describe("Tags on the page."),
489
+ links: z.array(z.record(z.unknown())).describe("Outgoing links."),
490
+ backlinks: z.array(z.record(z.unknown())).describe("Incoming links."),
491
+ timeline: z.array(z.record(z.unknown())).describe("Page timeline entries."),
492
+ chunks: z.array(z.record(z.unknown())).optional().describe("Optional page chunks."),
493
+ provenance: z.record(z.unknown()).optional().describe("Compact source provenance."),
494
+ },
495
+ health: {
496
+ status: z.string().describe("Health status."),
497
+ pages: z.number().describe("Stored page count."),
498
+ chunks: z.number().describe("Stored chunk count."),
499
+ mcp_version: z.string().describe("Memoark package version exposed by MCP."),
500
+ mcp_contract_version: z.string().describe("MCP contract version."),
501
+ legacy_tools_exposed: z.boolean().describe("Whether legacy MCP tools are exposed."),
502
+ read_only: z.boolean().describe("Whether this server hides write tools."),
503
+ capabilities: z.record(z.unknown()).describe("Capability flags."),
504
+ },
505
+ exploreGraph: {
506
+ focus: z.record(z.unknown()).describe("Focus page."),
507
+ nodes: z.array(z.record(z.unknown())).describe("Graph nodes."),
508
+ edges: z.array(z.record(z.unknown())).describe("Graph edges."),
509
+ },
510
+ putPage: {
511
+ ok: z.boolean().describe("Whether the write succeeded."),
512
+ slug: z.string().describe("Page slug."),
513
+ changed: z.boolean().describe("Whether content changed."),
514
+ content_hash: z.string().describe("Current content hash."),
515
+ previous_hash: z.string().optional().describe("Previous content hash, if any."),
516
+ updated_at: z.string().describe("Page update timestamp."),
517
+ },
518
+ timelineWrite: {
519
+ ok: z.boolean().describe("Whether the write succeeded."),
520
+ slug: z.string().describe("Page slug."),
521
+ date: z.string().describe("Timeline event date."),
522
+ summary: z.string().describe("Timeline event summary."),
523
+ },
524
+ linkWrite: {
525
+ ok: z.boolean().describe("Whether the write succeeded."),
526
+ action: z.string().describe("Performed link action."),
527
+ from: z.string().describe("Source slug."),
528
+ to: z.string().describe("Target slug."),
529
+ },
530
+ tagWrite: {
531
+ ok: z.boolean().describe("Whether the write succeeded."),
532
+ action: z.string().describe("Performed tag action."),
533
+ slug: z.string().describe("Page slug."),
534
+ tags: z.array(z.string()).describe("Managed tags."),
535
+ },
536
+ };
537
+ function registerPreferredTools(server, tools, options) {
538
+ server.registerTool("query", {
539
+ title: "Semantic Memory Query",
540
+ description: description("query", "Semantic search across Memoark memory.\n\nWhen to use: fuzzy, conceptual recall across people, projects, decisions, tasks, and prior work.\nWhen NOT to use: exact keyword matching; use `search` instead. Do not look for source-specific tools; use filters.\nReturns: ranked results with slug, title, type, snippet, score, and provenance.\nOn error: broaden filters or retry with fewer constraints."),
541
+ inputSchema: {
542
+ query: z.string().describe("Natural language search query, for example `上周部署方案`."),
543
+ ...memoryFilterInputSchema,
544
+ },
545
+ outputSchema: outputSchemas.results,
546
+ }, async (args) => {
547
+ const value = await tools.query(args);
548
+ return structuredText(value, { results: Array.isArray(value) ? value : [] });
549
+ });
550
+ server.registerTool("search", {
551
+ title: "Exact Memory Search",
552
+ description: description("search", "Keyword search across Memoark memory.\n\nWhen to use: exact words, identifiers, tokens, page titles, or known phrases.\nWhen NOT to use: fuzzy conceptual recall; use `query` instead.\nReturns: ranked keyword matches with provenance.\nOn error: simplify the query or relax filters."),
553
+ inputSchema: {
554
+ query: z.string().describe("Exact keyword query, for example `JWT token`."),
555
+ ...memoryFilterInputSchema,
556
+ },
557
+ outputSchema: outputSchemas.results,
558
+ }, async (args) => {
559
+ const value = await tools.search(args);
560
+ return structuredText(value, { results: Array.isArray(value) ? value : [] });
561
+ });
562
+ server.registerTool("get_page_context", {
563
+ title: "Get Page Context",
564
+ description: description("get_page_context", "Read a page plus nearby memory context.\n\nWhen to use: after finding a slug and needing page, tags, links, backlinks, timeline, or chunks in one call.\nWhen NOT to use: broad recall without a slug; use `query` or `search` first.\nReturns: page, tags, limited related context, and provenance.\nOn error: if the slug is missing, search for the correct slug first."),
565
+ inputSchema: {
566
+ slug: z.string().describe("Page slug to read, for example `projects/memoark`."),
567
+ include: z
568
+ .object({
569
+ links: z.boolean().optional().describe("Include outgoing links."),
570
+ backlinks: z.boolean().optional().describe("Include incoming links."),
571
+ timeline: z.boolean().optional().describe("Include page timeline entries."),
572
+ chunks: z.boolean().optional().describe("Include page content chunks."),
573
+ })
574
+ .optional()
575
+ .describe("Optional context sections to include."),
576
+ limit: z
577
+ .number()
578
+ .optional()
579
+ .describe("Maximum related items per section, default 20, max 100."),
580
+ },
581
+ outputSchema: outputSchemas.pageContext,
582
+ }, async (args) => {
583
+ const value = await tools.get_page_context(args);
584
+ return structuredText(value, value);
585
+ });
586
+ server.registerTool("timeline_feed", {
587
+ title: "Timeline Feed",
588
+ description: description("timeline_feed", "Read global timeline memories.\n\nWhen to use: recent activity reviews, date-bounded recall, and source-filtered timeline scans.\nWhen NOT to use: page-specific context; use `get_page_context`.\nReturns: timeline entries with slug, title, type, summary, time, snippet, and provenance.\nOn error: fix invalid date filters and retry."),
589
+ inputSchema: {
590
+ query: z
591
+ .string()
592
+ .optional()
593
+ .describe("Optional keyword filter for timeline summary/detail/title."),
594
+ ...memoryFilterInputSchema,
595
+ },
596
+ outputSchema: outputSchemas.timelineFeed,
597
+ }, async (args) => {
598
+ const value = await tools.timeline_feed(args);
599
+ return structuredText(value, { entries: Array.isArray(value) ? value : [] });
600
+ });
601
+ server.registerTool("explore_graph", {
602
+ title: "Explore Memory Graph",
603
+ description: description("explore_graph", "Explore graph relationships around a page.\n\nWhen to use: understand dependencies, mentions, collaborators, and nearby entities.\nWhen NOT to use: raw page content; use `get_page_context`.\nReturns: focus node, graph nodes, and edges with bounded depth.\nOn error: search for the correct slug first."),
604
+ inputSchema: {
605
+ slug: z.string().describe("Focus page slug."),
606
+ depth: z.number().optional().describe("Traversal depth, default 2, max 5."),
607
+ direction: z.enum(["in", "out", "both"]).optional().describe("Traversal direction."),
608
+ },
609
+ outputSchema: outputSchemas.exploreGraph,
610
+ }, async (args) => {
611
+ const value = await tools.explore_graph(args);
612
+ return structuredText(value, value);
613
+ });
614
+ server.registerTool("synthesize", {
615
+ title: "Synthesize Memory",
616
+ description: description("synthesize", "Synthesize a cited, gap-aware answer from memory using an intent template.\n\nWhen to use: you want a composed answer (not raw snippets) about an entity, time window, or query.\nWhen NOT to use: raw ranked snippets; use `query`/`search`.\nReturns: answer with inline [n] citations, citations[], and gaps[] (stale / missing).\nOn error: ensure the intent is registered and the scope is non-empty."),
617
+ inputSchema: {
618
+ intent: z.string().describe("Registered synthesis intent, for example `recall`."),
619
+ scope: z
620
+ .object({
621
+ entity: z.string().optional().describe("Anchor entity slug, e.g. `people/zhang-san`."),
622
+ query: z.string().optional().describe("Free-text semantic query."),
623
+ time: z
624
+ .object({ from: z.string(), to: z.string() })
625
+ .optional()
626
+ .describe("Time window (ISO dates)."),
627
+ types: z.array(z.string()).optional().describe("Limit to signal types."),
628
+ channels: z.array(z.string()).optional().describe("Limit to source channels."),
629
+ limit: z.number().optional().describe("Candidate cap, default 30."),
630
+ })
631
+ .partial()
632
+ .optional()
633
+ .describe("Retrieval scope (entity / time / query)."),
634
+ },
635
+ }, async (args) => text(await tools.synthesize(args)));
636
+ server.registerTool("recall", {
637
+ title: "Recall Memory (Synthesized)",
638
+ description: description("recall", "Recall a synthesized, cited summary about an entity, query, or time window.\n\nWhen to use: a quick composed recall with citations and gap flags.\nWhen NOT to use: raw snippets; use `query`/`search`.\nReturns: a SynthesisResult (answer + citations + gaps).\nOn error: provide at least one of entity, query, or time."),
639
+ inputSchema: {
640
+ entity: z.string().optional().describe("Anchor entity slug, e.g. `people/zhang-san`."),
641
+ query: z.string().optional().describe("Free-text semantic query."),
642
+ time: z
643
+ .object({ from: z.string(), to: z.string() })
644
+ .optional()
645
+ .describe("Time window (ISO dates)."),
646
+ },
647
+ }, async (args) => text(await tools.recall(args)));
648
+ server.registerTool("prep_for_person", {
649
+ title: "Prep for Person (Communication Strategy)",
650
+ description: description("prep_for_person", "Prepare goal-conditioned communication strategy for a person from their passively-inferred communication profile.\n\nWhen to use: before talking to someone — get evidence-cited, ethical suggestions on how to communicate with them, optionally toward a specific goal.\nWhen NOT to use: raw facts about the person; use `get_entity_profile`/`query`.\nReturns: a SynthesisResult (cited suggestions + gaps). Suggestions only — never manipulation. Profiling is passive and local; the four-color shell is a popular mapping, not a clinical diagnosis.\nOn error: ensure the person page slug exists."),
651
+ inputSchema: {
652
+ person: z.string().describe("Person page slug, e.g. `people/zhang-san`."),
653
+ goal: z.string().optional().describe("Optional goal for this conversation."),
654
+ },
655
+ }, async (args) => text(await tools.prep_for_person(args)));
656
+ server.registerTool("daily_report", {
657
+ title: "Daily Report (Cross-Channel)",
658
+ description: description("daily_report", "Generate a synthesized cross-channel daily report in 7 fixed sections.\n\nWhen to use: 'help me write today's daily report' — aggregate the day's signals (mail / IM / calendar / docs) into 今日概览 / 今日完成 / 推进中 / 我的待办 / 待回复与被@ / 人脉动态 / 明日提醒.\nWhen NOT to use: arbitrary recall; use `recall`/`query`.\nReturns: a SynthesisResult with sections[] + answer + citations + gaps.\nOn error: ensure the date is a valid ISO date."),
659
+ inputSchema: {
660
+ date: z
661
+ .string()
662
+ .optional()
663
+ .describe("Report date as `YYYY-MM-DD`. Defaults to today (local time)."),
664
+ },
665
+ }, async (args) => text(await tools.daily_report(args)));
666
+ server.registerTool("troubleshoot", {
667
+ title: "Troubleshoot (Playbook)",
668
+ description: description("troubleshoot", "Walk through a troubleshooting playbook in one shot.\n\nWhen to use: '怎么排查 X' — pull the matching playbook pages, ordered along their `precedes` chain, into a step-by-step procedure with branch meanings ('命中 X → 含义/下一步').\nWhen NOT to use: general recall; use `recall`/`query`.\nReturns: a SynthesisResult with ordered steps + [n] citations + gaps.\nOn error: rephrase the symptom or save a playbook page first."),
669
+ inputSchema: {
670
+ query: z
671
+ .string()
672
+ .describe("Symptom or problem to troubleshoot, for example `智驾无法激活`."),
673
+ },
674
+ }, async (args) => text(await tools.troubleshoot(args)));
675
+ if (!options.readOnly) {
676
+ registerWriteTools(server, tools);
677
+ }
678
+ server.registerTool("get_health", {
679
+ title: "Get Health",
680
+ description: description("get_health", "Return Memoark MCP health and capability metadata.\n\nWhen to use: diagnose database counts, MCP version, and legacy tool exposure.\nWhen NOT to use: retrieve memory content; use read tools.\nReturns: status, page/chunk counts, MCP version, and legacy setting.\nOn error: inspect server logs."),
681
+ inputSchema: {},
682
+ outputSchema: outputSchemas.health,
683
+ }, async () => {
684
+ const value = await tools.get_health();
685
+ return structuredText(value, value);
686
+ });
687
+ }
688
+ async function safeWrite(fn) {
689
+ try {
690
+ return await fn();
691
+ }
692
+ catch (err) {
693
+ const message = err instanceof Error ? err.message : String(err);
694
+ return structuredError("WRITE_FAILED", `Write operation failed: ${message}`, "Check the slug exists and input is valid, then retry.");
695
+ }
696
+ }
697
+ function registerWriteTools(server, tools) {
698
+ server.registerTool("put_page", {
699
+ title: "Put Page",
700
+ description: description("put_page", "Create or update a Memoark page idempotently.\n\nWhen to use: write a durable memory page.\nWhen NOT to use: append a dated event; use `add_timeline_entry`.\nReturns: ok, slug, changed flag, content hash, previous hash, and updated_at.\nOn error: fix slug or non-empty content."),
701
+ inputSchema: {
702
+ slug: z.string().describe("Stable page slug, for example `decisions/use-pglite`."),
703
+ content: z.string().describe("Full markdown content, optionally with YAML frontmatter."),
704
+ },
705
+ outputSchema: outputSchemas.putPage,
706
+ }, async (args) => {
707
+ const value = await safeWrite(() => tools.put_page(args));
708
+ return structuredText(value, value);
709
+ });
710
+ server.registerTool("add_timeline_entry", {
711
+ title: "Add Timeline Entry",
712
+ description: description("add_timeline_entry", "Append a dated memory event to an existing page.\n\nWhen to use: record project progress, decisions, or notable events.\nWhen NOT to use: full page replacement; use `put_page`.\nReturns: ok plus slug/date/summary.\nOn error: fix invalid date or find the correct page slug."),
713
+ inputSchema: {
714
+ slug: z.string().describe("Existing page slug."),
715
+ date: z.string().describe("ISO date or datetime for the event."),
716
+ summary: z.string().describe("Short event summary."),
717
+ detail: z.string().optional().describe("Optional event detail."),
718
+ source: z.string().optional().describe("Legacy display source string."),
719
+ provenance: z
720
+ .record(z.unknown())
721
+ .optional()
722
+ .describe("Optional SourceRef provenance object."),
723
+ },
724
+ outputSchema: outputSchemas.timelineWrite,
725
+ }, async (args) => {
726
+ const value = await safeWrite(() => tools.add_timeline_entry(args));
727
+ return structuredText(value, value);
728
+ });
729
+ server.registerTool("manage_links", {
730
+ title: "Manage Links",
731
+ description: description("manage_links", "Add or remove graph links between pages.\n\nWhen to use: maintain relationships such as mentions, depends_on, or works_on.\nWhen NOT to use: tagging; use `manage_tags`.\nReturns: ok plus action/from/to.\nOn error: search for missing page slugs before retrying."),
732
+ inputSchema: {
733
+ action: z.enum(["add", "remove"]).describe("Whether to add or remove the link."),
734
+ from: z.string().describe("Source page slug."),
735
+ to: z.string().describe("Target page slug."),
736
+ type: z.string().optional().describe("Relationship type, default `mentions`."),
737
+ context: z.string().optional().describe("Short relationship context."),
738
+ provenance: z
739
+ .record(z.unknown())
740
+ .optional()
741
+ .describe("Optional SourceRef provenance object."),
742
+ },
743
+ outputSchema: outputSchemas.linkWrite,
744
+ }, async (args) => {
745
+ const value = await safeWrite(() => tools.manage_links(args));
746
+ return structuredText(value, value);
747
+ });
748
+ server.registerTool("manage_tags", {
749
+ title: "Manage Tags",
750
+ description: description("manage_tags", "Add or remove tags on a page.\n\nWhen to use: classify memory pages with stable tags.\nWhen NOT to use: graph relationships; use `manage_links`.\nReturns: ok plus action/slug/tags.\nOn error: search for the page slug or provide at least one tag."),
751
+ inputSchema: {
752
+ action: z.enum(["add", "remove"]).describe("Whether to add or remove tags."),
753
+ slug: z.string().describe("Existing page slug."),
754
+ tags: z.array(z.string()).describe("One or more tag names."),
755
+ },
756
+ outputSchema: outputSchemas.tagWrite,
757
+ }, async (args) => {
758
+ const value = await safeWrite(() => tools.manage_tags(args));
759
+ return structuredText(value, value);
61
760
  });
62
- server.tool("query", { query: z.string(), limit: z.number().optional() }, async (args) => text(await tools.query(args)));
63
- server.tool("search", { query: z.string(), limit: z.number().optional() }, async (args) => text(await tools.search(args)));
64
- server.tool("get_page", { slug: z.string() }, async (args) => text(await tools.get_page(args)));
65
- server.tool("put_page", { slug: z.string(), content: z.string() }, async (args) => text(await tools.put_page(args)));
66
- server.tool("list_pages", { type: z.string().optional(), limit: z.number().optional() }, async (args) => text(await tools.list_pages(args)));
67
- server.tool("get_chunks", { slug: z.string() }, async (args) => text(await tools.get_chunks(args)));
68
- server.tool("add_link", {
69
- from: z.string(),
70
- to: z.string(),
71
- type: z.string().optional(),
72
- context: z.string().optional(),
73
- }, async (args) => text(await tools.add_link(args)));
74
- server.tool("remove_link", { from: z.string(), to: z.string() }, async (args) => text(await tools.remove_link(args)));
75
- server.tool("get_links", { slug: z.string() }, async (args) => text(await tools.get_links(args)));
76
- server.tool("get_backlinks", { slug: z.string() }, async (args) => text(await tools.get_backlinks(args)));
77
- server.tool("traverse_graph", {
78
- slug: z.string(),
79
- depth: z.number().optional(),
80
- direction: z.enum(["in", "out", "both"]).optional(),
761
+ }
762
+ function registerLegacyTools(server, tools, options) {
763
+ const legacy = "Legacy/debug/internal use only. Prefer high-intent memory tools by default.";
764
+ server.registerTool("get_page", {
765
+ title: "Legacy Get Page",
766
+ description: `${legacy} Read a page by slug.`,
767
+ inputSchema: { slug: z.string().describe("Page slug.") },
768
+ }, async (args) => text(await tools.get_page(args)));
769
+ server.registerTool("list_pages", {
770
+ title: "Legacy List Pages",
771
+ description: `${legacy} List pages with optional type and limit.`,
772
+ inputSchema: {
773
+ type: z.string().optional().describe("Optional page type."),
774
+ limit: z.number().optional().describe("Maximum pages, default 20, max 100."),
775
+ },
776
+ }, async (args) => text(await tools.list_pages(args)));
777
+ server.registerTool("get_chunks", {
778
+ title: "Legacy Get Chunks",
779
+ description: `${legacy} Read page chunks by slug.`,
780
+ inputSchema: {
781
+ slug: z.string().describe("Page slug."),
782
+ limit: z.number().optional().describe("Maximum chunks, default 20, max 100."),
783
+ },
784
+ }, async (args) => text(await tools.get_chunks(args)));
785
+ if (!options.readOnly) {
786
+ server.registerTool("add_link", {
787
+ title: "Legacy Add Link",
788
+ description: `${legacy} Add a graph link.`,
789
+ inputSchema: {
790
+ from: z.string().describe("Source slug."),
791
+ to: z.string().describe("Target slug."),
792
+ type: z.string().optional().describe("Relationship type."),
793
+ context: z.string().optional().describe("Relationship context."),
794
+ },
795
+ }, async (args) => text(await tools.add_link(args)));
796
+ server.registerTool("remove_link", {
797
+ title: "Legacy Remove Link",
798
+ description: `${legacy} Remove graph links between two slugs.`,
799
+ inputSchema: {
800
+ from: z.string().describe("Source slug."),
801
+ to: z.string().describe("Target slug."),
802
+ },
803
+ }, async (args) => text(await tools.remove_link(args)));
804
+ }
805
+ server.registerTool("get_links", {
806
+ title: "Legacy Get Links",
807
+ description: `${legacy} Read outgoing links.`,
808
+ inputSchema: {
809
+ slug: z.string().describe("Page slug."),
810
+ limit: z.number().optional().describe("Maximum links, default 50, max 200."),
811
+ },
812
+ }, async (args) => text(await tools.get_links(args)));
813
+ server.registerTool("get_backlinks", {
814
+ title: "Legacy Get Backlinks",
815
+ description: `${legacy} Read incoming links.`,
816
+ inputSchema: {
817
+ slug: z.string().describe("Page slug."),
818
+ limit: z.number().optional().describe("Maximum backlinks, default 50, max 200."),
819
+ },
820
+ }, async (args) => text(await tools.get_backlinks(args)));
821
+ server.registerTool("traverse_graph", {
822
+ title: "Legacy Traverse Graph",
823
+ description: `${legacy} Traverse graph around a slug.`,
824
+ inputSchema: {
825
+ slug: z.string().describe("Focus slug."),
826
+ depth: z.number().optional().describe("Traversal depth, max 5."),
827
+ direction: z.enum(["in", "out", "both"]).optional().describe("Traversal direction."),
828
+ },
81
829
  }, async (args) => text(await tools.traverse_graph(args)));
82
- server.tool("add_tag", { slug: z.string(), tag: z.string() }, async (args) => text(await tools.add_tag(args)));
83
- server.tool("remove_tag", { slug: z.string(), tag: z.string() }, async (args) => text(await tools.remove_tag(args)));
84
- server.tool("get_tags", { slug: z.string() }, async (args) => text(await tools.get_tags(args)));
85
- server.tool("add_timeline_entry", {
86
- slug: z.string(),
87
- date: z.string(),
88
- summary: z.string(),
89
- detail: z.string().optional(),
90
- source: z.string().optional(),
91
- }, async (args) => text(await tools.add_timeline_entry(args)));
92
- server.tool("get_timeline", { slug: z.string() }, async (args) => text(await tools.get_timeline(args)));
93
- server.tool("get_health", {}, async () => text(await tools.get_health()));
94
- server.tool("get_session_context", { days: z.number().optional() }, async (args) => text(await tools.get_session_context(args)));
95
- server.tool("list_signals_by_entity", {
96
- entity_slug: z.string(),
97
- signal_types: z.array(z.string()).optional(),
98
- limit: z.number().optional(),
830
+ if (!options.readOnly) {
831
+ server.registerTool("add_tag", {
832
+ title: "Legacy Add Tag",
833
+ description: `${legacy} Add one tag.`,
834
+ inputSchema: {
835
+ slug: z.string().describe("Page slug."),
836
+ tag: z.string().describe("Tag to add."),
837
+ },
838
+ }, async (args) => text(await tools.add_tag(args)));
839
+ server.registerTool("remove_tag", {
840
+ title: "Legacy Remove Tag",
841
+ description: `${legacy} Remove one tag.`,
842
+ inputSchema: {
843
+ slug: z.string().describe("Page slug."),
844
+ tag: z.string().describe("Tag to remove."),
845
+ },
846
+ }, async (args) => text(await tools.remove_tag(args)));
847
+ }
848
+ server.registerTool("get_tags", {
849
+ title: "Legacy Get Tags",
850
+ description: `${legacy} Read page tags.`,
851
+ inputSchema: { slug: z.string().describe("Page slug.") },
852
+ }, async (args) => text(await tools.get_tags(args)));
853
+ server.registerTool("get_timeline", {
854
+ title: "Legacy Get Timeline",
855
+ description: `${legacy} Read page timeline entries.`,
856
+ inputSchema: {
857
+ slug: z.string().describe("Page slug."),
858
+ limit: z.number().optional().describe("Maximum entries, default 20, max 100."),
859
+ },
860
+ }, async (args) => text(await tools.get_timeline(args)));
861
+ }
862
+ function registerResources(server, tools) {
863
+ server.registerResource("health", "memoark://health", {
864
+ title: "Memoark Health",
865
+ description: "Memoark MCP health, version, and capability metadata.",
866
+ mimeType: "application/json",
867
+ }, async (uri) => jsonResource(uri.toString(), await tools.get_health()));
868
+ server.registerResource("pages", "memoark://pages", {
869
+ title: "Memoark Pages",
870
+ description: "Bounded page index for browsing available memory pages.",
871
+ mimeType: "application/json",
872
+ }, async (uri) => jsonResource(uri.toString(), {
873
+ pages: await tools.list_pages({ limit: LIST_MAX_LIMIT }),
874
+ limit: LIST_MAX_LIMIT,
875
+ }));
876
+ server.registerResource("page", new ResourceTemplate("memoark://pages/{slug}", { list: undefined }), {
877
+ title: "Memoark Page",
878
+ description: "Read a Memoark page by URL-encoded slug.",
879
+ mimeType: "application/json",
880
+ }, async (uri, variables) => {
881
+ const page = await tools.get_page({ slug: decodeSlug(variables.slug) });
882
+ return jsonResource(uri.toString(), isToolError(page) ? page : { page });
883
+ });
884
+ server.registerResource("page-context", new ResourceTemplate("memoark://pages/{slug}/context", { list: undefined }), {
885
+ title: "Memoark Page Context",
886
+ description: "Read a page plus tags, links, backlinks, timeline, chunks, and provenance.",
887
+ mimeType: "application/json",
888
+ }, async (uri, variables) => jsonResource(uri.toString(), await tools.get_page_context({
889
+ slug: decodeSlug(variables.slug),
890
+ include: { links: true, backlinks: true, timeline: true, chunks: true },
891
+ limit: LIST_MAX_LIMIT,
892
+ })));
893
+ server.registerResource("page-timeline", new ResourceTemplate("memoark://pages/{slug}/timeline", { list: undefined }), {
894
+ title: "Memoark Page Timeline",
895
+ description: "Read bounded timeline entries for a Memoark page.",
896
+ mimeType: "application/json",
897
+ }, async (uri, variables) => {
898
+ const slug = decodeSlug(variables.slug);
899
+ const timeline = await tools.get_timeline({ slug, limit: LIST_MAX_LIMIT });
900
+ return jsonResource(uri.toString(), isToolError(timeline) ? timeline : { slug, timeline, limit: LIST_MAX_LIMIT });
901
+ });
902
+ }
903
+ function registerPrompts(server) {
904
+ server.registerPrompt("recall", {
905
+ title: "Recall Memory",
906
+ description: "Recall cross-source memories about a topic using query and unified filters.",
907
+ argsSchema: {
908
+ topic: z.string().describe("Topic, question, person, project, or decision to recall."),
909
+ platform: z.string().optional().describe("Optional platform filter such as `wechat`."),
910
+ source_type: z.string().optional().describe("Optional source type such as `dm`."),
911
+ participant: z.string().optional().describe("Optional exact participant display name."),
912
+ from: z.string().optional().describe("Optional ISO lower time bound."),
913
+ to: z.string().optional().describe("Optional ISO upper time bound."),
914
+ },
915
+ }, async ({ topic, platform, source_type, participant, from, to }) => ({
916
+ description: "Recall relevant Memoark memories with provenance.",
917
+ messages: [
918
+ {
919
+ role: "user",
920
+ content: {
921
+ type: "text",
922
+ text: [
923
+ `Use the \`query\` tool to recall Memoark memories about: ${topic}.`,
924
+ "Apply only the filters that are provided.",
925
+ platform ? `platform: ${platform}` : undefined,
926
+ source_type ? `source_type: ${source_type}` : undefined,
927
+ participant ? `participant: ${participant}` : undefined,
928
+ from ? `from: ${from}` : undefined,
929
+ to ? `to: ${to}` : undefined,
930
+ "Return a concise answer with cited provenance from the tool result.",
931
+ ]
932
+ .filter(Boolean)
933
+ .join("\n"),
934
+ },
935
+ },
936
+ ],
937
+ }));
938
+ server.registerPrompt("weekly-digest", {
939
+ title: "Weekly Digest",
940
+ description: "Summarize recent activity from timeline_feed.",
941
+ argsSchema: {
942
+ days: z.string().describe("Number of recent days to cover, for example `7`."),
943
+ platform: z.string().optional().describe("Optional platform filter."),
944
+ participant: z.string().optional().describe("Optional participant filter."),
945
+ },
946
+ }, async ({ days, platform, participant }) => ({
947
+ description: "Create a recent Memoark activity digest.",
948
+ messages: [
949
+ {
950
+ role: "user",
951
+ content: {
952
+ type: "text",
953
+ text: [
954
+ `Use \`timeline_feed\` to summarize the last ${days} days of Memoark activity.`,
955
+ "Use date filters if the client provides absolute dates.",
956
+ platform ? `Apply platform filter: ${platform}` : undefined,
957
+ participant ? `Apply participant filter: ${participant}` : undefined,
958
+ "Group results by project, decision, task, and open question.",
959
+ ]
960
+ .filter(Boolean)
961
+ .join("\n"),
962
+ },
963
+ },
964
+ ],
965
+ }));
966
+ server.registerPrompt("who-is", {
967
+ title: "Who Is",
968
+ description: "Build a person context brief from Memoark memory.",
969
+ argsSchema: {
970
+ person: z.string().describe("Person name or slug to investigate."),
971
+ },
972
+ }, async ({ person }) => ({
973
+ description: "Create a person brief.",
974
+ messages: [
975
+ {
976
+ role: "user",
977
+ content: {
978
+ type: "text",
979
+ text: `Use \`query\` with participant filter when helpful, then \`get_page_context\` for the best slug, to explain who ${person} is and how they relate to current work. Cite provenance.`,
980
+ },
981
+ },
982
+ ],
983
+ }));
984
+ server.registerPrompt("decision-log", {
985
+ title: "Decision Log",
986
+ description: "Collect decisions related to a topic.",
987
+ argsSchema: {
988
+ topic: z.string().describe("Decision topic, project, or keyword."),
989
+ from: z.string().optional().describe("Optional ISO lower time bound."),
990
+ to: z.string().optional().describe("Optional ISO upper time bound."),
991
+ },
992
+ }, async ({ topic, from, to }) => ({
993
+ description: "Create a decision log.",
994
+ messages: [
995
+ {
996
+ role: "user",
997
+ content: {
998
+ type: "text",
999
+ text: [
1000
+ `Use \`search\` for exact decision keywords and \`query\` for related decisions about: ${topic}.`,
1001
+ "Use type filters such as decision when appropriate.",
1002
+ from ? `from: ${from}` : undefined,
1003
+ to ? `to: ${to}` : undefined,
1004
+ "Return date, decision, rationale, status, and provenance.",
1005
+ ]
1006
+ .filter(Boolean)
1007
+ .join("\n"),
1008
+ },
1009
+ },
1010
+ ],
1011
+ }));
1012
+ server.registerPrompt("handoff", {
1013
+ title: "Project Handoff",
1014
+ description: "Prepare a project handoff brief using high-intent memory tools.",
1015
+ argsSchema: {
1016
+ project: z.string().describe("Project name or slug."),
1017
+ },
1018
+ }, async ({ project }) => ({
1019
+ description: "Create a project handoff brief.",
1020
+ messages: [
1021
+ {
1022
+ role: "user",
1023
+ content: {
1024
+ type: "text",
1025
+ text: `Use \`query\` to find the best project slug for ${project}, then use \`get_page_context\`, \`timeline_feed\`, and \`explore_graph\` to produce a handoff covering status, decisions, risks, owners, next steps, and provenance.`,
1026
+ },
1027
+ },
1028
+ ],
1029
+ }));
1030
+ }
1031
+ function registerMainTools(server, tools, ingestDeps) {
1032
+ server.registerTool("get_session_context", {
1033
+ title: "Get Session Context",
1034
+ description: description("get_session_context", "Load working memory for session bootstrap.\n\nWhen to use: at the start of every session to understand what is active and pending.\nReturns: recent activity summary for the given day window."),
1035
+ inputSchema: { days: z.number().optional().describe("Number of recent days, default 7.") },
1036
+ }, async (args) => text(await tools.get_session_context(args)));
1037
+ server.registerTool("list_signals_by_entity", {
1038
+ title: "List Signals by Entity",
1039
+ description: description("list_signals_by_entity", "List all signals anchored to an entity.\n\nWhen to use: retrieve decisions, tasks, knowledge, etc. for a specific person/project/tool.\nReturns: signals with type, title, and summary."),
1040
+ inputSchema: {
1041
+ entity_slug: z.string().describe("Entity slug, for example `entities/alice`."),
1042
+ signal_types: z.array(z.string()).optional().describe("Optional signal type filter."),
1043
+ limit: z.number().optional().describe("Maximum signals, default 20."),
1044
+ },
99
1045
  }, async (args) => text(await tools.list_signals_by_entity(args)));
100
- server.tool("get_entity_profile", { entity_slug: z.string() }, async (args) => text(await tools.get_entity_profile(args)));
1046
+ server.registerTool("get_entity_profile", {
1047
+ title: "Get Entity Profile",
1048
+ description: description("get_entity_profile", "Full profile for an entity: signals + timeline.\n\nWhen to use: need a comprehensive view of a person, project, or tool.\nReturns: entity page with all linked signals and timeline."),
1049
+ inputSchema: {
1050
+ entity_slug: z.string().describe("Entity slug, for example `entities/alice`."),
1051
+ },
1052
+ }, async (args) => text(await tools.get_entity_profile(args)));
1053
+ // ── Person identity (Layer 1: aliases / merge / rename) ──────────────
1054
+ const handleKind = z.enum(["feishu_open_id", "email", "name", "nickname", "slug"]);
1055
+ server.registerTool("link_person_alias", {
1056
+ title: "Link Person Alias",
1057
+ description: description("link_person_alias", "Add an alias handle to a canonical person page.\n\nWhen to use: link a new identifier (email, feishu_open_id, nickname) to a person.\nReturns: ok plus current handles."),
1058
+ inputSchema: {
1059
+ canonical_slug: z.string().describe("Canonical person page slug."),
1060
+ kind: handleKind.describe("Handle kind."),
1061
+ value: z.string().describe("Handle value."),
1062
+ strength: z
1063
+ .enum(["strong", "weak"])
1064
+ .optional()
1065
+ .describe("Handle strength, default strong."),
1066
+ },
1067
+ }, async (args) => text(await tools.link_person_alias(args)));
1068
+ server.registerTool("list_person_handles", {
1069
+ title: "List Person Handles",
1070
+ description: description("list_person_handles", "List all handles for a canonical person.\n\nWhen to use: check known aliases for a person.\nReturns: array of handles with kind, value, and strength."),
1071
+ inputSchema: {
1072
+ canonical_slug: z.string().describe("Canonical person page slug."),
1073
+ },
1074
+ }, async (args) => text(await tools.list_person_handles(args)));
1075
+ server.registerTool("remove_person_alias", {
1076
+ title: "Remove Person Alias",
1077
+ description: description("remove_person_alias", "Remove a handle from any person.\n\nWhen to use: correct a wrong alias assignment.\nReturns: ok."),
1078
+ inputSchema: {
1079
+ kind: handleKind.describe("Handle kind."),
1080
+ value: z.string().describe("Handle value to remove."),
1081
+ },
1082
+ }, async (args) => text(await tools.remove_person_alias(args)));
1083
+ server.registerTool("merge_persons", {
1084
+ title: "Merge Persons",
1085
+ description: description("merge_persons", "Merge one person into another, moving all handles.\n\nWhen to use: two person pages represent the same individual.\nReturns: ok plus merged handles."),
1086
+ inputSchema: {
1087
+ from: z.string().describe("Source person slug to merge from."),
1088
+ into: z.string().describe("Target person slug to merge into."),
1089
+ },
1090
+ }, async (args) => text(await tools.merge_persons(args)));
1091
+ server.registerTool("recanonicalize_person", {
1092
+ title: "Recanonicalize Person",
1093
+ description: description("recanonicalize_person", "Rename a person's canonical slug.\n\nWhen to use: correct or update a person's canonical page slug.\nReturns: ok plus updated handles."),
1094
+ inputSchema: {
1095
+ from: z.string().describe("Current canonical slug."),
1096
+ to: z.string().describe("New canonical slug."),
1097
+ },
1098
+ }, async (args) => text(await tools.recanonicalize_person(args)));
1099
+ if (ingestDeps) {
1100
+ server.registerTool("ingest_feishu_doc", {
1101
+ title: "Ingest Feishu Document",
1102
+ description: description("ingest_feishu_doc", "Ingest a Feishu document into Memoark.\n\nWhen to use: import a Feishu doc by URL or token.\nReturns: ingest result with slug and status."),
1103
+ inputSchema: {
1104
+ url_or_token: z.string().describe("Feishu document URL or token."),
1105
+ note: z.string().optional().describe("Optional note to attach."),
1106
+ tags: z.array(z.string()).optional().describe("Optional tags to apply."),
1107
+ force_refresh: z.boolean().optional().describe("Force re-ingest even if cached."),
1108
+ },
1109
+ }, async (args) => {
1110
+ const TIMEOUT_MS = 15_000;
1111
+ let timer;
1112
+ const timeout = new Promise((resolve) => {
1113
+ timer = setTimeout(() => resolve({
1114
+ ok: false,
1115
+ error: {
1116
+ code: "LLM_FAILED",
1117
+ doc_token: "",
1118
+ saved_as: "pointer",
1119
+ original_error: "timeout_15s",
1120
+ },
1121
+ }), TIMEOUT_MS);
1122
+ });
1123
+ const result = await Promise.race([ingestFeishuDoc(ingestDeps, args), timeout]);
1124
+ clearTimeout(timer);
1125
+ return text(result);
1126
+ });
1127
+ }
1128
+ }
1129
+ export function createMcpServer(stores, options = {}, ingestDeps) {
1130
+ const server = new McpServer({ name: "memoark", version: options.version ?? packageVersion }, { instructions: DIRECTIVE_L2 });
1131
+ const tools = createMcpToolHandlers(stores, {
1132
+ exposeLegacyTools: options.exposeLegacyTools ?? false,
1133
+ readOnly: options.readOnly ?? false,
1134
+ version: options.version ?? packageVersion,
1135
+ provider: options.provider,
1136
+ synthModel: options.synthModel,
1137
+ });
1138
+ registerPreferredTools(server, tools, options);
1139
+ registerMainTools(server, tools, ingestDeps);
1140
+ if (options.exposeLegacyTools)
1141
+ registerLegacyTools(server, tools, options);
1142
+ registerResources(server, tools);
1143
+ registerPrompts(server);
101
1144
  return server;
102
1145
  }
103
1146
  //# sourceMappingURL=mcp.js.map