ocean-brain 0.13.2 → 0.14.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 (158) hide show
  1. package/README.md +66 -2
  2. package/dist/mcp.js +439 -480
  3. package/package.json +2 -2
  4. package/server/client/dist/assets/{ArrowRight.es-BlNsZQFs.js → ArrowRight.es-nBJws3ws.js} +1 -1
  5. package/server/client/dist/assets/{AuxiliaryPanelHeader-HAttwVby.js → AuxiliaryPanelHeader-BN_2axQu.js} +1 -1
  6. package/server/client/dist/assets/{Calendar-DH3eSSRs.js → Calendar-vA4Xa4Qr.js} +1 -1
  7. package/server/client/dist/assets/{Callout-CcYyvHQP.js → Callout-Bo2P960P.js} +1 -1
  8. package/server/client/dist/assets/{CaretUp.es-C8iY5OlB.js → CaretUp.es-DDbqKn78.js} +1 -1
  9. package/server/client/dist/assets/{CheckCircle.es-Dqr3WImn.js → CheckCircle.es-CyY9aLBF.js} +1 -1
  10. package/server/client/dist/assets/{Copy.es-fMv0ETir.js → Copy.es-BwX4tEoU.js} +1 -1
  11. package/server/client/dist/assets/{File.es-CHtXXafR.js → File.es-DeTgvUnI.js} +1 -1
  12. package/server/client/dist/assets/{FileText.es-DPIwDCEe.js → FileText.es-C-bpw8dQ.js} +1 -1
  13. package/server/client/dist/assets/{FlowArrow.es-Dxee98ir.js → FlowArrow.es-CKcMBOmX.js} +1 -1
  14. package/server/client/dist/assets/{Graph-hVj6vAYJ.js → Graph-BNoSRJP3.js} +1 -1
  15. package/server/client/dist/assets/{Graph-CdEu8eAn.js → Graph-DYYxNAWn.js} +1 -1
  16. package/server/client/dist/assets/{Image.es-CjXY1JVf.js → Image.es-d4jWnijM.js} +1 -1
  17. package/server/client/dist/assets/{Info.es-CYxg-Vku.js → Info.es-DVzN2joA.js} +1 -1
  18. package/server/client/dist/assets/Integration-gDDtIJYJ.js +1 -0
  19. package/server/client/dist/assets/{LinkSimple.es-B-P-7Iem.js → LinkSimple.es-BD4aGTc3.js} +1 -1
  20. package/server/client/dist/assets/Note-FL31jb2U.js +19 -0
  21. package/server/client/dist/assets/{NotePropertiesPanel-6P7UfRBR.js → NotePropertiesPanel-os22Ppnu.js} +1 -1
  22. package/server/client/dist/assets/{Plus.es-Dy6onD-Z.js → Plus.es-Cl8zOOZa.js} +1 -1
  23. package/server/client/dist/assets/Reminders-Dt2JuGci.js +1 -0
  24. package/server/client/dist/assets/{Search-D9detGWk.js → Search-CPPTW2PO.js} +2 -2
  25. package/server/client/dist/assets/{SurfaceCard-D5QxOte_.js → SurfaceCard-jL2qzrzF.js} +1 -1
  26. package/server/client/dist/assets/{Tag-Bquqtx23.js → Tag-3DbGmuxa.js} +1 -1
  27. package/server/client/dist/assets/{TagNotes-CbjfxqAN.js → TagNotes-DLwRF8pT.js} +1 -1
  28. package/server/client/dist/assets/Trash.es-fxukjAO6.js +1 -0
  29. package/server/client/dist/assets/{ViewChip-BVkZAXyx.js → ViewChip-CVe_HNyd.js} +1 -1
  30. package/server/client/dist/assets/Views-8ghLRax7.js +1 -0
  31. package/server/client/dist/assets/{abnfDiagram-N423BO3Z-D44O4T7q.js → abnfDiagram-N423BO3Z-CPgGmCbn.js} +1 -1
  32. package/server/client/dist/assets/app-Bcl0U7r9.css +1 -0
  33. package/server/client/dist/assets/app-DzChh-V0.js +151 -0
  34. package/server/client/dist/assets/{arc-Cil9fXuW.js → arc-DqTgRpuF.js} +1 -1
  35. package/server/client/dist/assets/{architectureDiagram-T3A2C74G-Cjq6ayF2.js → architectureDiagram-T3A2C74G-rTYMAQ-J.js} +1 -1
  36. package/server/client/dist/assets/{blockDiagram-VBNYF7ZC-B2Xm5N0i.js → blockDiagram-VBNYF7ZC-BOluytVz.js} +1 -1
  37. package/server/client/dist/assets/{c4Diagram-5PPSVZJV-Dp0y-lCR.js → c4Diagram-5PPSVZJV-C8gbPpcb.js} +1 -1
  38. package/server/client/dist/assets/{calendar-data-wB0ifXdo.js → calendar-data-BQ24n348.js} +1 -1
  39. package/server/client/dist/assets/channel--aI9fJWX.js +1 -0
  40. package/server/client/dist/assets/{chunk-2GRJ4B5K-CWaaNkFx.js → chunk-2GRJ4B5K-CRER5Ka0.js} +1 -1
  41. package/server/client/dist/assets/{chunk-2Q5K7J3B-Ces-xz2S.js → chunk-2Q5K7J3B-hxmJWGu_.js} +1 -1
  42. package/server/client/dist/assets/{chunk-5RXB4S5H-B-4Chlfc.js → chunk-5RXB4S5H-Bpr5Us8Y.js} +1 -1
  43. package/server/client/dist/assets/{chunk-5VM5RSS4-C8_HVwuz.js → chunk-5VM5RSS4-CKUz9fAb.js} +1 -1
  44. package/server/client/dist/assets/{chunk-6Q2QTUOP-xZ8ofRqk.js → chunk-6Q2QTUOP-B8XEOHPN.js} +1 -1
  45. package/server/client/dist/assets/{chunk-GF5L2VYU-Bpa43a7s.js → chunk-GF5L2VYU-BBUD-HJi.js} +1 -1
  46. package/server/client/dist/assets/{chunk-JWPE2WC7-DsyTXbuq.js → chunk-JWPE2WC7-Dq6_B8-d.js} +1 -1
  47. package/server/client/dist/assets/{chunk-KBJHAD2P-BDhjm9-8.js → chunk-KBJHAD2P--2VZEwNJ.js} +1 -1
  48. package/server/client/dist/assets/{chunk-RYQCIY6F-Cn-jdp6I.js → chunk-RYQCIY6F-CKCwnKiC.js} +1 -1
  49. package/server/client/dist/assets/{chunk-XXDRQBXY-BKsc_0wh.js → chunk-XXDRQBXY-VDufR1Oi.js} +1 -1
  50. package/server/client/dist/assets/classDiagram-JCYQIIEL-BsbwE38V.js +1 -0
  51. package/server/client/dist/assets/classDiagram-v2-OCEON4UE-BsbwE38V.js +1 -0
  52. package/server/client/dist/assets/{cose-bilkent-JH36ORCC-jmn3Q4zj.js → cose-bilkent-JH36ORCC-D4nqOx8S.js} +1 -1
  53. package/server/client/dist/assets/{cynefinDiagram-MW4NZA55-BpLstuBJ.js → cynefinDiagram-MW4NZA55-CUuLMs-6.js} +1 -1
  54. package/server/client/dist/assets/{dagre-VZM6K2ZE-B5ft5Yzm.js → dagre-VZM6K2ZE-CUSkhZgS.js} +1 -1
  55. package/server/client/dist/assets/{diagram-7IWD3JNH-B_W9IsWZ.js → diagram-7IWD3JNH-D95eVcKx.js} +4 -4
  56. package/server/client/dist/assets/{diagram-B4RE2ZJO-32chS3GX.js → diagram-B4RE2ZJO-7OH_fb0K.js} +1 -1
  57. package/server/client/dist/assets/{diagram-LBJQPF4R-Bqupdb3-.js → diagram-LBJQPF4R-D9UwEIMd.js} +1 -1
  58. package/server/client/dist/assets/{diagram-Q27KOJAE-BD8jKBsv.js → diagram-Q27KOJAE-Dx7WFDuX.js} +1 -1
  59. package/server/client/dist/assets/{diagram-UB23O5K3-C40jAU8D.js → diagram-UB23O5K3-D2BYwAKT.js} +1 -1
  60. package/server/client/dist/assets/{ebnfDiagram-BXEA7PRR-BQfW9mFA.js → ebnfDiagram-BXEA7PRR-DbeA-fk4.js} +1 -1
  61. package/server/client/dist/assets/{erDiagram-JOGREHBK-ILZnLzz2.js → erDiagram-JOGREHBK-jWW-ICGO.js} +1 -1
  62. package/server/client/dist/assets/{flowDiagram-UKHOOZJN-CDmwr2dI.js → flowDiagram-UKHOOZJN-68iZdxxw.js} +1 -1
  63. package/server/client/dist/assets/{ganttDiagram-PKOTCBZU-DBcrUc49.js → ganttDiagram-PKOTCBZU-CxwY3VXg.js} +1 -1
  64. package/server/client/dist/assets/{gitGraphDiagram-DS77QQ5N-_xO6__FK.js → gitGraphDiagram-DS77QQ5N-Ci6o5MfZ.js} +1 -1
  65. package/server/client/dist/assets/{image.api-ChvlfuZg.js → image.api-CyIIEGkG.js} +1 -1
  66. package/server/client/dist/assets/index-Iy_BgP1R.js +1 -0
  67. package/server/client/dist/assets/{infoDiagram-6WML65LV-CuIIcu-L.js → infoDiagram-6WML65LV-DY7aMdjV.js} +1 -1
  68. package/server/client/dist/assets/integrations-LOdTs1T6.js +1 -0
  69. package/server/client/dist/assets/{ishikawaDiagram-WSZJBQD7-BVtRGTOj.js → ishikawaDiagram-WSZJBQD7-Bh5qKh1d.js} +1 -1
  70. package/server/client/dist/assets/{journeyDiagram-NVQOT4AX-D5-Nzb2N.js → journeyDiagram-NVQOT4AX-B_svXTTT.js} +1 -1
  71. package/server/client/dist/assets/{kanban-definition-27J2QSJJ-DmOhgC8H.js → kanban-definition-27J2QSJJ-CaJslHGg.js} +1 -1
  72. package/server/client/dist/assets/manage-image-DXedfr7U.js +1 -0
  73. package/server/client/dist/assets/{manage-image-detail-BIDHUR5-.js → manage-image-detail-DmifOPNU.js} +1 -1
  74. package/server/client/dist/assets/{mcp-admin-adapter-D0R8p9az.js → mcp-admin-adapter-CmKsg1vd.js} +1 -1
  75. package/server/client/dist/assets/mcp-d8IzsoRz.js +5 -0
  76. package/server/client/dist/assets/{mermaid.core-C2tL9tq7.js → mermaid.core-BfUQM5vF.js} +4 -4
  77. package/server/client/dist/assets/{mindmap-definition-FAOFIHXS-DuvsPqrh.js → mindmap-definition-FAOFIHXS-DGbkJda2.js} +1 -1
  78. package/server/client/dist/assets/{note-core-YhYEmI_R.js → note-core-DRBsBf9H.js} +9 -9
  79. package/server/client/dist/assets/{note-runtime-B3ats-dC.js → note-runtime-ztZvXRIu.js} +16 -16
  80. package/server/client/dist/assets/{note-ui-BtzPk8gA.js → note-ui-DRnAnwKv.js} +1 -1
  81. package/server/client/dist/assets/{pegDiagram-VL7TDLO6-D64crEZV.js → pegDiagram-VL7TDLO6-Bip6zQct.js} +1 -1
  82. package/server/client/dist/assets/{pieDiagram-7S7Q4E2Y--kl7vopC.js → pieDiagram-7S7Q4E2Y-CjfjGY8Y.js} +1 -1
  83. package/server/client/dist/assets/{placeholder-DKgxoe_f.js → placeholder-G6iEBbDy.js} +1 -1
  84. package/server/client/dist/assets/{properties-B7Fv95qY.js → properties-BPobx_Qk.js} +1 -1
  85. package/server/client/dist/assets/{quadrantDiagram-CIZ2JOQS-DuvJ4M2y.js → quadrantDiagram-CIZ2JOQS-B6F1yUT-.js} +1 -1
  86. package/server/client/dist/assets/{railroadDiagram-AXF67PYL-BMKmCOY7.js → railroadDiagram-AXF67PYL-D-0fLe0F.js} +1 -1
  87. package/server/client/dist/assets/{requirementDiagram-LRYGKXZP-DY1eJcy0.js → requirementDiagram-LRYGKXZP-tm4mnCyJ.js} +1 -1
  88. package/server/client/dist/assets/route-preload.js +2 -2
  89. package/server/client/dist/assets/{sankeyDiagram-W5VNT64P-C3Ka4GKM.js → sankeyDiagram-W5VNT64P-DSBWFurb.js} +1 -1
  90. package/server/client/dist/assets/{search-ddYl3qH-.js → search-DanMKCC8.js} +1 -1
  91. package/server/client/dist/assets/{search-admin-adapter-Daf7e2YQ.js → search-admin-adapter-7sTcWTmv.js} +1 -1
  92. package/server/client/dist/assets/{sequenceDiagram-SI44F4Z6-Bhpwtk2s.js → sequenceDiagram-SI44F4Z6-KrwjjJIV.js} +1 -1
  93. package/server/client/dist/assets/{sizeCapture-X5ZJPWSS-BFta71TW.js → sizeCapture-X5ZJPWSS-DL2cn_Q6.js} +1 -1
  94. package/server/client/dist/assets/{stateDiagram-OKZ733FA-D6GCn1io.js → stateDiagram-OKZ733FA-CKuNDH85.js} +1 -1
  95. package/server/client/dist/assets/{stateDiagram-v2-UEYNNEHI-g4yinGDn.js → stateDiagram-v2-UEYNNEHI-BFaj2wbM.js} +1 -1
  96. package/server/client/dist/assets/{swimlanes-SLNWSIFB-DLFvOjUl.js → swimlanes-SLNWSIFB-5S17nqhK.js} +2 -2
  97. package/server/client/dist/assets/swimlanesDiagram-ULZ7WXOC-dexsHJq2.js +8 -0
  98. package/server/client/dist/assets/{tag.api-DEUROVKQ.js → tag.api-kKZl04yY.js} +1 -1
  99. package/server/client/dist/assets/{time-PxWila4-.js → time-BwwQGbK-.js} +38 -38
  100. package/server/client/dist/assets/{timeline-definition-Z64GVDOM-DTSIekSM.js → timeline-definition-Z64GVDOM-CU1hp_7d.js} +3 -3
  101. package/server/client/dist/assets/{trash-BHida7BG.js → trash-Cepvi7G8.js} +1 -1
  102. package/server/client/dist/assets/url-Bp8sXMb4.js +1 -0
  103. package/server/client/dist/assets/{useNoteMutate-BjvZUmtX.js → useNoteMutate-D-mZKXjl.js} +3 -3
  104. package/server/client/dist/assets/{useReminderMutate-C5n-5LM-.js → useReminderMutate-Cexk1ydX.js} +1 -1
  105. package/server/client/dist/assets/{vennDiagram-T6HMQDX7-CMNqSI2X.js → vennDiagram-T6HMQDX7-MoiVLnil.js} +1 -1
  106. package/server/client/dist/assets/{wardleyDiagram-T6FBY63Y-DV-JOYqB.js → wardleyDiagram-T6FBY63Y-cZkfdI6C.js} +1 -1
  107. package/server/client/dist/assets/{xychartDiagram-ELKLHX3M-BUIniF4m.js → xychartDiagram-ELKLHX3M-B63hxD-r.js} +1 -1
  108. package/server/client/dist/index.html +6 -6
  109. package/server/dist/app.js +2 -0
  110. package/server/dist/features/integration/auth.js +21 -0
  111. package/server/dist/features/integration/manifest.js +87 -0
  112. package/server/dist/features/integration/schema.js +54 -0
  113. package/server/dist/features/integration/service.js +173 -0
  114. package/server/dist/features/mcp-admin/service.js +19 -83
  115. package/server/dist/features/note/graphql/note.field.resolver.js +1 -1
  116. package/server/dist/features/note/graphql/note.query.resolver.js +6 -0
  117. package/server/dist/features/note/graphql/note.type-defs.js +13 -1
  118. package/server/dist/features/note/http/authoring.js +493 -0
  119. package/server/dist/features/note/http/mcp.js +15 -463
  120. package/server/dist/features/note/services/authoring.js +17 -1
  121. package/server/dist/features/note/services/markdown-intent-write.js +1 -0
  122. package/server/dist/features/note/services/markdown-read.js +85 -0
  123. package/server/dist/features/note/services/properties.js +36 -30
  124. package/server/dist/features/note/services/search.js +17 -0
  125. package/server/dist/features/search/graphql/resolver.js +10 -1
  126. package/server/dist/features/search/graphql/type-defs.js +3 -0
  127. package/server/dist/features/tag/graphql/tag.query.resolver.js +10 -0
  128. package/server/dist/features/tag/graphql/tag.type-defs.js +1 -0
  129. package/server/dist/features/view/graphql/mcp.query.resolver.js +22 -0
  130. package/server/dist/features/view/graphql/view.query.resolver.js +2 -0
  131. package/server/dist/features/view/graphql/view.type-defs.js +27 -0
  132. package/server/dist/features/view/services/mcp-read.js +142 -0
  133. package/server/dist/features/view/services/workspace.js +12 -4
  134. package/server/dist/modules/auth-guard.js +24 -1
  135. package/server/dist/modules/integration-token.js +27 -0
  136. package/server/dist/modules/mcp-auth.js +37 -23
  137. package/server/dist/modules/mcp-token.js +6 -25
  138. package/server/dist/routes/api.js +2 -0
  139. package/server/dist/routes/graphql.js +2 -1
  140. package/server/dist/routes/integrations.js +117 -0
  141. package/server/dist/routes/mcp.js +29 -9
  142. package/server/prisma/migrations/20260917120000_0020_plugin_installations/migration.sql +39 -0
  143. package/server/prisma/migrations/20260917150000_0021_integration_connections/migration.sql +29 -0
  144. package/server/prisma/schema.prisma +34 -10
  145. package/server/client/dist/assets/Note-CpzLtGVM.js +0 -19
  146. package/server/client/dist/assets/Reminders-DnuALBL6.js +0 -1
  147. package/server/client/dist/assets/Trash.es-XEcWG_aq.js +0 -1
  148. package/server/client/dist/assets/Views-L4eC96Lx.js +0 -1
  149. package/server/client/dist/assets/app-BFM2V_Rx.js +0 -151
  150. package/server/client/dist/assets/app-DjDwGe0t.css +0 -1
  151. package/server/client/dist/assets/channel-DZFalh-h.js +0 -1
  152. package/server/client/dist/assets/classDiagram-JCYQIIEL-CkEhFvVV.js +0 -1
  153. package/server/client/dist/assets/classDiagram-v2-OCEON4UE-CkEhFvVV.js +0 -1
  154. package/server/client/dist/assets/index-CJPl3MJE.js +0 -1
  155. package/server/client/dist/assets/manage-image-CSJ3Flgc.js +0 -1
  156. package/server/client/dist/assets/mcp-D8footel.js +0 -5
  157. package/server/client/dist/assets/swimlanesDiagram-ULZ7WXOC-Ci4mcvmo.js +0 -8
  158. package/server/client/dist/assets/url-DKUMNVCI.js +0 -1
package/dist/mcp.js CHANGED
@@ -1,61 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/mcp.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
6
  import fs from "fs";
5
7
  import path from "path";
6
8
  import { fileURLToPath } from "url";
7
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
- import { z as z3 } from "zod";
10
-
11
- // src/mcp-note-output.ts
12
- var formatBackReferenceLink = (backReference) => {
13
- return `[[${backReference.title}]](note:${backReference.id})`;
14
- };
15
- var formatBackReferenceLine = (backReference) => {
16
- const referenceLink = formatBackReferenceLink(backReference);
17
- if (backReference.updatedAt) {
18
- return `- ${referenceLink} (updated: ${backReference.updatedAt})`;
19
- }
20
- return `- ${referenceLink}`;
21
- };
22
- var formatPropertyLine = (property) => {
23
- const displayValue = (property.option?.label ?? property.value) || "(empty)";
24
- const rawValue = property.option?.value ?? property.value;
25
- const rawValueLabel = rawValue ? `, value=${rawValue}` : "";
26
- return `- ${property.key} (${property.name}): ${displayValue} [${property.valueType}${rawValueLabel}]`;
27
- };
28
- var formatMcpReadNoteOutput = ({
29
- note,
30
- backReferences,
31
- maxLength
32
- }) => {
33
- let markdown = note.contentAsMarkdown;
34
- const totalLength = markdown.length;
35
- const truncated = maxLength > 0 && markdown.length > maxLength;
36
- if (truncated) {
37
- markdown = markdown.slice(0, maxLength);
38
- }
39
- const backReferenceLines = backReferences.length > 0 ? backReferences.map(formatBackReferenceLine) : ["- (none)"];
40
- const propertyLines = note.properties && note.properties.length > 0 ? note.properties.map(formatPropertyLine) : ["- (none)"];
41
- return [
42
- `# ${note.title}`,
43
- "",
44
- `Tags: ${note.tags.map((tag) => tag.name).join(", ") || "(none)"}`,
45
- "",
46
- "Properties:",
47
- ...propertyLines,
48
- `Created: ${note.createdAt}`,
49
- `Updated: ${note.updatedAt}`,
50
- ...truncated ? [`Content: ${totalLength} chars (showing first ${maxLength})`] : [],
51
- "",
52
- "Back References:",
53
- ...backReferenceLines,
54
- "",
55
- markdown,
56
- ...truncated ? ["\n... (truncated, use maxLength: 0 to read full content)"] : []
57
- ].join("\n");
58
- };
9
+ import { z as z6 } from "zod";
59
10
 
60
11
  // src/mcp-intent-write-tools.ts
61
12
  import { z as z2 } from "zod";
@@ -63,6 +14,14 @@ import { z as z2 } from "zod";
63
14
  // src/mcp-tool-support.ts
64
15
  import { z } from "zod";
65
16
  var noteLayoutSchema = z.enum(["narrow", "wide", "full"]);
17
+ var pageLimitSchema = (defaultLimit, maximum = 100) => z.number().int().positive().default(defaultLimit).transform((value) => Math.min(value, maximum));
18
+ var pageOffsetSchema = z.number().int().nonnegative().default(0);
19
+ var createPageInfo = (totalCount, limit, offset) => ({
20
+ limit,
21
+ offset,
22
+ hasMore: offset + limit < totalCount,
23
+ nextOffset: offset + limit < totalCount ? offset + limit : null
24
+ });
66
25
  var createMcpTextToolResult = (text) => ({
67
26
  content: [
68
27
  {
@@ -71,9 +30,11 @@ var createMcpTextToolResult = (text) => ({
71
30
  }
72
31
  ]
73
32
  });
74
- var createMcpJsonToolResult = (value) => {
75
- return createMcpTextToolResult(JSON.stringify(value, null, 2));
76
- };
33
+ var createMcpJsonToolResult = (value) => ({
34
+ ...createMcpTextToolResult(JSON.stringify(value, null, 2)),
35
+ structuredContent: value,
36
+ ...value.status === "failed" || value.status === "needs_disambiguation" ? { isError: true } : {}
37
+ });
77
38
 
78
39
  // src/mcp-intent-write-tools.ts
79
40
  var markdownPatchSelectorSchema = z2.discriminatedUnion("type", [
@@ -119,8 +80,8 @@ var markdownAppendPlacementSchema = z2.discriminatedUnion("type", [
119
80
  ]);
120
81
  var markdownWritePolicySchema = z2.object({
121
82
  allowNoop: z2.boolean().optional(),
122
- preserveTags: z2.union([z2.boolean(), z2.literal("warn")]).optional(),
123
- preserveReferences: z2.union([z2.boolean(), z2.literal("warn")]).optional()
83
+ preserveTags: z2.union([z2.boolean(), z2.literal("warn")]).optional().describe("true blocks tag-token count decreases; warn reports decreases. Does not preserve individual tag identities."),
84
+ preserveReferences: z2.union([z2.boolean(), z2.literal("warn")]).optional().describe("true blocks reference-count decreases; warn reports decreases. Does not preserve individual reference identities.")
124
85
  }).optional();
125
86
  var markdownWriteBaselineFields = {
126
87
  expectedUpdatedAt: z2.string().optional().describe("Expected note updatedAt from the read you based this edit on. Required unless baseMarkdownSha256 is provided."),
@@ -200,7 +161,7 @@ var registerIntentWriteTools = (server, {
200
161
  operation,
201
162
  ...policy ? { policy } : {}
202
163
  };
203
- const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/patch-markdown", payload);
164
+ const result = await jsonRequest2(serverUrl, writeToken, "/api/integrations/v1/notes/patch-markdown", payload);
204
165
  return createMcpJsonToolResult(result);
205
166
  }
206
167
  );
@@ -228,7 +189,7 @@ var registerIntentWriteTools = (server, {
228
189
  ...separator ? { separator } : {},
229
190
  ...policy ? { policy } : {}
230
191
  };
231
- const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/append-markdown", payload);
192
+ const result = await jsonRequest2(serverUrl, writeToken, "/api/integrations/v1/notes/append-markdown", payload);
232
193
  return createMcpJsonToolResult(result);
233
194
  }
234
195
  );
@@ -251,7 +212,7 @@ var registerIntentWriteTools = (server, {
251
212
  ...layout ? { layout } : {},
252
213
  ...properties ? { properties } : {}
253
214
  };
254
- const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/metadata", payload);
215
+ const result = await jsonRequest2(serverUrl, writeToken, "/api/integrations/v1/notes/metadata", payload);
255
216
  return createMcpJsonToolResult(result);
256
217
  }
257
218
  );
@@ -275,42 +236,328 @@ var registerIntentWriteTools = (server, {
275
236
  replacement,
276
237
  ...policy ? { policy } : {}
277
238
  };
278
- const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/replace-markdown", payload);
239
+ const result = await jsonRequest2(serverUrl, writeToken, "/api/integrations/v1/notes/replace-markdown", payload);
279
240
  return createMcpJsonToolResult(result);
280
241
  }
281
242
  );
282
243
  };
283
244
 
284
- // src/mcp-property-query-output.ts
285
- var formatPropertyQueryResponse = ({
286
- result,
287
- query,
288
- includeProperties,
289
- propertyKeys
290
- }) => ({
291
- query: {
292
- ...query,
293
- includeProperties,
294
- propertyKeys
295
- },
296
- totalCount: result.totalCount,
297
- notes: result.notes.map((note) => ({
298
- id: note.id,
299
- title: note.title,
300
- createdAt: note.createdAt,
301
- updatedAt: note.updatedAt,
302
- tags: note.tags.map((item) => item.name),
303
- ...includeProperties ? {
304
- properties: propertyKeys.length > 0 ? (note.properties ?? []).filter((property) => propertyKeys.includes(property.key)) : note.properties ?? []
305
- } : {}
306
- }))
245
+ // src/mcp-note-output.ts
246
+ import { z as z3 } from "zod";
247
+ var formatBackReferenceLink = (backReference) => {
248
+ return `[[${backReference.title}]](note:${backReference.id})`;
249
+ };
250
+ var formatBackReferenceLine = (backReference) => {
251
+ const referenceLink = formatBackReferenceLink(backReference);
252
+ if (backReference.updatedAt) {
253
+ return `- ${referenceLink} (updated: ${backReference.updatedAt})`;
254
+ }
255
+ return `- ${referenceLink}`;
256
+ };
257
+ var formatPropertyLine = (property) => {
258
+ const displayValue = (property.option?.label ?? property.value) || "(empty)";
259
+ const rawValue = property.option?.value ?? property.value;
260
+ const rawValueLabel = rawValue ? `, value=${rawValue}` : "";
261
+ return `- ${property.key} (${property.name}): ${displayValue} [${property.valueType}${rawValueLabel}]`;
262
+ };
263
+ var formatMcpReadNoteOutput = ({
264
+ note,
265
+ backReferences,
266
+ maxLength
267
+ }) => {
268
+ let markdown = note.contentAsMarkdown;
269
+ const totalLength = markdown.length;
270
+ const truncated = maxLength > 0 && markdown.length > maxLength;
271
+ if (truncated) {
272
+ markdown = markdown.slice(0, maxLength);
273
+ }
274
+ const backReferenceLines = backReferences.length > 0 ? backReferences.map(formatBackReferenceLine) : ["- (none)"];
275
+ const propertyLines = note.properties && note.properties.length > 0 ? note.properties.map(formatPropertyLine) : ["- (none)"];
276
+ return [
277
+ `# ${note.title}`,
278
+ "",
279
+ `Tags: ${note.tags.map((tag) => tag.name).join(", ") || "(none)"}`,
280
+ "",
281
+ "Properties:",
282
+ ...propertyLines,
283
+ `Created: ${note.createdAt}`,
284
+ `Updated: ${note.updatedAt}`,
285
+ ...truncated ? [`Content: ${totalLength} chars (showing first ${maxLength})`] : [],
286
+ "",
287
+ "Back References:",
288
+ ...backReferenceLines,
289
+ "",
290
+ markdown,
291
+ ...truncated ? ["\n... (truncated, use maxLength: 0 to read full content)"] : []
292
+ ].join("\n");
293
+ };
294
+ var readResultSchema = z3.object({
295
+ status: z3.enum(["read", "failed", "needs_disambiguation"]),
296
+ reason: z3.string().nullable(),
297
+ message: z3.string().nullable(),
298
+ note: z3.object({ id: z3.string(), title: z3.string(), createdAt: z3.string(), updatedAt: z3.string(), tags: z3.array(z3.object({ id: z3.string(), name: z3.string() })), properties: z3.array(z3.object({ key: z3.string(), name: z3.string(), value: z3.string(), valueType: z3.string(), option: z3.object({ label: z3.string(), value: z3.string() }).nullable() })) }),
299
+ markdown: z3.string(),
300
+ contentRange: z3.object({ start: z3.number(), end: z3.number(), totalLength: z3.number(), sectionEnd: z3.number(), hasMore: z3.boolean(), nextOffset: z3.number().nullable() }).nullable(),
301
+ candidates: z3.array(z3.object({ heading: z3.string(), level: z3.number(), start: z3.number(), end: z3.number() }))
302
+ });
303
+ var createMcpReadNoteResult = (raw, rawBackReferences) => {
304
+ const result = readResultSchema.parse(raw);
305
+ const backReferences = z3.array(z3.object({ id: z3.string(), title: z3.string() })).parse(rawBackReferences);
306
+ const structuredContent = { ...result, backReferences };
307
+ if (result.status !== "read") return createMcpJsonToolResult(structuredContent);
308
+ const range = result.contentRange;
309
+ const rangeText = range ? `
310
+ Content range: [${range.start}, ${range.end}) of ${range.totalLength} UTF-16 units; sectionEnd: ${range.sectionEnd}; nextOffset: ${range.nextOffset ?? "none"}` : "";
311
+ return {
312
+ ...createMcpTextToolResult(formatMcpReadNoteOutput({ note: { ...result.note, contentAsMarkdown: result.markdown }, backReferences, maxLength: 0 }) + rangeText),
313
+ structuredContent
314
+ };
315
+ };
316
+
317
+ // src/mcp-query-tools.ts
318
+ import { z as z4 } from "zod";
319
+ var propertyFilterSchema = z4.object({
320
+ key: z4.string().min(1),
321
+ valueType: z4.enum(["text", "url", "number", "date", "boolean", "select"]),
322
+ operator: z4.enum(["equals", "notEquals", "contains", "notContains", "before", "after", "exists", "notExists"]),
323
+ value: z4.string().nullable().optional().describe(
324
+ "Required except exists/notExists. select=option.value, date=YYYY-MM-DD, boolean=true/false. contains/notContains: text/url; before/after: date/number."
325
+ )
326
+ });
327
+ var queryConditionsSchema = z4.object({
328
+ tagNames: z4.array(z4.string()),
329
+ mode: z4.enum(["and", "or"]),
330
+ propertyFilters: z4.array(propertyFilterSchema.extend({ name: z4.string().optional() })),
331
+ sortBy: z4.enum(["updatedAt", "createdAt", "title"]),
332
+ sortOrder: z4.enum(["asc", "desc"])
333
+ });
334
+ var optionSchema = z4.object({
335
+ id: z4.string(),
336
+ label: z4.string(),
337
+ value: z4.string(),
338
+ color: z4.string().nullable(),
339
+ order: z4.number()
340
+ });
341
+ var noteSummarySchema = z4.object({
342
+ id: z4.string(),
343
+ title: z4.string(),
344
+ createdAt: z4.string(),
345
+ updatedAt: z4.string(),
346
+ tags: z4.array(z4.object({ id: z4.string(), name: z4.string() })),
347
+ properties: z4.array(
348
+ z4.object({
349
+ key: z4.string(),
350
+ name: z4.string(),
351
+ value: z4.string(),
352
+ valueType: z4.string(),
353
+ option: optionSchema.nullable()
354
+ })
355
+ ).optional()
356
+ });
357
+ var pageInfoSchema = z4.object({
358
+ limit: z4.number(),
359
+ offset: z4.number(),
360
+ hasMore: z4.boolean(),
361
+ nextOffset: z4.number().nullable()
362
+ });
363
+ var propertyKeysSchema = z4.array(z4.string().trim().min(1)).max(50).transform((keys) => [...new Set(keys.map((key) => key.toLowerCase().replace(/\s+/g, "-")))]);
364
+ var NOTE_SUMMARY_FIELDS = "id title createdAt updatedAt tags { id name } properties(keys: $propertyKeys) { key name value valueType option { id label value color order } }";
365
+ var QUERY_CONDITION_FIELDS = "tagNames mode propertyFilters { key name valueType operator value } sortBy sortOrder";
366
+ var registerQueryTools = (server, request) => {
367
+ server.registerTool(
368
+ "ocean_brain_query_notes",
369
+ {
370
+ description: "Query notes using tags and property conditions; no filters returns recent notes. mode combines tags only; property filters always use AND. Use list_properties for valid keys/types/options. Only requested propertyKeys are returned. No note bodies.",
371
+ inputSchema: {
372
+ tagNames: z4.array(z4.string().trim().min(1)).max(100).default([]),
373
+ mode: z4.enum(["and", "or"]).default("and"),
374
+ propertyFilters: z4.array(propertyFilterSchema).max(10).default([]),
375
+ sortBy: z4.enum(["updatedAt", "createdAt", "title"]).default("updatedAt"),
376
+ sortOrder: z4.enum(["asc", "desc"]).default("desc"),
377
+ propertyKeys: propertyKeysSchema.default([]),
378
+ limit: pageLimitSchema(20, 50),
379
+ offset: pageOffsetSchema
380
+ },
381
+ outputSchema: {
382
+ query: queryConditionsSchema,
383
+ totalCount: z4.number(),
384
+ notes: z4.array(noteSummarySchema),
385
+ missingTags: z4.array(z4.string()),
386
+ page: pageInfoSchema
387
+ },
388
+ annotations: { readOnlyHint: true }
389
+ },
390
+ async ({ limit, offset, propertyKeys, ...input }) => {
391
+ const data = await request(
392
+ `query ($input: NotesByPropertiesInput!, $names: [String!]!, $pagination: PaginationInput!, $propertyKeys: [String!]) {
393
+ notesByQuery(input: $input, pagination: $pagination) { query { ${QUERY_CONDITION_FIELDS} } totalCount notes { ${NOTE_SUMMARY_FIELDS} } }
394
+ tagsByNames(names: $names) { name }
395
+ }`,
396
+ {
397
+ input,
398
+ names: input.tagNames,
399
+ propertyKeys,
400
+ pagination: { limit, offset }
401
+ }
402
+ );
403
+ const result = z4.object({
404
+ query: queryConditionsSchema,
405
+ totalCount: z4.number(),
406
+ notes: z4.array(noteSummarySchema)
407
+ }).parse(data?.notesByQuery);
408
+ const tags = z4.array(z4.object({ name: z4.string() })).parse(data?.tagsByNames);
409
+ return createMcpJsonToolResult({
410
+ ...result,
411
+ missingTags: result.query.tagNames.filter((name) => !tags.some((tag) => tag.name === name)),
412
+ page: createPageInfo(result.totalCount, limit, offset)
413
+ });
414
+ }
415
+ );
416
+ };
417
+
418
+ // src/mcp-view-tools.ts
419
+ import { z as z5 } from "zod";
420
+ var sectionSchema = queryConditionsSchema.extend({
421
+ id: z5.string(),
422
+ tabId: z5.string(),
423
+ title: z5.string(),
424
+ displayType: z5.enum(["list", "table", "board", "calendar"]),
425
+ limit: z5.number(),
426
+ order: z5.number(),
427
+ displayOptions: z5.object({
428
+ tableColumns: z5.array(z5.string()),
429
+ tablePropertyKeys: z5.array(z5.string()),
430
+ boardGroupByPropertyKey: z5.string().nullable(),
431
+ calendarDateField: z5.enum(["createdAt", "updatedAt", "property"]),
432
+ calendarDatePropertyKey: z5.string().nullable()
433
+ })
307
434
  });
435
+ var SECTION_FIELDS = `id tabId title displayType limit order ${QUERY_CONDITION_FIELDS} displayOptions { tableColumns tablePropertyKeys boardGroupByPropertyKey calendarDateField calendarDatePropertyKey }`;
436
+ var viewReadSchema = z5.object({
437
+ section: sectionSchema,
438
+ totalCount: z5.number(),
439
+ rows: z5.array(
440
+ z5.object({
441
+ note: noteSummarySchema,
442
+ groupValue: z5.string().nullable(),
443
+ calendarDate: z5.string().nullable()
444
+ })
445
+ ),
446
+ groupProperty: z5.object({
447
+ key: z5.string(),
448
+ name: z5.string(),
449
+ options: z5.array(optionSchema)
450
+ }).nullable()
451
+ });
452
+ var registerViewTools = (server, request) => {
453
+ server.registerTool(
454
+ "ocean_brain_list_views",
455
+ {
456
+ description: "Find saved view sections by section or tab title. Returns IDs, saved conditions and display settings without loading notes or switching the active tab.",
457
+ inputSchema: {
458
+ query: z5.string().default(""),
459
+ limit: pageLimitSchema(20, 50),
460
+ offset: pageOffsetSchema
461
+ },
462
+ outputSchema: {
463
+ totalCount: z5.number(),
464
+ views: z5.array(
465
+ z5.object({
466
+ sectionId: z5.string(),
467
+ tabTitle: z5.string(),
468
+ section: sectionSchema
469
+ })
470
+ ),
471
+ page: pageInfoSchema
472
+ },
473
+ annotations: { readOnlyHint: true }
474
+ },
475
+ async ({ query, limit, offset }) => {
476
+ const data = await request(
477
+ `query ($query: String, $pagination: PaginationInput) { viewSections(query: $query, pagination: $pagination) { totalCount sections { tabTitle section { ${SECTION_FIELDS} } } } }`,
478
+ { query, pagination: { limit, offset } }
479
+ );
480
+ const result = z5.object({
481
+ totalCount: z5.number(),
482
+ sections: z5.array(z5.object({ tabTitle: z5.string(), section: sectionSchema }))
483
+ }).parse(data?.viewSections);
484
+ return createMcpJsonToolResult({
485
+ totalCount: result.totalCount,
486
+ views: result.sections.map((view) => ({
487
+ ...view,
488
+ sectionId: view.section.id
489
+ })),
490
+ page: createPageInfo(result.totalCount, limit, offset)
491
+ });
492
+ }
493
+ );
494
+ server.registerTool(
495
+ "ocean_brain_read_view",
496
+ {
497
+ description: "Read a saved view with its conditions, display settings and paged note rows. Board: omit groupValue for all columns; null means unclassified. Calendar: dateRange is required ([start,end), at most 32 days); saved date field controls order. propertyKeys overrides table columns; omit to use saved table property columns. section.limit is the UI display limit, separate from page.limit.",
498
+ inputSchema: {
499
+ sectionId: z5.string().min(1),
500
+ limit: pageLimitSchema(20, 50),
501
+ offset: pageOffsetSchema,
502
+ propertyKeys: propertyKeysSchema.optional(),
503
+ groupValue: z5.string().nullable().optional(),
504
+ dateRange: z5.object({ start: z5.string(), end: z5.string() }).optional()
505
+ },
506
+ outputSchema: {
507
+ ...viewReadSchema.shape,
508
+ selection: z5.object({
509
+ propertyKeys: z5.array(z5.string()),
510
+ groupValue: z5.string().nullable().optional(),
511
+ dateRange: z5.object({ start: z5.string(), end: z5.string() }).optional()
512
+ }),
513
+ page: pageInfoSchema
514
+ },
515
+ annotations: { readOnlyHint: true }
516
+ },
517
+ async ({ sectionId, limit, offset, propertyKeys, groupValue, dateRange }) => {
518
+ const data = await request(
519
+ `query ($id: ID!, $pagination: PaginationInput, $propertyKeys: [String!], $groupValue: String, $dateRange: DateRangeInput) {
520
+ readViewSection(id: $id, pagination: $pagination, propertyKeys: $propertyKeys, groupValue: $groupValue, dateRange: $dateRange) {
521
+ section { ${SECTION_FIELDS} } totalCount groupProperty { key name options { id label value color order } }
522
+ rows { groupValue calendarDate note { ${NOTE_SUMMARY_FIELDS} } }
523
+ }
524
+ }`,
525
+ {
526
+ id: sectionId,
527
+ pagination: { limit, offset },
528
+ propertyKeys,
529
+ groupValue,
530
+ dateRange
531
+ }
532
+ );
533
+ const result = viewReadSchema.parse(data?.readViewSection);
534
+ const keys = propertyKeys ?? (result.section.displayType === "table" ? result.section.displayOptions.tablePropertyKeys : []);
535
+ const selection = {
536
+ propertyKeys: keys,
537
+ ...groupValue !== void 0 ? {
538
+ groupValue: groupValue === null ? null : groupValue.trim().toLowerCase().replace(/\s+/g, "-")
539
+ } : {},
540
+ ...dateRange ? { dateRange } : {}
541
+ };
542
+ return createMcpJsonToolResult({
543
+ ...result,
544
+ selection,
545
+ rows: result.rows.map((row) => ({
546
+ ...row,
547
+ note: {
548
+ ...row.note,
549
+ properties: row.note.properties?.filter((property) => keys.includes(property.key))
550
+ }
551
+ })),
552
+ page: createPageInfo(result.totalCount, limit, offset)
553
+ });
554
+ }
555
+ );
556
+ };
308
557
 
309
558
  // src/mcp.ts
310
559
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
311
- var pkg = JSON.parse(
312
- fs.readFileSync(path.resolve(__dirname, "..", "package.json"), "utf-8")
313
- );
560
+ var pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, "..", "package.json"), "utf-8"));
314
561
  if (!pkg.oceanBrain?.mcpCompatibilityVersion) {
315
562
  throw new Error("Ocean Brain MCP compatibility version is required in package metadata.");
316
563
  }
@@ -326,6 +573,9 @@ var createMcpRequestHeaders = (token) => ({
326
573
  ...token ? { Authorization: `Bearer ${token}` } : {}
327
574
  });
328
575
  var OCEAN_BRAIN_MCP_TOOLS = {
576
+ queryNotes: "ocean_brain_query_notes",
577
+ listViews: "ocean_brain_list_views",
578
+ readView: "ocean_brain_read_view",
329
579
  searchNotes: "ocean_brain_search_notes",
330
580
  readNote: "ocean_brain_read_note",
331
581
  createNote: "ocean_brain_create_note",
@@ -335,20 +585,9 @@ var OCEAN_BRAIN_MCP_TOOLS = {
335
585
  replaceNoteMarkdown: "ocean_brain_replace_note_markdown",
336
586
  listTags: "ocean_brain_list_tags",
337
587
  listProperties: "ocean_brain_list_properties",
338
- queryNotesByProperties: "ocean_brain_query_notes_by_properties",
339
- listNotesByTag: "ocean_brain_list_notes_by_tag",
340
- listNotesByTags: "ocean_brain_list_notes_by_tags",
341
- listRecentNotes: "ocean_brain_list_recent_notes",
342
- findNoteCleanupCandidates: "ocean_brain_find_note_cleanup_candidates",
343
- createTag: "ocean_brain_create_tag",
344
588
  deleteNote: "ocean_brain_delete_note"
345
589
  };
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"]);
351
- var searchModeSchema = z3.enum(["hybrid", "lexical", "semantic"]);
590
+ var searchModeSchema = z6.enum(["hybrid", "lexical", "semantic"]);
352
591
  var graphqlSearchModes = {
353
592
  hybrid: "HYBRID",
354
593
  lexical: "LEXICAL",
@@ -367,51 +606,8 @@ var formatMcpGraphqlError = (error) => {
367
606
  }
368
607
  return `GraphQL error: ${error.message} (${suffix.join(", ")})`;
369
608
  };
370
- var propertyFilterSchema = z3.object({
371
- key: z3.string().describe("Property key from ocean_brain_list_properties, e.g. state"),
372
- valueType: propertyValueTypeSchema.describe("Property value type from the property definition"),
373
- operator: propertyFilterOperatorSchema.describe("Filter operator. before/after are only valid for date or number properties."),
374
- 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).")
375
- });
376
- var normalizeOceanBrainTagName = (name) => {
377
- const trimmedName = name.trim();
378
- if (!trimmedName) {
379
- throw new Error("Tag name is required.");
380
- }
381
- if (trimmedName.startsWith("#")) {
382
- throw new Error("Ocean Brain tags use @, not #. Example: @project");
383
- }
384
- const normalizedName = trimmedName.startsWith("@") ? trimmedName : `@${trimmedName}`;
385
- if (normalizedName === "@" || /\s/.test(normalizedName.slice(1))) {
386
- throw new Error("Ocean Brain tag names must be a single token. Example: @project");
387
- }
388
- return normalizedName;
389
- };
390
- var normalizeOceanBrainTagNames = (names) => {
391
- return Array.from(
392
- new Set(names.map(normalizeOceanBrainTagName))
393
- );
394
- };
395
- var fetchOceanBrainTags = async (serverUrl, token, query, graphqlRequest = graphql) => {
396
- const data = await graphqlRequest(serverUrl, token, `
397
- query ($searchFilter: SearchFilterInput, $pagination: PaginationInput) {
398
- allTags(searchFilter: $searchFilter, pagination: $pagination) {
399
- totalCount
400
- tags {
401
- id
402
- name
403
- referenceCount
404
- }
405
- }
406
- }
407
- `, {
408
- searchFilter: { query },
409
- pagination: { limit: 100, offset: 0 }
410
- });
411
- return data?.allTags;
412
- };
413
609
  async function graphql(serverUrl, token, query, variables) {
414
- const response = await fetch(`${serverUrl}/graphql/mcp`, {
610
+ const response = await fetch(`${serverUrl}/api/integrations/v1/graphql`, {
415
611
  method: "POST",
416
612
  headers: createMcpRequestHeaders(token),
417
613
  body: JSON.stringify({ query, variables })
@@ -455,17 +651,25 @@ var formatMcpSearchNotes = (notes) => notes.map((note) => ({
455
651
  }));
456
652
  var registerMcpTools = (server, serverUrl, token, options = {}) => {
457
653
  const graphqlRequest = options.graphqlRequest ?? graphql;
654
+ const queryRequest = (query, variables) => graphqlRequest(serverUrl, token, query, variables);
655
+ registerQueryTools(server, queryRequest);
656
+ registerViewTools(server, queryRequest);
458
657
  server.tool(
459
658
  OCEAN_BRAIN_MCP_TOOLS.searchNotes,
460
659
  "Search Ocean Brain notes by keyword or meaning. Lexical uses words only, hybrid combines words and meaning, and semantic uses meaning only. Results use one ranked result shape with match and semantic-search status. Use ocean_brain_read_note to get full content for a specific note.",
461
660
  {
462
- query: z3.string().describe("Search words or a natural-language description"),
463
- limit: z3.number().optional().default(10).describe("Results per page. Search returns at most 50 per page. (default: 10)"),
464
- offset: z3.number().int().min(0).optional().default(0).describe("Results to skip for pagination (default: 0)"),
661
+ query: z6.string().describe("Search words or a natural-language description"),
662
+ limit: pageLimitSchema(10, 50).describe(
663
+ "Results per page. Search returns at most 50 per page. (default: 10)"
664
+ ),
665
+ offset: z6.number().int().min(0).optional().default(0).describe("Results to skip for pagination (default: 0)"),
465
666
  mode: searchModeSchema.default("hybrid").describe("Search mode. Defaults to hybrid.")
466
667
  },
467
668
  async ({ query, limit, offset, mode }) => {
468
- const data = await graphqlRequest(serverUrl, token, `
669
+ const data = await graphqlRequest(
670
+ serverUrl,
671
+ token,
672
+ `
469
673
  query ($query: String!, $mode: SearchMode!, $pagination: PaginationInput!) {
470
674
  searchNotes(query: $query, mode: $mode, pagination: $pagination) {
471
675
  totalCount
@@ -476,6 +680,7 @@ var registerMcpTools = (server, serverUrl, token, options = {}) => {
476
680
  noteId
477
681
  lexical
478
682
  semantic
683
+ excerpt { text source start end }
479
684
  }
480
685
  notes {
481
686
  id
@@ -486,11 +691,13 @@ var registerMcpTools = (server, serverUrl, token, options = {}) => {
486
691
  }
487
692
  }
488
693
  }
489
- `, {
490
- query,
491
- mode: graphqlSearchModes[mode],
492
- pagination: { limit, offset }
493
- });
694
+ `,
695
+ {
696
+ query,
697
+ mode: graphqlSearchModes[mode],
698
+ pagination: { limit, offset }
699
+ }
700
+ );
494
701
  const result = data?.searchNotes;
495
702
  return createMcpJsonToolResult({
496
703
  totalCount: result.totalCount,
@@ -498,59 +705,68 @@ var registerMcpTools = (server, serverUrl, token, options = {}) => {
498
705
  semanticUsed: result.semanticUsed,
499
706
  semanticError: result.semanticError,
500
707
  matches: result.matches,
708
+ page: createPageInfo(result.totalCount, limit, offset),
501
709
  notes: formatMcpSearchNotes(result.notes)
502
710
  });
503
711
  }
504
712
  );
505
713
  server.tool(
506
714
  OCEAN_BRAIN_MCP_TOOLS.readNote,
507
- "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.",
715
+ "Read note metadata, markdown and back references. Default 1000 UTF-16 units; maxLength: 0 reads the remaining body or whole selected section. Use offset or exact heading, never both. For continuation pass contentRange.nextOffset and expectedUpdatedAt from this read; sectionEnd bounds a selected section. Ambiguous headings return positions. Prefer structuredContent for code consumers.",
508
716
  {
509
- id: z3.string().describe("Note ID"),
510
- maxLength: z3.number().optional().default(1e3).describe("Max content length in characters. 0 for full content. (default: 1000)")
717
+ id: z6.string().min(1),
718
+ maxLength: z6.number().int().nonnegative().default(1e3),
719
+ offset: z6.number().int().nonnegative().optional(),
720
+ heading: z6.string().min(1).optional(),
721
+ expectedUpdatedAt: z6.string().optional()
511
722
  },
512
- async ({ id, maxLength }) => {
513
- const data = await graphqlRequest(serverUrl, token, `
514
- query ($id: ID!) {
515
- note(id: $id) {
516
- id
517
- title
518
- contentAsMarkdown
519
- createdAt
520
- updatedAt
521
- tags { id name }
522
- properties { key name value valueType option { id label value color order } }
523
- }
524
- backReferences(id: $id) {
525
- id
526
- title
723
+ async (input) => {
724
+ if (input.heading !== void 0 && input.offset !== void 0)
725
+ throw new Error("Use either heading or offset, not both.");
726
+ const data = await graphqlRequest(
727
+ serverUrl,
728
+ token,
729
+ `
730
+ query ($id: ID!, $maxLength: Int, $offset: Int, $heading: String, $expectedUpdatedAt: String) {
731
+ noteRead(id: $id, maxLength: $maxLength, offset: $offset, heading: $heading, expectedUpdatedAt: $expectedUpdatedAt) {
732
+ status reason message markdown
733
+ candidates { heading level start end }
734
+ contentRange { start end totalLength sectionEnd hasMore nextOffset }
735
+ note { id title createdAt updatedAt tags { id name } properties { key name value valueType option { label value } } }
527
736
  }
737
+ backReferences(id: $id) { id title }
528
738
  }
529
- `, { id });
530
- const note = data?.note;
531
- const backReferences = data?.backReferences ?? [];
532
- const output = formatMcpReadNoteOutput({
533
- note,
534
- backReferences,
535
- maxLength
536
- });
537
- return createMcpTextToolResult(output);
739
+ `,
740
+ input
741
+ );
742
+ return createMcpReadNoteResult(data?.noteRead, data?.backReferences);
538
743
  }
539
744
  );
540
745
  server.tool(
541
746
  OCEAN_BRAIN_MCP_TOOLS.createNote,
542
747
  "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.",
543
748
  {
544
- title: z3.string().describe("Note title"),
545
- 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."),
546
- layout: noteLayoutSchema.optional().describe("Optional note layout: narrow, wide, or full. Prefer wide for most notes unless the user explicitly wants narrow or full.")
749
+ title: z6.string().describe("Note title"),
750
+ markdown: z6.string().optional().default("").describe(
751
+ "Markdown body for the new note. In MCP markdown, tags must use [@tag]. Defaults to an empty note body."
752
+ ),
753
+ layout: noteLayoutSchema.optional().describe(
754
+ "Optional note layout: narrow, wide, or full. Prefer wide for most notes unless the user explicitly wants narrow or full."
755
+ ),
756
+ properties: z6.object({
757
+ set: z6.array(z6.object({ key: z6.string().min(1), value: z6.string() })).max(50)
758
+ }).strict().optional().describe(
759
+ "Set existing properties atomically with creation. Same keys/values as update_note_metadata; definitions and select options must already exist."
760
+ )
547
761
  },
548
- async ({ title, markdown, layout }) => {
762
+ async ({ title, markdown, layout, properties }) => {
763
+ if (properties) metadataPropertyPatchSchema.parse(properties);
549
764
  const writeToken = requireWriteToken(token, OCEAN_BRAIN_MCP_TOOLS.createNote);
550
- const result = await jsonRequest(serverUrl, writeToken, "/api/mcp/notes/create", {
765
+ const result = await jsonRequest(serverUrl, writeToken, "/api/integrations/v1/notes/create", {
551
766
  title,
552
767
  markdown,
553
- ...layout ? { layout } : {}
768
+ ...layout ? { layout } : {},
769
+ ...properties ? { properties } : {}
554
770
  });
555
771
  return createMcpJsonToolResult(result);
556
772
  }
@@ -564,10 +780,17 @@ var registerMcpTools = (server, serverUrl, token, options = {}) => {
564
780
  });
565
781
  server.tool(
566
782
  OCEAN_BRAIN_MCP_TOOLS.listTags,
567
- "List Ocean Brain tags with their note counts.",
568
- {},
569
- async () => {
570
- const data = await graphqlRequest(serverUrl, token, `
783
+ "Search and page through Ocean Brain tags that are used by notes, with note counts.",
784
+ {
785
+ query: z6.string().default("").describe("Tag name search"),
786
+ limit: pageLimitSchema(100),
787
+ offset: pageOffsetSchema
788
+ },
789
+ async ({ query, limit, offset }) => {
790
+ const data = await graphqlRequest(
791
+ serverUrl,
792
+ token,
793
+ `
571
794
  query ($searchFilter: SearchFilterInput, $pagination: PaginationInput) {
572
795
  allTags(searchFilter: $searchFilter, pagination: $pagination) {
573
796
  totalCount
@@ -578,24 +801,32 @@ var registerMcpTools = (server, serverUrl, token, options = {}) => {
578
801
  }
579
802
  }
580
803
  }
581
- `, {
582
- searchFilter: { query: "" },
583
- pagination: { limit: 100, offset: 0 }
584
- });
804
+ `,
805
+ {
806
+ searchFilter: { query },
807
+ pagination: { limit, offset }
808
+ }
809
+ );
585
810
  const result = data?.allTags;
586
- return createMcpJsonToolResult(result);
811
+ return createMcpJsonToolResult({
812
+ ...result,
813
+ page: createPageInfo(result.totalCount, limit, offset)
814
+ });
587
815
  }
588
816
  );
589
817
  server.tool(
590
818
  OCEAN_BRAIN_MCP_TOOLS.listProperties,
591
819
  "List shared Ocean Brain property definitions. Use this before property queries so keys, value types, and select option values are valid.",
592
820
  {
593
- query: z3.string().optional().default("").describe("Optional property key/name search query"),
594
- limit: z3.number().optional().default(50).describe("Max results (default: 50, server max: 100)"),
595
- offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
821
+ query: z6.string().optional().default("").describe("Optional property key/name search query"),
822
+ limit: pageLimitSchema(50, 100).describe("Max results (default: 50, server max: 100)"),
823
+ offset: pageOffsetSchema.describe("Pagination offset (default: 0)")
596
824
  },
597
825
  async ({ query, limit, offset }) => {
598
- const data = await graphqlRequest(serverUrl, token, `
826
+ const data = await graphqlRequest(
827
+ serverUrl,
828
+ token,
829
+ `
599
830
  query ($query: String, $pagination: PaginationInput) {
600
831
  notePropertyKeys(query: $query, pagination: $pagination) {
601
832
  totalCount
@@ -609,301 +840,30 @@ var registerMcpTools = (server, serverUrl, token, options = {}) => {
609
840
  }
610
841
  }
611
842
  }
612
- `, {
613
- query,
614
- pagination: { limit, offset }
615
- });
843
+ `,
844
+ {
845
+ query,
846
+ pagination: { limit, offset }
847
+ }
848
+ );
616
849
  const result = data?.notePropertyKeys;
617
850
  return createMcpJsonToolResult({
618
851
  totalCount: result.totalCount,
619
- propertyKeys: result.keys
620
- });
621
- }
622
- );
623
- server.tool(
624
- OCEAN_BRAIN_MCP_TOOLS.listNotesByTag,
625
- "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.",
626
- {
627
- tag: z3.string().describe("Tag name to inspect. You can pass @project or project."),
628
- limit: z3.number().optional().default(20).describe("Max results (default: 20)"),
629
- offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
630
- },
631
- async ({ tag, limit, offset }) => {
632
- const normalizedTag = normalizeOceanBrainTagName(tag);
633
- const tagResult = await fetchOceanBrainTags(serverUrl, token, normalizedTag, graphqlRequest);
634
- const exactMatches = tagResult.tags.filter((item) => item.name === normalizedTag);
635
- if (exactMatches.length === 0) {
636
- return createMcpJsonToolResult({
637
- requestedTag: tag,
638
- normalizedTag,
639
- tagFound: false,
640
- noteCount: 0,
641
- notes: []
642
- });
643
- }
644
- const selectedTag = exactMatches[0];
645
- const notesData = await graphqlRequest(serverUrl, token, `
646
- query ($searchFilter: SearchFilterInput, $pagination: PaginationInput) {
647
- tagNotes(searchFilter: $searchFilter, pagination: $pagination) {
648
- totalCount
649
- notes {
650
- id
651
- title
652
- updatedAt
653
- tags { id name }
654
- }
655
- }
656
- }
657
- `, {
658
- searchFilter: { query: selectedTag.id },
659
- pagination: { limit, offset }
660
- });
661
- const noteResult = notesData?.tagNotes;
662
- return createMcpJsonToolResult({
663
- requestedTag: tag,
664
- normalizedTag,
665
- tagFound: true,
666
- duplicateExactMatchCount: exactMatches.length,
667
- tag: selectedTag,
668
- totalCount: noteResult.totalCount,
669
- notes: noteResult.notes.map((note) => ({
670
- id: note.id,
671
- title: note.title,
672
- updatedAt: note.updatedAt,
673
- tags: note.tags.map((item) => item.name)
674
- }))
675
- });
676
- }
677
- );
678
- server.tool(
679
- OCEAN_BRAIN_MCP_TOOLS.listNotesByTags,
680
- "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.",
681
- {
682
- tags: z3.array(z3.string()).min(1).describe("Tag names to inspect. You can pass @project or project values."),
683
- mode: tagMatchModeSchema.optional().default("and").describe("Tag match mode. Use and for intersection, or for union. Defaults to and."),
684
- limit: z3.number().optional().default(20).describe("Max results (default: 20)"),
685
- offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
686
- },
687
- async ({ tags, mode, limit, offset }) => {
688
- const normalizedTags = normalizeOceanBrainTagNames(tags);
689
- const tagResults = await Promise.all(
690
- normalizedTags.map(async (normalizedTag) => {
691
- const result = await fetchOceanBrainTags(serverUrl, token, normalizedTag, graphqlRequest);
692
- const exactMatches = result.tags.filter((item) => item.name === normalizedTag);
693
- return {
694
- normalizedTag,
695
- exactMatches
696
- };
697
- })
698
- );
699
- const matchedTags = tagResults.filter((tagResult) => tagResult.exactMatches.length > 0).map((tagResult) => {
700
- const selectedTag = tagResult.exactMatches[0];
701
- return {
702
- id: selectedTag.id,
703
- name: selectedTag.name,
704
- referenceCount: selectedTag.referenceCount,
705
- duplicateExactMatchCount: tagResult.exactMatches.length
706
- };
707
- });
708
- const missingTags = tagResults.filter((tagResult) => tagResult.exactMatches.length === 0).map((tagResult) => tagResult.normalizedTag);
709
- let noteResult = {
710
- totalCount: 0,
711
- notes: []
712
- };
713
- if (mode === "and" && missingTags.length === 0 || mode === "or" && matchedTags.length > 0) {
714
- const notesData = await graphqlRequest(serverUrl, token, `
715
- query ($tagNames: [String!]!, $mode: TagMatchMode!, $pagination: PaginationInput) {
716
- notesByTagNames(tagNames: $tagNames, mode: $mode, pagination: $pagination) {
717
- totalCount
718
- notes {
719
- id
720
- title
721
- updatedAt
722
- tags { id name }
723
- }
724
- }
725
- }
726
- `, {
727
- tagNames: normalizedTags,
728
- mode,
729
- pagination: { limit, offset }
730
- });
731
- noteResult = notesData?.notesByTagNames;
732
- }
733
- return createMcpJsonToolResult({
734
- requestedTags: tags,
735
- normalizedTags,
736
- mode,
737
- allTagsFound: missingTags.length === 0,
738
- missingTags,
739
- tags: matchedTags,
740
- totalCount: noteResult.totalCount,
741
- notes: noteResult.notes.map((note) => ({
742
- id: note.id,
743
- title: note.title,
744
- updatedAt: note.updatedAt,
745
- tags: note.tags.map((item) => item.name)
746
- }))
747
- });
748
- }
749
- );
750
- server.tool(
751
- OCEAN_BRAIN_MCP_TOOLS.queryNotesByProperties,
752
- "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.",
753
- {
754
- propertyFilters: z3.array(propertyFilterSchema).min(1).max(10).describe("Required property filters. Multiple property filters are combined with AND."),
755
- tagNames: z3.array(z3.string()).optional().default([]).describe("Optional tag filters. You can pass @project or project."),
756
- mode: tagMatchModeSchema.optional().default("and").describe("Tag match mode for tagNames only. Property filters are always combined with AND."),
757
- sortBy: viewSortBySchema.optional().default("updatedAt").describe("Sort field (default: updatedAt)"),
758
- sortOrder: viewSortOrderSchema.optional().default("desc").describe("Sort order (default: desc)"),
759
- includeProperties: z3.boolean().optional().default(false).describe("Include returned note properties. Defaults to false to reduce tokens."),
760
- propertyKeys: z3.array(z3.string()).optional().default([]).describe("Property keys to include in output; automatically includes properties."),
761
- limit: z3.number().optional().default(20).describe("Max results (default: 20, server max: 50)"),
762
- offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
763
- },
764
- async ({ propertyFilters, tagNames, mode, sortBy, sortOrder, includeProperties, propertyKeys, limit, offset }) => {
765
- const shouldIncludeProperties = includeProperties || propertyKeys.length > 0;
766
- const normalizedTagNames = normalizeOceanBrainTagNames(tagNames);
767
- const data = await graphqlRequest(serverUrl, token, `
768
- query ($input: NotesByPropertiesInput!, $pagination: PaginationInput, $includeProperties: Boolean!) {
769
- notesByProperties(input: $input, pagination: $pagination) {
770
- totalCount
771
- notes {
772
- id
773
- title
774
- createdAt
775
- updatedAt
776
- tags { id name }
777
- properties @include(if: $includeProperties) { key name value valueType option { id label value color order } }
778
- }
779
- }
780
- }
781
- `, {
782
- input: {
783
- tagNames: normalizedTagNames,
784
- mode,
785
- propertyFilters,
786
- sortBy,
787
- sortOrder
788
- },
789
- pagination: { limit, offset },
790
- includeProperties: shouldIncludeProperties
791
- });
792
- const result = data?.notesByProperties;
793
- return createMcpJsonToolResult(formatPropertyQueryResponse({
794
- result,
795
- query: {
796
- propertyFilters,
797
- tagNames: normalizedTagNames,
798
- mode,
799
- sortBy,
800
- sortOrder,
801
- limit,
802
- offset
803
- },
804
- includeProperties: shouldIncludeProperties,
805
- propertyKeys
806
- }));
807
- }
808
- );
809
- server.tool(
810
- OCEAN_BRAIN_MCP_TOOLS.listRecentNotes,
811
- "List recently updated Ocean Brain notes. Returns titles and tags only. Use ocean_brain_read_note to get full content for a specific note.",
812
- {
813
- limit: z3.number().optional().default(10).describe("Max results (default: 10)")
814
- },
815
- async ({ limit }) => {
816
- const data = await graphqlRequest(serverUrl, token, `
817
- query ($searchFilter: SearchFilterInput, $pagination: PaginationInput) {
818
- allNotes(searchFilter: $searchFilter, pagination: $pagination) {
819
- totalCount
820
- notes {
821
- id
822
- title
823
- updatedAt
824
- tags { id name }
825
- }
826
- }
827
- }
828
- `, {
829
- searchFilter: { query: "", sortBy: "updatedAt", sortOrder: "desc" },
830
- pagination: { limit, offset: 0 }
852
+ propertyKeys: result.keys,
853
+ page: createPageInfo(result.totalCount, limit, offset)
831
854
  });
832
- const result = data?.allNotes;
833
- const notes = result.notes.map((note) => ({
834
- id: note.id,
835
- title: note.title,
836
- updatedAt: note.updatedAt,
837
- tags: note.tags.map((t) => t.name)
838
- }));
839
- return createMcpJsonToolResult({ totalCount: result.totalCount, notes });
840
- }
841
- );
842
- server.tool(
843
- OCEAN_BRAIN_MCP_TOOLS.findNoteCleanupCandidates,
844
- "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.",
845
- {
846
- keywords: z3.string().optional().default("temp tmp draft test wip").describe("Keywords that mark a note as a cleanup candidate. Comma or space separated."),
847
- limit: z3.number().optional().default(20).describe("Max results (default: 20)"),
848
- offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
849
- },
850
- async ({ keywords, limit, offset }) => {
851
- const normalizedKeywords = keywords.split(/[,\s]+/).map((keyword) => keyword.trim()).filter(Boolean);
852
- const data = await graphqlRequest(serverUrl, token, `
853
- query ($query: String, $pagination: PaginationInput) {
854
- noteCleanupCandidates(query: $query, pagination: $pagination) {
855
- id
856
- title
857
- updatedAt
858
- pinned
859
- tagNames
860
- reminderCount
861
- backReferenceCount
862
- matchedTerms
863
- reasons
864
- requiresForce
865
- forceReasons
866
- }
867
- }
868
- `, {
869
- query: keywords,
870
- pagination: { limit, offset }
871
- });
872
- const result = data?.noteCleanupCandidates;
873
- return createMcpJsonToolResult({
874
- keywords: normalizedKeywords,
875
- limit,
876
- offset,
877
- candidateCount: result.length,
878
- notes: result
879
- });
880
- }
881
- );
882
- server.tool(
883
- OCEAN_BRAIN_MCP_TOOLS.createTag,
884
- "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.",
885
- {
886
- name: z3.string().describe("Tag name to create. You can pass @project or project.")
887
- },
888
- async ({ name }) => {
889
- const writeToken = requireWriteToken(token, OCEAN_BRAIN_MCP_TOOLS.createTag);
890
- const normalizedName = normalizeOceanBrainTagName(name);
891
- const result = await jsonRequest(serverUrl, writeToken, "/api/mcp/tags/create", {
892
- name: normalizedName
893
- });
894
- return createMcpJsonToolResult(result);
895
855
  }
896
856
  );
897
857
  server.tool(
898
858
  OCEAN_BRAIN_MCP_TOOLS.deleteNote,
899
859
  "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.",
900
860
  {
901
- id: z3.string().describe("Note ID to move to trash")
861
+ id: z6.string().describe("Note ID to move to trash")
902
862
  },
903
863
  async ({ id }) => {
904
864
  const writeToken = requireWriteToken(token, OCEAN_BRAIN_MCP_TOOLS.deleteNote);
905
865
  try {
906
- const result = await jsonRequest(serverUrl, writeToken, "/api/mcp/notes/delete", { id });
866
+ const result = await jsonRequest(serverUrl, writeToken, "/api/integrations/v1/notes/delete", { id });
907
867
  return createMcpJsonToolResult(result);
908
868
  } catch (error) {
909
869
  const message = error instanceof Error ? error.message : "Unknown MCP note delete error";
@@ -928,7 +888,6 @@ export {
928
888
  OCEAN_BRAIN_MCP_TOOLS,
929
889
  OCEAN_BRAIN_MCP_VERSION_HEADER,
930
890
  createMcpRequestHeaders,
931
- normalizeOceanBrainTagName,
932
891
  registerMcpTools,
933
892
  startMcpServer
934
893
  };