ocean-brain 0.7.4 → 0.8.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 (111) hide show
  1. package/dist/index.js +3 -3
  2. package/dist/mcp.js +180 -736
  3. package/package.json +4 -1
  4. package/server/client/dist/assets/ArrowRight.es-C4zlc54Z.js +1 -0
  5. package/server/client/dist/assets/{Calendar-Bz7jheg6.js → Calendar-COrN6Hjv.js} +2 -2
  6. package/server/client/dist/assets/Callout-BK0K9E74.js +1 -0
  7. package/server/client/dist/assets/Copy.es-zm8f_XXX.js +1 -0
  8. package/server/client/dist/assets/Graph-BH1fzhue.js +1 -0
  9. package/server/client/dist/assets/{Image-DxWhlIDG.js → Image-KXuHOG4b.js} +1 -1
  10. package/server/client/dist/assets/Image.es-BKahRbIm.js +1 -0
  11. package/server/client/dist/assets/Note-C143cKKw.js +12 -0
  12. package/server/client/dist/assets/Plus.es-BqeC8T8h.js +1 -0
  13. package/server/client/dist/assets/Reminders-BYG5rz1M.js +1 -0
  14. package/server/client/dist/assets/Search-DBWsIMDP.js +1 -0
  15. package/server/client/dist/assets/{SurfaceCard-CfL1jD-O.js → SurfaceCard-CNSQfYfM.js} +1 -1
  16. package/server/client/dist/assets/Tag-5aCfSO8Y.js +1 -0
  17. package/server/client/dist/assets/TagNotes-BPZjN-6l.js +1 -0
  18. package/server/client/dist/assets/Trash.es-CRQkTnXZ.js +1 -0
  19. package/server/client/dist/assets/ViewNotes-DGLNF96x.js +1 -0
  20. package/server/client/dist/assets/ViewSectionDialog-CzskgmMF.js +102 -0
  21. package/server/client/dist/assets/Views-DR5jT23G.js +1 -0
  22. package/server/client/dist/assets/{graph-vendor-CUxe67Lr.js → graph-vendor-UTvF2NS_.js} +2 -2
  23. package/server/client/dist/assets/image.api-8dlMwzMd.js +17 -0
  24. package/server/client/dist/assets/{index-C5BGzDgt.css → index-BEUqDuAZ.css} +1 -1
  25. package/server/client/dist/assets/{index-DiMt3Dz4.js → index-DGIAh3ZY.js} +1 -1
  26. package/server/client/dist/assets/index-VHp17GQX.js +342 -0
  27. package/server/client/dist/assets/manage-image-RqtWKas2.js +1 -0
  28. package/server/client/dist/assets/{manage-image-detail-Bqw8NURO.js → manage-image-detail-BTSIbLlQ.js} +1 -1
  29. package/server/client/dist/assets/mcp-EDurbggR.js +4 -0
  30. package/server/client/dist/assets/mcp-admin-adapter-DmGUo5G8.js +1 -0
  31. package/server/client/dist/assets/{note-core-Z6kqulGB.js → note-core-BklwyZHk.js} +1 -1
  32. package/server/client/dist/assets/{note-vendor-i1M2FSI2.js → note-vendor-DkYKUmLo.js} +1 -1
  33. package/server/client/dist/assets/{placeholder-Ro00byT7.js → placeholder-DeKgSBnC.js} +4 -4
  34. package/server/client/dist/assets/{properties-Tid6-ghT.js → properties-BjrZvOYn.js} +1 -1
  35. package/server/client/dist/assets/tag.api-Dl8DQd0m.js +21 -0
  36. package/server/client/dist/assets/trash-C5ntddLu.js +1 -0
  37. package/server/client/dist/assets/{useReminderMutate-BWymIujb.js → useReminderMutate-CoXXqJY2.js} +1 -1
  38. package/server/client/dist/index.html +5 -5
  39. package/server/dist/app.js +2 -2
  40. package/server/dist/app.js.map +1 -1
  41. package/server/dist/features/auth/http/api.js +5 -1
  42. package/server/dist/features/auth/http/api.js.map +1 -1
  43. package/server/dist/features/auth/http/login-page.js +1 -1
  44. package/server/dist/features/auth/http/pages.js +8 -1
  45. package/server/dist/features/auth/http/pages.js.map +1 -1
  46. package/server/dist/features/auth/service.js +16 -7
  47. package/server/dist/features/auth/service.js.map +1 -1
  48. package/server/dist/features/image/http/upload.js +34 -23
  49. package/server/dist/features/image/http/upload.js.map +1 -1
  50. package/server/dist/features/image/services/validation.js +111 -0
  51. package/server/dist/features/image/services/validation.js.map +1 -0
  52. package/server/dist/features/mcp-admin/http/handlers.js +11 -3
  53. package/server/dist/features/mcp-admin/http/handlers.js.map +1 -1
  54. package/server/dist/features/note/http/mcp.js +8 -43
  55. package/server/dist/features/note/http/mcp.js.map +1 -1
  56. package/server/dist/features/note/services/cleanup.js +2 -2
  57. package/server/dist/features/note/services/cleanup.js.map +1 -1
  58. package/server/dist/features/note/services/markdown-intent-write.js +11 -43
  59. package/server/dist/features/note/services/markdown-intent-write.js.map +1 -1
  60. package/server/dist/features/note/services/markdown-patch.js +4 -123
  61. package/server/dist/features/note/services/markdown-patch.js.map +1 -1
  62. package/server/dist/features/note/services/write.js +13 -13
  63. package/server/dist/features/note/services/write.js.map +1 -1
  64. package/server/dist/features/tag/graphql/tag.query.resolver.js +16 -1
  65. package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -1
  66. package/server/dist/features/tag/graphql/tag.type-defs.js +2 -0
  67. package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -1
  68. package/server/dist/modules/app-version.js +105 -0
  69. package/server/dist/modules/app-version.js.map +1 -0
  70. package/server/dist/modules/auth-guard.js +21 -2
  71. package/server/dist/modules/auth-guard.js.map +1 -1
  72. package/server/dist/modules/auth-redirect.js +10 -0
  73. package/server/dist/modules/auth-redirect.js.map +1 -0
  74. package/server/dist/modules/mcp-auth.js +62 -1
  75. package/server/dist/modules/mcp-auth.js.map +1 -1
  76. package/server/dist/modules/rate-limit.js +15 -1
  77. package/server/dist/modules/rate-limit.js.map +1 -1
  78. package/server/dist/modules/server-events-handler.js +17 -0
  79. package/server/dist/modules/server-events-handler.js.map +1 -1
  80. package/server/dist/modules/session-store.js +149 -0
  81. package/server/dist/modules/session-store.js.map +1 -0
  82. package/server/dist/paths.js +1 -0
  83. package/server/dist/paths.js.map +1 -1
  84. package/server/dist/routes/api.js +39 -20
  85. package/server/dist/routes/api.js.map +1 -1
  86. package/server/dist/routes/auth-pages.js +13 -2
  87. package/server/dist/routes/auth-pages.js.map +1 -1
  88. package/server/dist/routes/client.js +63 -3
  89. package/server/dist/routes/client.js.map +1 -1
  90. package/server/dist/routes/graphql.js +23 -19
  91. package/server/dist/routes/graphql.js.map +1 -1
  92. package/server/client/dist/assets/Callout-DlFLkTCG.js +0 -1
  93. package/server/client/dist/assets/Graph-BdxtJvog.js +0 -1
  94. package/server/client/dist/assets/Image.es-BFodqKS5.js +0 -1
  95. package/server/client/dist/assets/Note-BgMxsWZT.js +0 -22
  96. package/server/client/dist/assets/Plus.es-Yq7J1Ur4.js +0 -1
  97. package/server/client/dist/assets/Reminders-CUsR56ff.js +0 -1
  98. package/server/client/dist/assets/Search-DncrGgOZ.js +0 -1
  99. package/server/client/dist/assets/Tag-CKDLs2Y6.js +0 -1
  100. package/server/client/dist/assets/TagNotes-ByX_Qias.js +0 -1
  101. package/server/client/dist/assets/Trash.es-BPrF6TPV.js +0 -1
  102. package/server/client/dist/assets/ViewNotes-r1kxKZ8O.js +0 -1
  103. package/server/client/dist/assets/ViewSectionTableRenderer-DQ4UeCnm.js +0 -102
  104. package/server/client/dist/assets/Views-FzFBtNT2.js +0 -1
  105. package/server/client/dist/assets/image.api-Bcg3TFCG.js +0 -17
  106. package/server/client/dist/assets/index-BFfQKAUY.js +0 -361
  107. package/server/client/dist/assets/manage-image-DzDlncCM.js +0 -1
  108. package/server/client/dist/assets/mcp-BqasqTWC.js +0 -1
  109. package/server/client/dist/assets/trash-bHK02-Up.js +0 -1
  110. package/server/dist/features/image/services/remote-fetch.js +0 -176
  111. package/server/dist/features/image/services/remote-fetch.js.map +0 -1
package/dist/mcp.js CHANGED
@@ -1,226 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/mcp.ts
4
- import fs2 from "fs";
5
- import path2 from "path";
4
+ import fs from "fs";
5
+ 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 z3 } from "zod";
10
-
11
- // src/mcp-write-safety.ts
12
- import crypto from "crypto";
13
- import fs from "fs";
14
- import os from "os";
15
- import path from "path";
16
- import { z } from "zod";
17
- var destructiveMcpWriteFields = {
18
- dryRun: z.boolean().optional().default(true).describe("Preview the destructive change first. Commit only after dry-run returns operation.operationId and operation.confirmToken."),
19
- operationId: z.string().optional().describe("Dry-run response operation.operationId. Required when dryRun is false."),
20
- confirmToken: z.string().optional().describe("Dry-run response operation.confirmToken. Required when dryRun is false."),
21
- force: z.boolean().optional().default(false).describe("Explicitly confirm a bulk destructive change when the preview says force is required.")
22
- };
23
- var confirmedMcpWriteFields = {
24
- dryRun: z.boolean().optional().default(true).describe("Preview the write first. Commit only after dry-run returns operation.operationId and operation.confirmToken."),
25
- operationId: z.string().optional().describe("Dry-run response operation.operationId. Required when dryRun is false."),
26
- confirmToken: z.string().optional().describe("Dry-run response operation.confirmToken. Required when dryRun is false.")
27
- };
28
- var McpCliWriteSafetyError = class extends Error {
29
- code;
30
- constructor(code, message) {
31
- super(message);
32
- this.name = "McpCliWriteSafetyError";
33
- this.code = code;
34
- }
35
- };
36
- var DEFAULT_TTL_MS = 10 * 60 * 1e3;
37
- var DEFAULT_ROOT_DIR = path.resolve(os.homedir(), ".ocean-brain", "mcp-write-safety");
38
- var createError = (code, message) => new McpCliWriteSafetyError(code, message);
39
- var ensureDirExists = (dirPath) => {
40
- fs.mkdirSync(dirPath, { recursive: true });
41
- };
42
- var hashToken = (token) => crypto.createHash("sha256").update(token).digest("hex");
43
- var toPublicPendingOperation = (operation) => ({
44
- actor: operation.actor,
45
- affectedIds: operation.affectedIds,
46
- createdAt: operation.createdAt,
47
- estimatedChangeCount: operation.estimatedChangeCount,
48
- expiresAt: operation.expiresAt,
49
- force: operation.force,
50
- ...operation.operationFingerprint ? { operationFingerprint: operation.operationFingerprint } : {},
51
- operationId: operation.operationId,
52
- risk: operation.risk,
53
- summary: operation.summary,
54
- toolName: operation.toolName
55
- });
56
- var parsePendingOperation = (raw) => JSON.parse(raw);
57
- var defaultMcpWriteSafetyDir = () => {
58
- return path.resolve(process.env.OCEAN_BRAIN_MCP_WRITE_SAFETY_DIR || DEFAULT_ROOT_DIR);
59
- };
60
- var formatMcpGraphqlError = (error) => {
61
- const suffix = [];
62
- if (error.extensions?.code) {
63
- suffix.push(`code=${error.extensions.code}`);
64
- }
65
- if (error.extensions?.operationId) {
66
- suffix.push(`operationId=${error.extensions.operationId}`);
67
- }
68
- if (suffix.length === 0) {
69
- return `GraphQL error: ${error.message}`;
70
- }
71
- return `GraphQL error: ${error.message} (${suffix.join(", ")})`;
72
- };
73
- var createMcpWriteSafetyCoordinator = (options = {}) => {
74
- const rootDir = path.resolve(options.rootDir ?? defaultMcpWriteSafetyDir());
75
- const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
76
- const now = options.now ?? (() => /* @__PURE__ */ new Date());
77
- const randomBytes = options.randomBytes ?? ((size) => crypto.randomBytes(size));
78
- const pendingDir = path.resolve(rootDir, "pending");
79
- const logPath = path.resolve(rootDir, "operations.jsonl");
80
- const appendLog = (entry) => {
81
- ensureDirExists(rootDir);
82
- fs.appendFileSync(logPath, `${JSON.stringify(entry)}
83
- `, "utf-8");
84
- };
85
- const buildLogEntry = (operation, phase, detail) => ({
86
- actor: operation.actor,
87
- affectedIds: operation.affectedIds,
88
- estimatedChangeCount: operation.estimatedChangeCount,
89
- force: operation.force,
90
- operationId: operation.operationId,
91
- phase,
92
- risk: operation.risk,
93
- summary: operation.summary,
94
- timestamp: now().toISOString(),
95
- toolName: operation.toolName,
96
- ...detail ? { detail } : {}
97
- });
98
- const getPendingPath = (operationId) => path.resolve(pendingDir, `${operationId}.json`);
99
- const cleanupExpiredOperations = () => {
100
- if (!fs.existsSync(pendingDir)) {
101
- return;
102
- }
103
- for (const fileName of fs.readdirSync(pendingDir)) {
104
- const filePath = path.resolve(pendingDir, fileName);
105
- const pendingOperation = parsePendingOperation(fs.readFileSync(filePath, "utf-8"));
106
- if (new Date(pendingOperation.expiresAt).getTime() > now().getTime()) {
107
- continue;
108
- }
109
- fs.rmSync(filePath, { force: true });
110
- appendLog(buildLogEntry(toPublicPendingOperation(pendingOperation), "expired"));
111
- }
112
- };
113
- const listPendingOperations = () => {
114
- cleanupExpiredOperations();
115
- if (!fs.existsSync(pendingDir)) {
116
- return [];
117
- }
118
- return fs.readdirSync(pendingDir).map((fileName) => path.resolve(pendingDir, fileName)).map((filePath) => parsePendingOperation(fs.readFileSync(filePath, "utf-8"))).map(toPublicPendingOperation).sort((left, right) => left.createdAt.localeCompare(right.createdAt));
119
- };
120
- const prepareOperation = (input) => {
121
- ensureDirExists(pendingDir);
122
- const operationId = `mcpw_${randomBytes(8).toString("hex")}`;
123
- const confirmToken = `confirm_${randomBytes(12).toString("hex")}`;
124
- const currentTime = now();
125
- const createdAt = currentTime.toISOString();
126
- const expiresAt = new Date(currentTime.getTime() + ttlMs).toISOString();
127
- const pendingOperation = {
128
- actor: input.actor,
129
- affectedIds: input.affectedIds,
130
- confirmTokenHash: hashToken(confirmToken),
131
- createdAt,
132
- estimatedChangeCount: input.estimatedChangeCount,
133
- expiresAt,
134
- force: Boolean(input.force),
135
- ...input.operationFingerprint ? { operationFingerprint: input.operationFingerprint } : {},
136
- operationId,
137
- risk: input.risk,
138
- summary: input.summary,
139
- toolName: input.toolName
140
- };
141
- fs.writeFileSync(getPendingPath(operationId), JSON.stringify(pendingOperation, null, 2), "utf-8");
142
- appendLog(buildLogEntry(toPublicPendingOperation(pendingOperation), "prepared"));
143
- return {
144
- ...toPublicPendingOperation(pendingOperation),
145
- confirmToken
146
- };
147
- };
148
- const requireConfirmedOperation = (request, expected) => {
149
- if (!request.operationId || !request.confirmToken) {
150
- throw createError(
151
- "CONFIRMATION_REQUIRED",
152
- "A destructive MCP write requires operationId and confirmToken after a dry run."
153
- );
154
- }
155
- const pendingPath = getPendingPath(request.operationId);
156
- if (!fs.existsSync(pendingPath)) {
157
- throw createError("INVALID_CONFIRMATION", "The write confirmation was not found or has already been consumed.");
158
- }
159
- const pendingOperation = parsePendingOperation(fs.readFileSync(pendingPath, "utf-8"));
160
- const publicPendingOperation = toPublicPendingOperation(pendingOperation);
161
- if (new Date(pendingOperation.expiresAt).getTime() <= now().getTime()) {
162
- fs.rmSync(pendingPath, { force: true });
163
- appendLog(buildLogEntry(publicPendingOperation, "expired"));
164
- throw createError("CONFIRMATION_EXPIRED", "The write confirmation expired. Run dryRun again.");
165
- }
166
- if (pendingOperation.toolName !== expected.toolName) {
167
- appendLog(buildLogEntry(publicPendingOperation, "rejected", "tool_mismatch"));
168
- throw createError("INVALID_CONFIRMATION", "The write confirmation does not match this MCP tool.");
169
- }
170
- if (expected.operationFingerprint && pendingOperation.operationFingerprint !== expected.operationFingerprint) {
171
- appendLog(buildLogEntry(publicPendingOperation, "rejected", "operation_fingerprint_mismatch"));
172
- throw createError("INVALID_CONFIRMATION", "The write confirmation does not match this MCP operation.");
173
- }
174
- if (pendingOperation.confirmTokenHash !== hashToken(request.confirmToken)) {
175
- appendLog(buildLogEntry(publicPendingOperation, "rejected", "token_mismatch"));
176
- throw createError("INVALID_CONFIRMATION", "The write confirmation token is invalid.");
177
- }
178
- fs.rmSync(pendingPath, { force: true });
179
- appendLog(buildLogEntry(publicPendingOperation, "confirmed"));
180
- return publicPendingOperation;
181
- };
182
- const ensureDestructiveWriteRequest = (request, input) => {
183
- if (request.dryRun ?? true) {
184
- return {
185
- kind: "dry-run",
186
- operation: prepareOperation({
187
- ...input,
188
- force: request.force ?? input.force
189
- })
190
- };
191
- }
192
- return {
193
- kind: "confirmed",
194
- operation: requireConfirmedOperation(request, {
195
- toolName: input.toolName,
196
- operationFingerprint: input.operationFingerprint
197
- })
198
- };
199
- };
200
- const recordWriteResult = (operation, success, detail) => {
201
- appendLog(buildLogEntry(operation, success ? "executed" : "failed", detail));
202
- };
203
- const getStatus = () => {
204
- const pendingOperations = listPendingOperations();
205
- return {
206
- confirmationTtlMs: ttlMs,
207
- logPath,
208
- pendingDir,
209
- pendingOperationCount: pendingOperations.length,
210
- pendingOperations,
211
- rootDir,
212
- writeToolsEnabled: false
213
- };
214
- };
215
- return {
216
- ensureDestructiveWriteRequest,
217
- getStatus,
218
- listPendingOperations,
219
- prepareOperation,
220
- recordWriteResult,
221
- requireConfirmedOperation
222
- };
223
- };
9
+ import { z as z2 } from "zod";
224
10
 
225
11
  // src/mcp-note-output.ts
226
12
  var formatBackReferenceLink = (backReference) => {
@@ -272,69 +58,65 @@ var formatMcpReadNoteOutput = ({
272
58
  };
273
59
 
274
60
  // src/mcp-intent-write-tools.ts
275
- import crypto2 from "crypto";
276
- import { z as z2 } from "zod";
277
- var markdownPatchSelectorSchema = z2.discriminatedUnion("type", [
278
- z2.object({
279
- type: z2.literal("exact_text"),
280
- text: z2.string(),
281
- before: z2.string().optional(),
282
- after: z2.string().optional()
61
+ 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()
283
68
  }),
284
- z2.object({
285
- type: z2.literal("match_candidate"),
286
- text: z2.string(),
287
- matchIndex: z2.number().int().nonnegative(),
288
- lineStart: z2.number().int().positive(),
289
- matchSha256: z2.string(),
290
- surroundingHash: z2.string(),
291
- positionHint: z2.enum(["first", "last"]).optional()
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()
292
77
  })
293
78
  ]);
294
- var markdownPatchOperationSchema = z2.discriminatedUnion("type", [
295
- z2.object({
296
- type: z2.literal("replace"),
297
- replacement: z2.string()
79
+ var markdownPatchOperationSchema = z.discriminatedUnion("type", [
80
+ z.object({
81
+ type: z.literal("replace"),
82
+ replacement: z.string()
298
83
  }),
299
- z2.object({
300
- type: z2.literal("insert_before"),
301
- insertion: z2.string()
84
+ z.object({
85
+ type: z.literal("insert_before"),
86
+ insertion: z.string()
302
87
  }),
303
- z2.object({
304
- type: z2.literal("insert_after"),
305
- insertion: z2.string()
88
+ z.object({
89
+ type: z.literal("insert_after"),
90
+ insertion: z.string()
306
91
  })
307
92
  ]);
308
- var markdownAppendPlacementSchema = z2.discriminatedUnion("type", [
309
- z2.object({
310
- type: z2.literal("end")
93
+ var markdownAppendPlacementSchema = z.discriminatedUnion("type", [
94
+ z.object({
95
+ type: z.literal("end")
311
96
  }),
312
- z2.object({
313
- type: z2.literal("after_heading"),
314
- heading: z2.string(),
315
- level: z2.number().int().min(1).max(6).optional()
97
+ z.object({
98
+ type: z.literal("after_heading"),
99
+ heading: z.string(),
100
+ level: z.number().int().min(1).max(6).optional()
316
101
  })
317
102
  ]);
318
- var markdownWritePolicySchema = z2.object({
319
- allowNoop: z2.boolean().optional(),
320
- maxChangedChars: z2.number().int().nonnegative().optional(),
321
- maxChangedLines: z2.number().int().nonnegative().optional(),
322
- diffPreviewMaxChars: z2.number().int().nonnegative().optional(),
323
- preserveTags: z2.union([z2.boolean(), z2.literal("warn")]).optional(),
324
- preserveReferences: z2.union([z2.boolean(), z2.literal("warn")]).optional()
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()
325
107
  }).optional();
326
108
  var MCP_METADATA_PROPERTY_PATCH_LIMIT = 50;
327
109
  var normalizeMetadataPropertyKey = (key) => key.trim().toLowerCase().replace(/\s+/g, "-");
328
- var metadataPropertyPatchSchema = z2.object({
329
- set: z2.array(z2.object({
330
- key: z2.string().min(1).describe("Property key from ocean_brain_list_properties."),
331
- value: z2.string().describe("String value: select option.value, date YYYY-MM-DD, boolean true/false, number finite string, url http(s).")
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).")
332
114
  })).max(MCP_METADATA_PROPERTY_PATCH_LIMIT).optional().describe("Set up to 50 property values. Keys must be unique."),
333
- 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.")
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.")
334
116
  }).superRefine((patch, context) => {
335
117
  if ((patch.set?.length ?? 0) === 0 && (patch.deleteKeys?.length ?? 0) === 0) {
336
118
  context.addIssue({
337
- code: z2.ZodIssueCode.custom,
119
+ code: z.ZodIssueCode.custom,
338
120
  message: "properties must include set or deleteKeys."
339
121
  });
340
122
  return;
@@ -344,7 +126,7 @@ var metadataPropertyPatchSchema = z2.object({
344
126
  const key = normalizeMetadataPropertyKey(item.key);
345
127
  if (setKeys.has(key)) {
346
128
  context.addIssue({
347
- code: z2.ZodIssueCode.custom,
129
+ code: z.ZodIssueCode.custom,
348
130
  message: `properties.set contains duplicate key ${key}.`
349
131
  });
350
132
  }
@@ -355,154 +137,41 @@ var metadataPropertyPatchSchema = z2.object({
355
137
  const key = normalizeMetadataPropertyKey(rawKey);
356
138
  if (deleteKeys.has(key)) {
357
139
  context.addIssue({
358
- code: z2.ZodIssueCode.custom,
140
+ code: z.ZodIssueCode.custom,
359
141
  message: `properties.deleteKeys contains duplicate key ${key}.`
360
142
  });
361
143
  }
362
144
  if (setKeys.has(key)) {
363
145
  context.addIssue({
364
- code: z2.ZodIssueCode.custom,
146
+ code: z.ZodIssueCode.custom,
365
147
  message: `properties cannot set and delete ${key} in one request.`
366
148
  });
367
149
  }
368
150
  deleteKeys.add(key);
369
151
  }
370
152
  }).optional();
371
- var sha256 = (value) => crypto2.createHash("sha256").update(value).digest("hex");
372
- var isRecord = (value) => {
373
- return typeof value === "object" && value !== null && !Array.isArray(value);
374
- };
375
- var stableJsonStringify = (value) => {
376
- if (Array.isArray(value)) {
377
- return `[${value.map(stableJsonStringify).join(",")}]`;
378
- }
379
- if (isRecord(value)) {
380
- const properties = Object.keys(value).filter((key) => value[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${stableJsonStringify(value[key])}`);
381
- return `{${properties.join(",")}}`;
382
- }
383
- return JSON.stringify(value) ?? "null";
384
- };
385
- var normalizeServerUrl = (serverUrl) => {
386
- try {
387
- const url = new URL(serverUrl);
388
- url.hash = "";
389
- return url.toString().replace(/\/$/, "");
390
- } catch {
391
- return serverUrl.replace(/\/$/, "");
392
- }
393
- };
394
- var normalizeIntentWritePayloadForFingerprint = (toolName, payload) => {
395
- const normalizedPayload = { ...payload };
396
- if (toolName === "ocean_brain_append_note_markdown") {
397
- normalizedPayload.placement ??= { type: "end" };
398
- normalizedPayload.separator ??= "\n\n";
399
- }
400
- return normalizedPayload;
401
- };
402
- var createIntentWriteOperationFingerprint = (serverUrl, token, toolName, payload) => {
403
- return sha256(stableJsonStringify({
404
- payload: normalizeIntentWritePayloadForFingerprint(toolName, payload),
405
- serverUrl: normalizeServerUrl(serverUrl),
406
- tokenDigest: token ? sha256(token) : null,
407
- toolName
408
- }));
409
- };
410
- var getErrorDetail = (error) => {
411
- return error instanceof Error ? error.message : "Unknown MCP write error";
412
- };
413
- var requireCommitConfirmationFields = (request) => {
414
- if (request.dryRun !== false) {
415
- return;
416
- }
417
- if (!request.operationId || !request.confirmToken) {
418
- throw new Error("Commit mode requires both operationId and confirmToken from the dry-run response.");
419
- }
420
- };
421
- var resolvePayloadWithBaseline = async (fetchNoteWriteBaseline, writeToken, id, payload) => {
422
- if (typeof payload.expectedUpdatedAt === "string" || typeof payload.baseMarkdownSha256 === "string") {
423
- return payload;
424
- }
425
- const baseline = await fetchNoteWriteBaseline(id, writeToken);
426
- return {
427
- ...payload,
428
- expectedUpdatedAt: baseline.updatedAt
429
- };
430
- };
431
- var prepareConfirmedWriteOperation = (writeSafety, result, input) => {
432
- if (result.status !== "dry_run") {
433
- return result;
434
- }
435
- return {
436
- ...result,
437
- operation: writeSafety.prepareOperation({
438
- actor: "mcp-bearer",
439
- affectedIds: input.affectedIds,
440
- estimatedChangeCount: result.proposed?.changedLineCount ?? 1,
441
- operationFingerprint: input.operationFingerprint,
442
- risk: "high-impact",
443
- summary: input.summary,
444
- toolName: input.toolName
445
- })
446
- };
447
- };
448
- var requireConfirmedWriteOperation = (writeSafety, request, input) => {
449
- const intent = writeSafety.ensureDestructiveWriteRequest(
450
- request,
451
- {
452
- actor: "mcp-bearer",
453
- affectedIds: input.affectedIds,
454
- estimatedChangeCount: 1,
455
- operationFingerprint: input.operationFingerprint,
456
- risk: "high-impact",
457
- summary: input.summary,
458
- toolName: input.toolName
459
- }
460
- );
461
- if (intent.kind !== "confirmed") {
462
- throw new Error("Expected a confirmed MCP write operation.");
463
- }
464
- return intent.operation;
465
- };
466
- var executeConfirmedWrite = async (writeSafety, operation, request) => {
467
- try {
468
- const result = await request();
469
- writeSafety.recordWriteResult(
470
- operation,
471
- result.status === "applied",
472
- result.status === "applied" ? void 0 : result.reason
473
- );
474
- return result;
475
- } catch (error) {
476
- writeSafety.recordWriteResult(operation, false, getErrorDetail(error));
477
- throw error;
478
- }
479
- };
480
153
  var registerIntentWriteTools = (server, {
481
- fetchNoteWriteBaseline,
482
154
  jsonRequest: jsonRequest2,
483
155
  requireWriteToken: requireWriteToken2,
484
156
  serverUrl,
485
157
  token,
486
- tools,
487
- writeSafety
158
+ tools
488
159
  }) => {
489
160
  server.tool(
490
161
  tools.patchNoteMarkdown,
491
- "Patch a small part of an Ocean Brain note. Dry-run is the default; commit requires operationId and confirmToken from the dry-run result.",
162
+ "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.",
492
163
  {
493
- id: z2.string().describe("Note ID to patch"),
494
- expectedUpdatedAt: z2.string().optional().describe("Expected note updatedAt from a prior read. Auto-fetched when omitted unless baseMarkdownSha256 is provided."),
495
- baseMarkdownSha256: z2.string().optional().describe("SHA-256 of the current markdown. Optional alternative to expectedUpdatedAt."),
496
- intent: z2.string().describe("Human-readable reason for the patch."),
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."),
497
168
  selector: markdownPatchSelectorSchema.describe("Exact text or previously returned match candidate."),
498
169
  operation: markdownPatchOperationSchema.describe("replace, insert_before, or insert_after."),
499
- policy: markdownWritePolicySchema,
500
- ...confirmedMcpWriteFields
170
+ policy: markdownWritePolicySchema
501
171
  },
502
- async ({ id, expectedUpdatedAt, baseMarkdownSha256, intent, selector, operation, policy, dryRun, operationId, confirmToken }) => {
172
+ async ({ id, expectedUpdatedAt, baseMarkdownSha256, intent, selector, operation, policy }) => {
503
173
  const writeToken = requireWriteToken2(token, tools.patchNoteMarkdown);
504
- requireCommitConfirmationFields({ dryRun, operationId, confirmToken });
505
- const payload = await resolvePayloadWithBaseline(fetchNoteWriteBaseline, writeToken, id, {
174
+ const payload = {
506
175
  id,
507
176
  ...expectedUpdatedAt ? { expectedUpdatedAt } : {},
508
177
  ...baseMarkdownSha256 ? { baseMarkdownSha256 } : {},
@@ -510,43 +179,8 @@ var registerIntentWriteTools = (server, {
510
179
  selector,
511
180
  operation,
512
181
  ...policy ? { policy } : {}
513
- });
514
- const operationFingerprint = createIntentWriteOperationFingerprint(serverUrl, writeToken, tools.patchNoteMarkdown, payload);
515
- if (dryRun ?? true) {
516
- const result2 = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/patch-markdown", {
517
- ...payload,
518
- dryRun: true
519
- });
520
- return {
521
- content: [{
522
- type: "text",
523
- text: JSON.stringify(prepareConfirmedWriteOperation(writeSafety, result2, {
524
- affectedIds: [id],
525
- operationFingerprint,
526
- summary: intent,
527
- toolName: tools.patchNoteMarkdown
528
- }), null, 2)
529
- }]
530
- };
531
- }
532
- const confirmedOperation = requireConfirmedWriteOperation(
533
- writeSafety,
534
- { dryRun, operationId, confirmToken },
535
- {
536
- affectedIds: [id],
537
- operationFingerprint,
538
- summary: intent,
539
- toolName: tools.patchNoteMarkdown
540
- }
541
- );
542
- const result = await executeConfirmedWrite(
543
- writeSafety,
544
- confirmedOperation,
545
- () => jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/patch-markdown", {
546
- ...payload,
547
- dryRun: false
548
- })
549
- );
182
+ };
183
+ const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/patch-markdown", payload);
550
184
  return {
551
185
  content: [{
552
186
  type: "text",
@@ -557,22 +191,20 @@ var registerIntentWriteTools = (server, {
557
191
  );
558
192
  server.tool(
559
193
  tools.appendNoteMarkdown,
560
- "Append markdown to a note without replacing existing body content. Dry-run is the default; commit requires operationId and confirmToken.",
194
+ "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.",
561
195
  {
562
- id: z2.string().describe("Note ID to append to"),
563
- expectedUpdatedAt: z2.string().optional().describe("Expected note updatedAt from a prior read. Auto-fetched when omitted unless baseMarkdownSha256 is provided."),
564
- baseMarkdownSha256: z2.string().optional().describe("SHA-256 of the current markdown. Optional alternative to expectedUpdatedAt."),
565
- intent: z2.string().describe("Human-readable reason for the append."),
566
- insertion: z2.string().describe("Markdown to append. Tags are body tokens such as [@tag] or [#tag]."),
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]."),
567
201
  placement: markdownAppendPlacementSchema.optional().describe("Default is end. after_heading requires one unique matching heading."),
568
- separator: z2.enum(["\n\n", "\n"]).optional().describe("Separator inserted around appended markdown. Defaults to a blank line."),
569
- policy: markdownWritePolicySchema,
570
- ...confirmedMcpWriteFields
202
+ separator: z.enum(["\n\n", "\n"]).optional().describe("Separator inserted around appended markdown. Defaults to a blank line."),
203
+ policy: markdownWritePolicySchema
571
204
  },
572
- async ({ id, expectedUpdatedAt, baseMarkdownSha256, intent, insertion, placement, separator, policy, dryRun, operationId, confirmToken }) => {
205
+ async ({ id, expectedUpdatedAt, baseMarkdownSha256, intent, insertion, placement, separator, policy }) => {
573
206
  const writeToken = requireWriteToken2(token, tools.appendNoteMarkdown);
574
- requireCommitConfirmationFields({ dryRun, operationId, confirmToken });
575
- const payload = await resolvePayloadWithBaseline(fetchNoteWriteBaseline, writeToken, id, {
207
+ const payload = {
576
208
  id,
577
209
  ...expectedUpdatedAt ? { expectedUpdatedAt } : {},
578
210
  ...baseMarkdownSha256 ? { baseMarkdownSha256 } : {},
@@ -581,43 +213,8 @@ var registerIntentWriteTools = (server, {
581
213
  ...placement ? { placement } : {},
582
214
  ...separator ? { separator } : {},
583
215
  ...policy ? { policy } : {}
584
- });
585
- const operationFingerprint = createIntentWriteOperationFingerprint(serverUrl, writeToken, tools.appendNoteMarkdown, payload);
586
- if (dryRun ?? true) {
587
- const result2 = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/append-markdown", {
588
- ...payload,
589
- dryRun: true
590
- });
591
- return {
592
- content: [{
593
- type: "text",
594
- text: JSON.stringify(prepareConfirmedWriteOperation(writeSafety, result2, {
595
- affectedIds: [id],
596
- operationFingerprint,
597
- summary: intent,
598
- toolName: tools.appendNoteMarkdown
599
- }), null, 2)
600
- }]
601
- };
602
- }
603
- const confirmedOperation = requireConfirmedWriteOperation(
604
- writeSafety,
605
- { dryRun, operationId, confirmToken },
606
- {
607
- affectedIds: [id],
608
- operationFingerprint,
609
- summary: intent,
610
- toolName: tools.appendNoteMarkdown
611
- }
612
- );
613
- const result = await executeConfirmedWrite(
614
- writeSafety,
615
- confirmedOperation,
616
- () => jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/append-markdown", {
617
- ...payload,
618
- dryRun: false
619
- })
620
- );
216
+ };
217
+ const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/append-markdown", payload);
621
218
  return {
622
219
  content: [{
623
220
  type: "text",
@@ -628,62 +225,24 @@ var registerIntentWriteTools = (server, {
628
225
  );
629
226
  server.tool(
630
227
  tools.updateNoteMetadata,
631
- "Update note title/layout/properties. 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.",
228
+ "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.",
632
229
  {
633
- id: z2.string().describe("Note ID to update"),
634
- expectedUpdatedAt: z2.string().optional().describe("Expected note updatedAt from a prior read. Auto-fetched when omitted."),
635
- title: z2.string().optional().describe("New note title"),
636
- layout: z2.enum(["narrow", "wide", "full"]).optional().describe("New note layout"),
637
- properties: metadataPropertyPatchSchema.describe("Patch existing shared property values. Include set and/or deleteKeys; empty patches are rejected."),
638
- ...confirmedMcpWriteFields
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"),
234
+ properties: metadataPropertyPatchSchema.describe("Patch existing shared property values. Include set and/or deleteKeys; empty patches are rejected.")
639
235
  },
640
- async ({ id, expectedUpdatedAt, title, layout, properties, dryRun, operationId, confirmToken }) => {
236
+ async ({ id, expectedUpdatedAt, title, layout, properties }) => {
641
237
  const writeToken = requireWriteToken2(token, tools.updateNoteMetadata);
642
- requireCommitConfirmationFields({ dryRun, operationId, confirmToken });
643
- const payload = await resolvePayloadWithBaseline(fetchNoteWriteBaseline, writeToken, id, {
238
+ const payload = {
644
239
  id,
645
240
  ...expectedUpdatedAt ? { expectedUpdatedAt } : {},
646
241
  ...title !== void 0 ? { title } : {},
647
242
  ...layout ? { layout } : {},
648
243
  ...properties ? { properties } : {}
649
- });
650
- const summary = properties ? `Update note ${id} metadata and properties` : `Update note ${id} metadata`;
651
- const operationFingerprint = createIntentWriteOperationFingerprint(serverUrl, writeToken, tools.updateNoteMetadata, payload);
652
- if (dryRun ?? true) {
653
- const result2 = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/metadata", {
654
- ...payload,
655
- dryRun: true
656
- });
657
- return {
658
- content: [{
659
- type: "text",
660
- text: JSON.stringify(prepareConfirmedWriteOperation(writeSafety, result2, {
661
- affectedIds: [id],
662
- operationFingerprint,
663
- summary,
664
- toolName: tools.updateNoteMetadata
665
- }), null, 2)
666
- }]
667
- };
668
- }
669
- const confirmedOperation = requireConfirmedWriteOperation(
670
- writeSafety,
671
- { dryRun, operationId, confirmToken },
672
- {
673
- affectedIds: [id],
674
- operationFingerprint,
675
- summary,
676
- toolName: tools.updateNoteMetadata
677
- }
678
- );
679
- const result = await executeConfirmedWrite(
680
- writeSafety,
681
- confirmedOperation,
682
- () => jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/metadata", {
683
- ...payload,
684
- dryRun: false
685
- })
686
- );
244
+ };
245
+ const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/metadata", payload);
687
246
  return {
688
247
  content: [{
689
248
  type: "text",
@@ -694,63 +253,26 @@ var registerIntentWriteTools = (server, {
694
253
  );
695
254
  server.tool(
696
255
  tools.replaceNoteMarkdown,
697
- "Replace a note body as a high-impact full overwrite. Dry-run returns a full diff; commit requires operationId and confirmToken.",
256
+ "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.",
698
257
  {
699
- id: z2.string().describe("Note ID to replace"),
700
- expectedUpdatedAt: z2.string().optional().describe("Expected note updatedAt from a prior read. Auto-fetched when omitted unless baseMarkdownSha256 is provided."),
701
- baseMarkdownSha256: z2.string().optional().describe("SHA-256 of the current markdown. Optional alternative to expectedUpdatedAt."),
702
- intent: z2.string().describe("Human-readable reason for the full replace."),
703
- replacement: z2.string().describe("Complete replacement markdown body. Tags are body tokens such as [@tag] or [#tag]."),
704
- policy: markdownWritePolicySchema,
705
- ...confirmedMcpWriteFields
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]."),
263
+ policy: markdownWritePolicySchema
706
264
  },
707
- async ({ id, expectedUpdatedAt, baseMarkdownSha256, intent, replacement, policy, dryRun, operationId, confirmToken }) => {
265
+ async ({ id, expectedUpdatedAt, baseMarkdownSha256, intent, replacement, policy }) => {
708
266
  const writeToken = requireWriteToken2(token, tools.replaceNoteMarkdown);
709
- requireCommitConfirmationFields({ dryRun, operationId, confirmToken });
710
- const payload = await resolvePayloadWithBaseline(fetchNoteWriteBaseline, writeToken, id, {
267
+ const payload = {
711
268
  id,
712
269
  ...expectedUpdatedAt ? { expectedUpdatedAt } : {},
713
270
  ...baseMarkdownSha256 ? { baseMarkdownSha256 } : {},
714
271
  intent,
715
272
  replacement,
716
273
  ...policy ? { policy } : {}
717
- });
718
- const operationFingerprint = createIntentWriteOperationFingerprint(serverUrl, writeToken, tools.replaceNoteMarkdown, payload);
719
- if (dryRun ?? true) {
720
- const result2 = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/replace-markdown", {
721
- ...payload,
722
- dryRun: true
723
- });
724
- return {
725
- content: [{
726
- type: "text",
727
- text: JSON.stringify(prepareConfirmedWriteOperation(writeSafety, result2, {
728
- affectedIds: [id],
729
- operationFingerprint,
730
- summary: intent,
731
- toolName: tools.replaceNoteMarkdown
732
- }), null, 2)
733
- }]
734
- };
735
- }
736
- const confirmedOperation = requireConfirmedWriteOperation(
737
- writeSafety,
738
- { dryRun, operationId, confirmToken },
739
- {
740
- affectedIds: [id],
741
- operationFingerprint,
742
- summary: intent,
743
- toolName: tools.replaceNoteMarkdown
744
- }
745
- );
746
- const result = await executeConfirmedWrite(
747
- writeSafety,
748
- confirmedOperation,
749
- () => jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/replace-markdown", {
750
- ...payload,
751
- dryRun: false
752
- })
753
- );
274
+ };
275
+ const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/replace-markdown", payload);
754
276
  return {
755
277
  content: [{
756
278
  type: "text",
@@ -787,10 +309,24 @@ var formatPropertyQueryResponse = ({
787
309
  });
788
310
 
789
311
  // src/mcp.ts
790
- var __dirname = path2.dirname(fileURLToPath(import.meta.url));
312
+ var __dirname = path.dirname(fileURLToPath(import.meta.url));
791
313
  var pkg = JSON.parse(
792
- fs2.readFileSync(path2.resolve(__dirname, "..", "package.json"), "utf-8")
314
+ fs.readFileSync(path.resolve(__dirname, "..", "package.json"), "utf-8")
793
315
  );
316
+ if (!pkg.oceanBrain?.mcpCompatibilityVersion) {
317
+ throw new Error("Ocean Brain MCP compatibility version is required in package metadata.");
318
+ }
319
+ var OCEAN_BRAIN_MCP_VERSION_HEADER = "X-Ocean-Brain-MCP-Version";
320
+ var OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER = "X-Ocean-Brain-MCP-Compatibility-Version";
321
+ var OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER = "X-Ocean-Brain-MCP-Client-Version";
322
+ var OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION = pkg.oceanBrain.mcpCompatibilityVersion;
323
+ var createMcpRequestHeaders = (token) => ({
324
+ "Content-Type": "application/json",
325
+ [OCEAN_BRAIN_MCP_VERSION_HEADER]: OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION,
326
+ [OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER]: OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION,
327
+ [OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER]: pkg.version,
328
+ ...token ? { Authorization: `Bearer ${token}` } : {}
329
+ });
794
330
  var OCEAN_BRAIN_MCP_TOOLS = {
795
331
  searchNotes: "ocean_brain_search_notes",
796
332
  readNote: "ocean_brain_read_note",
@@ -806,22 +342,34 @@ var OCEAN_BRAIN_MCP_TOOLS = {
806
342
  listNotesByTag: "ocean_brain_list_notes_by_tag",
807
343
  listNotesByTags: "ocean_brain_list_notes_by_tags",
808
344
  listRecentNotes: "ocean_brain_list_recent_notes",
809
- writeSafetyStatus: "ocean_brain_write_safety_status",
810
345
  findNoteCleanupCandidates: "ocean_brain_find_note_cleanup_candidates",
811
346
  createTag: "ocean_brain_create_tag",
812
347
  deleteNote: "ocean_brain_delete_note"
813
348
  };
814
- var noteLayoutSchema = z3.enum(["narrow", "wide", "full"]);
815
- var tagMatchModeSchema = z3.enum(["and", "or"]);
816
- var propertyValueTypeSchema = z3.enum(["text", "url", "number", "date", "boolean", "select"]);
817
- var propertyFilterOperatorSchema = z3.enum(["equals", "before", "after", "exists", "notExists"]);
818
- var viewSortBySchema = z3.enum(["updatedAt", "createdAt", "title"]);
819
- var viewSortOrderSchema = z3.enum(["asc", "desc"]);
820
- var propertyFilterSchema = z3.object({
821
- key: z3.string().describe("Property key from ocean_brain_list_properties, e.g. state"),
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"]);
355
+ var formatMcpGraphqlError = (error) => {
356
+ const suffix = [];
357
+ if (error.extensions?.code) {
358
+ suffix.push(`code=${error.extensions.code}`);
359
+ }
360
+ if (error.extensions?.operationId) {
361
+ suffix.push(`operationId=${error.extensions.operationId}`);
362
+ }
363
+ if (suffix.length === 0) {
364
+ return `GraphQL error: ${error.message}`;
365
+ }
366
+ return `GraphQL error: ${error.message} (${suffix.join(", ")})`;
367
+ };
368
+ var propertyFilterSchema = z2.object({
369
+ key: z2.string().describe("Property key from ocean_brain_list_properties, e.g. state"),
822
370
  valueType: propertyValueTypeSchema.describe("Property value type from the property definition"),
823
371
  operator: propertyFilterOperatorSchema.describe("Filter operator. before/after are only valid for date or number properties."),
824
- 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).")
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).")
825
373
  });
826
374
  var normalizeOceanBrainTagName = (name) => {
827
375
  const trimmedName = name.trim();
@@ -860,14 +408,14 @@ var fetchOceanBrainTags = async (serverUrl, token, query) => {
860
408
  async function graphql(serverUrl, token, query, variables) {
861
409
  const response = await fetch(`${serverUrl}/graphql/mcp`, {
862
410
  method: "POST",
863
- headers: {
864
- "Content-Type": "application/json",
865
- ...token ? { Authorization: `Bearer ${token}` } : {}
866
- },
411
+ headers: createMcpRequestHeaders(token),
867
412
  body: JSON.stringify({ query, variables })
868
413
  });
869
414
  if (!response.ok) {
870
- throw new Error(`GraphQL request failed: ${response.status} ${response.statusText}`);
415
+ const errorBody = await response.json().catch(() => void 0);
416
+ throw new Error(
417
+ errorBody?.message ? `GraphQL request failed: ${errorBody.code || response.status} ${errorBody.message}` : `GraphQL request failed: ${response.status} ${response.statusText}`
418
+ );
871
419
  }
872
420
  const result = await response.json();
873
421
  if (result.errors?.length) {
@@ -878,10 +426,7 @@ async function graphql(serverUrl, token, query, variables) {
878
426
  async function jsonRequest(serverUrl, token, pathName, body) {
879
427
  const response = await fetch(`${serverUrl}${pathName}`, {
880
428
  method: "POST",
881
- headers: {
882
- "Content-Type": "application/json",
883
- ...token ? { Authorization: `Bearer ${token}` } : {}
884
- },
429
+ headers: createMcpRequestHeaders(token),
885
430
  body: JSON.stringify(body)
886
431
  });
887
432
  const result = await response.json();
@@ -890,30 +435,23 @@ async function jsonRequest(serverUrl, token, pathName, body) {
890
435
  }
891
436
  return result;
892
437
  }
893
- var fetchMcpNoteWriteBaseline = async (serverUrl, token, id, request = jsonRequest) => {
894
- const result = await request(serverUrl, token, "/api/mcp/notes/baseline", { id });
895
- return result.note;
896
- };
897
438
  var requireWriteToken = (token, toolName) => {
898
439
  if (token) {
899
440
  return token;
900
441
  }
901
442
  throw new Error(`${toolName} requires an MCP bearer token. Set --token or --token-file.`);
902
443
  };
903
- async function startMcpServer(serverUrl, token, options = {}) {
444
+ async function startMcpServer(serverUrl, token) {
904
445
  const server = new McpServer({
905
446
  name: "ocean-brain",
906
447
  version: pkg.version
907
448
  });
908
- const writeSafety = createMcpWriteSafetyCoordinator({
909
- rootDir: options.writeSafetyDir ?? defaultMcpWriteSafetyDir()
910
- });
911
449
  server.tool(
912
450
  OCEAN_BRAIN_MCP_TOOLS.searchNotes,
913
451
  "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.",
914
452
  {
915
- query: z3.string().describe("Search keyword"),
916
- limit: z3.number().optional().default(10).describe("Max results (default: 10)")
453
+ query: z2.string().describe("Search keyword"),
454
+ limit: z2.number().optional().default(10).describe("Max results (default: 10)")
917
455
  },
918
456
  async ({ query, limit }) => {
919
457
  const data = await graphql(serverUrl, token, `
@@ -953,8 +491,8 @@ async function startMcpServer(serverUrl, token, options = {}) {
953
491
  OCEAN_BRAIN_MCP_TOOLS.readNote,
954
492
  "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.",
955
493
  {
956
- id: z3.string().describe("Note ID"),
957
- maxLength: z3.number().optional().default(1e3).describe("Max content length in characters. 0 for full content. (default: 1000)")
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)")
958
496
  },
959
497
  async ({ id, maxLength }) => {
960
498
  const data = await graphql(serverUrl, token, `
@@ -993,8 +531,8 @@ async function startMcpServer(serverUrl, token, options = {}) {
993
531
  OCEAN_BRAIN_MCP_TOOLS.createNote,
994
532
  "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.",
995
533
  {
996
- title: z3.string().describe("Note title"),
997
- markdown: z3.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."),
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."),
998
536
  layout: noteLayoutSchema.optional().describe("Optional note layout: narrow, wide, or full. Prefer wide for most notes unless the user explicitly wants narrow or full.")
999
537
  },
1000
538
  async ({ title, markdown, layout }) => {
@@ -1014,11 +552,11 @@ async function startMcpServer(serverUrl, token, options = {}) {
1014
552
  );
1015
553
  server.tool(
1016
554
  OCEAN_BRAIN_MCP_TOOLS.updateNote,
1017
- "Legacy full-field note update through the MCP write path. Prefer patch/append/metadata tools for small changes; use this only when intentionally replacing provided fields.",
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.",
1018
556
  {
1019
- id: z3.string().describe("Note ID to update"),
1020
- title: z3.string().optional().describe("New note title"),
1021
- markdown: z3.string().optional().describe("Replacement markdown body. In MCP markdown, tags must use [@tag] or [#tag]."),
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]."),
1022
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.")
1023
561
  },
1024
562
  async ({ id, title, markdown, layout }) => {
@@ -1038,13 +576,11 @@ async function startMcpServer(serverUrl, token, options = {}) {
1038
576
  }
1039
577
  );
1040
578
  registerIntentWriteTools(server, {
1041
- fetchNoteWriteBaseline: (id, writeToken) => fetchMcpNoteWriteBaseline(serverUrl, writeToken, id),
1042
579
  jsonRequest,
1043
580
  requireWriteToken,
1044
581
  serverUrl,
1045
582
  token,
1046
- tools: OCEAN_BRAIN_MCP_TOOLS,
1047
- writeSafety
583
+ tools: OCEAN_BRAIN_MCP_TOOLS
1048
584
  });
1049
585
  server.tool(
1050
586
  OCEAN_BRAIN_MCP_TOOLS.listTags,
@@ -1079,9 +615,9 @@ async function startMcpServer(serverUrl, token, options = {}) {
1079
615
  OCEAN_BRAIN_MCP_TOOLS.listProperties,
1080
616
  "List shared Ocean Brain property definitions. Use this before property queries so keys, value types, and select option values are valid.",
1081
617
  {
1082
- query: z3.string().optional().default("").describe("Optional property key/name search query"),
1083
- limit: z3.number().optional().default(50).describe("Max results (default: 50, server max: 100)"),
1084
- offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
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)")
1085
621
  },
1086
622
  async ({ query, limit, offset }) => {
1087
623
  const data = await graphql(serverUrl, token, `
@@ -1118,9 +654,9 @@ async function startMcpServer(serverUrl, token, options = {}) {
1118
654
  OCEAN_BRAIN_MCP_TOOLS.listNotesByTag,
1119
655
  "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.",
1120
656
  {
1121
- tag: z3.string().describe("Tag name to inspect. You can pass @project or project."),
1122
- limit: z3.number().optional().default(20).describe("Max results (default: 20)"),
1123
- offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
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)")
1124
660
  },
1125
661
  async ({ tag, limit, offset }) => {
1126
662
  const normalizedTag = normalizeOceanBrainTagName(tag);
@@ -1183,10 +719,10 @@ async function startMcpServer(serverUrl, token, options = {}) {
1183
719
  OCEAN_BRAIN_MCP_TOOLS.listNotesByTags,
1184
720
  "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.",
1185
721
  {
1186
- tags: z3.array(z3.string()).min(1).describe("Tag names to inspect. You can pass @project or project values."),
722
+ tags: z2.array(z2.string()).min(1).describe("Tag names to inspect. You can pass @project or project values."),
1187
723
  mode: tagMatchModeSchema.optional().default("and").describe("Tag match mode. Use and for intersection, or for union. Defaults to and."),
1188
- limit: z3.number().optional().default(20).describe("Max results (default: 20)"),
1189
- offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
724
+ limit: z2.number().optional().default(20).describe("Max results (default: 20)"),
725
+ offset: z2.number().optional().default(0).describe("Pagination offset (default: 0)")
1190
726
  },
1191
727
  async ({ tags, mode, limit, offset }) => {
1192
728
  const normalizedTags = normalizeOceanBrainTagNames(tags);
@@ -1260,15 +796,15 @@ async function startMcpServer(serverUrl, token, options = {}) {
1260
796
  OCEAN_BRAIN_MCP_TOOLS.queryNotesByProperties,
1261
797
  "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.",
1262
798
  {
1263
- propertyFilters: z3.array(propertyFilterSchema).min(1).max(10).describe("Required property filters. Multiple property filters are combined with AND."),
1264
- tagNames: z3.array(z3.string()).optional().default([]).describe("Optional tag filters. You can pass @project, #project, or project."),
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."),
1265
801
  mode: tagMatchModeSchema.optional().default("and").describe("Tag match mode for tagNames only. Property filters are always combined with AND."),
1266
802
  sortBy: viewSortBySchema.optional().default("updatedAt").describe("Sort field (default: updatedAt)"),
1267
803
  sortOrder: viewSortOrderSchema.optional().default("desc").describe("Sort order (default: desc)"),
1268
- includeProperties: z3.boolean().optional().default(false).describe("Include returned note properties. Defaults to false to reduce tokens."),
1269
- propertyKeys: z3.array(z3.string()).optional().default([]).describe("Property keys to include in output; automatically includes properties."),
1270
- limit: z3.number().optional().default(20).describe("Max results (default: 20, server max: 50)"),
1271
- offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
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)")
1272
808
  },
1273
809
  async ({ propertyFilters, tagNames, mode, sortBy, sortOrder, includeProperties, propertyKeys, limit, offset }) => {
1274
810
  const shouldIncludeProperties = includeProperties || propertyKeys.length > 0;
@@ -1327,7 +863,7 @@ async function startMcpServer(serverUrl, token, options = {}) {
1327
863
  OCEAN_BRAIN_MCP_TOOLS.listRecentNotes,
1328
864
  "List recently updated Ocean Brain notes. Returns titles and tags only. Use ocean_brain_read_note to get full content for a specific note.",
1329
865
  {
1330
- limit: z3.number().optional().default(10).describe("Max results (default: 10)")
866
+ limit: z2.number().optional().default(10).describe("Max results (default: 10)")
1331
867
  },
1332
868
  async ({ limit }) => {
1333
869
  const data = await graphql(serverUrl, token, `
@@ -1361,40 +897,13 @@ async function startMcpServer(serverUrl, token, options = {}) {
1361
897
  };
1362
898
  }
1363
899
  );
1364
- server.tool(
1365
- OCEAN_BRAIN_MCP_TOOLS.writeSafetyStatus,
1366
- "Inspect pending destructive write confirmations and local operation log state for enabled Ocean Brain MCP write tools.",
1367
- {},
1368
- async () => {
1369
- return {
1370
- content: [{
1371
- type: "text",
1372
- text: JSON.stringify({
1373
- ...writeSafety.getStatus(),
1374
- destructiveWriteTools: [OCEAN_BRAIN_MCP_TOOLS.deleteNote],
1375
- writeTools: [
1376
- OCEAN_BRAIN_MCP_TOOLS.createNote,
1377
- OCEAN_BRAIN_MCP_TOOLS.updateNote,
1378
- OCEAN_BRAIN_MCP_TOOLS.patchNoteMarkdown,
1379
- OCEAN_BRAIN_MCP_TOOLS.appendNoteMarkdown,
1380
- OCEAN_BRAIN_MCP_TOOLS.updateNoteMetadata,
1381
- OCEAN_BRAIN_MCP_TOOLS.replaceNoteMarkdown,
1382
- OCEAN_BRAIN_MCP_TOOLS.createTag,
1383
- OCEAN_BRAIN_MCP_TOOLS.deleteNote
1384
- ],
1385
- writeToolsEnabled: true
1386
- }, null, 2)
1387
- }]
1388
- };
1389
- }
1390
- );
1391
900
  server.tool(
1392
901
  OCEAN_BRAIN_MCP_TOOLS.findNoteCleanupCandidates,
1393
- "Find Ocean Brain note cleanup candidates for temporary or draft notes before deletion. Use this before ocean_brain_delete_note.",
902
+ "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.",
1394
903
  {
1395
- keywords: z3.string().optional().default("temp tmp draft test wip").describe("Keywords that mark a note as a cleanup candidate. Comma or space separated."),
1396
- limit: z3.number().optional().default(20).describe("Max results (default: 20)"),
1397
- offset: z3.number().optional().default(0).describe("Pagination offset (default: 0)")
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)")
1398
907
  },
1399
908
  async ({ keywords, limit, offset }) => {
1400
909
  const normalizedKeywords = keywords.split(/[,\s]+/).map((keyword) => keyword.trim()).filter(Boolean);
@@ -1437,7 +946,7 @@ async function startMcpServer(serverUrl, token, options = {}) {
1437
946
  OCEAN_BRAIN_MCP_TOOLS.createTag,
1438
947
  "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.",
1439
948
  {
1440
- name: z3.string().describe("Tag name to create. You can pass @project or project.")
949
+ name: z2.string().describe("Tag name to create. You can pass @project or project.")
1441
950
  },
1442
951
  async ({ name }) => {
1443
952
  const writeToken = requireWriteToken(token, OCEAN_BRAIN_MCP_TOOLS.createTag);
@@ -1454,92 +963,23 @@ async function startMcpServer(serverUrl, token, options = {}) {
1454
963
  );
1455
964
  server.tool(
1456
965
  OCEAN_BRAIN_MCP_TOOLS.deleteNote,
1457
- "Delete an Ocean Brain note safely through a dry-run and confirmation flow. Start with dryRun=true, then call again with dryRun=false, operationId, and confirmToken.",
966
+ "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.",
1458
967
  {
1459
- id: z3.string().describe("Note ID to delete"),
1460
- ...destructiveMcpWriteFields
968
+ id: z2.string().describe("Note ID to move to trash")
1461
969
  },
1462
- async ({ id, dryRun, operationId, confirmToken, force }) => {
970
+ async ({ id }) => {
1463
971
  const writeToken = requireWriteToken(token, OCEAN_BRAIN_MCP_TOOLS.deleteNote);
1464
- const data = await graphql(serverUrl, writeToken, `
1465
- query ($id: ID!) {
1466
- noteCleanupPreview(id: $id) {
1467
- id
1468
- title
1469
- updatedAt
1470
- pinned
1471
- tagNames
1472
- reminderCount
1473
- backReferences {
1474
- id
1475
- title
1476
- }
1477
- orphanedTagNames
1478
- requiresForce
1479
- forceReasons
1480
- }
1481
- }
1482
- `, { id });
1483
- const preview = data?.noteCleanupPreview;
1484
- if (!preview) {
1485
- return {
1486
- content: [{
1487
- type: "text",
1488
- text: JSON.stringify({
1489
- deleted: false,
1490
- reason: "NOTE_NOT_FOUND",
1491
- id
1492
- }, null, 2)
1493
- }]
1494
- };
1495
- }
1496
- const intent = writeSafety.ensureDestructiveWriteRequest(
1497
- { dryRun, operationId, confirmToken, force },
1498
- {
1499
- actor: "mcp-bearer",
1500
- affectedIds: [preview.id],
1501
- estimatedChangeCount: 1,
1502
- force: preview.requiresForce,
1503
- risk: "destructive",
1504
- summary: `Delete note ${preview.id} (${preview.title})`,
1505
- toolName: OCEAN_BRAIN_MCP_TOOLS.deleteNote
1506
- }
1507
- );
1508
- if (intent.kind === "dry-run") {
1509
- return {
1510
- content: [{
1511
- type: "text",
1512
- text: JSON.stringify({
1513
- mode: "dry-run",
1514
- preview,
1515
- operation: intent.operation
1516
- }, null, 2)
1517
- }]
1518
- };
1519
- }
1520
- if (intent.operation.force && !force) {
1521
- throw new Error(`${OCEAN_BRAIN_MCP_TOOLS.deleteNote} requires force=true because ${preview.forceReasons.join(", ")}.`);
1522
- }
1523
972
  try {
1524
- const result = await jsonRequest(serverUrl, writeToken, "/api/mcp/notes/delete", {
1525
- id: preview.id
1526
- });
1527
- writeSafety.recordWriteResult(intent.operation, true);
973
+ const result = await jsonRequest(serverUrl, writeToken, "/api/mcp/notes/delete", { id });
1528
974
  return {
1529
975
  content: [{
1530
976
  type: "text",
1531
- text: JSON.stringify({
1532
- mode: "commit",
1533
- preview,
1534
- operation: intent.operation,
1535
- result
1536
- }, null, 2)
977
+ text: JSON.stringify(result, null, 2)
1537
978
  }]
1538
979
  };
1539
980
  } catch (error) {
1540
981
  const message = error instanceof Error ? error.message : "Unknown MCP note delete error";
1541
- writeSafety.recordWriteResult(intent.operation, false, message);
1542
- throw error;
982
+ throw new Error(message);
1543
983
  }
1544
984
  }
1545
985
  );
@@ -1547,7 +987,11 @@ async function startMcpServer(serverUrl, token, options = {}) {
1547
987
  await server.connect(transport);
1548
988
  }
1549
989
  export {
990
+ OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,
991
+ OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION,
992
+ OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,
1550
993
  OCEAN_BRAIN_MCP_TOOLS,
1551
- fetchMcpNoteWriteBaseline,
994
+ OCEAN_BRAIN_MCP_VERSION_HEADER,
995
+ createMcpRequestHeaders,
1552
996
  startMcpServer
1553
997
  };