@creator-notes/cnotes 0.16.11 → 0.16.15

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 (56) hide show
  1. package/.mcp.json +3 -3
  2. package/README.md +3 -3
  3. package/dist/cn.js +3 -4
  4. package/dist/cn.js.map +1 -1
  5. package/dist/commands/auth.d.ts.map +1 -1
  6. package/dist/commands/auth.js +1 -2
  7. package/dist/commands/auth.js.map +1 -1
  8. package/dist/commands/canvas.d.ts.map +1 -1
  9. package/dist/commands/canvas.js +15 -16
  10. package/dist/commands/canvas.js.map +1 -1
  11. package/dist/commands/mcp.js +9 -9
  12. package/dist/commands/mcp.js.map +1 -1
  13. package/dist/commands/notes.d.ts.map +1 -1
  14. package/dist/commands/notes.js +65 -2
  15. package/dist/commands/notes.js.map +1 -1
  16. package/dist/commands/operations.d.ts +2 -2
  17. package/dist/commands/operations.d.ts.map +1 -1
  18. package/dist/commands/operations.js +3 -4
  19. package/dist/commands/operations.js.map +1 -1
  20. package/dist/commands/types.d.ts.map +1 -1
  21. package/dist/commands/types.js +46 -1
  22. package/dist/commands/types.js.map +1 -1
  23. package/dist/commands/versions.d.ts.map +1 -1
  24. package/dist/commands/versions.js +18 -1
  25. package/dist/commands/versions.js.map +1 -1
  26. package/dist/lib/api-client.d.ts +1 -1
  27. package/dist/lib/api-client.d.ts.map +1 -1
  28. package/dist/lib/api-client.js +3 -4
  29. package/dist/lib/api-client.js.map +1 -1
  30. package/dist/lib/auth-store.d.ts.map +1 -1
  31. package/dist/lib/auth-store.js +1 -2
  32. package/dist/lib/auth-store.js.map +1 -1
  33. package/dist/lib/build-schema.d.ts +6 -6
  34. package/dist/lib/build-schema.js +6 -6
  35. package/dist/lib/build-schema.js.map +1 -1
  36. package/dist/lib/claude-session.d.ts +1 -1
  37. package/dist/lib/claude-session.d.ts.map +1 -1
  38. package/dist/lib/claude-session.js +3 -4
  39. package/dist/lib/claude-session.js.map +1 -1
  40. package/dist/lib/config.d.ts.map +1 -1
  41. package/dist/lib/config.js +8 -9
  42. package/dist/lib/config.js.map +1 -1
  43. package/dist/lib/errors.d.ts +13 -0
  44. package/dist/lib/errors.d.ts.map +1 -1
  45. package/dist/lib/errors.js +33 -2
  46. package/dist/lib/errors.js.map +1 -1
  47. package/dist/lib/install-mcp.js +6 -6
  48. package/dist/lib/install-mcp.js.map +1 -1
  49. package/dist/lib/mention-lint.d.ts +53 -0
  50. package/dist/lib/mention-lint.d.ts.map +1 -0
  51. package/dist/lib/mention-lint.js +104 -0
  52. package/dist/lib/mention-lint.js.map +1 -0
  53. package/dist/mcp-server.js +121 -72
  54. package/dist/mcp-server.js.map +1 -1
  55. package/package.json +1 -1
  56. package/skills/cnotes/SKILL.md +87 -13
@@ -10,9 +10,9 @@
10
10
  // "command": "node",
11
11
  // "args": ["/path/to/pm-notes/cli/dist/mcp-server.js"],
12
12
  // "env": {
13
- // "CN_SERVER": "https://your-codespace.app.github.dev",
14
- // "CN_TOKEN": "your-token",
15
- // "CN_WORKSPACE": "your-workspace-id"
13
+ // "CNOTES_SERVER": "https://your-codespace.app.github.dev",
14
+ // "CNOTES_TOKEN": "your-token",
15
+ // "CNOTES_WORKSPACE": "your-workspace-id"
16
16
  // }
17
17
  // }
18
18
  // }
@@ -30,21 +30,21 @@ import { buildSchema } from "./lib/build-schema.js";
30
30
  // ---------------------------------------------------------------------------
31
31
  // Environment
32
32
  // ---------------------------------------------------------------------------
33
- const CN_SERVER = process.env.CN_SERVER;
34
- const CN_TOKEN = process.env.CN_TOKEN;
35
- const CN_WORKSPACE = process.env.CN_WORKSPACE;
36
- if (!CN_SERVER || !CN_TOKEN || !CN_WORKSPACE) {
33
+ const CNOTES_SERVER = process.env.CNOTES_SERVER;
34
+ const CNOTES_TOKEN = process.env.CNOTES_TOKEN;
35
+ const CNOTES_WORKSPACE = process.env.CNOTES_WORKSPACE;
36
+ if (!CNOTES_SERVER || !CNOTES_TOKEN || !CNOTES_WORKSPACE) {
37
37
  const missing = [
38
- !CN_SERVER && "CN_SERVER",
39
- !CN_TOKEN && "CN_TOKEN",
40
- !CN_WORKSPACE && "CN_WORKSPACE",
38
+ !CNOTES_SERVER && "CNOTES_SERVER",
39
+ !CNOTES_TOKEN && "CNOTES_TOKEN",
40
+ !CNOTES_WORKSPACE && "CNOTES_WORKSPACE",
41
41
  ].filter(Boolean);
42
42
  process.stderr.write(`cnotes-mcp: missing required env vars: ${missing.join(", ")}\n` +
43
43
  "Set them in your Claude Desktop config or shell environment.\n");
44
44
  process.exit(1);
45
45
  }
46
- const api = new ApiClient(CN_SERVER, CN_TOKEN);
47
- const workspaceId = CN_WORKSPACE;
46
+ const api = new ApiClient(CNOTES_SERVER, CNOTES_TOKEN);
47
+ const workspaceId = CNOTES_WORKSPACE;
48
48
  // ---------------------------------------------------------------------------
49
49
  // Helpers
50
50
  // ---------------------------------------------------------------------------
@@ -108,7 +108,7 @@ server.resource("types", "cnotes://types", async () => ({
108
108
  // ---------------------------------------------------------------------------
109
109
  // Notes tools
110
110
  // ---------------------------------------------------------------------------
111
- server.tool("cn_notes_list", "List notes in the workspace. Returns metadata by default. Set includeContent=true to include full note content (useful to avoid multiple cn_notes_get calls).", {
111
+ server.tool("cnotes_notes_list", "List notes in the workspace. Returns metadata by default. Set includeContent=true to include full note content (useful to avoid multiple cnotes_notes_get calls).", {
112
112
  search: z.string().optional().describe("Full-text search query"),
113
113
  type: z.string().optional().describe("Filter by note type (e.g. Meeting, PRD)"),
114
114
  tags: z.string().optional().describe("Filter by tags (comma-separated)"),
@@ -126,7 +126,7 @@ server.tool("cn_notes_list", "List notes in the workspace. Returns metadata by d
126
126
  format: includeContent ? "markdown" : undefined,
127
127
  limit: String(limit ?? 20),
128
128
  })));
129
- server.tool("cn_notes_get", "Get one or more notes by display ID (e.g. MEETING-12, PRD-3). Always returns an array, in input order, in a single round-trip. Pass one ID for one note, or many IDs to batch-fetch — never call this tool multiple times in parallel for different IDs.", {
129
+ server.tool("cnotes_notes_get", "Get one or more notes by display ID (e.g. MEETING-12, PRD-3). Always returns an array, in input order, in a single round-trip. Pass one ID for one note, or many IDs to batch-fetch — never call this tool multiple times in parallel for different IDs.", {
130
130
  ids: z
131
131
  .array(z.string())
132
132
  .min(1)
@@ -151,13 +151,13 @@ server.tool("cn_notes_get", "Get one or more notes by display ID (e.g. MEETING-1
151
151
  // Preserve input order.
152
152
  return ids.map((id) => byDisplayId.get(id));
153
153
  }));
154
- server.tool("cn_notes_delete", "Delete (archive) a note.", {
154
+ server.tool("cnotes_notes_delete", "Delete (archive) a note.", {
155
155
  id: z.string().describe("Note display ID or Convex ID"),
156
156
  }, async ({ id }) => withErrorHandling(async () => {
157
157
  const noteId = await resolveNoteId(api, workspaceId, id);
158
158
  return api.delete(`/api/notes/${noteId}`);
159
159
  }));
160
- server.tool("cn_notes_bulk_archive", "Archive or unarchive multiple notes in one operation. More efficient than calling cn_notes_delete multiple times.", {
160
+ server.tool("cnotes_notes_bulk_archive", "Archive or unarchive multiple notes in one operation. More efficient than calling cnotes_notes_delete multiple times.", {
161
161
  ids: z.array(z.string()).describe("Array of note display IDs or Convex IDs"),
162
162
  unarchive: z.boolean().optional().default(false).describe("Set true to unarchive instead of archive"),
163
163
  }, async ({ ids, unarchive }) => withErrorHandling(async () => {
@@ -168,7 +168,7 @@ server.tool("cn_notes_bulk_archive", "Archive or unarchive multiple notes in one
168
168
  isArchived: !unarchive,
169
169
  });
170
170
  }));
171
- server.tool("cn_notes_bulk_retype", "Retype multiple notes to a new type in one operation. Each note is atomically retyped (new note created, old archived, cross-linked).", {
171
+ server.tool("cnotes_notes_bulk_retype", "Retype multiple notes to a new type in one operation. Each note is atomically retyped (new note created, old archived, cross-linked).", {
172
172
  ids: z.array(z.string()).describe("Array of note display IDs or Convex IDs to retype"),
173
173
  targetType: z.string().describe("Target type name (must be an existing supertag, e.g. 'Insight')"),
174
174
  }, async ({ ids, targetType }) => withErrorHandling(async () => {
@@ -179,7 +179,7 @@ server.tool("cn_notes_bulk_retype", "Retype multiple notes to a new type in one
179
179
  targetType,
180
180
  });
181
181
  }));
182
- server.tool("cn_notes_update", "Update note metadata (type, tags, pin/archive status). Does NOT change content — use cn_versions_create for that. Title is read-only (derived from # h1 heading in content).", {
182
+ server.tool("cnotes_notes_update", "Update note metadata (type, tags, pin/archive status). Does NOT change content — use cnotes_versions_create for that. Title is read-only (derived from # h1 heading in content).", {
183
183
  id: z.string().describe("Note display ID or Convex ID"),
184
184
  type: z.string().optional().describe("New note type (must be an existing supertag, e.g. 'Insight')"),
185
185
  tags: z.string().optional().describe("Replace tags (comma-separated). Use empty string to clear."),
@@ -214,16 +214,43 @@ const NoteCreateItem = z.object({
214
214
  markdown: z.string().describe("Note content as markdown (the first # h1 heading becomes the title)"),
215
215
  tags: z.array(z.string()).optional().describe("Tags for this note"),
216
216
  });
217
- server.tool("cn_notes_create", "Create one or more interlinked notes in a single atomic transaction. Pass `notes` as a single item object or an array of items. Title is derived from the # h1 heading in each note's markdown. Relationship link syntax inside markdown: use [@key: Title](relationship:references) to cross-reference another note in the same batch (the @key is replaced with the new note's display ID after creation), or [NOTE-12: Title](relationship:references) to reference an existing note. ALWAYS include the `: Title` part — the title-less form `[NOTE-12](relationship:references)` saves a chip with `title: null` that renders as \"Untitled\" in the UI. The relationship name is free-form (e.g. references, supports, triggers); use `references` if unsure. If validation fails, the response includes a structured `details.items` array pinpointing which item (by index + key) is malformed and why.", {
217
+ server.tool("cnotes_notes_create", "Create one or more interlinked notes in a single atomic transaction. Before authoring, read the target type's rubric (cnotes_types_show, or the validationPrompt field from cnotes_types_list) and write to it so the note lands well-formed. Pass `notes` as a single item object or an array of items. Title is derived from the # h1 heading in each note's markdown. Relationship link syntax inside markdown: use [@key: Title](relationship:references) to cross-reference another note in the same batch (the @key is replaced with the new note's display ID after creation), or [NOTE-12: Title](relationship:references) to reference an existing note. ALWAYS include the `: Title` part — the title-less form `[NOTE-12](relationship:references)` saves a chip with `title: null` that renders as \"Untitled\" in the UI. The relationship name is free-form (e.g. references, supports, triggers); use `references` if unsure. The response echoes each used type's rubric under `rubrics` — revise in-loop if a note falls short. If validation fails, the response includes a structured `details.items` array pinpointing which item (by index + key) is malformed and why.", {
218
218
  notes: z
219
219
  .union([NoteCreateItem, z.array(NoteCreateItem).min(1)])
220
220
  .describe("Either a single item object or a non-empty array of items."),
221
- }, async ({ notes }) => withErrorHandling(() => api.post("/api/notes/bulk", {
222
- action: "bulkCreate",
223
- workspaceId,
224
- notes: Array.isArray(notes) ? notes : [notes],
225
- })));
226
- server.tool("cn_notes_retype", "Change a single note's type. Atomic operation: creates a new note with the target type, archives the old one, and cross-links both.", {
221
+ }, async ({ notes }) => withErrorHandling(async () => {
222
+ const items = Array.isArray(notes) ? notes : [notes];
223
+ const result = await api.post("/api/notes/bulk", {
224
+ action: "bulkCreate",
225
+ workspaceId,
226
+ notes: items,
227
+ });
228
+ // A1 — echo the rubric for each type used so the agent sees the standard
229
+ // for what it just wrote and can revise in-loop. Zero LLM. Non-fatal: the
230
+ // create already succeeded if we got here.
231
+ try {
232
+ const supertags = await api.get("/api/supertags", {
233
+ workspaceId,
234
+ });
235
+ const byName = new Map();
236
+ for (const s of Array.isArray(supertags) ? supertags : []) {
237
+ const nm = String(s.name || "").toLowerCase();
238
+ if (nm && s.validationPrompt)
239
+ byName.set(nm, String(s.validationPrompt));
240
+ }
241
+ const rubrics = Array.from(new Set(items.map((n) => String(n.type))))
242
+ .map((t) => ({ type: t, rubric: byName.get(t.toLowerCase()) || "" }))
243
+ .filter((r) => r.rubric);
244
+ if (rubrics.length > 0 && result && typeof result === "object") {
245
+ result.rubrics = rubrics;
246
+ }
247
+ }
248
+ catch {
249
+ // Skip the rubric echo if supertags can't be fetched.
250
+ }
251
+ return result;
252
+ }));
253
+ server.tool("cnotes_notes_retype", "Change a single note's type. Atomic operation: creates a new note with the target type, archives the old one, and cross-links both.", {
227
254
  noteId: z.string().describe("Display ID of the note to retype (e.g. NOTE-1)"),
228
255
  targetType: z.string().describe("Target type name (must be an existing supertag)"),
229
256
  }, async ({ noteId, targetType }) => withErrorHandling(() => api.post("/api/notes/retype", {
@@ -234,7 +261,7 @@ server.tool("cn_notes_retype", "Change a single note's type. Atomic operation: c
234
261
  // ---------------------------------------------------------------------------
235
262
  // Search tools
236
263
  // ---------------------------------------------------------------------------
237
- server.tool("cn_search_semantic", "Semantic (vector) search across notes. Better for meaning-based queries.", {
264
+ server.tool("cnotes_search_semantic", "Semantic (vector) search across notes. Better for meaning-based queries.", {
238
265
  query: z.string().describe("Search query"),
239
266
  limit: z.number().optional().default(10).describe("Max results"),
240
267
  canvasId: z.string().optional().describe("Scope search to notes on a canvas"),
@@ -247,26 +274,26 @@ server.tool("cn_search_semantic", "Semantic (vector) search across notes. Better
247
274
  // ---------------------------------------------------------------------------
248
275
  // Canvas tools
249
276
  // ---------------------------------------------------------------------------
250
- server.tool("cn_canvas_list", "List canvases in the workspace. Archived canvases are excluded by default.", {
277
+ server.tool("cnotes_canvas_list", "List canvases in the workspace. Archived canvases are excluded by default.", {
251
278
  includeArchived: z.boolean().optional().default(false).describe("Include archived canvases"),
252
279
  }, async ({ includeArchived }) => withErrorHandling(() => api.get("/api/canvas", {
253
280
  workspaceId,
254
281
  ...(includeArchived ? { includeArchived: "true" } : {}),
255
282
  })));
256
- server.tool("cn_canvas_get", "Get canvas details including nodes, edges, and canvas links. Richtext node content is returned as markdown.", {
283
+ server.tool("cnotes_canvas_get", "Get canvas details including nodes, edges, and canvas links. Richtext node content is returned as markdown.", {
257
284
  canvasId: z.string().describe("Canvas ID"),
258
285
  }, async ({ canvasId }) => withErrorHandling(() => api.get(`/api/canvas/${encodeURIComponent(canvasId)}`, { workspaceId, format: "markdown" })));
259
- server.tool("cn_canvas_read", "Read every note on a canvas as one concatenated markdown document, in display order (top-to-bottom, then left-to-right). Use this to think across a canvas in a single round-trip — preferred over canvas_get + N notes_get calls.", {
286
+ server.tool("cnotes_canvas_read", "Read every note on a canvas as one concatenated markdown document, in display order (top-to-bottom, then left-to-right). Use this to think across a canvas in a single round-trip — preferred over canvas_get + N notes_get calls.", {
260
287
  canvasId: z.string().describe("Canvas ID"),
261
288
  }, async ({ canvasId }) => withErrorHandling(async () => readCanvasMarkdown(api, workspaceId, canvasId)));
262
- server.tool("cn_canvas_create", "Create a new canvas. Goal and target audience are required — every canvas must declare its purpose and who it serves.", {
289
+ server.tool("cnotes_canvas_create", "Create a new canvas. Goal and target audience are required — every canvas must declare its purpose and who it serves.", {
263
290
  name: z.string().describe("Canvas name"),
264
291
  goal: z.string().describe("Canvas goal — the purpose, desired outcome, and what it should communicate (markdown). Required."),
265
292
  targetAudience: z.string().describe("Target audience — who this canvas is meant to serve or inform. Required."),
266
293
  }, async ({ name, goal, targetAudience }) => {
267
294
  return withErrorHandling(() => api.post("/api/canvas", { workspaceId, title: name, goal, targetAudience }));
268
295
  });
269
- server.tool("cn_canvas_update", "Update canvas metadata: title, goal (purpose/outcome), and/or target audience. Use this to set or change the intent metadata that guides agents working on this canvas.", {
296
+ server.tool("cnotes_canvas_update", "Update canvas metadata: title, goal (purpose/outcome), and/or target audience. Use this to set or change the intent metadata that guides agents working on this canvas.", {
270
297
  canvasId: z.string().describe("Canvas ID"),
271
298
  title: z.string().optional().describe("New canvas title"),
272
299
  goal: z.string().optional().describe("Canvas goal — the purpose, desired outcome, and what it should communicate (markdown)"),
@@ -284,21 +311,21 @@ server.tool("cn_canvas_update", "Update canvas metadata: title, goal (purpose/ou
284
311
  body.targetAudience = targetAudience;
285
312
  return withErrorHandling(() => api.patch(`/api/canvas/${encodeURIComponent(canvasId)}`, body, { workspaceId }));
286
313
  });
287
- server.tool("cn_canvas_delete", "Delete a canvas.", {
314
+ server.tool("cnotes_canvas_delete", "Delete a canvas.", {
288
315
  canvasId: z.string().describe("Canvas ID"),
289
316
  }, async ({ canvasId }) => withErrorHandling(() => api.delete(`/api/canvas/${canvasId}`)));
290
- server.tool("cn_canvas_archive", "Archive or unarchive a canvas. Archiving is a soft delete (sets archivedAt timestamp). Use unarchive=true to restore.", {
317
+ server.tool("cnotes_canvas_archive", "Archive or unarchive a canvas. Archiving is a soft delete (sets archivedAt timestamp). Use unarchive=true to restore.", {
291
318
  canvasId: z.string().describe("Canvas ID"),
292
319
  unarchive: z.boolean().optional().default(false).describe("Set true to unarchive/restore instead of archive"),
293
320
  }, async ({ canvasId, unarchive }) => withErrorHandling(() => api.post(`/api/canvas/${canvasId}`, {
294
321
  action: unarchive ? "unarchive" : "archive",
295
322
  })));
296
- server.tool("cn_canvas_set_home", "Set a canvas as the workspace home/default canvas. Clears any previous home canvas.", {
323
+ server.tool("cnotes_canvas_set_home", "Set a canvas as the workspace home/default canvas. Clears any previous home canvas.", {
297
324
  canvasId: z.string().describe("Canvas ID to set as home"),
298
325
  }, async ({ canvasId }) => withErrorHandling(() => api.post(`/api/canvas/${canvasId}`, { action: "setAsHome" })));
299
326
  // --- Deprecated single-item wrappers (delegate to bulk_add) ---
300
327
  // Kept for backward compatibility with existing MCP clients.
301
- server.tool("cn_canvas_add_node", "[Deprecated: use cn_canvas_bulk_add] Add a note to a canvas as a node.", {
328
+ server.tool("cnotes_canvas_add_node", "[Deprecated: use cnotes_canvas_bulk_add] Add a note to a canvas as a node.", {
302
329
  canvasId: z.string().describe("Canvas ID"),
303
330
  noteId: z.string().describe("Note display ID or Convex ID"),
304
331
  x: z.number().optional().default(100).describe("X position"),
@@ -307,7 +334,7 @@ server.tool("cn_canvas_add_node", "[Deprecated: use cn_canvas_bulk_add] Add a no
307
334
  action: "bulkAddNodes",
308
335
  items: [{ type: "note", noteId, positionX: x, positionY: y }],
309
336
  })));
310
- server.tool("cn_canvas_add_text", "[Deprecated: use cn_canvas_bulk_add] Add a text annotation node to a canvas.", {
337
+ server.tool("cnotes_canvas_add_text", "[Deprecated: use cnotes_canvas_bulk_add] Add a text annotation node to a canvas.", {
311
338
  canvasId: z.string().describe("Canvas ID"),
312
339
  text: z.string().describe("Text content"),
313
340
  size: z.enum(["heading", "paragraph"]).optional().default("heading").describe("Text size"),
@@ -318,9 +345,9 @@ server.tool("cn_canvas_add_text", "[Deprecated: use cn_canvas_bulk_add] Add a te
318
345
  action: "bulkAddNodes",
319
346
  items: [{ type: "text", content: text, fontSize: size === "paragraph" ? 18 : 32, colorVariant: color, positionX: x, positionY: y }],
320
347
  })));
321
- server.tool("cn_canvas_add_richtext", "[Deprecated: use cn_canvas_bulk_add] Add a richtext annotation node to a canvas.", {
348
+ server.tool("cnotes_canvas_add_richtext", "[Deprecated: use cnotes_canvas_bulk_add] Add a richtext annotation node to a canvas.", {
322
349
  canvasId: z.string().describe("Canvas ID"),
323
- content: z.string().describe("Richtext content as markdown. To link to notes, use [NOTE-123: Title](relationship:references)."),
350
+ content: z.string().describe("Richtext content as markdown. Plain markdown ONLY do NOT use [NOTE-X](relationship:...) or [[...]] mention syntax here (it corrupts the card, which renders blank); reference notes as plain display IDs (\"see RISK-64\"). Keep it sticky-sized (≤ ~3 sentences): richtext is unversioned, unsearchable presentation furniture — knowledge worth keeping belongs in a typed note."),
324
351
  size: z.enum(["small", "medium", "large"]).optional().default("small").describe("Display size"),
325
352
  color: z.string().optional().describe("Background color hex"),
326
353
  x: z.number().optional().default(100).describe("X position"),
@@ -329,7 +356,7 @@ server.tool("cn_canvas_add_richtext", "[Deprecated: use cn_canvas_bulk_add] Add
329
356
  action: "bulkAddNodes",
330
357
  items: [{ type: "richtext", content, size, colorHex: color, positionX: x, positionY: y }],
331
358
  })));
332
- server.tool("cn_canvas_add_list", "[Deprecated: use cn_canvas_bulk_add] Add a list/grid node grouping notes on a canvas. The description is the only label; its first paragraph derives the searchable title.", {
359
+ server.tool("cnotes_canvas_add_list", "[Deprecated: use cnotes_canvas_bulk_add] Add a list/grid node grouping notes on a canvas. The description is the only label; its first paragraph derives the searchable title.", {
333
360
  canvasId: z.string().describe("Canvas ID"),
334
361
  description: z.string().min(1).describe("Rich-text description (markdown). Required. First paragraph derives the searchable title."),
335
362
  noteIds: z.string().optional().describe("Comma-separated note IDs"),
@@ -347,7 +374,7 @@ server.tool("cn_canvas_add_list", "[Deprecated: use cn_canvas_bulk_add] Add a li
347
374
  positionY: y,
348
375
  }],
349
376
  })));
350
- server.tool("cn_canvas_add_link", "[Deprecated: use cn_canvas_bulk_add] Add a link to another canvas.", {
377
+ server.tool("cnotes_canvas_add_link", "[Deprecated: use cnotes_canvas_bulk_add] Add a link to another canvas.", {
351
378
  canvasId: z.string().describe("Canvas ID"),
352
379
  targetCanvasId: z.string().describe("Target canvas ID to link to"),
353
380
  x: z.number().optional().default(100).describe("X position"),
@@ -357,7 +384,7 @@ server.tool("cn_canvas_add_link", "[Deprecated: use cn_canvas_bulk_add] Add a li
357
384
  items: [{ type: "canvas", linkedCanvasId: targetCanvasId, positionX: x, positionY: y }],
358
385
  })));
359
386
  // --- Primary bulk tool ---
360
- server.tool("cn_canvas_bulk_add", "Add one or more items to a canvas. Use this for ALL canvas additions — single or multiple. Supports all node types: note, text, richtext, list, canvas (link to another canvas), and edge. ALWAYS prefer this over repeated single-item calls. Items are processed in order so edges can reference nodes created earlier in the same batch via sourceItemIndex/targetItemIndex.", {
387
+ server.tool("cnotes_canvas_bulk_add", "Add one or more items to a canvas. Use this for ALL canvas additions — single or multiple. Supports all node types: note, text, richtext, list, canvas (link to another canvas), and edge. ALWAYS prefer this over repeated single-item calls. Items are processed in order so edges can reference nodes created earlier in the same batch via sourceItemIndex/targetItemIndex.\n\nELEMENT CHOICE — notes are the knowledge tier (typed, versioned, searchable, the only content agents read back in full); everything else is presentation furniture. Default any claim/fact/decision/finding worth keeping to a typed NOTE. Use text for one-line section labels, richtext only for sticky-sized framing (orientation banners, emphasis quoting a note, image tiles — never the sole home of a claim), list for grouping member notes (description = one-line frame; N distinct things = N notes), canvas for portals, edge for note-to-note relationships (notes only — text/richtext endpoints fail; mirror the relationship as a mention in the note bodies).", {
361
388
  canvasId: z.string().describe("Canvas ID"),
362
389
  notes: z.array(z.object({
363
390
  noteId: z.string().describe("Note display ID or Convex ID"),
@@ -381,7 +408,7 @@ server.tool("cn_canvas_bulk_add", "Add one or more items to a canvas. Use this f
381
408
  }),
382
409
  z.object({
383
410
  type: z.literal("richtext"),
384
- content: z.string().describe("Markdown content. To link to notes, use [NOTE-123: Title](relationship:references)."),
411
+ content: z.string().describe("Markdown content. Plain markdown ONLY no [NOTE-X](relationship:...) / [[...]] mention syntax (corrupts the card); reference notes as plain display IDs. Sticky-sized (≤ ~3 sentences); knowledge worth keeping goes in a note."),
385
412
  x: z.number().describe("X position"),
386
413
  y: z.number().describe("Y position"),
387
414
  size: z.enum(["small", "medium", "large"]).optional().describe("Node size"),
@@ -389,7 +416,7 @@ server.tool("cn_canvas_bulk_add", "Add one or more items to a canvas. Use this f
389
416
  }),
390
417
  z.object({
391
418
  type: z.literal("list"),
392
- description: z.string().min(1).describe("Rich-text description (markdown). Required. First paragraph derives the searchable title — this is the only label for list nodes."),
419
+ description: z.string().min(1).describe("One-line FRAME (markdown). Required. First paragraph derives the searchable title — this is the only label for list nodes. The CONTENT of a list is its member notes (noteIds), never the description: N distinct things = N typed notes grouped here. No mention syntax."),
393
420
  x: z.number().describe("X position"),
394
421
  y: z.number().describe("Y position"),
395
422
  viewMode: z.enum(["list", "grid"]).optional(),
@@ -439,14 +466,14 @@ server.tool("cn_canvas_bulk_add", "Add one or more items to a canvas. Use this f
439
466
  items: allItems,
440
467
  });
441
468
  }));
442
- server.tool("cn_canvas_place", "Place items on a canvas using a DECLARATIVE LAYOUT (no x/y math). The server measures every item, packs them with no overlaps, and inserts them in a single batch. PREFER THIS over cn_canvas_bulk_add whenever you can express the layout as stacks/grids/anchors — which is almost always. The agent should describe STRUCTURE (rows, columns, what's near what), not pixel coordinates.\n\nThe call is best-effort, NOT atomic: per-item failures are reported in the response (HTTP 207) but the items that succeeded stay on the canvas. Always inspect `items[].error` and `edges[].error` in the response.\n\nThe `doc` argument is a layout document:\n { root: <LayoutNode>, edges?: [{from, to, label?}], origin?: {x, y} }\n\n<LayoutNode> is one of:\n • { kind: 'stack', axis: 'vertical'|'horizontal', gap?: 'tight'|'medium'|'spacious'|<px>, align?: 'start'|'center'|'end', items: [<LayoutNode>...] }\n • { kind: 'grid', columns: <n>, gap?: ..., items: [<LayoutNode>...] }\n • { kind: 'anchor', to: '<displayId or canvas node id>', direction: 'right'|'left'|'above'|'below', gap?: ..., child: <LayoutNode> }\n • { kind: 'item', type: 'note'|'text'|'richtext'|'list'|'canvas', key?: '<name>', ...item-specific fields }\n\nLeaf item shapes (no x/y!):\n note: { kind:'item', type:'note', noteId:'NOTE-3', key?:'a' }\n text: { kind:'item', type:'text', content:'Hello', fontSize?:32, colorVariant?:'normal'|'muted'|'highlighted' }\n richtext: { kind:'item', type:'richtext', content:'# Markdown OK', size?:'small'|'medium'|'large', colorHex?:'#3B82F6' }\n list: { kind:'item', type:'list', name:'My list', noteIds?:['NOTE-1','NOTE-2'], viewMode?:'list'|'grid' }\n canvas: { kind:'item', type:'canvas', linkedCanvasId:'<id>' }\n\nEdges reference endpoints by, in priority order: `@<key>` (any item with a `key` placed in the same call), the original noteId/displayId of a note placed in this call (e.g. 'NOTE-7'), or the displayId / convex id of a node already on the canvas.\n\nExample (a SWOT-like quadrant):\n {\n root: {\n kind:'stack', axis:'vertical', gap:'spacious',\n items:[\n { kind:'stack', axis:'horizontal', gap:'spacious', items:[\n { kind:'grid', columns:2, items:[\n { kind:'item', type:'note', noteId:'STR-1' },\n { kind:'item', type:'note', noteId:'STR-2' },\n ]},\n { kind:'grid', columns:2, items:[\n { kind:'item', type:'note', noteId:'WEAK-1' },\n ]},\n ]},\n { kind:'stack', axis:'horizontal', gap:'spacious', items:[ /* opportunities + threats */ ]},\n ],\n },\n }", {
469
+ server.tool("cnotes_canvas_place", "Place items on a canvas using a DECLARATIVE LAYOUT (no x/y math). The server measures every item, packs them with no overlaps, and inserts them in a single batch. PREFER THIS over cnotes_canvas_bulk_add whenever you can express the layout as stacks/grids/anchors — which is almost always. The agent should describe STRUCTURE (rows, columns, what's near what), not pixel coordinates.\n\nELEMENT CHOICE: default content to typed NOTES (the only versioned, searchable tier agents can read back); text = one-line labels, richtext = sticky-sized framing only, list = member notes behind a one-line frame, edges connect notes only.\n\nThe call is best-effort, NOT atomic: per-item failures are reported in the response (HTTP 207) but the items that succeeded stay on the canvas. Always inspect `items[].error` and `edges[].error` in the response.\n\nThe `doc` argument is a layout document:\n { root: <LayoutNode>, edges?: [{from, to, label?}], origin?: {x, y} }\n\n<LayoutNode> is one of:\n • { kind: 'stack', axis: 'vertical'|'horizontal', gap?: 'tight'|'medium'|'spacious'|<px>, align?: 'start'|'center'|'end', items: [<LayoutNode>...] }\n • { kind: 'grid', columns: <n>, gap?: ..., items: [<LayoutNode>...] }\n • { kind: 'anchor', to: '<displayId or canvas node id>', direction: 'right'|'left'|'above'|'below', gap?: ..., child: <LayoutNode> }\n • { kind: 'item', type: 'note'|'text'|'richtext'|'list'|'canvas', key?: '<name>', ...item-specific fields }\n\nLeaf item shapes (no x/y!):\n note: { kind:'item', type:'note', noteId:'NOTE-3', key?:'a' }\n text: { kind:'item', type:'text', content:'Hello', fontSize?:32, colorVariant?:'normal'|'muted'|'highlighted' } // one-line section labels only\n richtext: { kind:'item', type:'richtext', content:'# Markdown OK', size?:'small'|'medium'|'large', colorHex?:'<hex>' } // sticky-sized framing only; plain markdown, NO [NOTE-X](relationship:...) mention syntax (corrupts the card); knowledge worth keeping = a note\n list: { kind:'item', type:'list', description:'One-line frame — first paragraph becomes the title', noteIds?:['NOTE-1','NOTE-2'], viewMode?:'list'|'grid' } // description is REQUIRED and is a frame, never the content: N distinct things = N notes in noteIds\n canvas: { kind:'item', type:'canvas', linkedCanvasId:'<id>' }\n\nEdges reference endpoints by, in priority order: `@<key>` (any item with a `key` placed in the same call), the original noteId/displayId of a note placed in this call (e.g. 'NOTE-7'), or the displayId / convex id of a node already on the canvas.\n\nExample (a SWOT-like quadrant):\n {\n root: {\n kind:'stack', axis:'vertical', gap:'spacious',\n items:[\n { kind:'stack', axis:'horizontal', gap:'spacious', items:[\n { kind:'grid', columns:2, items:[\n { kind:'item', type:'note', noteId:'STR-1' },\n { kind:'item', type:'note', noteId:'STR-2' },\n ]},\n { kind:'grid', columns:2, items:[\n { kind:'item', type:'note', noteId:'WEAK-1' },\n ]},\n ]},\n { kind:'stack', axis:'horizontal', gap:'spacious', items:[ /* opportunities + threats */ ]},\n ],\n },\n }", {
443
470
  canvasId: z.string().describe("Canvas ID"),
444
471
  doc: z.any().describe("Layout document. See tool description for shape."),
445
472
  }, async ({ canvasId, doc }) => withErrorHandling(() => api.post(`/api/canvas/${encodeURIComponent(canvasId)}?workspaceId=${encodeURIComponent(workspaceId)}`, {
446
473
  action: "placeLayout",
447
474
  doc,
448
475
  })));
449
- server.tool("cn_canvas_bulk_move", "Move multiple nodes to new positions in one operation. More efficient than calling cn_canvas_move_node multiple times.", {
476
+ server.tool("cnotes_canvas_bulk_move", "Move multiple nodes to new positions in one operation. More efficient than calling cnotes_canvas_move_node multiple times.", {
450
477
  canvasId: z.string().describe("Canvas ID"),
451
478
  moves: z.array(z.object({
452
479
  nodeId: z.string().describe("Canvas node ID"),
@@ -463,14 +490,14 @@ server.tool("cn_canvas_bulk_move", "Move multiple nodes to new positions in one
463
490
  positionY: m.y,
464
491
  })),
465
492
  })));
466
- server.tool("cn_canvas_bulk_remove", "Remove multiple nodes from a canvas in one operation. More efficient than calling cn_canvas_remove_node multiple times.", {
493
+ server.tool("cnotes_canvas_bulk_remove", "Remove multiple nodes from a canvas in one operation. More efficient than calling cnotes_canvas_remove_node multiple times.", {
467
494
  canvasId: z.string().describe("Canvas ID"),
468
495
  nodeIds: z.array(z.string()).describe("Array of node IDs to remove"),
469
496
  }, async ({ canvasId, nodeIds }) => withErrorHandling(() => api.post(`/api/canvas/${canvasId}`, {
470
497
  action: "bulkRemoveNodes",
471
498
  nodeIds,
472
499
  })));
473
- server.tool("cn_canvas_add_edge", "Connect two nodes on a canvas with an edge. Defaults to an arrow at the target end and a solid line.", {
500
+ server.tool("cnotes_canvas_add_edge", "Connect two nodes on a canvas with an edge. Defaults to an arrow at the target end and a solid line.", {
474
501
  canvasId: z.string().describe("Canvas ID"),
475
502
  sourceNodeId: z.string().describe("Source node ID"),
476
503
  targetNodeId: z.string().describe("Target node ID"),
@@ -494,7 +521,7 @@ server.tool("cn_canvas_add_edge", "Connect two nodes on a canvas with an edge. D
494
521
  lineStyle,
495
522
  });
496
523
  }));
497
- server.tool("cn_canvas_update_edge", "Update an edge: change its label, toggle arrowheads, switch line style, or reverse direction. Only the fields you pass are changed.", {
524
+ server.tool("cnotes_canvas_update_edge", "Update an edge: change its label, toggle arrowheads, switch line style, or reverse direction. Only the fields you pass are changed.", {
498
525
  canvasId: z.string().describe("Canvas ID"),
499
526
  edgeId: z.string().describe("Canvas edge ID"),
500
527
  label: z
@@ -523,7 +550,7 @@ server.tool("cn_canvas_update_edge", "Update an edge: change its label, toggle a
523
550
  body.reverse = reverse;
524
551
  return api.post(`/api/canvas/${canvasId}`, body);
525
552
  }));
526
- server.tool("cn_canvas_move_node", "Move a node to a new position on a canvas.", {
553
+ server.tool("cnotes_canvas_move_node", "Move a node to a new position on a canvas.", {
527
554
  canvasId: z.string().describe("Canvas ID"),
528
555
  nodeId: z.string().describe("Canvas node ID"),
529
556
  nodeType: z.enum(["note", "text", "list", "canvas", "richtext"]).optional().default("note").describe("Node type"),
@@ -536,27 +563,27 @@ server.tool("cn_canvas_move_node", "Move a node to a new position on a canvas.",
536
563
  positionX: x,
537
564
  positionY: y,
538
565
  })));
539
- server.tool("cn_canvas_remove_node", "Remove a node from a canvas.", {
566
+ server.tool("cnotes_canvas_remove_node", "Remove a node from a canvas.", {
540
567
  canvasId: z.string().describe("Canvas ID"),
541
568
  nodeId: z.string().describe("Canvas node ID"),
542
569
  }, async ({ canvasId, nodeId }) => withErrorHandling(() => api.post(`/api/canvas/${canvasId}`, {
543
570
  action: "removeNode",
544
571
  nodeId,
545
572
  })));
546
- server.tool("cn_canvas_remove_edge", "Remove an edge from a canvas.", {
573
+ server.tool("cnotes_canvas_remove_edge", "Remove an edge from a canvas.", {
547
574
  canvasId: z.string().describe("Canvas ID"),
548
575
  edgeId: z.string().describe("Canvas edge ID"),
549
576
  }, async ({ canvasId, edgeId }) => withErrorHandling(() => api.post(`/api/canvas/${canvasId}`, {
550
577
  action: "removeEdge",
551
578
  edgeId,
552
579
  })));
553
- server.tool("cn_canvas_digest", "Get the AI-generated digest of a canvas: narrative summary, key themes, and a per-note summary list covering every note on the canvas. Run this first when starting work on any canvas — it gives you full orientation in a single call, so you don't need to read each note individually. Each note entry has shape { displayId, title, type, summary }; when summary is null the AI summary hasn't generated yet. The notes list is always populated, even when digest status is 'none' or 'stale'.", {
580
+ server.tool("cnotes_canvas_digest", "Get the AI-generated digest of a canvas: narrative summary, key themes, and a per-note summary list covering every note on the canvas. Run this first when starting work on any canvas — it gives you full orientation in a single call, so you don't need to read each note individually. Each note entry has shape { displayId, title, type, summary }; when summary is null the AI summary hasn't generated yet. The notes list is always populated, even when digest status is 'none' or 'stale'.", {
554
581
  canvasId: z.string().describe("Canvas ID"),
555
582
  }, async ({ canvasId }) => withErrorHandling(() => api.get(`/api/canvas/${encodeURIComponent(canvasId)}/digest`, { workspaceId })));
556
- server.tool("cn_canvas_digest_generate", "Request digest (re)generation for a canvas. Use when digest status is 'none' or you want a fresh summary. The digest is generated async — poll cn_canvas_digest after ~60s.", {
583
+ server.tool("cnotes_canvas_digest_generate", "Request digest (re)generation for a canvas. Use when digest status is 'none' or you want a fresh summary. The digest is generated async — poll cnotes_canvas_digest after ~60s.", {
557
584
  canvasId: z.string().describe("Canvas ID"),
558
585
  }, async ({ canvasId }) => withErrorHandling(() => api.post(`/api/canvas/${encodeURIComponent(canvasId)}/digest?workspaceId=${encodeURIComponent(workspaceId)}`, {})));
559
- server.tool("cn_canvas_activity", "Show recent activity on a canvas — edits, agent runs, gestures, restores — newest first. Use this to find out what changed and who changed it before making further edits, or to confirm a previous agent run actually applied. Returns { canvasName, operations: Array<{ _id, operationType, actorName, actorSource ('human'|'agent'|'import'|'api'), agentRunId, userPrompt, status ('in_progress'|'applied'|'reverted'|'failed'), eventCount, affectedEntityIds, summary, structuredSummary: { headline, summary, byCategory[], themes[] }, startedAt, completedAt }>}.", {
586
+ server.tool("cnotes_canvas_activity", "Show recent activity on a canvas — edits, agent runs, gestures, restores — newest first. Use this to find out what changed and who changed it before making further edits, or to confirm a previous agent run actually applied. Returns { canvasName, operations: Array<{ _id, operationType, actorName, actorSource ('human'|'agent'|'import'|'api'), agentRunId, userPrompt, status ('in_progress'|'applied'|'reverted'|'failed'), eventCount, affectedEntityIds, summary, structuredSummary: { headline, summary, byCategory[], themes[] }, startedAt, completedAt }>}.", {
560
587
  canvasId: z.string().describe("Canvas ID"),
561
588
  limit: z.number().int().min(1).max(200).optional().describe("Max entries (default 50, max 200)"),
562
589
  }, async ({ canvasId, limit }) => {
@@ -565,8 +592,8 @@ server.tool("cn_canvas_activity", "Show recent activity on a canvas — edits, a
565
592
  params.limit = String(limit);
566
593
  return withErrorHandling(() => api.get(`/api/canvas/${encodeURIComponent(canvasId)}/activity`, params));
567
594
  });
568
- server.tool("cn_canvas_templates", "List available canvas templates.", {}, async () => withErrorHandling(() => api.get("/api/canvas/template")));
569
- server.tool("cn_canvas_from_template", "Create a canvas from a predefined template.", {
595
+ server.tool("cnotes_canvas_templates", "List available canvas templates.", {}, async () => withErrorHandling(() => api.get("/api/canvas/template")));
596
+ server.tool("cnotes_canvas_from_template", "Create a canvas from a predefined template.", {
570
597
  templateId: z.string().describe("Template ID"),
571
598
  title: z.string().optional().describe("Custom canvas title"),
572
599
  }, async ({ templateId, title }) => withErrorHandling(() => api.post("/api/canvas/template", {
@@ -577,7 +604,7 @@ server.tool("cn_canvas_from_template", "Create a canvas from a predefined templa
577
604
  // ---------------------------------------------------------------------------
578
605
  // Timeline
579
606
  // ---------------------------------------------------------------------------
580
- server.tool("cn_timeline", "Show recent changes across notes. Supports relative time (7d, 2w) or ISO dates.", {
607
+ server.tool("cnotes_timeline", "Show recent changes across notes. Supports relative time (7d, 2w) or ISO dates.", {
581
608
  since: z.string().optional().default("7d").describe("Start of range (e.g. 7d, 2w, 2026-03-21)"),
582
609
  until: z.string().optional().describe("End of range (default: now)"),
583
610
  type: z.string().optional().describe("Filter by note type (comma-separated)"),
@@ -618,8 +645,30 @@ function parseDuration(input) {
618
645
  // ---------------------------------------------------------------------------
619
646
  // Types (supertags)
620
647
  // ---------------------------------------------------------------------------
621
- server.tool("cn_types_list", "List note types (supertags) in the workspace.", {}, async () => withErrorHandling(() => api.get("/api/supertags", { workspaceId })));
622
- server.tool("cn_types_create", "Create a new note type (supertag).", {
648
+ server.tool("cnotes_types_list", "List note types (supertags) in the workspace. Each entry carries a `validationPrompt` (its quality rubric — what a good instance contains); read it before authoring.", {}, async () => withErrorHandling(() => api.get("/api/supertags", { workspaceId })));
649
+ server.tool("cnotes_types_show", "Show one note type's quality rubric (validationPrompt) — what a good instance of that type contains. Read this BEFORE authoring a note of this type so it lands well-formed.", {
650
+ name: z.string().describe("Type name (e.g. 'Decision', 'Insight')"),
651
+ }, async ({ name }) => withErrorHandling(async () => {
652
+ const list = await api.get("/api/supertags", { workspaceId });
653
+ const want = name.trim().toLowerCase();
654
+ const match = (Array.isArray(list) ? list : []).find((s) => String(s.name || "").toLowerCase() === want ||
655
+ String(s.displayName || "").toLowerCase() === want);
656
+ if (!match) {
657
+ return {
658
+ error: `Type "${name}" not found`,
659
+ hint: "Call cnotes_types_list to see valid type names.",
660
+ };
661
+ }
662
+ return {
663
+ name: match.name,
664
+ displayName: match.displayName,
665
+ prefix: match.prefix,
666
+ description: match.description ?? null,
667
+ rubric: match.validationPrompt ?? null,
668
+ validationEnabled: match.validationEnabled ?? false,
669
+ };
670
+ }));
671
+ server.tool("cnotes_types_create", "Create a new note type (supertag).", {
623
672
  name: z.string().describe("Type name, PascalCase, no spaces (e.g. PainPoint, UserStory)"),
624
673
  displayName: z.string().optional().describe("Display name (can have spaces)"),
625
674
  prefix: z.string().optional().describe("ID prefix, uppercase (e.g. PAINPOINT, USERSTORY)"),
@@ -633,7 +682,7 @@ server.tool("cn_types_create", "Create a new note type (supertag).", {
633
682
  description,
634
683
  color,
635
684
  })));
636
- server.tool("cn_types_update", "Update a note type (supertag).", {
685
+ server.tool("cnotes_types_update", "Update a note type (supertag).", {
637
686
  name: z.string().describe("Type name to update"),
638
687
  displayName: z.string().optional().describe("New display name"),
639
688
  prefix: z.string().optional().describe("New ID prefix (uppercase)"),
@@ -654,7 +703,7 @@ server.tool("cn_types_update", "Update a note type (supertag).", {
654
703
  // ---------------------------------------------------------------------------
655
704
  // Relationships
656
705
  // ---------------------------------------------------------------------------
657
- server.tool("cn_relationships_list", "List note relationships in the workspace.", {
706
+ server.tool("cnotes_relationships_list", "List note relationships in the workspace.", {
658
707
  noteDisplayId: z.string().optional().describe("Filter by note display ID"),
659
708
  }, async ({ noteDisplayId }) => withErrorHandling(() => api.get("/api/relationships", {
660
709
  workspaceId,
@@ -663,7 +712,7 @@ server.tool("cn_relationships_list", "List note relationships in the workspace."
663
712
  // ---------------------------------------------------------------------------
664
713
  // Memory (Zep)
665
714
  // ---------------------------------------------------------------------------
666
- server.tool("cn_memory_query", "Search facts and entities in the knowledge graph by relevance.", {
715
+ server.tool("cnotes_memory_query", "Search facts and entities in the knowledge graph by relevance.", {
667
716
  query: z.string().describe("Search query"),
668
717
  limit: z.number().optional().default(10).describe("Max results per category"),
669
718
  }, async ({ query, limit }) => withErrorHandling(() => api.post("/api/zep/query", {
@@ -671,7 +720,7 @@ server.tool("cn_memory_query", "Search facts and entities in the knowledge graph
671
720
  query,
672
721
  limit: limit ?? 10,
673
722
  })));
674
- server.tool("cn_memory_facts", "List all workspace facts from the knowledge graph.", {}, async () => withErrorHandling(async () => {
723
+ server.tool("cnotes_memory_facts", "List all workspace facts from the knowledge graph.", {}, async () => withErrorHandling(async () => {
675
724
  const data = await api.post("/api/zep/query", {
676
725
  workspaceId,
677
726
  query: "all facts",
@@ -679,7 +728,7 @@ server.tool("cn_memory_facts", "List all workspace facts from the knowledge grap
679
728
  });
680
729
  return data.allFacts;
681
730
  }));
682
- server.tool("cn_memory_entities", "List all workspace entities (people, topics, orgs) from the knowledge graph.", {}, async () => withErrorHandling(async () => {
731
+ server.tool("cnotes_memory_entities", "List all workspace entities (people, topics, orgs) from the knowledge graph.", {}, async () => withErrorHandling(async () => {
683
732
  const data = await api.post("/api/zep/query", {
684
733
  workspaceId,
685
734
  query: "all entities",
@@ -690,7 +739,7 @@ server.tool("cn_memory_entities", "List all workspace entities (people, topics,
690
739
  // ---------------------------------------------------------------------------
691
740
  // Versions
692
741
  // ---------------------------------------------------------------------------
693
- server.tool("cn_versions_list", "List versions of a note.", {
742
+ server.tool("cnotes_versions_list", "List versions of a note.", {
694
743
  noteId: z.string().describe("Note display ID or Convex ID"),
695
744
  }, async ({ noteId }) => withErrorHandling(async () => {
696
745
  const notesList = await api.get("/api/notes", {
@@ -703,7 +752,7 @@ server.tool("cn_versions_list", "List versions of a note.", {
703
752
  throw new Error(`Note not found: ${noteId}`);
704
753
  return note.versions || [];
705
754
  }));
706
- server.tool("cn_versions_create", "Create a new version of a note. Content is markdown. To link to other notes, use [NOTE-123: Title](relationship:references) inside the markdown — always include the title (the part after the colon) or the chip renders as 'Untitled'. The relationship name is free-form (e.g. references, triggers, caused-by); use 'references' if unsure.", {
755
+ server.tool("cnotes_versions_create", "Create a new version of a note. Content is markdown. To link to other notes, use [NOTE-123: Title](relationship:references) inside the markdown — always include the title (the part after the colon) or the chip renders as 'Untitled'. The relationship name is free-form (e.g. references, triggers, caused-by); use 'references' if unsure.", {
707
756
  noteId: z.string().describe("Note display ID or Convex ID"),
708
757
  markdown: z.string().describe("New content as markdown"),
709
758
  description: z.string().describe("Change description (required)"),
@@ -717,8 +766,8 @@ server.tool("cn_versions_create", "Create a new version of a note. Content is ma
717
766
  // ---------------------------------------------------------------------------
718
767
  // Workspace
719
768
  // ---------------------------------------------------------------------------
720
- server.tool("cn_workspace_list", "List all workspaces the authenticated user belongs to.", {}, async () => withErrorHandling(() => api.get("/api/workspaces")));
721
- server.tool("cn_workspace_current", "Get details about the current workspace.", {}, async () => withErrorHandling(() => api.get(`/api/workspaces/${workspaceId}`)));
769
+ server.tool("cnotes_workspace_list", "List all workspaces the authenticated user belongs to.", {}, async () => withErrorHandling(() => api.get("/api/workspaces")));
770
+ server.tool("cnotes_workspace_current", "Get details about the current workspace.", {}, async () => withErrorHandling(() => api.get(`/api/workspaces/${workspaceId}`)));
722
771
  // ---------------------------------------------------------------------------
723
772
  // Files
724
773
  // ---------------------------------------------------------------------------
@@ -729,7 +778,7 @@ const FILE_MIME_MAP = {
729
778
  ".gif": "image/gif",
730
779
  ".webp": "image/webp",
731
780
  };
732
- server.tool("cn_files_upload", "Upload an image file to the workspace. Returns the public URL. Supports .jpg, .jpeg, .png, .gif, .webp (max 5MB).", {
781
+ server.tool("cnotes_files_upload", "Upload an image file to the workspace. Returns the public URL. Supports .jpg, .jpeg, .png, .gif, .webp (max 5MB).", {
733
782
  filePath: z.string().describe("Absolute path to the image file on disk"),
734
783
  }, async ({ filePath }) => withErrorHandling(async () => {
735
784
  const absPath = path.resolve(filePath);
@@ -759,14 +808,14 @@ server.tool("cn_files_upload", "Upload an image file to the workspace. Returns t
759
808
  const result = await api.postFormData("/api/upload-image", buildFormData);
760
809
  return {
761
810
  ...result,
762
- fullUrl: `${CN_SERVER}${result.url}`,
763
- markdown: `![${filename}](${CN_SERVER}${result.url})`,
811
+ fullUrl: `${CNOTES_SERVER}${result.url}`,
812
+ markdown: `![${filename}](${CNOTES_SERVER}${result.url})`,
764
813
  };
765
814
  }));
766
815
  // ---------------------------------------------------------------------------
767
816
  // Schema
768
817
  // ---------------------------------------------------------------------------
769
- server.tool("cn_schema", "Describe this workspace in one call: valid note types (with prefixes), common relationship slugs, resource field shapes, and the canvas node kinds. Call this once at the start of a session instead of guessing type names or reading docs.", {}, async () => withErrorHandling(() => buildSchema(api, workspaceId)));
818
+ server.tool("cnotes_schema", "Describe this workspace in one call: valid note types (with prefixes), common relationship slugs, resource field shapes, and the canvas node kinds. Call this once at the start of a session instead of guessing type names or reading docs.", {}, async () => withErrorHandling(() => buildSchema(api, workspaceId)));
770
819
  // ---------------------------------------------------------------------------
771
820
  // Start
772
821
  // ---------------------------------------------------------------------------