ocean-brain 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/README.md +115 -0
  2. package/dist/mcp.js +205 -294
  3. package/package.json +7 -6
  4. package/server/client/dist/assets/ArrowRight.es-Bm-ZXpEC.js +1 -0
  5. package/server/client/dist/assets/Calendar-BsNs3NzO.js +25 -0
  6. package/server/client/dist/assets/Callout-CviyPHKO.js +1 -0
  7. package/server/client/dist/assets/Copy.es-BPreRnP9.js +1 -0
  8. package/server/client/dist/assets/Graph-vXQQ0a-Q.js +1 -0
  9. package/server/client/dist/assets/Graph-yPE3ljW0.js +46 -0
  10. package/server/client/dist/assets/Image.es-DnR3qc_c.js +1 -0
  11. package/server/client/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  12. package/server/client/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  13. package/server/client/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  14. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  15. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  16. package/server/client/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  17. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  18. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  19. package/server/client/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  20. package/server/client/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  21. package/server/client/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  22. package/server/client/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  23. package/server/client/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  24. package/server/client/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  25. package/server/client/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  26. package/server/client/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  27. package/server/client/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  28. package/server/client/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  29. package/server/client/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  30. package/server/client/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  31. package/server/client/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  32. package/server/client/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  33. package/server/client/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  34. package/server/client/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  35. package/server/client/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  36. package/server/client/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  37. package/server/client/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  38. package/server/client/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  39. package/server/client/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  40. package/server/client/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  41. package/server/client/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  42. package/server/client/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  43. package/server/client/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  44. package/server/client/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  45. package/server/client/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  46. package/server/client/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  47. package/server/client/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  48. package/server/client/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  49. package/server/client/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  50. package/server/client/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  51. package/server/client/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  52. package/server/client/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  53. package/server/client/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  54. package/server/client/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  55. package/server/client/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  56. package/server/client/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  57. package/server/client/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  58. package/server/client/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  59. package/server/client/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  60. package/server/client/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  61. package/server/client/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  62. package/server/client/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  63. package/server/client/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  64. package/server/client/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  65. package/server/client/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  66. package/server/client/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  67. package/server/client/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  68. package/server/client/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  69. package/server/client/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  70. package/server/client/dist/assets/Note-CmreVzvQ.js +25 -0
  71. package/server/client/dist/assets/NotePropertiesPanel-DXYgE8h2.js +2 -0
  72. package/server/client/dist/assets/PencilLine.es-B9V6u64t.js +1 -0
  73. package/server/client/dist/assets/Plus.es-CRRp2u5J.js +1 -0
  74. package/server/client/dist/assets/Reminders-BOtfNeYD.js +1 -0
  75. package/server/client/dist/assets/Search-C-FB3pKf.js +1 -0
  76. package/server/client/dist/assets/SurfaceCard-GMt5xM7v.js +1 -0
  77. package/server/client/dist/assets/Tag-lBxDRE77.js +1 -0
  78. package/server/client/dist/assets/TagNotes-Bgorp_rC.js +1 -0
  79. package/server/client/dist/assets/Trash.es-svHOb5LN.js +1 -0
  80. package/server/client/dist/assets/ViewNotes-C7d10VFf.js +1 -0
  81. package/server/client/dist/assets/ViewSectionDialog-Bhw-ILop.js +102 -0
  82. package/server/client/dist/assets/Views-C7V3saUo.js +1 -0
  83. package/server/client/dist/assets/abnfDiagram-VRR7QNED-CWlV5dXw.js +1 -0
  84. package/server/client/dist/assets/app-DQBV36r_.js +50 -0
  85. package/server/client/dist/assets/app-ZgwR5t_I.css +1 -0
  86. package/server/client/dist/assets/arc-BV-n6RUX.js +1 -0
  87. package/server/client/dist/assets/architectureDiagram-ZJ3FMSHR-CekzMXdu.js +36 -0
  88. package/server/client/dist/assets/blockDiagram-677ZJIJ3-B49yHoxq.js +132 -0
  89. package/server/client/dist/assets/c4Diagram-LMCZKHZV-0AfB9NBC.js +10 -0
  90. package/server/client/dist/assets/channel-CigOulR-.js +1 -0
  91. package/server/client/dist/assets/chunk-2Q5K7J3B-DaPQAU1q.js +1 -0
  92. package/server/client/dist/assets/chunk-32BRIVSS-CfL3kZAg.js +1 -0
  93. package/server/client/dist/assets/chunk-5VM5RSS4-DeOwWPE_.js +15 -0
  94. package/server/client/dist/assets/chunk-EX3LRPZG-C3W1oc9y.js +231 -0
  95. package/server/client/dist/assets/chunk-JWPE2WC7-_Oqh79OY.js +1 -0
  96. package/server/client/dist/assets/chunk-MOJQB5TN-DDKNNzI6.js +88 -0
  97. package/server/client/dist/assets/chunk-RYQCIY6F-DCHah7CC.js +1 -0
  98. package/server/client/dist/assets/chunk-V7JOEXUC-uUOIzgxj.js +206 -0
  99. package/server/client/dist/assets/chunk-VR4S4FIN-DDVw2WPs.js +1 -0
  100. package/server/client/dist/assets/chunk-XXDRQBXY-Coo4UFm2.js +1 -0
  101. package/server/client/dist/assets/classDiagram-OUVF2IWQ-D13ISIOW.js +1 -0
  102. package/server/client/dist/assets/classDiagram-v2-EOCWNBFH-D13ISIOW.js +1 -0
  103. package/server/client/dist/assets/colors-Cc3OSVma.js +1 -0
  104. package/server/client/dist/assets/cose-bilkent-JH36ORCC-C3pHmHpD.js +1 -0
  105. package/server/client/dist/assets/cynefin-VYW2F7L2-DU_G4Atc.js +166 -0
  106. package/server/client/dist/assets/cynefinDiagram-TSTJHNR4-DZ6iiJFo.js +62 -0
  107. package/server/client/dist/assets/cytoscape.esm-D3_iZ_3b.js +321 -0
  108. package/server/client/dist/assets/dagre-VKFMJZFB-DNELbhwC.js +4 -0
  109. package/server/client/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  110. package/server/client/dist/assets/diagram-FQU43EPY-CMEvZJ61.js +3 -0
  111. package/server/client/dist/assets/diagram-G47NLZAW-BYDtf6j9.js +24 -0
  112. package/server/client/dist/assets/diagram-NH7WQ7WH-CmXfcCQA.js +24 -0
  113. package/server/client/dist/assets/diagram-OA4YK3LP-BflTKJxU.js +30 -0
  114. package/server/client/dist/assets/diagram-WEI45ONY-KW3fKqna.js +41 -0
  115. package/server/client/dist/assets/ebnfDiagram-CCIWWBDH-CbyhY5Nj.js +1 -0
  116. package/server/client/dist/assets/erDiagram-Q63AITRT-CC2eZV8g.js +85 -0
  117. package/server/client/dist/assets/floating-ui.react-dom-BWTnA00G.js +1 -0
  118. package/server/client/dist/assets/flowDiagram-23GEKE2U-unplo7Qm.js +156 -0
  119. package/server/client/dist/assets/ganttDiagram-NO4QXBWP-BcBK5HcZ.js +292 -0
  120. package/server/client/dist/assets/gitGraphDiagram-IHSO6WYX-DgVbbb5k.js +106 -0
  121. package/server/client/dist/assets/graph-C5DFNLrC.js +1 -0
  122. package/server/client/dist/assets/image-delete-confirmation-D7xrODQV.js +1 -0
  123. package/server/client/dist/assets/image.api-QmHYjp3z.js +17 -0
  124. package/server/client/dist/assets/index-CaF9d-zG.js +1 -0
  125. package/server/client/dist/assets/infoDiagram-FWYZ7A6U-N_D9RdfG.js +2 -0
  126. package/server/client/dist/assets/init-Gi6I4Gst.js +1 -0
  127. package/server/client/dist/assets/isObject-U9U2HXiq.js +1 -0
  128. package/server/client/dist/assets/ishikawaDiagram-FXEZZL3T-BBKrzmB2.js +70 -0
  129. package/server/client/dist/assets/journeyDiagram-5HDEW3XC-NdCxLmQB.js +139 -0
  130. package/server/client/dist/assets/kanban-definition-HUTT4EX6-Ck7t4-tt.js +89 -0
  131. package/server/client/dist/assets/katex-DywYRkvg.css +1 -0
  132. package/server/client/dist/assets/katex-HP8lGamR.js +257 -0
  133. package/server/client/dist/assets/katex-x288kqHd.js +257 -0
  134. package/server/client/dist/assets/layout-Cz0MDlGi.js +1 -0
  135. package/server/client/dist/assets/linear-D6yRgDLq.js +1 -0
  136. package/server/client/dist/assets/manage-image-DCDF8AM9.js +1 -0
  137. package/server/client/dist/assets/manage-image-detail-e0ViRAj-.js +1 -0
  138. package/server/client/dist/assets/map-Mm1V-_Na.js +1 -0
  139. package/server/client/dist/assets/mcp-admin-adapter-D39G3udJ.js +1 -0
  140. package/server/client/dist/assets/{mcp-Dd98N9Hq.js → mcp-uFZkdhCi.js} +4 -4
  141. package/server/client/dist/assets/mermaid.core-B14ZAmWA.js +308 -0
  142. package/server/client/dist/assets/min-tv6RD-6E.js +1 -0
  143. package/server/client/dist/assets/mindmap-definition-LN4V7U3C-BTUqSdcP.js +96 -0
  144. package/server/client/dist/assets/note-core-DUTqUh5N.js +13 -0
  145. package/server/client/dist/assets/note-runtime-zN5ddTba.js +168 -0
  146. package/server/client/dist/assets/note-ui-CxK8ymUc.js +2 -0
  147. package/server/client/dist/assets/{index-DPI6YoDt.css → note-ui-DL8owV-U.css} +1 -1
  148. package/server/client/dist/assets/note.api-BQovzEzd.js +356 -0
  149. package/server/client/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  150. package/server/client/dist/assets/pegDiagram-2B236MQR-Cjcu3mVz.js +1 -0
  151. package/server/client/dist/assets/pieDiagram-ENE6RG2P-BxI_c22Z.js +39 -0
  152. package/server/client/dist/assets/placeholder-D3b8lZGE.js +34 -0
  153. package/server/client/dist/assets/preload-helper-BXl3LOEh.js +1 -0
  154. package/server/client/dist/assets/properties-M-yWq1D7.js +1 -0
  155. package/server/client/dist/assets/quadrantDiagram-ABIIQ3AL-DGm0a-yR.js +7 -0
  156. package/server/client/dist/assets/railroadDiagram-RFXS5EU6-DmCDu89O.js +1 -0
  157. package/server/client/dist/assets/requirementDiagram-TGXJPOKE-CBpUt7Ig.js +84 -0
  158. package/server/client/dist/assets/route-preload.js +2 -0
  159. package/server/client/dist/assets/sankeyDiagram-HTMAVEWB-BjGnDZlp.js +40 -0
  160. package/server/client/dist/assets/search-DFnzZKLd.js +1 -0
  161. package/server/client/dist/assets/sequenceDiagram-DBY2YBRQ-B7rpCfS6.js +162 -0
  162. package/server/client/dist/assets/sizeCapture-X5ZJPWSS-CUJhD0uW.js +1 -0
  163. package/server/client/dist/assets/stateDiagram-2N3HPSRC-Y9-3XqRX.js +1 -0
  164. package/server/client/dist/assets/stateDiagram-v2-6OUMAXLB-DbHXhT02.js +1 -0
  165. package/server/client/dist/assets/swimlanes-5IMT3BWC-Cs1NO9ng.js +2 -0
  166. package/server/client/dist/assets/swimlanesDiagram-G3AALYLV-DQnrq2ZC.js +8 -0
  167. package/server/client/dist/assets/{tag.api-CwmC8crB.js → tag.api-DUfjr8bg.js} +3 -3
  168. package/server/client/dist/assets/time-CfAYZipR.js +9 -0
  169. package/server/client/dist/assets/timeline-definition-FHXFAJF6-8fqD4WNL.js +120 -0
  170. package/server/client/dist/assets/toNumber-DE3J3kMD.js +1 -0
  171. package/server/client/dist/assets/transform-BUB79_za.js +1 -0
  172. package/server/client/dist/assets/trash-CClKfP1x.js +1 -0
  173. package/server/client/dist/assets/url-9nrbQs7Z.js +1 -0
  174. package/server/client/dist/assets/{useReminderMutate-DuVKxpBh.js → useReminderMutate--omDLmBV.js} +2 -2
  175. package/server/client/dist/assets/vennDiagram-L72KCM5P-BPqKkd5a.js +34 -0
  176. package/server/client/dist/assets/wardleyDiagram-EHGQE667-Cq11kU2M.js +78 -0
  177. package/server/client/dist/assets/xychartDiagram-FW5EYKEG-DJ6UzFj-.js +7 -0
  178. package/server/client/dist/index.html +10 -5
  179. package/server/dist/app.js +1 -1
  180. package/server/dist/features/image/services/delete.js +25 -1
  181. package/server/dist/features/note/graphql/note.mutation.resolver.js +38 -12
  182. package/server/dist/features/note/graphql/note.type-defs.js +1 -1
  183. package/server/dist/features/note/http/mcp.js +1 -60
  184. package/server/dist/features/note/services/authoring.js +2 -70
  185. package/server/dist/features/note/services/markdown-patch.js +80 -8
  186. package/server/dist/features/note/services/properties.js +21 -1
  187. package/server/dist/features/note/services/property-restore.js +26 -0
  188. package/server/dist/features/note/services/snapshot.js +79 -59
  189. package/server/dist/features/note/services/trash.js +16 -31
  190. package/server/dist/features/search/embedding-api-key-store.js +57 -0
  191. package/server/dist/features/search/embedding-client.js +179 -0
  192. package/server/dist/features/search/graphql/index.js +6 -0
  193. package/server/dist/features/search/graphql/resolver.js +20 -0
  194. package/server/dist/features/search/graphql/type-defs.js +30 -0
  195. package/server/dist/features/search/http/handlers.js +108 -0
  196. package/server/dist/features/search/hybrid-ranking.js +55 -0
  197. package/server/dist/features/search/hybrid-search.js +174 -0
  198. package/server/dist/features/search/note-change.js +16 -0
  199. package/server/dist/features/search/note-chunking.js +75 -0
  200. package/server/dist/features/search/search-config.js +150 -0
  201. package/server/dist/features/search/search-manager.js +431 -0
  202. package/server/dist/features/search/semantic-indexer.js +138 -0
  203. package/server/dist/features/search/sqlite-vector-index.js +636 -0
  204. package/server/dist/features/search/url-normalization.js +10 -0
  205. package/server/dist/features/tag/services/organization.js +3 -0
  206. package/server/dist/features/view/services/workspace.js +1 -1
  207. package/server/dist/main.js +2 -0
  208. package/server/dist/modules/blocknote.js +266 -80
  209. package/server/dist/modules/error-handler.js +8 -0
  210. package/server/dist/paths.js +7 -2
  211. package/server/dist/routes/api.js +37 -0
  212. package/server/dist/routes/mcp.js +0 -5
  213. package/server/dist/schema/index.js +3 -0
  214. package/server/client/dist/assets/ArrowRight.es-CbA8ey6a.js +0 -1
  215. package/server/client/dist/assets/Calendar-CgWgr1BV.js +0 -25
  216. package/server/client/dist/assets/Callout-DKruIT0f.js +0 -1
  217. package/server/client/dist/assets/Copy.es-DFbgpaNJ.js +0 -1
  218. package/server/client/dist/assets/Graph-kCltGRlY.js +0 -1
  219. package/server/client/dist/assets/Image-7W0DZwMo.js +0 -1
  220. package/server/client/dist/assets/Image.es-5pXLx9TU.js +0 -1
  221. package/server/client/dist/assets/Note-BW6Np_5g.js +0 -20
  222. package/server/client/dist/assets/Plus.es-B0sg64VJ.js +0 -1
  223. package/server/client/dist/assets/Reminders-C10fHQcl.js +0 -1
  224. package/server/client/dist/assets/Search-tf2nV4v0.js +0 -1
  225. package/server/client/dist/assets/SurfaceCard-pydtvHfu.js +0 -1
  226. package/server/client/dist/assets/Tag-uCoYbISy.js +0 -1
  227. package/server/client/dist/assets/TagNotes-CqOcQiRw.js +0 -1
  228. package/server/client/dist/assets/Trash.es-w32Goxfa.js +0 -1
  229. package/server/client/dist/assets/ViewNotes-slPXXoOY.js +0 -1
  230. package/server/client/dist/assets/ViewSectionDialog-brTrsF6G.js +0 -102
  231. package/server/client/dist/assets/Views-B_wsb-XJ.js +0 -1
  232. package/server/client/dist/assets/graph-vendor-BAF95C0g.js +0 -46
  233. package/server/client/dist/assets/image.api-CYsjXiMr.js +0 -17
  234. package/server/client/dist/assets/index-BR05aSXr.js +0 -342
  235. package/server/client/dist/assets/index-D83QbOcw.js +0 -1
  236. package/server/client/dist/assets/manage-image-BYIhWu56.js +0 -1
  237. package/server/client/dist/assets/manage-image-detail-6KgmQvbN.js +0 -1
  238. package/server/client/dist/assets/mcp-admin-adapter-DOgBsYpl.js +0 -1
  239. package/server/client/dist/assets/note-core-CMGUaW1v.js +0 -176
  240. package/server/client/dist/assets/note-vendor-Hoz9Q4-4.js +0 -54
  241. package/server/client/dist/assets/placeholder-siHGg9aK.js +0 -34
  242. package/server/client/dist/assets/properties-CyVRKgcT.js +0 -1
  243. package/server/client/dist/assets/trash-wojQBK9Z.js +0 -1
package/dist/mcp.js CHANGED
@@ -6,7 +6,7 @@ import path from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
- import { z as z2 } from "zod";
9
+ import { z as z3 } from "zod";
10
10
 
11
11
  // src/mcp-note-output.ts
12
12
  var formatBackReferenceLink = (backReference) => {
@@ -58,65 +58,86 @@ var formatMcpReadNoteOutput = ({
58
58
  };
59
59
 
60
60
  // src/mcp-intent-write-tools.ts
61
+ import { z as z2 } from "zod";
62
+
63
+ // src/mcp-tool-support.ts
61
64
  import { z } from "zod";
62
- var markdownPatchSelectorSchema = z.discriminatedUnion("type", [
63
- z.object({
64
- type: z.literal("exact_text"),
65
- text: z.string(),
66
- before: z.string().optional(),
67
- after: z.string().optional()
65
+ var noteLayoutSchema = z.enum(["narrow", "wide", "full"]);
66
+ var createMcpTextToolResult = (text) => ({
67
+ content: [
68
+ {
69
+ type: "text",
70
+ text
71
+ }
72
+ ]
73
+ });
74
+ var createMcpJsonToolResult = (value) => {
75
+ return createMcpTextToolResult(JSON.stringify(value, null, 2));
76
+ };
77
+
78
+ // src/mcp-intent-write-tools.ts
79
+ var markdownPatchSelectorSchema = z2.discriminatedUnion("type", [
80
+ z2.object({
81
+ type: z2.literal("exact_text"),
82
+ text: z2.string(),
83
+ before: z2.string().optional(),
84
+ after: z2.string().optional()
68
85
  }),
69
- z.object({
70
- type: z.literal("match_candidate"),
71
- text: z.string(),
72
- matchIndex: z.number().int().nonnegative(),
73
- lineStart: z.number().int().positive(),
74
- matchSha256: z.string(),
75
- surroundingHash: z.string(),
76
- positionHint: z.enum(["first", "last"]).optional()
86
+ z2.object({
87
+ type: z2.literal("match_candidate"),
88
+ text: z2.string(),
89
+ matchIndex: z2.number().int().nonnegative(),
90
+ lineStart: z2.number().int().positive(),
91
+ matchSha256: z2.string(),
92
+ surroundingHash: z2.string(),
93
+ positionHint: z2.enum(["first", "last"]).optional()
77
94
  })
78
95
  ]);
79
- var markdownPatchOperationSchema = z.discriminatedUnion("type", [
80
- z.object({
81
- type: z.literal("replace"),
82
- replacement: z.string()
96
+ var markdownPatchOperationSchema = z2.discriminatedUnion("type", [
97
+ z2.object({
98
+ type: z2.literal("replace"),
99
+ replacement: z2.string()
83
100
  }),
84
- z.object({
85
- type: z.literal("insert_before"),
86
- insertion: z.string()
101
+ z2.object({
102
+ type: z2.literal("insert_before"),
103
+ insertion: z2.string()
87
104
  }),
88
- z.object({
89
- type: z.literal("insert_after"),
90
- insertion: z.string()
105
+ z2.object({
106
+ type: z2.literal("insert_after"),
107
+ insertion: z2.string()
91
108
  })
92
109
  ]);
93
- var markdownAppendPlacementSchema = z.discriminatedUnion("type", [
94
- z.object({
95
- type: z.literal("end")
110
+ var markdownAppendPlacementSchema = z2.discriminatedUnion("type", [
111
+ z2.object({
112
+ type: z2.literal("end")
96
113
  }),
97
- z.object({
98
- type: z.literal("after_heading"),
99
- heading: z.string(),
100
- level: z.number().int().min(1).max(6).optional()
114
+ z2.object({
115
+ type: z2.literal("after_heading"),
116
+ heading: z2.string(),
117
+ level: z2.number().int().min(1).max(6).optional()
101
118
  })
102
119
  ]);
103
- var markdownWritePolicySchema = z.object({
104
- allowNoop: z.boolean().optional(),
105
- preserveTags: z.union([z.boolean(), z.literal("warn")]).optional(),
106
- preserveReferences: z.union([z.boolean(), z.literal("warn")]).optional()
120
+ var markdownWritePolicySchema = z2.object({
121
+ allowNoop: z2.boolean().optional(),
122
+ preserveTags: z2.union([z2.boolean(), z2.literal("warn")]).optional(),
123
+ preserveReferences: z2.union([z2.boolean(), z2.literal("warn")]).optional()
107
124
  }).optional();
125
+ var markdownWriteBaselineFields = {
126
+ expectedUpdatedAt: z2.string().optional().describe("Expected note updatedAt from the read you based this edit on. Required unless baseMarkdownSha256 is provided."),
127
+ baseMarkdownSha256: z2.string().optional().describe("SHA-256 of the current markdown. Optional alternative to expectedUpdatedAt.")
128
+ };
108
129
  var MCP_METADATA_PROPERTY_PATCH_LIMIT = 50;
109
130
  var normalizeMetadataPropertyKey = (key) => key.trim().toLowerCase().replace(/\s+/g, "-");
110
- var metadataPropertyPatchSchema = z.object({
111
- set: z.array(z.object({
112
- key: z.string().min(1).describe("Property key from ocean_brain_list_properties."),
113
- value: z.string().describe("String value: select option.value, date YYYY-MM-DD, boolean true/false, number finite string, url http(s).")
131
+ var metadataPropertyPatchSchema = z2.object({
132
+ set: z2.array(z2.object({
133
+ key: z2.string().min(1).describe("Property key from ocean_brain_list_properties."),
134
+ value: z2.string().describe("String value: select option.value, date YYYY-MM-DD, boolean true/false, number finite string, url http(s).")
114
135
  })).max(MCP_METADATA_PROPERTY_PATCH_LIMIT).optional().describe("Set up to 50 property values. Keys must be unique."),
115
- deleteKeys: z.array(z.string().min(1)).max(MCP_METADATA_PROPERTY_PATCH_LIMIT).optional().describe("Remove up to 50 property values from this note. Keys must be unique and not also in set.")
136
+ deleteKeys: z2.array(z2.string().min(1)).max(MCP_METADATA_PROPERTY_PATCH_LIMIT).optional().describe("Remove up to 50 property values from this note. Keys must be unique and not also in set.")
116
137
  }).superRefine((patch, context) => {
117
138
  if ((patch.set?.length ?? 0) === 0 && (patch.deleteKeys?.length ?? 0) === 0) {
118
139
  context.addIssue({
119
- code: z.ZodIssueCode.custom,
140
+ code: z2.ZodIssueCode.custom,
120
141
  message: "properties must include set or deleteKeys."
121
142
  });
122
143
  return;
@@ -126,7 +147,7 @@ var metadataPropertyPatchSchema = z.object({
126
147
  const key = normalizeMetadataPropertyKey(item.key);
127
148
  if (setKeys.has(key)) {
128
149
  context.addIssue({
129
- code: z.ZodIssueCode.custom,
150
+ code: z2.ZodIssueCode.custom,
130
151
  message: `properties.set contains duplicate key ${key}.`
131
152
  });
132
153
  }
@@ -137,13 +158,13 @@ var metadataPropertyPatchSchema = z.object({
137
158
  const key = normalizeMetadataPropertyKey(rawKey);
138
159
  if (deleteKeys.has(key)) {
139
160
  context.addIssue({
140
- code: z.ZodIssueCode.custom,
161
+ code: z2.ZodIssueCode.custom,
141
162
  message: `properties.deleteKeys contains duplicate key ${key}.`
142
163
  });
143
164
  }
144
165
  if (setKeys.has(key)) {
145
166
  context.addIssue({
146
- code: z.ZodIssueCode.custom,
167
+ code: z2.ZodIssueCode.custom,
147
168
  message: `properties cannot set and delete ${key} in one request.`
148
169
  });
149
170
  }
@@ -161,10 +182,9 @@ var registerIntentWriteTools = (server, {
161
182
  tools.patchNoteMarkdown,
162
183
  "Patch a specific part of a note, like finding text with `rg` and editing only that match. Use for localized edits: fix one sentence, replace one paragraph, insert before/after exact text, or edit one section. Do not use for whole-note rewrites; use ocean_brain_replace_note_markdown instead.",
163
184
  {
164
- id: z.string().describe("Note ID to patch"),
165
- expectedUpdatedAt: z.string().optional().describe("Expected note updatedAt from the read you based this edit on. Required unless baseMarkdownSha256 is provided."),
166
- baseMarkdownSha256: z.string().optional().describe("SHA-256 of the current markdown. Optional alternative to expectedUpdatedAt."),
167
- intent: z.string().describe("Human-readable reason for the patch."),
185
+ id: z2.string().describe("Note ID to patch"),
186
+ ...markdownWriteBaselineFields,
187
+ intent: z2.string().describe("Human-readable reason for the patch."),
168
188
  selector: markdownPatchSelectorSchema.describe("Exact text or previously returned match candidate."),
169
189
  operation: markdownPatchOperationSchema.describe("replace, insert_before, or insert_after."),
170
190
  policy: markdownWritePolicySchema
@@ -181,25 +201,19 @@ var registerIntentWriteTools = (server, {
181
201
  ...policy ? { policy } : {}
182
202
  };
183
203
  const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/patch-markdown", payload);
184
- return {
185
- content: [{
186
- type: "text",
187
- text: JSON.stringify(result, null, 2)
188
- }]
189
- };
204
+ return createMcpJsonToolResult(result);
190
205
  }
191
206
  );
192
207
  server.tool(
193
208
  tools.appendNoteMarkdown,
194
209
  "Append markdown without changing existing content, like `cat addition.md >> note.md`. Use for adding logs, status updates, meeting notes, or new sections at the end or after a heading. Do not use to modify or replace existing body content.",
195
210
  {
196
- id: z.string().describe("Note ID to append to"),
197
- expectedUpdatedAt: z.string().optional().describe("Expected note updatedAt from the read you based this edit on. Required unless baseMarkdownSha256 is provided."),
198
- baseMarkdownSha256: z.string().optional().describe("SHA-256 of the current markdown. Optional alternative to expectedUpdatedAt."),
199
- intent: z.string().describe("Human-readable reason for the append."),
200
- insertion: z.string().describe("Markdown to append. Tags are body tokens such as [@tag] or [#tag]."),
211
+ id: z2.string().describe("Note ID to append to"),
212
+ ...markdownWriteBaselineFields,
213
+ intent: z2.string().describe("Human-readable reason for the append."),
214
+ insertion: z2.string().describe("Markdown to append. Tags are body tokens such as [@tag]."),
201
215
  placement: markdownAppendPlacementSchema.optional().describe("Default is end. after_heading requires one unique matching heading."),
202
- separator: z.enum(["\n\n", "\n"]).optional().describe("Separator inserted around appended markdown. Defaults to a blank line."),
216
+ separator: z2.enum(["\n\n", "\n"]).optional().describe("Separator inserted around appended markdown. Defaults to a blank line."),
203
217
  policy: markdownWritePolicySchema
204
218
  },
205
219
  async ({ id, expectedUpdatedAt, baseMarkdownSha256, intent, insertion, placement, separator, policy }) => {
@@ -215,22 +229,17 @@ var registerIntentWriteTools = (server, {
215
229
  ...policy ? { policy } : {}
216
230
  };
217
231
  const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/append-markdown", payload);
218
- return {
219
- content: [{
220
- type: "text",
221
- text: JSON.stringify(result, null, 2)
222
- }]
223
- };
232
+ return createMcpJsonToolResult(result);
224
233
  }
225
234
  );
226
235
  server.tool(
227
236
  tools.updateNoteMetadata,
228
237
  "Update note metadata only: title, layout, or properties. This does not edit the note body. For properties, call ocean_brain_list_properties first. Use existing property key and string value only; do not send valueType. select=option.value, date=YYYY-MM-DD, boolean=true/false, number=finite string, url=http(s). Use deleteKeys to remove values. Definitions are not created.",
229
238
  {
230
- id: z.string().describe("Note ID to update"),
231
- expectedUpdatedAt: z.string().describe("Expected note updatedAt from the read you based this metadata edit on. Required to prevent stale writes."),
232
- title: z.string().optional().describe("New note title"),
233
- layout: z.enum(["narrow", "wide", "full"]).optional().describe("New note layout"),
239
+ id: z2.string().describe("Note ID to update"),
240
+ expectedUpdatedAt: z2.string().describe("Expected note updatedAt from the read you based this metadata edit on. Required to prevent stale writes."),
241
+ title: z2.string().optional().describe("New note title"),
242
+ layout: noteLayoutSchema.optional().describe("New note layout"),
234
243
  properties: metadataPropertyPatchSchema.describe("Patch existing shared property values. Include set and/or deleteKeys; empty patches are rejected.")
235
244
  },
236
245
  async ({ id, expectedUpdatedAt, title, layout, properties }) => {
@@ -243,23 +252,17 @@ var registerIntentWriteTools = (server, {
243
252
  ...properties ? { properties } : {}
244
253
  };
245
254
  const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/metadata", payload);
246
- return {
247
- content: [{
248
- type: "text",
249
- text: JSON.stringify(result, null, 2)
250
- }]
251
- };
255
+ return createMcpJsonToolResult(result);
252
256
  }
253
257
  );
254
258
  server.tool(
255
259
  tools.replaceNoteMarkdown,
256
260
  "Replace the entire note body, like overwriting a markdown file with `cat new.md > note.md`. Use when the user explicitly asks to rewrite, replace, regenerate, restructure, or overwrite the whole note. Do not use for localized edits such as fixing one sentence, changing one paragraph, or inserting under a heading; use ocean_brain_patch_note_markdown instead.",
257
261
  {
258
- id: z.string().describe("Note ID to replace"),
259
- expectedUpdatedAt: z.string().optional().describe("Expected note updatedAt from the read you based this edit on. Required unless baseMarkdownSha256 is provided."),
260
- baseMarkdownSha256: z.string().optional().describe("SHA-256 of the current markdown. Optional alternative to expectedUpdatedAt."),
261
- intent: z.string().describe("Human-readable reason for the full replace."),
262
- replacement: z.string().describe("Complete replacement markdown body. Tags are body tokens such as [@tag] or [#tag]."),
262
+ id: z2.string().describe("Note ID to replace"),
263
+ ...markdownWriteBaselineFields,
264
+ intent: z2.string().describe("Human-readable reason for the full replace."),
265
+ replacement: z2.string().describe("Complete replacement markdown body. Tags are body tokens such as [@tag]."),
263
266
  policy: markdownWritePolicySchema
264
267
  },
265
268
  async ({ id, expectedUpdatedAt, baseMarkdownSha256, intent, replacement, policy }) => {
@@ -273,12 +276,7 @@ var registerIntentWriteTools = (server, {
273
276
  ...policy ? { policy } : {}
274
277
  };
275
278
  const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/replace-markdown", payload);
276
- return {
277
- content: [{
278
- type: "text",
279
- text: JSON.stringify(result, null, 2)
280
- }]
281
- };
279
+ return createMcpJsonToolResult(result);
282
280
  }
283
281
  );
284
282
  };
@@ -331,7 +329,6 @@ var OCEAN_BRAIN_MCP_TOOLS = {
331
329
  searchNotes: "ocean_brain_search_notes",
332
330
  readNote: "ocean_brain_read_note",
333
331
  createNote: "ocean_brain_create_note",
334
- updateNote: "ocean_brain_update_note",
335
332
  patchNoteMarkdown: "ocean_brain_patch_note_markdown",
336
333
  appendNoteMarkdown: "ocean_brain_append_note_markdown",
337
334
  updateNoteMetadata: "ocean_brain_update_note_metadata",
@@ -346,12 +343,11 @@ var OCEAN_BRAIN_MCP_TOOLS = {
346
343
  createTag: "ocean_brain_create_tag",
347
344
  deleteNote: "ocean_brain_delete_note"
348
345
  };
349
- var noteLayoutSchema = z2.enum(["narrow", "wide", "full"]);
350
- var tagMatchModeSchema = z2.enum(["and", "or"]);
351
- var propertyValueTypeSchema = z2.enum(["text", "url", "number", "date", "boolean", "select"]);
352
- var propertyFilterOperatorSchema = z2.enum(["equals", "before", "after", "exists", "notExists"]);
353
- var viewSortBySchema = z2.enum(["updatedAt", "createdAt", "title"]);
354
- var viewSortOrderSchema = z2.enum(["asc", "desc"]);
346
+ var tagMatchModeSchema = z3.enum(["and", "or"]);
347
+ var propertyValueTypeSchema = z3.enum(["text", "url", "number", "date", "boolean", "select"]);
348
+ var propertyFilterOperatorSchema = z3.enum(["equals", "before", "after", "exists", "notExists"]);
349
+ var viewSortBySchema = z3.enum(["updatedAt", "createdAt", "title"]);
350
+ var viewSortOrderSchema = z3.enum(["asc", "desc"]);
355
351
  var formatMcpGraphqlError = (error) => {
356
352
  const suffix = [];
357
353
  if (error.extensions?.code) {
@@ -365,17 +361,20 @@ var formatMcpGraphqlError = (error) => {
365
361
  }
366
362
  return `GraphQL error: ${error.message} (${suffix.join(", ")})`;
367
363
  };
368
- var propertyFilterSchema = z2.object({
369
- key: z2.string().describe("Property key from ocean_brain_list_properties, e.g. state"),
364
+ var propertyFilterSchema = z3.object({
365
+ key: z3.string().describe("Property key from ocean_brain_list_properties, e.g. state"),
370
366
  valueType: propertyValueTypeSchema.describe("Property value type from the property definition"),
371
367
  operator: propertyFilterOperatorSchema.describe("Filter operator. before/after are only valid for date or number properties."),
372
- value: z2.string().nullable().optional().describe("Filter value. Required unless operator is exists or notExists. select=option.value, date=YYYY-MM-DD, boolean=true/false, number=finite, url=http(s).")
368
+ value: z3.string().nullable().optional().describe("Filter value. Required unless operator is exists or notExists. select=option.value, date=YYYY-MM-DD, boolean=true/false, number=finite, url=http(s).")
373
369
  });
374
370
  var normalizeOceanBrainTagName = (name) => {
375
371
  const trimmedName = name.trim();
376
372
  if (!trimmedName) {
377
373
  throw new Error("Tag name is required.");
378
374
  }
375
+ if (trimmedName.startsWith("#")) {
376
+ throw new Error("Ocean Brain tags use @, not #. Example: @project");
377
+ }
379
378
  const normalizedName = trimmedName.startsWith("@") ? trimmedName : `@${trimmedName}`;
380
379
  if (normalizedName === "@" || /\s/.test(normalizedName.slice(1))) {
381
380
  throw new Error("Ocean Brain tag names must be a single token. Example: @project");
@@ -441,17 +440,13 @@ var requireWriteToken = (token, toolName) => {
441
440
  }
442
441
  throw new Error(`${toolName} requires an MCP bearer token. Set --token or --token-file.`);
443
442
  };
444
- async function startMcpServer(serverUrl, token) {
445
- const server = new McpServer({
446
- name: "ocean-brain",
447
- version: pkg.version
448
- });
443
+ var registerMcpTools = (server, serverUrl, token) => {
449
444
  server.tool(
450
445
  OCEAN_BRAIN_MCP_TOOLS.searchNotes,
451
446
  "Search Ocean Brain notes by keyword. Returns matching note titles and tags. Use ocean_brain_read_note to get full content for a specific note.",
452
447
  {
453
- query: z2.string().describe("Search keyword"),
454
- limit: z2.number().optional().default(10).describe("Max results (default: 10)")
448
+ query: z3.string().describe("Search keyword"),
449
+ limit: z3.number().optional().default(10).describe("Max results (default: 10)")
455
450
  },
456
451
  async ({ query, limit }) => {
457
452
  const data = await graphql(serverUrl, token, `
@@ -479,20 +474,15 @@ async function startMcpServer(serverUrl, token) {
479
474
  tags: note.tags.map((t) => t.name),
480
475
  preview: note.contentAsMarkdown.slice(0, 100)
481
476
  }));
482
- return {
483
- content: [{
484
- type: "text",
485
- text: JSON.stringify({ totalCount: result.totalCount, notes }, null, 2)
486
- }]
487
- };
477
+ return createMcpJsonToolResult({ totalCount: result.totalCount, notes });
488
478
  }
489
479
  );
490
480
  server.tool(
491
481
  OCEAN_BRAIN_MCP_TOOLS.readNote,
492
482
  "Read an Ocean Brain note by ID. Returns properties, tags, truncated content by default (1000 chars), and a back-reference summary. Set maxLength to 0 only when full content is necessary.",
493
483
  {
494
- id: z2.string().describe("Note ID"),
495
- maxLength: z2.number().optional().default(1e3).describe("Max content length in characters. 0 for full content. (default: 1000)")
484
+ id: z3.string().describe("Note ID"),
485
+ maxLength: z3.number().optional().default(1e3).describe("Max content length in characters. 0 for full content. (default: 1000)")
496
486
  },
497
487
  async ({ id, maxLength }) => {
498
488
  const data = await graphql(serverUrl, token, `
@@ -519,20 +509,15 @@ async function startMcpServer(serverUrl, token) {
519
509
  backReferences,
520
510
  maxLength
521
511
  });
522
- return {
523
- content: [{
524
- type: "text",
525
- text: output
526
- }]
527
- };
512
+ return createMcpTextToolResult(output);
528
513
  }
529
514
  );
530
515
  server.tool(
531
516
  OCEAN_BRAIN_MCP_TOOLS.createNote,
532
517
  "Create an Ocean Brain note from markdown. Search/read first to avoid duplicates; prefer patching an existing note when the intent is a local change.",
533
518
  {
534
- title: z2.string().describe("Note title"),
535
- markdown: z2.string().optional().default("").describe("Markdown body for the new note. In MCP markdown, tags must use [@tag] or [#tag]. Defaults to an empty note body."),
519
+ title: z3.string().describe("Note title"),
520
+ markdown: z3.string().optional().default("").describe("Markdown body for the new note. In MCP markdown, tags must use [@tag]. Defaults to an empty note body."),
536
521
  layout: noteLayoutSchema.optional().describe("Optional note layout: narrow, wide, or full. Prefer wide for most notes unless the user explicitly wants narrow or full.")
537
522
  },
538
523
  async ({ title, markdown, layout }) => {
@@ -542,37 +527,7 @@ async function startMcpServer(serverUrl, token) {
542
527
  markdown,
543
528
  ...layout ? { layout } : {}
544
529
  });
545
- return {
546
- content: [{
547
- type: "text",
548
- text: JSON.stringify(result, null, 2)
549
- }]
550
- };
551
- }
552
- );
553
- server.tool(
554
- OCEAN_BRAIN_MCP_TOOLS.updateNote,
555
- "Legacy full-field note update kept for backward compatibility. Prefer ocean_brain_patch_note_markdown for localized body edits, ocean_brain_append_note_markdown for additions, ocean_brain_replace_note_markdown for whole-body rewrites, and ocean_brain_update_note_metadata for title/layout/properties. Use this only when a legacy client needs combined field updates.",
556
- {
557
- id: z2.string().describe("Note ID to update"),
558
- title: z2.string().optional().describe("New note title"),
559
- markdown: z2.string().optional().describe("Replacement markdown body. In MCP markdown, tags must use [@tag] or [#tag]."),
560
- layout: noteLayoutSchema.optional().describe("Optional note layout: narrow, wide, or full. Prefer wide for most notes unless the user explicitly wants narrow or full.")
561
- },
562
- async ({ id, title, markdown, layout }) => {
563
- const writeToken = requireWriteToken(token, OCEAN_BRAIN_MCP_TOOLS.updateNote);
564
- const result = await jsonRequest(serverUrl, writeToken, "/api/mcp/notes/update", {
565
- id,
566
- ...title !== void 0 ? { title } : {},
567
- ...markdown !== void 0 ? { markdown } : {},
568
- ...layout ? { layout } : {}
569
- });
570
- return {
571
- content: [{
572
- type: "text",
573
- text: JSON.stringify(result, null, 2)
574
- }]
575
- };
530
+ return createMcpJsonToolResult(result);
576
531
  }
577
532
  );
578
533
  registerIntentWriteTools(server, {
@@ -603,21 +558,16 @@ async function startMcpServer(serverUrl, token) {
603
558
  pagination: { limit: 100, offset: 0 }
604
559
  });
605
560
  const result = data?.allTags;
606
- return {
607
- content: [{
608
- type: "text",
609
- text: JSON.stringify(result, null, 2)
610
- }]
611
- };
561
+ return createMcpJsonToolResult(result);
612
562
  }
613
563
  );
614
564
  server.tool(
615
565
  OCEAN_BRAIN_MCP_TOOLS.listProperties,
616
566
  "List shared Ocean Brain property definitions. Use this before property queries so keys, value types, and select option values are valid.",
617
567
  {
618
- query: z2.string().optional().default("").describe("Optional property key/name search query"),
619
- limit: z2.number().optional().default(50).describe("Max results (default: 50, server max: 100)"),
620
- offset: z2.number().optional().default(0).describe("Pagination offset (default: 0)")
568
+ query: z3.string().optional().default("").describe("Optional property key/name search query"),
569
+ limit: z3.number().optional().default(50).describe("Max results (default: 50, server max: 100)"),
570
+ offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
621
571
  },
622
572
  async ({ query, limit, offset }) => {
623
573
  const data = await graphql(serverUrl, token, `
@@ -639,42 +589,32 @@ async function startMcpServer(serverUrl, token) {
639
589
  pagination: { limit, offset }
640
590
  });
641
591
  const result = data?.notePropertyKeys;
642
- return {
643
- content: [{
644
- type: "text",
645
- text: JSON.stringify({
646
- totalCount: result.totalCount,
647
- propertyKeys: result.keys
648
- }, null, 2)
649
- }]
650
- };
592
+ return createMcpJsonToolResult({
593
+ totalCount: result.totalCount,
594
+ propertyKeys: result.keys
595
+ });
651
596
  }
652
597
  );
653
598
  server.tool(
654
599
  OCEAN_BRAIN_MCP_TOOLS.listNotesByTag,
655
600
  "List Ocean Brain notes for a specific tag name. The tool accepts either @tag or tag and resolves it to the canonical Ocean Brain tag name first.",
656
601
  {
657
- tag: z2.string().describe("Tag name to inspect. You can pass @project or project."),
658
- limit: z2.number().optional().default(20).describe("Max results (default: 20)"),
659
- offset: z2.number().optional().default(0).describe("Pagination offset (default: 0)")
602
+ tag: z3.string().describe("Tag name to inspect. You can pass @project or project."),
603
+ limit: z3.number().optional().default(20).describe("Max results (default: 20)"),
604
+ offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
660
605
  },
661
606
  async ({ tag, limit, offset }) => {
662
607
  const normalizedTag = normalizeOceanBrainTagName(tag);
663
608
  const tagResult = await fetchOceanBrainTags(serverUrl, token, normalizedTag);
664
609
  const exactMatches = tagResult.tags.filter((item) => item.name === normalizedTag);
665
610
  if (exactMatches.length === 0) {
666
- return {
667
- content: [{
668
- type: "text",
669
- text: JSON.stringify({
670
- requestedTag: tag,
671
- normalizedTag,
672
- tagFound: false,
673
- noteCount: 0,
674
- notes: []
675
- }, null, 2)
676
- }]
677
- };
611
+ return createMcpJsonToolResult({
612
+ requestedTag: tag,
613
+ normalizedTag,
614
+ tagFound: false,
615
+ noteCount: 0,
616
+ notes: []
617
+ });
678
618
  }
679
619
  const selectedTag = exactMatches[0];
680
620
  const notesData = await graphql(serverUrl, token, `
@@ -694,35 +634,30 @@ async function startMcpServer(serverUrl, token) {
694
634
  pagination: { limit, offset }
695
635
  });
696
636
  const noteResult = notesData?.tagNotes;
697
- return {
698
- content: [{
699
- type: "text",
700
- text: JSON.stringify({
701
- requestedTag: tag,
702
- normalizedTag,
703
- tagFound: true,
704
- duplicateExactMatchCount: exactMatches.length,
705
- tag: selectedTag,
706
- totalCount: noteResult.totalCount,
707
- notes: noteResult.notes.map((note) => ({
708
- id: note.id,
709
- title: note.title,
710
- updatedAt: note.updatedAt,
711
- tags: note.tags.map((item) => item.name)
712
- }))
713
- }, null, 2)
714
- }]
715
- };
637
+ return createMcpJsonToolResult({
638
+ requestedTag: tag,
639
+ normalizedTag,
640
+ tagFound: true,
641
+ duplicateExactMatchCount: exactMatches.length,
642
+ tag: selectedTag,
643
+ totalCount: noteResult.totalCount,
644
+ notes: noteResult.notes.map((note) => ({
645
+ id: note.id,
646
+ title: note.title,
647
+ updatedAt: note.updatedAt,
648
+ tags: note.tags.map((item) => item.name)
649
+ }))
650
+ });
716
651
  }
717
652
  );
718
653
  server.tool(
719
654
  OCEAN_BRAIN_MCP_TOOLS.listNotesByTags,
720
655
  "List Ocean Brain notes for multiple tag names. Supports AND/OR matching and resolves each input tag to the canonical Ocean Brain tag name first.",
721
656
  {
722
- tags: z2.array(z2.string()).min(1).describe("Tag names to inspect. You can pass @project or project values."),
657
+ tags: z3.array(z3.string()).min(1).describe("Tag names to inspect. You can pass @project or project values."),
723
658
  mode: tagMatchModeSchema.optional().default("and").describe("Tag match mode. Use and for intersection, or for union. Defaults to and."),
724
- limit: z2.number().optional().default(20).describe("Max results (default: 20)"),
725
- offset: z2.number().optional().default(0).describe("Pagination offset (default: 0)")
659
+ limit: z3.number().optional().default(20).describe("Max results (default: 20)"),
660
+ offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
726
661
  },
727
662
  async ({ tags, mode, limit, offset }) => {
728
663
  const normalizedTags = normalizeOceanBrainTagNames(tags);
@@ -770,44 +705,40 @@ async function startMcpServer(serverUrl, token) {
770
705
  });
771
706
  noteResult = notesData?.notesByTagNames;
772
707
  }
773
- return {
774
- content: [{
775
- type: "text",
776
- text: JSON.stringify({
777
- requestedTags: tags,
778
- normalizedTags,
779
- mode,
780
- allTagsFound: missingTags.length === 0,
781
- missingTags,
782
- tags: matchedTags,
783
- totalCount: noteResult.totalCount,
784
- notes: noteResult.notes.map((note) => ({
785
- id: note.id,
786
- title: note.title,
787
- updatedAt: note.updatedAt,
788
- tags: note.tags.map((item) => item.name)
789
- }))
790
- }, null, 2)
791
- }]
792
- };
708
+ return createMcpJsonToolResult({
709
+ requestedTags: tags,
710
+ normalizedTags,
711
+ mode,
712
+ allTagsFound: missingTags.length === 0,
713
+ missingTags,
714
+ tags: matchedTags,
715
+ totalCount: noteResult.totalCount,
716
+ notes: noteResult.notes.map((note) => ({
717
+ id: note.id,
718
+ title: note.title,
719
+ updatedAt: note.updatedAt,
720
+ tags: note.tags.map((item) => item.name)
721
+ }))
722
+ });
793
723
  }
794
724
  );
795
725
  server.tool(
796
726
  OCEAN_BRAIN_MCP_TOOLS.queryNotesByProperties,
797
727
  "Call ocean_brain_list_properties first. Requires >=1 propertyFilter; use search/recent for broad lists. Use key/valueType from the property definition. Values are strings: select=option.value, date=YYYY-MM-DD, boolean=true/false, number=finite, url=http(s). exists/notExists need no value. Property filters use AND; tagNames use mode. Summaries are returned by default; use propertyKeys for needed property details.",
798
728
  {
799
- propertyFilters: z2.array(propertyFilterSchema).min(1).max(10).describe("Required property filters. Multiple property filters are combined with AND."),
800
- tagNames: z2.array(z2.string()).optional().default([]).describe("Optional tag filters. You can pass @project, #project, or project."),
729
+ propertyFilters: z3.array(propertyFilterSchema).min(1).max(10).describe("Required property filters. Multiple property filters are combined with AND."),
730
+ tagNames: z3.array(z3.string()).optional().default([]).describe("Optional tag filters. You can pass @project or project."),
801
731
  mode: tagMatchModeSchema.optional().default("and").describe("Tag match mode for tagNames only. Property filters are always combined with AND."),
802
732
  sortBy: viewSortBySchema.optional().default("updatedAt").describe("Sort field (default: updatedAt)"),
803
733
  sortOrder: viewSortOrderSchema.optional().default("desc").describe("Sort order (default: desc)"),
804
- includeProperties: z2.boolean().optional().default(false).describe("Include returned note properties. Defaults to false to reduce tokens."),
805
- propertyKeys: z2.array(z2.string()).optional().default([]).describe("Property keys to include in output; automatically includes properties."),
806
- limit: z2.number().optional().default(20).describe("Max results (default: 20, server max: 50)"),
807
- offset: z2.number().optional().default(0).describe("Pagination offset (default: 0)")
734
+ includeProperties: z3.boolean().optional().default(false).describe("Include returned note properties. Defaults to false to reduce tokens."),
735
+ propertyKeys: z3.array(z3.string()).optional().default([]).describe("Property keys to include in output; automatically includes properties."),
736
+ limit: z3.number().optional().default(20).describe("Max results (default: 20, server max: 50)"),
737
+ offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
808
738
  },
809
739
  async ({ propertyFilters, tagNames, mode, sortBy, sortOrder, includeProperties, propertyKeys, limit, offset }) => {
810
740
  const shouldIncludeProperties = includeProperties || propertyKeys.length > 0;
741
+ const normalizedTagNames = normalizeOceanBrainTagNames(tagNames);
811
742
  const data = await graphql(serverUrl, token, `
812
743
  query ($input: NotesByPropertiesInput!, $pagination: PaginationInput, $includeProperties: Boolean!) {
813
744
  notesByProperties(input: $input, pagination: $pagination) {
@@ -824,7 +755,7 @@ async function startMcpServer(serverUrl, token) {
824
755
  }
825
756
  `, {
826
757
  input: {
827
- tagNames,
758
+ tagNames: normalizedTagNames,
828
759
  mode,
829
760
  propertyFilters,
830
761
  sortBy,
@@ -834,36 +765,27 @@ async function startMcpServer(serverUrl, token) {
834
765
  includeProperties: shouldIncludeProperties
835
766
  });
836
767
  const result = data?.notesByProperties;
837
- return {
838
- content: [{
839
- type: "text",
840
- text: JSON.stringify(
841
- formatPropertyQueryResponse({
842
- result,
843
- query: {
844
- propertyFilters,
845
- tagNames,
846
- mode,
847
- sortBy,
848
- sortOrder,
849
- limit,
850
- offset
851
- },
852
- includeProperties: shouldIncludeProperties,
853
- propertyKeys
854
- }),
855
- null,
856
- 2
857
- )
858
- }]
859
- };
768
+ return createMcpJsonToolResult(formatPropertyQueryResponse({
769
+ result,
770
+ query: {
771
+ propertyFilters,
772
+ tagNames: normalizedTagNames,
773
+ mode,
774
+ sortBy,
775
+ sortOrder,
776
+ limit,
777
+ offset
778
+ },
779
+ includeProperties: shouldIncludeProperties,
780
+ propertyKeys
781
+ }));
860
782
  }
861
783
  );
862
784
  server.tool(
863
785
  OCEAN_BRAIN_MCP_TOOLS.listRecentNotes,
864
786
  "List recently updated Ocean Brain notes. Returns titles and tags only. Use ocean_brain_read_note to get full content for a specific note.",
865
787
  {
866
- limit: z2.number().optional().default(10).describe("Max results (default: 10)")
788
+ limit: z3.number().optional().default(10).describe("Max results (default: 10)")
867
789
  },
868
790
  async ({ limit }) => {
869
791
  const data = await graphql(serverUrl, token, `
@@ -889,21 +811,16 @@ async function startMcpServer(serverUrl, token) {
889
811
  updatedAt: note.updatedAt,
890
812
  tags: note.tags.map((t) => t.name)
891
813
  }));
892
- return {
893
- content: [{
894
- type: "text",
895
- text: JSON.stringify({ totalCount: result.totalCount, notes }, null, 2)
896
- }]
897
- };
814
+ return createMcpJsonToolResult({ totalCount: result.totalCount, notes });
898
815
  }
899
816
  );
900
817
  server.tool(
901
818
  OCEAN_BRAIN_MCP_TOOLS.findNoteCleanupCandidates,
902
819
  "Find Ocean Brain note cleanup candidates for temporary or draft notes before deletion. This is optional discovery; ocean_brain_delete_note moves a single note to trash directly.",
903
820
  {
904
- keywords: z2.string().optional().default("temp tmp draft test wip").describe("Keywords that mark a note as a cleanup candidate. Comma or space separated."),
905
- limit: z2.number().optional().default(20).describe("Max results (default: 20)"),
906
- offset: z2.number().optional().default(0).describe("Pagination offset (default: 0)")
821
+ keywords: z3.string().optional().default("temp tmp draft test wip").describe("Keywords that mark a note as a cleanup candidate. Comma or space separated."),
822
+ limit: z3.number().optional().default(20).describe("Max results (default: 20)"),
823
+ offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
907
824
  },
908
825
  async ({ keywords, limit, offset }) => {
909
826
  const normalizedKeywords = keywords.split(/[,\s]+/).map((keyword) => keyword.trim()).filter(Boolean);
@@ -928,61 +845,54 @@ async function startMcpServer(serverUrl, token) {
928
845
  pagination: { limit, offset }
929
846
  });
930
847
  const result = data?.noteCleanupCandidates;
931
- return {
932
- content: [{
933
- type: "text",
934
- text: JSON.stringify({
935
- keywords: normalizedKeywords,
936
- limit,
937
- offset,
938
- candidateCount: result.length,
939
- notes: result
940
- }, null, 2)
941
- }]
942
- };
848
+ return createMcpJsonToolResult({
849
+ keywords: normalizedKeywords,
850
+ limit,
851
+ offset,
852
+ candidateCount: result.length,
853
+ notes: result
854
+ });
943
855
  }
944
856
  );
945
857
  server.tool(
946
858
  OCEAN_BRAIN_MCP_TOOLS.createTag,
947
859
  "Create an Ocean Brain tag through the MCP write path. The tool accepts either @tag or tag and creates the canonical Ocean Brain tag if it does not already exist.",
948
860
  {
949
- name: z2.string().describe("Tag name to create. You can pass @project or project.")
861
+ name: z3.string().describe("Tag name to create. You can pass @project or project.")
950
862
  },
951
863
  async ({ name }) => {
952
864
  const writeToken = requireWriteToken(token, OCEAN_BRAIN_MCP_TOOLS.createTag);
865
+ const normalizedName = normalizeOceanBrainTagName(name);
953
866
  const result = await jsonRequest(serverUrl, writeToken, "/api/mcp/tags/create", {
954
- name
867
+ name: normalizedName
955
868
  });
956
- return {
957
- content: [{
958
- type: "text",
959
- text: JSON.stringify(result, null, 2)
960
- }]
961
- };
869
+ return createMcpJsonToolResult(result);
962
870
  }
963
871
  );
964
872
  server.tool(
965
873
  OCEAN_BRAIN_MCP_TOOLS.deleteNote,
966
874
  "Move an Ocean Brain note to trash, like `mv note.md trash/`. This is a recoverable trash move, not permanent deletion. Deleted-note tag names are kept as restore metadata; orphan tags are not a blocking condition.",
967
875
  {
968
- id: z2.string().describe("Note ID to move to trash")
876
+ id: z3.string().describe("Note ID to move to trash")
969
877
  },
970
878
  async ({ id }) => {
971
879
  const writeToken = requireWriteToken(token, OCEAN_BRAIN_MCP_TOOLS.deleteNote);
972
880
  try {
973
881
  const result = await jsonRequest(serverUrl, writeToken, "/api/mcp/notes/delete", { id });
974
- return {
975
- content: [{
976
- type: "text",
977
- text: JSON.stringify(result, null, 2)
978
- }]
979
- };
882
+ return createMcpJsonToolResult(result);
980
883
  } catch (error) {
981
884
  const message = error instanceof Error ? error.message : "Unknown MCP note delete error";
982
885
  throw new Error(message);
983
886
  }
984
887
  }
985
888
  );
889
+ };
890
+ async function startMcpServer(serverUrl, token) {
891
+ const server = new McpServer({
892
+ name: "ocean-brain",
893
+ version: pkg.version
894
+ });
895
+ registerMcpTools(server, serverUrl, token);
986
896
  const transport = new StdioServerTransport();
987
897
  await server.connect(transport);
988
898
  }
@@ -993,5 +903,6 @@ export {
993
903
  OCEAN_BRAIN_MCP_TOOLS,
994
904
  OCEAN_BRAIN_MCP_VERSION_HEADER,
995
905
  createMcpRequestHeaders,
906
+ normalizeOceanBrainTagName,
996
907
  startMcpServer
997
908
  };