ocean-brain 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/dist/mcp.js +230 -291
- package/package.json +5 -5
- package/server/client/dist/assets/ArrowRight.es-g2YZTklZ.js +1 -0
- package/server/client/dist/assets/Calendar-BP6HTuSn.js +25 -0
- package/server/client/dist/assets/Callout-B_IPSF_5.js +1 -0
- package/server/client/dist/assets/Copy.es-CNxe4ujr.js +1 -0
- package/server/client/dist/assets/Graph-D9GeO71k.js +1 -0
- package/server/client/dist/assets/Graph-Xha3fwhp.js +46 -0
- package/server/client/dist/assets/Image-BLEjLUqd.js +1 -0
- package/server/client/dist/assets/Image.es-DnON8cv2.js +1 -0
- package/server/client/dist/assets/Note-DjrCYtat.js +24 -0
- package/server/client/dist/assets/NotePropertiesPanel-2DgXEM4B.js +2 -0
- package/server/client/dist/assets/Plus.es-CQ8wn2is.js +1 -0
- package/server/client/dist/assets/Reminders-B4l3KdlK.js +1 -0
- package/server/client/dist/assets/Search-BN5sf7bE.js +1 -0
- package/server/client/dist/assets/SurfaceCard-BZm73nyS.js +1 -0
- package/server/client/dist/assets/Tag-oSnmViVp.js +1 -0
- package/server/client/dist/assets/TagNotes-BDTCcJcL.js +1 -0
- package/server/client/dist/assets/Trash.es-0aPpA-3S.js +1 -0
- package/server/client/dist/assets/ViewNotes-CiZtGJNE.js +1 -0
- package/server/client/dist/assets/{ViewSectionDialog-brTrsF6G.js → ViewSectionDialog-Ctl_694Y.js} +17 -17
- package/server/client/dist/assets/Views-D-zlatYD.js +1 -0
- package/server/client/dist/assets/app-DmqIOMWF.css +1 -0
- package/server/client/dist/assets/app-cTRcKMGc.js +26 -0
- package/server/client/dist/assets/floating-ui.react-dom-wWLZ1CdM.js +1 -0
- package/server/client/dist/assets/{image.api-CYsjXiMr.js → image.api-CVuuvTBk.js} +1 -1
- package/server/client/dist/assets/index-DKJChPr4.js +1 -0
- package/server/client/dist/assets/manage-image-BZjaxp-f.js +1 -0
- package/server/client/dist/assets/manage-image-detail-DPv0FQ2m.js +1 -0
- package/server/client/dist/assets/{mcp-Dd98N9Hq.js → mcp-CT_9IV2c.js} +2 -2
- package/server/client/dist/assets/mcp-admin-adapter-xAuyZc_M.js +1 -0
- package/server/client/dist/assets/note-core-Buwzyxi7.js +13 -0
- package/server/client/dist/assets/note-runtime-CPDVOFEo.js +168 -0
- package/server/client/dist/assets/note-ui-CHI-tzj-.js +2 -0
- package/server/client/dist/assets/{index-DPI6YoDt.css → note-ui-DL8owV-U.css} +1 -1
- package/server/client/dist/assets/note.api-D_00ZzJC.js +356 -0
- package/server/client/dist/assets/placeholder-DbdGiYGL.js +34 -0
- package/server/client/dist/assets/preload-helper-BXl3LOEh.js +1 -0
- package/server/client/dist/assets/properties-D447wsWk.js +1 -0
- package/server/client/dist/assets/route-preload.js +2 -0
- package/server/client/dist/assets/{tag.api-CwmC8crB.js → tag.api-CX0SE06J.js} +1 -1
- package/server/client/dist/assets/time-B3pt8vt4.js +9 -0
- package/server/client/dist/assets/trash-8sH7EZgc.js +1 -0
- package/server/client/dist/assets/url-BSnWpmJx.js +1 -0
- package/server/client/dist/assets/{useReminderMutate-DuVKxpBh.js → useReminderMutate-CLK1tHvT.js} +4 -4
- package/server/client/dist/index.html +10 -5
- package/server/dist/app.js +1 -1
- package/server/dist/modules/blocknote.js +262 -75
- package/server/dist/modules/error-handler.js +8 -0
- package/server/client/dist/assets/ArrowRight.es-CbA8ey6a.js +0 -1
- package/server/client/dist/assets/Calendar-CgWgr1BV.js +0 -25
- package/server/client/dist/assets/Callout-DKruIT0f.js +0 -1
- package/server/client/dist/assets/Copy.es-DFbgpaNJ.js +0 -1
- package/server/client/dist/assets/Graph-kCltGRlY.js +0 -1
- package/server/client/dist/assets/Image-7W0DZwMo.js +0 -1
- package/server/client/dist/assets/Image.es-5pXLx9TU.js +0 -1
- package/server/client/dist/assets/Note-BW6Np_5g.js +0 -20
- package/server/client/dist/assets/Plus.es-B0sg64VJ.js +0 -1
- package/server/client/dist/assets/Reminders-C10fHQcl.js +0 -1
- package/server/client/dist/assets/Search-tf2nV4v0.js +0 -1
- package/server/client/dist/assets/SurfaceCard-pydtvHfu.js +0 -1
- package/server/client/dist/assets/Tag-uCoYbISy.js +0 -1
- package/server/client/dist/assets/TagNotes-CqOcQiRw.js +0 -1
- package/server/client/dist/assets/Trash.es-w32Goxfa.js +0 -1
- package/server/client/dist/assets/ViewNotes-slPXXoOY.js +0 -1
- package/server/client/dist/assets/Views-B_wsb-XJ.js +0 -1
- package/server/client/dist/assets/graph-vendor-BAF95C0g.js +0 -46
- package/server/client/dist/assets/index-BR05aSXr.js +0 -342
- package/server/client/dist/assets/index-D83QbOcw.js +0 -1
- package/server/client/dist/assets/manage-image-BYIhWu56.js +0 -1
- package/server/client/dist/assets/manage-image-detail-6KgmQvbN.js +0 -1
- package/server/client/dist/assets/mcp-admin-adapter-DOgBsYpl.js +0 -1
- package/server/client/dist/assets/note-core-CMGUaW1v.js +0 -176
- package/server/client/dist/assets/note-vendor-Hoz9Q4-4.js +0 -54
- package/server/client/dist/assets/placeholder-siHGg9aK.js +0 -34
- package/server/client/dist/assets/properties-CyVRKgcT.js +0 -1
- package/server/client/dist/assets/trash-wojQBK9Z.js +0 -1
package/dist/mcp.js
CHANGED
|
@@ -6,7 +6,7 @@ import path from "path";
|
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
-
import { z as
|
|
9
|
+
import { z as z4 } from "zod";
|
|
10
10
|
|
|
11
11
|
// src/mcp-note-output.ts
|
|
12
12
|
var formatBackReferenceLink = (backReference) => {
|
|
@@ -58,65 +58,86 @@ var formatMcpReadNoteOutput = ({
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
// src/mcp-intent-write-tools.ts
|
|
61
|
+
import { z as z2 } from "zod";
|
|
62
|
+
|
|
63
|
+
// src/mcp-tool-support.ts
|
|
61
64
|
import { z } from "zod";
|
|
62
|
-
var
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
var noteLayoutSchema = z.enum(["narrow", "wide", "full"]);
|
|
66
|
+
var createMcpTextToolResult = (text) => ({
|
|
67
|
+
content: [
|
|
68
|
+
{
|
|
69
|
+
type: "text",
|
|
70
|
+
text
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
var createMcpJsonToolResult = (value) => {
|
|
75
|
+
return createMcpTextToolResult(JSON.stringify(value, null, 2));
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/mcp-intent-write-tools.ts
|
|
79
|
+
var markdownPatchSelectorSchema = z2.discriminatedUnion("type", [
|
|
80
|
+
z2.object({
|
|
81
|
+
type: z2.literal("exact_text"),
|
|
82
|
+
text: z2.string(),
|
|
83
|
+
before: z2.string().optional(),
|
|
84
|
+
after: z2.string().optional()
|
|
68
85
|
}),
|
|
69
|
-
|
|
70
|
-
type:
|
|
71
|
-
text:
|
|
72
|
-
matchIndex:
|
|
73
|
-
lineStart:
|
|
74
|
-
matchSha256:
|
|
75
|
-
surroundingHash:
|
|
76
|
-
positionHint:
|
|
86
|
+
z2.object({
|
|
87
|
+
type: z2.literal("match_candidate"),
|
|
88
|
+
text: z2.string(),
|
|
89
|
+
matchIndex: z2.number().int().nonnegative(),
|
|
90
|
+
lineStart: z2.number().int().positive(),
|
|
91
|
+
matchSha256: z2.string(),
|
|
92
|
+
surroundingHash: z2.string(),
|
|
93
|
+
positionHint: z2.enum(["first", "last"]).optional()
|
|
77
94
|
})
|
|
78
95
|
]);
|
|
79
|
-
var markdownPatchOperationSchema =
|
|
80
|
-
|
|
81
|
-
type:
|
|
82
|
-
replacement:
|
|
96
|
+
var markdownPatchOperationSchema = z2.discriminatedUnion("type", [
|
|
97
|
+
z2.object({
|
|
98
|
+
type: z2.literal("replace"),
|
|
99
|
+
replacement: z2.string()
|
|
83
100
|
}),
|
|
84
|
-
|
|
85
|
-
type:
|
|
86
|
-
insertion:
|
|
101
|
+
z2.object({
|
|
102
|
+
type: z2.literal("insert_before"),
|
|
103
|
+
insertion: z2.string()
|
|
87
104
|
}),
|
|
88
|
-
|
|
89
|
-
type:
|
|
90
|
-
insertion:
|
|
105
|
+
z2.object({
|
|
106
|
+
type: z2.literal("insert_after"),
|
|
107
|
+
insertion: z2.string()
|
|
91
108
|
})
|
|
92
109
|
]);
|
|
93
|
-
var markdownAppendPlacementSchema =
|
|
94
|
-
|
|
95
|
-
type:
|
|
110
|
+
var markdownAppendPlacementSchema = z2.discriminatedUnion("type", [
|
|
111
|
+
z2.object({
|
|
112
|
+
type: z2.literal("end")
|
|
96
113
|
}),
|
|
97
|
-
|
|
98
|
-
type:
|
|
99
|
-
heading:
|
|
100
|
-
level:
|
|
114
|
+
z2.object({
|
|
115
|
+
type: z2.literal("after_heading"),
|
|
116
|
+
heading: z2.string(),
|
|
117
|
+
level: z2.number().int().min(1).max(6).optional()
|
|
101
118
|
})
|
|
102
119
|
]);
|
|
103
|
-
var markdownWritePolicySchema =
|
|
104
|
-
allowNoop:
|
|
105
|
-
preserveTags:
|
|
106
|
-
preserveReferences:
|
|
120
|
+
var markdownWritePolicySchema = z2.object({
|
|
121
|
+
allowNoop: z2.boolean().optional(),
|
|
122
|
+
preserveTags: z2.union([z2.boolean(), z2.literal("warn")]).optional(),
|
|
123
|
+
preserveReferences: z2.union([z2.boolean(), z2.literal("warn")]).optional()
|
|
107
124
|
}).optional();
|
|
125
|
+
var markdownWriteBaselineFields = {
|
|
126
|
+
expectedUpdatedAt: z2.string().optional().describe("Expected note updatedAt from the read you based this edit on. Required unless baseMarkdownSha256 is provided."),
|
|
127
|
+
baseMarkdownSha256: z2.string().optional().describe("SHA-256 of the current markdown. Optional alternative to expectedUpdatedAt.")
|
|
128
|
+
};
|
|
108
129
|
var MCP_METADATA_PROPERTY_PATCH_LIMIT = 50;
|
|
109
130
|
var normalizeMetadataPropertyKey = (key) => key.trim().toLowerCase().replace(/\s+/g, "-");
|
|
110
|
-
var metadataPropertyPatchSchema =
|
|
111
|
-
set:
|
|
112
|
-
key:
|
|
113
|
-
value:
|
|
131
|
+
var metadataPropertyPatchSchema = z2.object({
|
|
132
|
+
set: z2.array(z2.object({
|
|
133
|
+
key: z2.string().min(1).describe("Property key from ocean_brain_list_properties."),
|
|
134
|
+
value: z2.string().describe("String value: select option.value, date YYYY-MM-DD, boolean true/false, number finite string, url http(s).")
|
|
114
135
|
})).max(MCP_METADATA_PROPERTY_PATCH_LIMIT).optional().describe("Set up to 50 property values. Keys must be unique."),
|
|
115
|
-
deleteKeys:
|
|
136
|
+
deleteKeys: z2.array(z2.string().min(1)).max(MCP_METADATA_PROPERTY_PATCH_LIMIT).optional().describe("Remove up to 50 property values from this note. Keys must be unique and not also in set.")
|
|
116
137
|
}).superRefine((patch, context) => {
|
|
117
138
|
if ((patch.set?.length ?? 0) === 0 && (patch.deleteKeys?.length ?? 0) === 0) {
|
|
118
139
|
context.addIssue({
|
|
119
|
-
code:
|
|
140
|
+
code: z2.ZodIssueCode.custom,
|
|
120
141
|
message: "properties must include set or deleteKeys."
|
|
121
142
|
});
|
|
122
143
|
return;
|
|
@@ -126,7 +147,7 @@ var metadataPropertyPatchSchema = z.object({
|
|
|
126
147
|
const key = normalizeMetadataPropertyKey(item.key);
|
|
127
148
|
if (setKeys.has(key)) {
|
|
128
149
|
context.addIssue({
|
|
129
|
-
code:
|
|
150
|
+
code: z2.ZodIssueCode.custom,
|
|
130
151
|
message: `properties.set contains duplicate key ${key}.`
|
|
131
152
|
});
|
|
132
153
|
}
|
|
@@ -137,13 +158,13 @@ var metadataPropertyPatchSchema = z.object({
|
|
|
137
158
|
const key = normalizeMetadataPropertyKey(rawKey);
|
|
138
159
|
if (deleteKeys.has(key)) {
|
|
139
160
|
context.addIssue({
|
|
140
|
-
code:
|
|
161
|
+
code: z2.ZodIssueCode.custom,
|
|
141
162
|
message: `properties.deleteKeys contains duplicate key ${key}.`
|
|
142
163
|
});
|
|
143
164
|
}
|
|
144
165
|
if (setKeys.has(key)) {
|
|
145
166
|
context.addIssue({
|
|
146
|
-
code:
|
|
167
|
+
code: z2.ZodIssueCode.custom,
|
|
147
168
|
message: `properties cannot set and delete ${key} in one request.`
|
|
148
169
|
});
|
|
149
170
|
}
|
|
@@ -161,10 +182,9 @@ var registerIntentWriteTools = (server, {
|
|
|
161
182
|
tools.patchNoteMarkdown,
|
|
162
183
|
"Patch a specific part of a note, like finding text with `rg` and editing only that match. Use for localized edits: fix one sentence, replace one paragraph, insert before/after exact text, or edit one section. Do not use for whole-note rewrites; use ocean_brain_replace_note_markdown instead.",
|
|
163
184
|
{
|
|
164
|
-
id:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
intent: z.string().describe("Human-readable reason for the patch."),
|
|
185
|
+
id: z2.string().describe("Note ID to patch"),
|
|
186
|
+
...markdownWriteBaselineFields,
|
|
187
|
+
intent: z2.string().describe("Human-readable reason for the patch."),
|
|
168
188
|
selector: markdownPatchSelectorSchema.describe("Exact text or previously returned match candidate."),
|
|
169
189
|
operation: markdownPatchOperationSchema.describe("replace, insert_before, or insert_after."),
|
|
170
190
|
policy: markdownWritePolicySchema
|
|
@@ -181,25 +201,19 @@ var registerIntentWriteTools = (server, {
|
|
|
181
201
|
...policy ? { policy } : {}
|
|
182
202
|
};
|
|
183
203
|
const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/patch-markdown", payload);
|
|
184
|
-
return
|
|
185
|
-
content: [{
|
|
186
|
-
type: "text",
|
|
187
|
-
text: JSON.stringify(result, null, 2)
|
|
188
|
-
}]
|
|
189
|
-
};
|
|
204
|
+
return createMcpJsonToolResult(result);
|
|
190
205
|
}
|
|
191
206
|
);
|
|
192
207
|
server.tool(
|
|
193
208
|
tools.appendNoteMarkdown,
|
|
194
209
|
"Append markdown without changing existing content, like `cat addition.md >> note.md`. Use for adding logs, status updates, meeting notes, or new sections at the end or after a heading. Do not use to modify or replace existing body content.",
|
|
195
210
|
{
|
|
196
|
-
id:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
insertion: z.string().describe("Markdown to append. Tags are body tokens such as [@tag] or [#tag]."),
|
|
211
|
+
id: z2.string().describe("Note ID to append to"),
|
|
212
|
+
...markdownWriteBaselineFields,
|
|
213
|
+
intent: z2.string().describe("Human-readable reason for the append."),
|
|
214
|
+
insertion: z2.string().describe("Markdown to append. Tags are body tokens such as [@tag] or [#tag]."),
|
|
201
215
|
placement: markdownAppendPlacementSchema.optional().describe("Default is end. after_heading requires one unique matching heading."),
|
|
202
|
-
separator:
|
|
216
|
+
separator: z2.enum(["\n\n", "\n"]).optional().describe("Separator inserted around appended markdown. Defaults to a blank line."),
|
|
203
217
|
policy: markdownWritePolicySchema
|
|
204
218
|
},
|
|
205
219
|
async ({ id, expectedUpdatedAt, baseMarkdownSha256, intent, insertion, placement, separator, policy }) => {
|
|
@@ -215,22 +229,17 @@ var registerIntentWriteTools = (server, {
|
|
|
215
229
|
...policy ? { policy } : {}
|
|
216
230
|
};
|
|
217
231
|
const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/append-markdown", payload);
|
|
218
|
-
return
|
|
219
|
-
content: [{
|
|
220
|
-
type: "text",
|
|
221
|
-
text: JSON.stringify(result, null, 2)
|
|
222
|
-
}]
|
|
223
|
-
};
|
|
232
|
+
return createMcpJsonToolResult(result);
|
|
224
233
|
}
|
|
225
234
|
);
|
|
226
235
|
server.tool(
|
|
227
236
|
tools.updateNoteMetadata,
|
|
228
237
|
"Update note metadata only: title, layout, or properties. This does not edit the note body. For properties, call ocean_brain_list_properties first. Use existing property key and string value only; do not send valueType. select=option.value, date=YYYY-MM-DD, boolean=true/false, number=finite string, url=http(s). Use deleteKeys to remove values. Definitions are not created.",
|
|
229
238
|
{
|
|
230
|
-
id:
|
|
231
|
-
expectedUpdatedAt:
|
|
232
|
-
title:
|
|
233
|
-
layout:
|
|
239
|
+
id: z2.string().describe("Note ID to update"),
|
|
240
|
+
expectedUpdatedAt: z2.string().describe("Expected note updatedAt from the read you based this metadata edit on. Required to prevent stale writes."),
|
|
241
|
+
title: z2.string().optional().describe("New note title"),
|
|
242
|
+
layout: noteLayoutSchema.optional().describe("New note layout"),
|
|
234
243
|
properties: metadataPropertyPatchSchema.describe("Patch existing shared property values. Include set and/or deleteKeys; empty patches are rejected.")
|
|
235
244
|
},
|
|
236
245
|
async ({ id, expectedUpdatedAt, title, layout, properties }) => {
|
|
@@ -243,23 +252,17 @@ var registerIntentWriteTools = (server, {
|
|
|
243
252
|
...properties ? { properties } : {}
|
|
244
253
|
};
|
|
245
254
|
const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/metadata", payload);
|
|
246
|
-
return
|
|
247
|
-
content: [{
|
|
248
|
-
type: "text",
|
|
249
|
-
text: JSON.stringify(result, null, 2)
|
|
250
|
-
}]
|
|
251
|
-
};
|
|
255
|
+
return createMcpJsonToolResult(result);
|
|
252
256
|
}
|
|
253
257
|
);
|
|
254
258
|
server.tool(
|
|
255
259
|
tools.replaceNoteMarkdown,
|
|
256
260
|
"Replace the entire note body, like overwriting a markdown file with `cat new.md > note.md`. Use when the user explicitly asks to rewrite, replace, regenerate, restructure, or overwrite the whole note. Do not use for localized edits such as fixing one sentence, changing one paragraph, or inserting under a heading; use ocean_brain_patch_note_markdown instead.",
|
|
257
261
|
{
|
|
258
|
-
id:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
replacement: z.string().describe("Complete replacement markdown body. Tags are body tokens such as [@tag] or [#tag]."),
|
|
262
|
+
id: z2.string().describe("Note ID to replace"),
|
|
263
|
+
...markdownWriteBaselineFields,
|
|
264
|
+
intent: z2.string().describe("Human-readable reason for the full replace."),
|
|
265
|
+
replacement: z2.string().describe("Complete replacement markdown body. Tags are body tokens such as [@tag] or [#tag]."),
|
|
263
266
|
policy: markdownWritePolicySchema
|
|
264
267
|
},
|
|
265
268
|
async ({ id, expectedUpdatedAt, baseMarkdownSha256, intent, replacement, policy }) => {
|
|
@@ -273,12 +276,33 @@ var registerIntentWriteTools = (server, {
|
|
|
273
276
|
...policy ? { policy } : {}
|
|
274
277
|
};
|
|
275
278
|
const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/replace-markdown", payload);
|
|
276
|
-
return
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
279
|
+
return createMcpJsonToolResult(result);
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// src/mcp-legacy-write-tools.ts
|
|
285
|
+
import { z as z3 } from "zod";
|
|
286
|
+
var LEGACY_UPDATE_NOTE_DESCRIPTION = "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.";
|
|
287
|
+
var registerLegacyWriteTools = (server, { jsonRequest: jsonRequest2, requireWriteToken: requireWriteToken2, serverUrl, token, tools }) => {
|
|
288
|
+
server.tool(
|
|
289
|
+
tools.updateNote,
|
|
290
|
+
LEGACY_UPDATE_NOTE_DESCRIPTION,
|
|
291
|
+
{
|
|
292
|
+
id: z3.string().describe("Note ID to update"),
|
|
293
|
+
title: z3.string().optional().describe("New note title"),
|
|
294
|
+
markdown: z3.string().optional().describe("Replacement markdown body. In MCP markdown, tags must use [@tag] or [#tag]."),
|
|
295
|
+
layout: noteLayoutSchema.optional().describe("Optional note layout: narrow, wide, or full. Prefer wide for most notes unless the user explicitly wants narrow or full.")
|
|
296
|
+
},
|
|
297
|
+
async ({ id, title, markdown, layout }) => {
|
|
298
|
+
const writeToken = requireWriteToken2(token, tools.updateNote);
|
|
299
|
+
const result = await jsonRequest2(serverUrl, writeToken, "/api/mcp/notes/update", {
|
|
300
|
+
id,
|
|
301
|
+
...title !== void 0 ? { title } : {},
|
|
302
|
+
...markdown !== void 0 ? { markdown } : {},
|
|
303
|
+
...layout ? { layout } : {}
|
|
304
|
+
});
|
|
305
|
+
return createMcpJsonToolResult(result);
|
|
282
306
|
}
|
|
283
307
|
);
|
|
284
308
|
};
|
|
@@ -346,12 +370,11 @@ var OCEAN_BRAIN_MCP_TOOLS = {
|
|
|
346
370
|
createTag: "ocean_brain_create_tag",
|
|
347
371
|
deleteNote: "ocean_brain_delete_note"
|
|
348
372
|
};
|
|
349
|
-
var
|
|
350
|
-
var
|
|
351
|
-
var
|
|
352
|
-
var
|
|
353
|
-
var
|
|
354
|
-
var viewSortOrderSchema = z2.enum(["asc", "desc"]);
|
|
373
|
+
var tagMatchModeSchema = z4.enum(["and", "or"]);
|
|
374
|
+
var propertyValueTypeSchema = z4.enum(["text", "url", "number", "date", "boolean", "select"]);
|
|
375
|
+
var propertyFilterOperatorSchema = z4.enum(["equals", "before", "after", "exists", "notExists"]);
|
|
376
|
+
var viewSortBySchema = z4.enum(["updatedAt", "createdAt", "title"]);
|
|
377
|
+
var viewSortOrderSchema = z4.enum(["asc", "desc"]);
|
|
355
378
|
var formatMcpGraphqlError = (error) => {
|
|
356
379
|
const suffix = [];
|
|
357
380
|
if (error.extensions?.code) {
|
|
@@ -365,11 +388,11 @@ var formatMcpGraphqlError = (error) => {
|
|
|
365
388
|
}
|
|
366
389
|
return `GraphQL error: ${error.message} (${suffix.join(", ")})`;
|
|
367
390
|
};
|
|
368
|
-
var propertyFilterSchema =
|
|
369
|
-
key:
|
|
391
|
+
var propertyFilterSchema = z4.object({
|
|
392
|
+
key: z4.string().describe("Property key from ocean_brain_list_properties, e.g. state"),
|
|
370
393
|
valueType: propertyValueTypeSchema.describe("Property value type from the property definition"),
|
|
371
394
|
operator: propertyFilterOperatorSchema.describe("Filter operator. before/after are only valid for date or number properties."),
|
|
372
|
-
value:
|
|
395
|
+
value: z4.string().nullable().optional().describe("Filter value. Required unless operator is exists or notExists. select=option.value, date=YYYY-MM-DD, boolean=true/false, number=finite, url=http(s).")
|
|
373
396
|
});
|
|
374
397
|
var normalizeOceanBrainTagName = (name) => {
|
|
375
398
|
const trimmedName = name.trim();
|
|
@@ -441,17 +464,13 @@ var requireWriteToken = (token, toolName) => {
|
|
|
441
464
|
}
|
|
442
465
|
throw new Error(`${toolName} requires an MCP bearer token. Set --token or --token-file.`);
|
|
443
466
|
};
|
|
444
|
-
|
|
445
|
-
const server = new McpServer({
|
|
446
|
-
name: "ocean-brain",
|
|
447
|
-
version: pkg.version
|
|
448
|
-
});
|
|
467
|
+
var registerMcpTools = (server, serverUrl, token) => {
|
|
449
468
|
server.tool(
|
|
450
469
|
OCEAN_BRAIN_MCP_TOOLS.searchNotes,
|
|
451
470
|
"Search Ocean Brain notes by keyword. Returns matching note titles and tags. Use ocean_brain_read_note to get full content for a specific note.",
|
|
452
471
|
{
|
|
453
|
-
query:
|
|
454
|
-
limit:
|
|
472
|
+
query: z4.string().describe("Search keyword"),
|
|
473
|
+
limit: z4.number().optional().default(10).describe("Max results (default: 10)")
|
|
455
474
|
},
|
|
456
475
|
async ({ query, limit }) => {
|
|
457
476
|
const data = await graphql(serverUrl, token, `
|
|
@@ -479,20 +498,15 @@ async function startMcpServer(serverUrl, token) {
|
|
|
479
498
|
tags: note.tags.map((t) => t.name),
|
|
480
499
|
preview: note.contentAsMarkdown.slice(0, 100)
|
|
481
500
|
}));
|
|
482
|
-
return {
|
|
483
|
-
content: [{
|
|
484
|
-
type: "text",
|
|
485
|
-
text: JSON.stringify({ totalCount: result.totalCount, notes }, null, 2)
|
|
486
|
-
}]
|
|
487
|
-
};
|
|
501
|
+
return createMcpJsonToolResult({ totalCount: result.totalCount, notes });
|
|
488
502
|
}
|
|
489
503
|
);
|
|
490
504
|
server.tool(
|
|
491
505
|
OCEAN_BRAIN_MCP_TOOLS.readNote,
|
|
492
506
|
"Read an Ocean Brain note by ID. Returns properties, tags, truncated content by default (1000 chars), and a back-reference summary. Set maxLength to 0 only when full content is necessary.",
|
|
493
507
|
{
|
|
494
|
-
id:
|
|
495
|
-
maxLength:
|
|
508
|
+
id: z4.string().describe("Note ID"),
|
|
509
|
+
maxLength: z4.number().optional().default(1e3).describe("Max content length in characters. 0 for full content. (default: 1000)")
|
|
496
510
|
},
|
|
497
511
|
async ({ id, maxLength }) => {
|
|
498
512
|
const data = await graphql(serverUrl, token, `
|
|
@@ -519,20 +533,15 @@ async function startMcpServer(serverUrl, token) {
|
|
|
519
533
|
backReferences,
|
|
520
534
|
maxLength
|
|
521
535
|
});
|
|
522
|
-
return
|
|
523
|
-
content: [{
|
|
524
|
-
type: "text",
|
|
525
|
-
text: output
|
|
526
|
-
}]
|
|
527
|
-
};
|
|
536
|
+
return createMcpTextToolResult(output);
|
|
528
537
|
}
|
|
529
538
|
);
|
|
530
539
|
server.tool(
|
|
531
540
|
OCEAN_BRAIN_MCP_TOOLS.createNote,
|
|
532
541
|
"Create an Ocean Brain note from markdown. Search/read first to avoid duplicates; prefer patching an existing note when the intent is a local change.",
|
|
533
542
|
{
|
|
534
|
-
title:
|
|
535
|
-
markdown:
|
|
543
|
+
title: z4.string().describe("Note title"),
|
|
544
|
+
markdown: z4.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."),
|
|
536
545
|
layout: noteLayoutSchema.optional().describe("Optional note layout: narrow, wide, or full. Prefer wide for most notes unless the user explicitly wants narrow or full.")
|
|
537
546
|
},
|
|
538
547
|
async ({ title, markdown, layout }) => {
|
|
@@ -542,39 +551,16 @@ async function startMcpServer(serverUrl, token) {
|
|
|
542
551
|
markdown,
|
|
543
552
|
...layout ? { layout } : {}
|
|
544
553
|
});
|
|
545
|
-
return
|
|
546
|
-
content: [{
|
|
547
|
-
type: "text",
|
|
548
|
-
text: JSON.stringify(result, null, 2)
|
|
549
|
-
}]
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
);
|
|
553
|
-
server.tool(
|
|
554
|
-
OCEAN_BRAIN_MCP_TOOLS.updateNote,
|
|
555
|
-
"Legacy full-field note update kept for backward compatibility. Prefer ocean_brain_patch_note_markdown for localized body edits, ocean_brain_append_note_markdown for additions, ocean_brain_replace_note_markdown for whole-body rewrites, and ocean_brain_update_note_metadata for title/layout/properties. Use this only when a legacy client needs combined field updates.",
|
|
556
|
-
{
|
|
557
|
-
id: z2.string().describe("Note ID to update"),
|
|
558
|
-
title: z2.string().optional().describe("New note title"),
|
|
559
|
-
markdown: z2.string().optional().describe("Replacement markdown body. In MCP markdown, tags must use [@tag] or [#tag]."),
|
|
560
|
-
layout: noteLayoutSchema.optional().describe("Optional note layout: narrow, wide, or full. Prefer wide for most notes unless the user explicitly wants narrow or full.")
|
|
561
|
-
},
|
|
562
|
-
async ({ id, title, markdown, layout }) => {
|
|
563
|
-
const writeToken = requireWriteToken(token, OCEAN_BRAIN_MCP_TOOLS.updateNote);
|
|
564
|
-
const result = await jsonRequest(serverUrl, writeToken, "/api/mcp/notes/update", {
|
|
565
|
-
id,
|
|
566
|
-
...title !== void 0 ? { title } : {},
|
|
567
|
-
...markdown !== void 0 ? { markdown } : {},
|
|
568
|
-
...layout ? { layout } : {}
|
|
569
|
-
});
|
|
570
|
-
return {
|
|
571
|
-
content: [{
|
|
572
|
-
type: "text",
|
|
573
|
-
text: JSON.stringify(result, null, 2)
|
|
574
|
-
}]
|
|
575
|
-
};
|
|
554
|
+
return createMcpJsonToolResult(result);
|
|
576
555
|
}
|
|
577
556
|
);
|
|
557
|
+
registerLegacyWriteTools(server, {
|
|
558
|
+
jsonRequest,
|
|
559
|
+
requireWriteToken,
|
|
560
|
+
serverUrl,
|
|
561
|
+
token,
|
|
562
|
+
tools: OCEAN_BRAIN_MCP_TOOLS
|
|
563
|
+
});
|
|
578
564
|
registerIntentWriteTools(server, {
|
|
579
565
|
jsonRequest,
|
|
580
566
|
requireWriteToken,
|
|
@@ -603,21 +589,16 @@ async function startMcpServer(serverUrl, token) {
|
|
|
603
589
|
pagination: { limit: 100, offset: 0 }
|
|
604
590
|
});
|
|
605
591
|
const result = data?.allTags;
|
|
606
|
-
return
|
|
607
|
-
content: [{
|
|
608
|
-
type: "text",
|
|
609
|
-
text: JSON.stringify(result, null, 2)
|
|
610
|
-
}]
|
|
611
|
-
};
|
|
592
|
+
return createMcpJsonToolResult(result);
|
|
612
593
|
}
|
|
613
594
|
);
|
|
614
595
|
server.tool(
|
|
615
596
|
OCEAN_BRAIN_MCP_TOOLS.listProperties,
|
|
616
597
|
"List shared Ocean Brain property definitions. Use this before property queries so keys, value types, and select option values are valid.",
|
|
617
598
|
{
|
|
618
|
-
query:
|
|
619
|
-
limit:
|
|
620
|
-
offset:
|
|
599
|
+
query: z4.string().optional().default("").describe("Optional property key/name search query"),
|
|
600
|
+
limit: z4.number().optional().default(50).describe("Max results (default: 50, server max: 100)"),
|
|
601
|
+
offset: z4.number().optional().default(0).describe("Pagination offset (default: 0)")
|
|
621
602
|
},
|
|
622
603
|
async ({ query, limit, offset }) => {
|
|
623
604
|
const data = await graphql(serverUrl, token, `
|
|
@@ -639,42 +620,32 @@ async function startMcpServer(serverUrl, token) {
|
|
|
639
620
|
pagination: { limit, offset }
|
|
640
621
|
});
|
|
641
622
|
const result = data?.notePropertyKeys;
|
|
642
|
-
return {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
totalCount: result.totalCount,
|
|
647
|
-
propertyKeys: result.keys
|
|
648
|
-
}, null, 2)
|
|
649
|
-
}]
|
|
650
|
-
};
|
|
623
|
+
return createMcpJsonToolResult({
|
|
624
|
+
totalCount: result.totalCount,
|
|
625
|
+
propertyKeys: result.keys
|
|
626
|
+
});
|
|
651
627
|
}
|
|
652
628
|
);
|
|
653
629
|
server.tool(
|
|
654
630
|
OCEAN_BRAIN_MCP_TOOLS.listNotesByTag,
|
|
655
631
|
"List Ocean Brain notes for a specific tag name. The tool accepts either @tag or tag and resolves it to the canonical Ocean Brain tag name first.",
|
|
656
632
|
{
|
|
657
|
-
tag:
|
|
658
|
-
limit:
|
|
659
|
-
offset:
|
|
633
|
+
tag: z4.string().describe("Tag name to inspect. You can pass @project or project."),
|
|
634
|
+
limit: z4.number().optional().default(20).describe("Max results (default: 20)"),
|
|
635
|
+
offset: z4.number().optional().default(0).describe("Pagination offset (default: 0)")
|
|
660
636
|
},
|
|
661
637
|
async ({ tag, limit, offset }) => {
|
|
662
638
|
const normalizedTag = normalizeOceanBrainTagName(tag);
|
|
663
639
|
const tagResult = await fetchOceanBrainTags(serverUrl, token, normalizedTag);
|
|
664
640
|
const exactMatches = tagResult.tags.filter((item) => item.name === normalizedTag);
|
|
665
641
|
if (exactMatches.length === 0) {
|
|
666
|
-
return {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
noteCount: 0,
|
|
674
|
-
notes: []
|
|
675
|
-
}, null, 2)
|
|
676
|
-
}]
|
|
677
|
-
};
|
|
642
|
+
return createMcpJsonToolResult({
|
|
643
|
+
requestedTag: tag,
|
|
644
|
+
normalizedTag,
|
|
645
|
+
tagFound: false,
|
|
646
|
+
noteCount: 0,
|
|
647
|
+
notes: []
|
|
648
|
+
});
|
|
678
649
|
}
|
|
679
650
|
const selectedTag = exactMatches[0];
|
|
680
651
|
const notesData = await graphql(serverUrl, token, `
|
|
@@ -694,35 +665,30 @@ async function startMcpServer(serverUrl, token) {
|
|
|
694
665
|
pagination: { limit, offset }
|
|
695
666
|
});
|
|
696
667
|
const noteResult = notesData?.tagNotes;
|
|
697
|
-
return {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
tags: note.tags.map((item) => item.name)
|
|
712
|
-
}))
|
|
713
|
-
}, null, 2)
|
|
714
|
-
}]
|
|
715
|
-
};
|
|
668
|
+
return createMcpJsonToolResult({
|
|
669
|
+
requestedTag: tag,
|
|
670
|
+
normalizedTag,
|
|
671
|
+
tagFound: true,
|
|
672
|
+
duplicateExactMatchCount: exactMatches.length,
|
|
673
|
+
tag: selectedTag,
|
|
674
|
+
totalCount: noteResult.totalCount,
|
|
675
|
+
notes: noteResult.notes.map((note) => ({
|
|
676
|
+
id: note.id,
|
|
677
|
+
title: note.title,
|
|
678
|
+
updatedAt: note.updatedAt,
|
|
679
|
+
tags: note.tags.map((item) => item.name)
|
|
680
|
+
}))
|
|
681
|
+
});
|
|
716
682
|
}
|
|
717
683
|
);
|
|
718
684
|
server.tool(
|
|
719
685
|
OCEAN_BRAIN_MCP_TOOLS.listNotesByTags,
|
|
720
686
|
"List Ocean Brain notes for multiple tag names. Supports AND/OR matching and resolves each input tag to the canonical Ocean Brain tag name first.",
|
|
721
687
|
{
|
|
722
|
-
tags:
|
|
688
|
+
tags: z4.array(z4.string()).min(1).describe("Tag names to inspect. You can pass @project or project values."),
|
|
723
689
|
mode: tagMatchModeSchema.optional().default("and").describe("Tag match mode. Use and for intersection, or for union. Defaults to and."),
|
|
724
|
-
limit:
|
|
725
|
-
offset:
|
|
690
|
+
limit: z4.number().optional().default(20).describe("Max results (default: 20)"),
|
|
691
|
+
offset: z4.number().optional().default(0).describe("Pagination offset (default: 0)")
|
|
726
692
|
},
|
|
727
693
|
async ({ tags, mode, limit, offset }) => {
|
|
728
694
|
const normalizedTags = normalizeOceanBrainTagNames(tags);
|
|
@@ -770,41 +736,36 @@ async function startMcpServer(serverUrl, token) {
|
|
|
770
736
|
});
|
|
771
737
|
noteResult = notesData?.notesByTagNames;
|
|
772
738
|
}
|
|
773
|
-
return {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
tags: note.tags.map((item) => item.name)
|
|
789
|
-
}))
|
|
790
|
-
}, null, 2)
|
|
791
|
-
}]
|
|
792
|
-
};
|
|
739
|
+
return createMcpJsonToolResult({
|
|
740
|
+
requestedTags: tags,
|
|
741
|
+
normalizedTags,
|
|
742
|
+
mode,
|
|
743
|
+
allTagsFound: missingTags.length === 0,
|
|
744
|
+
missingTags,
|
|
745
|
+
tags: matchedTags,
|
|
746
|
+
totalCount: noteResult.totalCount,
|
|
747
|
+
notes: noteResult.notes.map((note) => ({
|
|
748
|
+
id: note.id,
|
|
749
|
+
title: note.title,
|
|
750
|
+
updatedAt: note.updatedAt,
|
|
751
|
+
tags: note.tags.map((item) => item.name)
|
|
752
|
+
}))
|
|
753
|
+
});
|
|
793
754
|
}
|
|
794
755
|
);
|
|
795
756
|
server.tool(
|
|
796
757
|
OCEAN_BRAIN_MCP_TOOLS.queryNotesByProperties,
|
|
797
758
|
"Call ocean_brain_list_properties first. Requires >=1 propertyFilter; use search/recent for broad lists. Use key/valueType from the property definition. Values are strings: select=option.value, date=YYYY-MM-DD, boolean=true/false, number=finite, url=http(s). exists/notExists need no value. Property filters use AND; tagNames use mode. Summaries are returned by default; use propertyKeys for needed property details.",
|
|
798
759
|
{
|
|
799
|
-
propertyFilters:
|
|
800
|
-
tagNames:
|
|
760
|
+
propertyFilters: z4.array(propertyFilterSchema).min(1).max(10).describe("Required property filters. Multiple property filters are combined with AND."),
|
|
761
|
+
tagNames: z4.array(z4.string()).optional().default([]).describe("Optional tag filters. You can pass @project, #project, or project."),
|
|
801
762
|
mode: tagMatchModeSchema.optional().default("and").describe("Tag match mode for tagNames only. Property filters are always combined with AND."),
|
|
802
763
|
sortBy: viewSortBySchema.optional().default("updatedAt").describe("Sort field (default: updatedAt)"),
|
|
803
764
|
sortOrder: viewSortOrderSchema.optional().default("desc").describe("Sort order (default: desc)"),
|
|
804
|
-
includeProperties:
|
|
805
|
-
propertyKeys:
|
|
806
|
-
limit:
|
|
807
|
-
offset:
|
|
765
|
+
includeProperties: z4.boolean().optional().default(false).describe("Include returned note properties. Defaults to false to reduce tokens."),
|
|
766
|
+
propertyKeys: z4.array(z4.string()).optional().default([]).describe("Property keys to include in output; automatically includes properties."),
|
|
767
|
+
limit: z4.number().optional().default(20).describe("Max results (default: 20, server max: 50)"),
|
|
768
|
+
offset: z4.number().optional().default(0).describe("Pagination offset (default: 0)")
|
|
808
769
|
},
|
|
809
770
|
async ({ propertyFilters, tagNames, mode, sortBy, sortOrder, includeProperties, propertyKeys, limit, offset }) => {
|
|
810
771
|
const shouldIncludeProperties = includeProperties || propertyKeys.length > 0;
|
|
@@ -834,36 +795,27 @@ async function startMcpServer(serverUrl, token) {
|
|
|
834
795
|
includeProperties: shouldIncludeProperties
|
|
835
796
|
});
|
|
836
797
|
const result = data?.notesByProperties;
|
|
837
|
-
return {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
},
|
|
852
|
-
includeProperties: shouldIncludeProperties,
|
|
853
|
-
propertyKeys
|
|
854
|
-
}),
|
|
855
|
-
null,
|
|
856
|
-
2
|
|
857
|
-
)
|
|
858
|
-
}]
|
|
859
|
-
};
|
|
798
|
+
return createMcpJsonToolResult(formatPropertyQueryResponse({
|
|
799
|
+
result,
|
|
800
|
+
query: {
|
|
801
|
+
propertyFilters,
|
|
802
|
+
tagNames,
|
|
803
|
+
mode,
|
|
804
|
+
sortBy,
|
|
805
|
+
sortOrder,
|
|
806
|
+
limit,
|
|
807
|
+
offset
|
|
808
|
+
},
|
|
809
|
+
includeProperties: shouldIncludeProperties,
|
|
810
|
+
propertyKeys
|
|
811
|
+
}));
|
|
860
812
|
}
|
|
861
813
|
);
|
|
862
814
|
server.tool(
|
|
863
815
|
OCEAN_BRAIN_MCP_TOOLS.listRecentNotes,
|
|
864
816
|
"List recently updated Ocean Brain notes. Returns titles and tags only. Use ocean_brain_read_note to get full content for a specific note.",
|
|
865
817
|
{
|
|
866
|
-
limit:
|
|
818
|
+
limit: z4.number().optional().default(10).describe("Max results (default: 10)")
|
|
867
819
|
},
|
|
868
820
|
async ({ limit }) => {
|
|
869
821
|
const data = await graphql(serverUrl, token, `
|
|
@@ -889,21 +841,16 @@ async function startMcpServer(serverUrl, token) {
|
|
|
889
841
|
updatedAt: note.updatedAt,
|
|
890
842
|
tags: note.tags.map((t) => t.name)
|
|
891
843
|
}));
|
|
892
|
-
return {
|
|
893
|
-
content: [{
|
|
894
|
-
type: "text",
|
|
895
|
-
text: JSON.stringify({ totalCount: result.totalCount, notes }, null, 2)
|
|
896
|
-
}]
|
|
897
|
-
};
|
|
844
|
+
return createMcpJsonToolResult({ totalCount: result.totalCount, notes });
|
|
898
845
|
}
|
|
899
846
|
);
|
|
900
847
|
server.tool(
|
|
901
848
|
OCEAN_BRAIN_MCP_TOOLS.findNoteCleanupCandidates,
|
|
902
849
|
"Find Ocean Brain note cleanup candidates for temporary or draft notes before deletion. This is optional discovery; ocean_brain_delete_note moves a single note to trash directly.",
|
|
903
850
|
{
|
|
904
|
-
keywords:
|
|
905
|
-
limit:
|
|
906
|
-
offset:
|
|
851
|
+
keywords: z4.string().optional().default("temp tmp draft test wip").describe("Keywords that mark a note as a cleanup candidate. Comma or space separated."),
|
|
852
|
+
limit: z4.number().optional().default(20).describe("Max results (default: 20)"),
|
|
853
|
+
offset: z4.number().optional().default(0).describe("Pagination offset (default: 0)")
|
|
907
854
|
},
|
|
908
855
|
async ({ keywords, limit, offset }) => {
|
|
909
856
|
const normalizedKeywords = keywords.split(/[,\s]+/).map((keyword) => keyword.trim()).filter(Boolean);
|
|
@@ -928,61 +875,53 @@ async function startMcpServer(serverUrl, token) {
|
|
|
928
875
|
pagination: { limit, offset }
|
|
929
876
|
});
|
|
930
877
|
const result = data?.noteCleanupCandidates;
|
|
931
|
-
return {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
candidateCount: result.length,
|
|
939
|
-
notes: result
|
|
940
|
-
}, null, 2)
|
|
941
|
-
}]
|
|
942
|
-
};
|
|
878
|
+
return createMcpJsonToolResult({
|
|
879
|
+
keywords: normalizedKeywords,
|
|
880
|
+
limit,
|
|
881
|
+
offset,
|
|
882
|
+
candidateCount: result.length,
|
|
883
|
+
notes: result
|
|
884
|
+
});
|
|
943
885
|
}
|
|
944
886
|
);
|
|
945
887
|
server.tool(
|
|
946
888
|
OCEAN_BRAIN_MCP_TOOLS.createTag,
|
|
947
889
|
"Create an Ocean Brain tag through the MCP write path. The tool accepts either @tag or tag and creates the canonical Ocean Brain tag if it does not already exist.",
|
|
948
890
|
{
|
|
949
|
-
name:
|
|
891
|
+
name: z4.string().describe("Tag name to create. You can pass @project or project.")
|
|
950
892
|
},
|
|
951
893
|
async ({ name }) => {
|
|
952
894
|
const writeToken = requireWriteToken(token, OCEAN_BRAIN_MCP_TOOLS.createTag);
|
|
953
895
|
const result = await jsonRequest(serverUrl, writeToken, "/api/mcp/tags/create", {
|
|
954
896
|
name
|
|
955
897
|
});
|
|
956
|
-
return
|
|
957
|
-
content: [{
|
|
958
|
-
type: "text",
|
|
959
|
-
text: JSON.stringify(result, null, 2)
|
|
960
|
-
}]
|
|
961
|
-
};
|
|
898
|
+
return createMcpJsonToolResult(result);
|
|
962
899
|
}
|
|
963
900
|
);
|
|
964
901
|
server.tool(
|
|
965
902
|
OCEAN_BRAIN_MCP_TOOLS.deleteNote,
|
|
966
903
|
"Move an Ocean Brain note to trash, like `mv note.md trash/`. This is a recoverable trash move, not permanent deletion. Deleted-note tag names are kept as restore metadata; orphan tags are not a blocking condition.",
|
|
967
904
|
{
|
|
968
|
-
id:
|
|
905
|
+
id: z4.string().describe("Note ID to move to trash")
|
|
969
906
|
},
|
|
970
907
|
async ({ id }) => {
|
|
971
908
|
const writeToken = requireWriteToken(token, OCEAN_BRAIN_MCP_TOOLS.deleteNote);
|
|
972
909
|
try {
|
|
973
910
|
const result = await jsonRequest(serverUrl, writeToken, "/api/mcp/notes/delete", { id });
|
|
974
|
-
return
|
|
975
|
-
content: [{
|
|
976
|
-
type: "text",
|
|
977
|
-
text: JSON.stringify(result, null, 2)
|
|
978
|
-
}]
|
|
979
|
-
};
|
|
911
|
+
return createMcpJsonToolResult(result);
|
|
980
912
|
} catch (error) {
|
|
981
913
|
const message = error instanceof Error ? error.message : "Unknown MCP note delete error";
|
|
982
914
|
throw new Error(message);
|
|
983
915
|
}
|
|
984
916
|
}
|
|
985
917
|
);
|
|
918
|
+
};
|
|
919
|
+
async function startMcpServer(serverUrl, token) {
|
|
920
|
+
const server = new McpServer({
|
|
921
|
+
name: "ocean-brain",
|
|
922
|
+
version: pkg.version
|
|
923
|
+
});
|
|
924
|
+
registerMcpTools(server, serverUrl, token);
|
|
986
925
|
const transport = new StdioServerTransport();
|
|
987
926
|
await server.connect(transport);
|
|
988
927
|
}
|