remnote-mcp-server 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +14 -9
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +7 -2
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config.d.ts +2 -0
  7. package/dist/config.js +26 -2
  8. package/dist/config.js.map +1 -1
  9. package/dist/daemon.js.map +1 -1
  10. package/dist/http-server.d.ts +2 -1
  11. package/dist/http-server.js +4 -2
  12. package/dist/http-server.js.map +1 -1
  13. package/dist/index.js +3 -8
  14. package/dist/index.js.map +1 -1
  15. package/dist/media.d.ts +32 -0
  16. package/dist/media.js +184 -0
  17. package/dist/media.js.map +1 -0
  18. package/dist/remnote-cli/cli.js +8 -2
  19. package/dist/remnote-cli/cli.js.map +1 -1
  20. package/dist/remnote-cli/client/mcp-server-client.d.ts +1 -0
  21. package/dist/remnote-cli/client/mcp-server-client.js +24 -4
  22. package/dist/remnote-cli/client/mcp-server-client.js.map +1 -1
  23. package/dist/remnote-cli/commands/create.js +5 -2
  24. package/dist/remnote-cli/commands/create.js.map +1 -1
  25. package/dist/remnote-cli/commands/journal.js +1 -1
  26. package/dist/remnote-cli/commands/journal.js.map +1 -1
  27. package/dist/remnote-cli/commands/media.d.ts +2 -0
  28. package/dist/remnote-cli/commands/media.js +62 -0
  29. package/dist/remnote-cli/commands/media.js.map +1 -0
  30. package/dist/remnote-cli/commands/read.js +29 -3
  31. package/dist/remnote-cli/commands/read.js.map +1 -1
  32. package/dist/remnote-cli/commands/search.d.ts +1 -0
  33. package/dist/remnote-cli/commands/search.js +91 -7
  34. package/dist/remnote-cli/commands/search.js.map +1 -1
  35. package/dist/remnote-cli/commands/update.js +1 -1
  36. package/dist/remnote-cli/commands/update.js.map +1 -1
  37. package/dist/remnote-cli/commands/write-actions.d.ts +3 -0
  38. package/dist/remnote-cli/commands/write-actions.js +158 -2
  39. package/dist/remnote-cli/commands/write-actions.js.map +1 -1
  40. package/dist/schemas/remnote-schemas.d.ts +107 -9
  41. package/dist/schemas/remnote-schemas.js +194 -50
  42. package/dist/schemas/remnote-schemas.js.map +1 -1
  43. package/dist/shutdown.d.ts +15 -0
  44. package/dist/shutdown.js +59 -0
  45. package/dist/shutdown.js.map +1 -0
  46. package/dist/tools/index.d.ts +3364 -585
  47. package/dist/tools/index.js +685 -41
  48. package/dist/tools/index.js.map +1 -1
  49. package/dist/types/bridge.d.ts +1 -0
  50. package/dist/websocket-server.d.ts +5 -1
  51. package/dist/websocket-server.js +18 -2
  52. package/dist/websocket-server.js.map +1 -1
  53. package/mcpb/remnote-local/manifest.json +26 -6
  54. package/mcpb/remnote-local/package.json +1 -1
  55. package/mcpb/remnote-local/remnote-local.mcpb +0 -0
  56. package/mcpb/remnote-local/server/fallback-tools.generated.js +285 -14
  57. package/mcpb/remnote-local/server/index.js +4 -2
  58. package/package.json +4 -4
@@ -3,18 +3,39 @@ import { CreateNoteSchema } from '../schemas/remnote-schemas.js';
3
3
  import { SearchSchema } from '../schemas/remnote-schemas.js';
4
4
  import { SearchByTagSchema } from '../schemas/remnote-schemas.js';
5
5
  import { ReadNoteSchema } from '../schemas/remnote-schemas.js';
6
+ import { GetMediaSchema } from '../schemas/remnote-schemas.js';
6
7
  import { UpdateNoteSchema } from '../schemas/remnote-schemas.js';
8
+ import { SetDocumentStatusSchema } from '../schemas/remnote-schemas.js';
9
+ import { ListChildrenSchema } from '../schemas/remnote-schemas.js';
10
+ import { MoveNoteSchema } from '../schemas/remnote-schemas.js';
7
11
  import { InsertChildrenSchema } from '../schemas/remnote-schemas.js';
8
12
  import { ReplaceChildrenSchema } from '../schemas/remnote-schemas.js';
9
13
  import { UpdateTagsSchema } from '../schemas/remnote-schemas.js';
14
+ import { SetPropertySchema } from '../schemas/remnote-schemas.js';
10
15
  import { AppendJournalSchema } from '../schemas/remnote-schemas.js';
11
16
  import { ReadTableSchema } from '../schemas/remnote-schemas.js';
12
17
  import { checkVersionCompatibility } from '../version-compat.js';
18
+ import { parseMediaLocator, resolveManagedImage } from '../media.js';
19
+ const MEDIA_CAPABILITY = 'media.images.v1';
13
20
  const NAVIGATION_PRESET = {
14
- includeContent: 'structured',
21
+ contentMode: 'structured',
22
+ view: 'compact',
15
23
  depth: 1,
16
24
  childLimit: 500,
17
25
  };
26
+ const ANCESTOR_SCHEMA = {
27
+ type: 'object',
28
+ properties: {
29
+ remId: { type: 'string', description: 'Ancestor Rem ID' },
30
+ title: { type: 'string', description: 'Rendered ancestor title' },
31
+ remType: {
32
+ type: 'string',
33
+ description: 'Ancestor Rem classification when available',
34
+ },
35
+ },
36
+ required: ['remId', 'title'],
37
+ additionalProperties: false,
38
+ };
18
39
  const TAG_INFO_SCHEMA = {
19
40
  type: 'object',
20
41
  properties: {
@@ -24,19 +45,70 @@ const TAG_INFO_SCHEMA = {
24
45
  required: ['tagRemId', 'name'],
25
46
  additionalProperties: false,
26
47
  };
48
+ const INLINE_REF_SCHEMA = {
49
+ type: 'object',
50
+ properties: {
51
+ text: { type: 'string', description: 'Rendered target text used in output' },
52
+ targetRemId: { type: 'string', description: 'Exact target Rem ID' },
53
+ kind: { type: 'string', enum: ['rem'], description: 'Inline reference kind' },
54
+ },
55
+ required: ['text', 'targetRemId', 'kind'],
56
+ additionalProperties: false,
57
+ };
58
+ const MATCHED_REM_SCHEMA = {
59
+ type: 'object',
60
+ properties: {
61
+ remId: { type: 'string', description: 'Directly tagged Rem ID' },
62
+ title: { type: 'string', description: 'Rendered title with markdown formatting' },
63
+ headline: { type: 'string', description: 'Display-oriented full line' },
64
+ inlineRefs: {
65
+ type: 'array',
66
+ items: INLINE_REF_SCHEMA,
67
+ description: 'Resolvable inline Rem references from the matched Rem title/headline',
68
+ },
69
+ remType: {
70
+ type: 'string',
71
+ description: 'Rem classification: document, dailyDocument, concept, descriptor, portal, or text',
72
+ },
73
+ parentRemId: {
74
+ type: 'string',
75
+ description: 'Direct parent Rem ID (omitted for top-level Rems)',
76
+ },
77
+ parentTitle: {
78
+ type: 'string',
79
+ description: 'Direct parent title/front text (omitted for top-level Rems)',
80
+ },
81
+ ancestors: {
82
+ type: 'array',
83
+ items: ANCESTOR_SCHEMA,
84
+ description: 'Parent-first ancestor chain when ancestorDepth is greater than zero',
85
+ },
86
+ ancestorsTruncated: {
87
+ type: 'boolean',
88
+ description: 'Whether the ancestor chain was capped by ancestorDepth',
89
+ },
90
+ tags: {
91
+ type: 'array',
92
+ items: TAG_INFO_SCHEMA,
93
+ description: 'Direct tags applied to the matched Rem as exact tag Rem IDs plus names',
94
+ },
95
+ },
96
+ required: ['remId', 'title', 'headline', 'remType'],
97
+ additionalProperties: false,
98
+ };
27
99
  export const CREATE_NOTE_TOOL = {
28
100
  name: 'remnote_create_note',
29
- description: 'Create a new note in RemNote with optional content, parent, and exact tag Rem IDs. Supports hierarchical markdown in content and flashcard syntax (e.g. "- Term :: Definition"). At least one of title or content must be provided. Recommended preflight once per session: remnote_status.',
101
+ description: 'Create a new note in RemNote with optional content, parent, and exact tag Rem IDs. Supports hierarchical markdown, flashcard syntax (e.g. "- Term :: Definition"), and exact inline Rem references as [[id:<remId>]]. At least one of title or content must be provided. Recommended preflight once per session: remnote_status.',
30
102
  inputSchema: {
31
103
  type: 'object',
32
104
  properties: {
33
105
  title: {
34
106
  type: 'string',
35
- description: 'The title of the note (optional if content is provided)',
107
+ description: 'The title of the note (optional if content is provided). Supports exact Rem references as [[id:<remId>]].',
36
108
  },
37
109
  content: {
38
110
  type: 'string',
39
- description: 'Content as plain text, child bullets or hierarchical markdown',
111
+ description: 'Content as plain text, child bullets, or hierarchical markdown. Use [[id:<remId>]] for exact Rem references.',
40
112
  },
41
113
  parentId: { type: 'string', description: 'Parent Rem ID' },
42
114
  tagRemIds: {
@@ -44,6 +116,10 @@ export const CREATE_NOTE_TOOL = {
44
116
  items: { type: 'string' },
45
117
  description: 'Exact tag Rem IDs to apply',
46
118
  },
119
+ asDocument: {
120
+ type: 'boolean',
121
+ description: 'Mark the created title/root Rem as a document while preserving any concept/card status',
122
+ },
47
123
  },
48
124
  required: [],
49
125
  additionalProperties: false,
@@ -67,17 +143,35 @@ export const CREATE_NOTE_TOOL = {
67
143
  };
68
144
  export const SEARCH_TOOL = {
69
145
  name: 'remnote_search',
70
- description: 'Search the RemNote knowledge base. For whole-KB orientation, prefer includeContent="structured" with depth=1 and childLimit=500, then follow remIds with remnote_read_note. Use includeContent="markdown" for human-readable summaries.',
146
+ description: 'Search the RemNote knowledge base. Supports cursor paging through hasMore/nextCursor. For whole-KB orientation, prefer contentMode="structured" with view="compact", depth=1, and childLimit=500. Request ancestorDepth when hierarchy context matters.',
71
147
  inputSchema: {
72
148
  type: 'object',
73
149
  properties: {
74
150
  query: { type: 'string', description: 'Search query text' },
151
+ parentRemId: {
152
+ type: 'string',
153
+ minLength: 1,
154
+ description: "Optional non-empty Rem ID. Scope the search to within this Rem's subtree. The Rem itself is excluded from results.",
155
+ },
75
156
  limit: { type: 'number', description: 'Maximum results (1-150, default: 50)' },
76
- includeContent: {
157
+ cursor: {
158
+ type: 'string',
159
+ description: 'Opaque cursor returned by a previous remnote_search response',
160
+ },
161
+ contentMode: {
77
162
  type: 'string',
78
163
  enum: ['none', 'markdown', 'structured'],
79
164
  description: 'Content rendering mode: "none" omits content (default), "markdown" renders child subtree as indented markdown, "structured" returns nested child objects with remIds',
80
165
  },
166
+ view: {
167
+ type: 'string',
168
+ enum: ['compact', 'standard', 'full'],
169
+ description: 'Output detail level: compact, standard, or full',
170
+ },
171
+ ancestorDepth: {
172
+ type: 'number',
173
+ description: 'Number of parent Rems to include, direct parent first (0-20, default: 0)',
174
+ },
81
175
  depth: {
82
176
  type: 'number',
83
177
  description: 'Depth of child hierarchy to render for markdown/structured content (0-10, default: 1)',
@@ -108,6 +202,11 @@ export const SEARCH_TOOL = {
108
202
  type: 'string',
109
203
  description: 'Display-oriented full line: title + type-aware delimiter + detail (e.g. "Term :: Definition")',
110
204
  },
205
+ inlineRefs: {
206
+ type: 'array',
207
+ items: INLINE_REF_SCHEMA,
208
+ description: 'Resolvable inline Rem references from the rendered title/headline. Markdown text preserves these as [[Target Title]].',
209
+ },
111
210
  parentRemId: {
112
211
  type: 'string',
113
212
  description: 'Direct parent Rem ID (omitted for top-level Rems)',
@@ -116,6 +215,15 @@ export const SEARCH_TOOL = {
116
215
  type: 'string',
117
216
  description: 'Direct parent title/front text (omitted for top-level Rems)',
118
217
  },
218
+ ancestors: {
219
+ type: 'array',
220
+ items: ANCESTOR_SCHEMA,
221
+ description: 'Parent-first ancestor chain when ancestorDepth is greater than zero',
222
+ },
223
+ ancestorsTruncated: {
224
+ type: 'boolean',
225
+ description: 'Whether the ancestor chain was capped by ancestorDepth',
226
+ },
119
227
  aliases: {
120
228
  type: 'array',
121
229
  items: { type: 'string' },
@@ -130,17 +238,35 @@ export const SEARCH_TOOL = {
130
238
  type: 'string',
131
239
  description: 'Rem classification: document, dailyDocument, concept, descriptor, portal, or text',
132
240
  },
241
+ matchedRems: {
242
+ type: 'array',
243
+ items: MATCHED_REM_SCHEMA,
244
+ description: 'For remnote_search_by_tag context mode, direct Rems carrying the requested tag that produced this context result',
245
+ },
246
+ contextRemId: {
247
+ type: 'string',
248
+ description: 'For remnote_search_by_tag tagged mode, resolved ancestor/context Rem ID for this direct match',
249
+ },
250
+ contextTitle: {
251
+ type: 'string',
252
+ description: 'For remnote_search_by_tag tagged mode, resolved ancestor/context title for this direct match',
253
+ },
254
+ contextReason: {
255
+ type: 'string',
256
+ enum: ['ancestor-document', 'ancestor-concept', 'ancestor-context', 'self'],
257
+ description: 'For remnote_search_by_tag tagged mode, why the context Rem was selected',
258
+ },
133
259
  cardDirection: {
134
260
  type: 'string',
135
261
  description: 'Flashcard direction: forward, reverse, or bidirectional (omitted if not a flashcard)',
136
262
  },
137
263
  content: {
138
264
  type: 'string',
139
- description: 'Rendered markdown content of child subtree (only when includeContent="markdown")',
265
+ description: 'Rendered markdown content of child subtree (only when contentMode="markdown")',
140
266
  },
141
267
  contentStructured: {
142
268
  type: 'array',
143
- description: 'Structured child subtree with nested remIds and metadata (only when includeContent="structured")',
269
+ description: 'Structured child subtree with nested remIds and metadata (only when contentMode="structured")',
144
270
  items: {
145
271
  type: 'object',
146
272
  properties: {
@@ -150,6 +276,11 @@ export const SEARCH_TOOL = {
150
276
  type: 'string',
151
277
  description: 'Display-oriented child line with type-aware delimiter',
152
278
  },
279
+ inlineRefs: {
280
+ type: 'array',
281
+ items: INLINE_REF_SCHEMA,
282
+ description: 'Resolvable inline Rem references from the child title/headline',
283
+ },
153
284
  aliases: {
154
285
  type: 'array',
155
286
  items: { type: 'string' },
@@ -170,11 +301,11 @@ export const SEARCH_TOOL = {
170
301
  },
171
302
  children: {
172
303
  type: 'array',
173
- description: 'Nested child nodes (same shape recursively)',
304
+ description: 'Nested child nodes (same shape recursively; omitted for leaf nodes)',
174
305
  items: { type: 'object' },
175
306
  },
176
307
  },
177
- required: ['remId', 'title', 'headline', 'remType', 'children'],
308
+ required: ['remId', 'title', 'headline', 'remType'],
178
309
  },
179
310
  },
180
311
  contentProperties: {
@@ -198,12 +329,29 @@ export const SEARCH_TOOL = {
198
329
  },
199
330
  },
200
331
  },
332
+ hasMore: {
333
+ type: 'boolean',
334
+ description: 'Whether more results are available through nextCursor',
335
+ },
336
+ nextCursor: {
337
+ type: 'string',
338
+ description: 'Opaque cursor for the next page when hasMore is true',
339
+ },
340
+ truncated: {
341
+ type: 'boolean',
342
+ description: 'Whether search results may be incomplete because the bridge hit its snapshot cap',
343
+ },
344
+ truncationReason: {
345
+ type: 'string',
346
+ enum: ['cursor_snapshot_limit'],
347
+ description: 'Reason search results may be incomplete when truncated is true',
348
+ },
201
349
  },
202
350
  },
203
351
  };
204
352
  export const SEARCH_BY_TAG_TOOL = {
205
353
  name: 'remnote_search_by_tag',
206
- description: 'Find notes by exact tag Rem ID and return resolved ancestor context targets (nearest document/daily document when available, otherwise nearest non-document ancestor). Does not look up tags by name or alias. Supports the same includeContent modes as remnote_search.',
354
+ description: 'Find notes by exact tag Rem ID. Supports cursor paging through hasMore/nextCursor. Default resultMode="context" returns resolved ancestor context targets with matchedRems; resultMode="tagged" returns directly tagged Rems with context metadata. Request ancestorDepth to include parent-first ancestors on both context results and matchedRems.',
207
355
  inputSchema: {
208
356
  type: 'object',
209
357
  properties: {
@@ -211,12 +359,26 @@ export const SEARCH_BY_TAG_TOOL = {
211
359
  type: 'string',
212
360
  description: 'Exact tag Rem ID to search',
213
361
  },
362
+ resultMode: {
363
+ type: 'string',
364
+ enum: ['context', 'tagged'],
365
+ description: '"context" returns resolved ancestor context targets with matchedRems (default); "tagged" returns directly tagged Rems with context metadata',
366
+ },
214
367
  limit: { type: 'number', description: 'Maximum results (1-150, default: 50)' },
215
- includeContent: {
368
+ contentMode: {
216
369
  type: 'string',
217
370
  enum: ['none', 'markdown', 'structured'],
218
371
  description: 'Content rendering mode: "none" omits content (default), "markdown" renders child subtree as indented markdown, "structured" returns nested child objects with remIds',
219
372
  },
373
+ view: {
374
+ type: 'string',
375
+ enum: ['compact', 'standard', 'full'],
376
+ description: 'Output detail level: compact, standard, or full',
377
+ },
378
+ ancestorDepth: {
379
+ type: 'number',
380
+ description: 'Number of parent Rems to include, direct parent first (0-20, default: 0)',
381
+ },
220
382
  depth: {
221
383
  type: 'number',
222
384
  description: 'Depth of child hierarchy to render for markdown/structured content (0-10, default: 1)',
@@ -229,14 +391,31 @@ export const SEARCH_BY_TAG_TOOL = {
229
391
  type: 'number',
230
392
  description: 'Maximum character length for rendered content (default: 3000)',
231
393
  },
394
+ cursor: {
395
+ type: 'string',
396
+ description: 'Opaque cursor returned by a previous remnote_search_by_tag response',
397
+ },
398
+ timeoutMs: {
399
+ type: 'number',
400
+ description: 'Per-call bridge wait timeout in milliseconds (1-60000, default: 15000). Does not cancel plugin-side work.',
401
+ },
232
402
  },
233
403
  required: ['tagRemId'],
234
404
  },
235
- outputSchema: SEARCH_TOOL.outputSchema,
405
+ outputSchema: {
406
+ type: 'object',
407
+ properties: {
408
+ results: SEARCH_TOOL.outputSchema.properties.results,
409
+ hasMore: SEARCH_TOOL.outputSchema.properties.hasMore,
410
+ nextCursor: SEARCH_TOOL.outputSchema.properties.nextCursor,
411
+ truncated: SEARCH_TOOL.outputSchema.properties.truncated,
412
+ truncationReason: SEARCH_TOOL.outputSchema.properties.truncationReason,
413
+ },
414
+ },
236
415
  };
237
416
  export const READ_NOTE_TOOL = {
238
417
  name: 'remnote_read_note',
239
- description: 'Read a specific note from RemNote by its Rem ID. For hierarchy traversal, prefer includeContent="structured" and start shallow (depth=1, childLimit=500), then deepen only selected branches. Use includeContent="markdown" for summarization.',
418
+ description: 'Read a specific note from RemNote by its Rem ID. For hierarchy traversal, prefer contentMode="structured" and start shallow (depth=1, childLimit=500), then deepen only selected branches. Request ancestorDepth when placement context matters.',
240
419
  inputSchema: {
241
420
  type: 'object',
242
421
  properties: {
@@ -245,11 +424,20 @@ export const READ_NOTE_TOOL = {
245
424
  type: 'number',
246
425
  description: 'Depth of child hierarchy to render (0-10, default: 5)',
247
426
  },
248
- includeContent: {
427
+ contentMode: {
249
428
  type: 'string',
250
429
  enum: ['none', 'markdown', 'structured'],
251
430
  description: 'Content rendering mode: "markdown" renders child subtree (default), "structured" returns nested child objects with remIds, "none" omits content',
252
431
  },
432
+ view: {
433
+ type: 'string',
434
+ enum: ['compact', 'standard', 'full'],
435
+ description: 'Output detail level: compact, standard, or full',
436
+ },
437
+ ancestorDepth: {
438
+ type: 'number',
439
+ description: 'Number of parent Rems to include, direct parent first (0-20, default: 0)',
440
+ },
253
441
  childLimit: {
254
442
  type: 'number',
255
443
  description: 'Maximum children per level (1-500, default: 100)',
@@ -258,6 +446,10 @@ export const READ_NOTE_TOOL = {
258
446
  type: 'number',
259
447
  description: 'Maximum character length for rendered content (default: 100000)',
260
448
  },
449
+ includeMediaMetadata: {
450
+ type: 'boolean',
451
+ description: 'Include ordered image metadata from the root Rem text and backText fields (default: false)',
452
+ },
261
453
  },
262
454
  required: ['remId'],
263
455
  },
@@ -270,6 +462,11 @@ export const READ_NOTE_TOOL = {
270
462
  type: 'string',
271
463
  description: 'Display-oriented full line: title + type-aware delimiter + detail (e.g. "Term :: Definition")',
272
464
  },
465
+ inlineRefs: {
466
+ type: 'array',
467
+ items: INLINE_REF_SCHEMA,
468
+ description: 'Resolvable inline Rem references from the rendered title/headline. Markdown text preserves these as [[Target Title]].',
469
+ },
273
470
  parentRemId: {
274
471
  type: 'string',
275
472
  description: 'Direct parent Rem ID (omitted for top-level Rems)',
@@ -278,6 +475,15 @@ export const READ_NOTE_TOOL = {
278
475
  type: 'string',
279
476
  description: 'Direct parent title/front text (omitted for top-level Rems)',
280
477
  },
478
+ ancestors: {
479
+ type: 'array',
480
+ items: ANCESTOR_SCHEMA,
481
+ description: 'Parent-first ancestor chain when ancestorDepth is greater than zero',
482
+ },
483
+ ancestorsTruncated: {
484
+ type: 'boolean',
485
+ description: 'Whether the ancestor chain was capped by ancestorDepth',
486
+ },
281
487
  aliases: {
282
488
  type: 'array',
283
489
  items: { type: 'string' },
@@ -298,11 +504,11 @@ export const READ_NOTE_TOOL = {
298
504
  },
299
505
  content: {
300
506
  type: 'string',
301
- description: 'Rendered markdown content of child subtree (when includeContent="markdown", which is the default)',
507
+ description: 'Rendered markdown content of child subtree (when contentMode="markdown", which is the default)',
302
508
  },
303
509
  contentStructured: {
304
510
  type: 'array',
305
- description: 'Structured child subtree with nested remIds and metadata (only when includeContent="structured")',
511
+ description: 'Structured child subtree with nested remIds and metadata (only when contentMode="structured")',
306
512
  items: {
307
513
  type: 'object',
308
514
  properties: {
@@ -312,6 +518,11 @@ export const READ_NOTE_TOOL = {
312
518
  type: 'string',
313
519
  description: 'Display-oriented child line with type-aware delimiter',
314
520
  },
521
+ inlineRefs: {
522
+ type: 'array',
523
+ items: INLINE_REF_SCHEMA,
524
+ description: 'Resolvable inline Rem references from the child title/headline',
525
+ },
315
526
  aliases: {
316
527
  type: 'array',
317
528
  items: { type: 'string' },
@@ -332,11 +543,11 @@ export const READ_NOTE_TOOL = {
332
543
  },
333
544
  children: {
334
545
  type: 'array',
335
- description: 'Nested child nodes (same shape recursively)',
546
+ description: 'Nested child nodes (same shape recursively; omitted for leaf nodes)',
336
547
  items: { type: 'object' },
337
548
  },
338
549
  },
339
- required: ['remId', 'title', 'headline', 'remType', 'children'],
550
+ required: ['remId', 'title', 'headline', 'remType'],
340
551
  },
341
552
  },
342
553
  contentProperties: {
@@ -357,17 +568,193 @@ export const READ_NOTE_TOOL = {
357
568
  },
358
569
  },
359
570
  },
571
+ media: {
572
+ type: 'array',
573
+ description: 'Ordered root-Rem image metadata from text followed by backText when includeMediaMetadata is true',
574
+ items: {
575
+ type: 'object',
576
+ properties: {
577
+ mediaId: { type: 'string' },
578
+ kind: { type: 'string', enum: ['image'] },
579
+ field: { type: 'string', enum: ['text', 'backText'] },
580
+ elementIndex: { type: 'number' },
581
+ imageIndex: { type: 'number' },
582
+ imgId: { type: 'string' },
583
+ title: { type: 'string' },
584
+ dimensions: {
585
+ type: 'object',
586
+ properties: { width: { type: 'number' }, height: { type: 'number' } },
587
+ required: ['width', 'height'],
588
+ },
589
+ mimeType: { type: 'string' },
590
+ source: { type: 'string', enum: ['remnote_managed_local'] },
591
+ },
592
+ required: ['mediaId', 'kind', 'field', 'elementIndex', 'imageIndex'],
593
+ },
594
+ },
360
595
  },
361
596
  },
362
597
  };
598
+ export const GET_MEDIA_TOOL = {
599
+ name: 'remnote_get_media',
600
+ description: 'Retrieve a RemNote-managed local image as MCP-native image content. First call remnote_read_note with includeMediaMetadata=true, then pass the returned remId, field, and mediaId. Requires bridge capability media.images.v1. External URL retrieval is not supported.',
601
+ inputSchema: {
602
+ type: 'object',
603
+ properties: {
604
+ remId: { type: 'string', description: 'Root Rem ID containing the image' },
605
+ field: {
606
+ type: 'string',
607
+ enum: ['text', 'backText'],
608
+ description: 'Rich-text field containing the image',
609
+ },
610
+ mediaId: {
611
+ type: 'string',
612
+ description: 'Stable media ID returned by remnote_read_note',
613
+ },
614
+ maxInlineBytes: {
615
+ type: 'number',
616
+ description: 'Maximum bytes to inline (default 5 MiB, hard maximum 10 MiB)',
617
+ },
618
+ },
619
+ required: ['remId', 'field', 'mediaId'],
620
+ additionalProperties: false,
621
+ },
622
+ outputSchema: {
623
+ type: 'object',
624
+ properties: {
625
+ remId: { type: 'string' },
626
+ mediaId: { type: 'string' },
627
+ kind: { type: 'string', enum: ['image'] },
628
+ field: { type: 'string', enum: ['text', 'backText'] },
629
+ elementIndex: { type: 'number' },
630
+ imageIndex: { type: 'number' },
631
+ imgId: { type: 'string' },
632
+ title: { type: 'string' },
633
+ dimensions: {
634
+ type: 'object',
635
+ properties: { width: { type: 'number' }, height: { type: 'number' } },
636
+ required: ['width', 'height'],
637
+ },
638
+ mimeType: { type: 'string' },
639
+ source: { type: 'string', enum: ['remnote_managed_local'] },
640
+ sizeBytes: { type: 'number' },
641
+ },
642
+ required: [
643
+ 'remId',
644
+ 'mediaId',
645
+ 'kind',
646
+ 'field',
647
+ 'elementIndex',
648
+ 'imageIndex',
649
+ 'mimeType',
650
+ 'source',
651
+ 'sizeBytes',
652
+ ],
653
+ },
654
+ };
655
+ export const LIST_CHILDREN_TOOL = {
656
+ name: 'remnote_list_children',
657
+ description: 'List direct child Rems under a parent without rendering a subtree. Use for cheap hierarchy traversal; page with nextCursor when hasMore is true.',
658
+ inputSchema: {
659
+ type: 'object',
660
+ properties: {
661
+ parentRemId: { type: 'string', description: 'Parent Rem ID whose direct children to list' },
662
+ limit: { type: 'number', description: 'Maximum direct children (1-150, default: 50)' },
663
+ cursor: {
664
+ type: 'string',
665
+ description: 'Opaque cursor returned by a previous remnote_list_children response',
666
+ },
667
+ view: {
668
+ type: 'string',
669
+ enum: ['compact', 'standard', 'full'],
670
+ description: 'Output detail level for child metadata: compact, standard, or full',
671
+ },
672
+ ancestorDepth: {
673
+ type: 'number',
674
+ description: 'Number of parent Rems to include for each child, direct parent first',
675
+ },
676
+ },
677
+ required: ['parentRemId'],
678
+ },
679
+ outputSchema: {
680
+ type: 'object',
681
+ properties: {
682
+ children: {
683
+ type: 'array',
684
+ items: SEARCH_TOOL.outputSchema.properties.results.items,
685
+ },
686
+ hasMore: SEARCH_TOOL.outputSchema.properties.hasMore,
687
+ nextCursor: SEARCH_TOOL.outputSchema.properties.nextCursor,
688
+ totalChildren: {
689
+ type: 'number',
690
+ description: 'Total direct children observed when listing this parent',
691
+ },
692
+ },
693
+ },
694
+ };
695
+ export const MOVE_NOTE_TOOL = {
696
+ name: 'remnote_move_note',
697
+ description: 'Safely move an existing Rem and its subtree under a new parent. Defaults to dryRun=true; pass dryRun=false only after user approval. expectedOldParentRemId rejects stale move proposals.',
698
+ inputSchema: {
699
+ type: 'object',
700
+ properties: {
701
+ remId: { type: 'string', description: 'Rem ID to move' },
702
+ newParentRemId: { type: 'string', description: 'New parent Rem ID' },
703
+ position: {
704
+ type: 'string',
705
+ enum: ['first', 'last', 'before', 'after'],
706
+ description: 'Where to place the moved Rem under the new parent (default: last)',
707
+ },
708
+ siblingRemId: {
709
+ type: 'string',
710
+ description: 'Sibling Rem ID required for before/after positioning',
711
+ },
712
+ dryRun: {
713
+ type: 'boolean',
714
+ description: 'Preview without mutating RemNote (default: true)',
715
+ },
716
+ expectedOldParentRemId: {
717
+ type: 'string',
718
+ description: 'Reject if the current direct parent differs from this Rem ID',
719
+ },
720
+ ancestorDepth: {
721
+ type: 'number',
722
+ description: 'Number of parent Rems to include before/after the move',
723
+ },
724
+ },
725
+ required: ['remId', 'newParentRemId'],
726
+ },
727
+ outputSchema: {
728
+ type: 'object',
729
+ properties: {
730
+ remId: { type: 'string' },
731
+ title: { type: 'string' },
732
+ dryRun: { type: 'boolean' },
733
+ oldParentRemId: { type: 'string' },
734
+ oldParentTitle: { type: 'string' },
735
+ newParentRemId: { type: 'string' },
736
+ newParentTitle: { type: 'string' },
737
+ position: { type: 'string', enum: ['first', 'last', 'before', 'after'] },
738
+ siblingRemId: { type: 'string' },
739
+ ancestorsBefore: { type: 'array', items: ANCESTOR_SCHEMA },
740
+ ancestorsBeforeTruncated: { type: 'boolean' },
741
+ ancestorsAfter: { type: 'array', items: ANCESTOR_SCHEMA },
742
+ ancestorsAfterTruncated: { type: 'boolean' },
743
+ },
744
+ required: ['remId', 'title', 'dryRun', 'newParentRemId', 'newParentTitle', 'position'],
745
+ },
746
+ };
363
747
  export const UPDATE_NOTE_TOOL = {
364
748
  name: 'remnote_update_note',
365
- description: 'Update note metadata in RemNote. Use this tool for title changes only.',
749
+ description: 'Update note metadata in RemNote. Use this tool for title changes only. The title supports exact Rem references as [[id:<remId>]].',
366
750
  inputSchema: {
367
751
  type: 'object',
368
752
  properties: {
369
753
  remId: { type: 'string', description: 'The Rem ID to update' },
370
- title: { type: 'string', description: 'New title' },
754
+ title: {
755
+ type: 'string',
756
+ description: 'New title. Use [[id:<remId>]] for exact Rem references.',
757
+ },
371
758
  },
372
759
  required: ['remId', 'title'],
373
760
  additionalProperties: false,
@@ -389,6 +776,100 @@ export const UPDATE_NOTE_TOOL = {
389
776
  required: ['remIds', 'titles'],
390
777
  },
391
778
  };
779
+ export const SET_DOCUMENT_STATUS_TOOL = {
780
+ name: 'remnote_set_document_status',
781
+ description: 'Preview or set whether an existing Rem is marked as a document. Uses dryRun=true by default, preserves Rem ID, children, parent, tags, and concept/card status, and requires write operations to be enabled.',
782
+ inputSchema: {
783
+ type: 'object',
784
+ properties: {
785
+ remId: { type: 'string', description: 'The Rem ID whose document status should change' },
786
+ isDocument: {
787
+ type: 'boolean',
788
+ description: 'Whether the Rem should be marked as a document',
789
+ },
790
+ dryRun: {
791
+ type: 'boolean',
792
+ description: 'Preview the document-status change without mutating RemNote (default: true)',
793
+ },
794
+ expectedOldRemType: {
795
+ type: 'string',
796
+ enum: ['document', 'dailyDocument', 'concept', 'descriptor', 'portal', 'text'],
797
+ description: 'Optional stale-context guard; reject if current remType differs from this value',
798
+ },
799
+ },
800
+ required: ['remId', 'isDocument'],
801
+ additionalProperties: false,
802
+ },
803
+ outputSchema: {
804
+ type: 'object',
805
+ properties: {
806
+ remId: { type: 'string', description: 'The Rem ID that was checked or changed' },
807
+ title: { type: 'string', description: 'Rendered title/front text' },
808
+ oldRemType: {
809
+ type: 'string',
810
+ description: 'Bridge remType before the requested document-status change',
811
+ },
812
+ newRemType: {
813
+ type: 'string',
814
+ description: 'Bridge remType after the change, or previewed remType for dry runs',
815
+ },
816
+ oldIsDocument: {
817
+ type: 'boolean',
818
+ description: 'Document status before the requested change',
819
+ },
820
+ newIsDocument: {
821
+ type: 'boolean',
822
+ description: 'Document status after the change, or previewed status for dry runs',
823
+ },
824
+ requestedIsDocument: {
825
+ type: 'boolean',
826
+ description: 'Requested document status',
827
+ },
828
+ dryRun: {
829
+ type: 'boolean',
830
+ description: 'Whether the request was only a preview',
831
+ },
832
+ changed: {
833
+ type: 'boolean',
834
+ description: 'Whether RemNote was actually mutated by this call',
835
+ },
836
+ wouldChange: {
837
+ type: 'boolean',
838
+ description: 'Whether the requested status differs from the current status',
839
+ },
840
+ sdkSupportsDocumentStatus: {
841
+ type: 'boolean',
842
+ description: 'Whether the bridge runtime exposes a safe SDK document-status setter',
843
+ },
844
+ warnings: {
845
+ type: 'array',
846
+ items: { type: 'string' },
847
+ description: 'Important side-effect notes, especially around preserved card status',
848
+ },
849
+ cardDirectionBefore: {
850
+ type: 'string',
851
+ description: 'Card direction before the change when available',
852
+ },
853
+ cardDirectionAfter: {
854
+ type: 'string',
855
+ description: 'Card direction after the change when available',
856
+ },
857
+ },
858
+ required: [
859
+ 'remId',
860
+ 'title',
861
+ 'oldRemType',
862
+ 'newRemType',
863
+ 'oldIsDocument',
864
+ 'newIsDocument',
865
+ 'requestedIsDocument',
866
+ 'dryRun',
867
+ 'changed',
868
+ 'wouldChange',
869
+ 'sdkSupportsDocumentStatus',
870
+ ],
871
+ },
872
+ };
392
873
  export const INSERT_CHILDREN_TOOL = {
393
874
  name: 'remnote_insert_children',
394
875
  description: 'Insert new child Rems under a parent at a deterministic position without replacing existing children. Use this for tag description nodes and hierarchy maintenance.',
@@ -399,7 +880,10 @@ export const INSERT_CHILDREN_TOOL = {
399
880
  type: 'string',
400
881
  description: 'Parent Rem ID that will receive the new children',
401
882
  },
402
- content: { type: 'string', description: 'Markdown content to insert as child Rems' },
883
+ content: {
884
+ type: 'string',
885
+ description: 'Markdown content to insert as child Rems. Use [[id:<remId>]] for exact Rem references.',
886
+ },
403
887
  position: {
404
888
  type: 'string',
405
889
  enum: ['first', 'last', 'before', 'after'],
@@ -427,7 +911,7 @@ export const REPLACE_CHILDREN_TOOL = {
427
911
  },
428
912
  content: {
429
913
  type: 'string',
430
- description: 'Markdown content to use as replacement children; empty string clears all direct children',
914
+ description: 'Markdown content to use as replacement children; empty string clears all direct children. Use [[id:<remId>]] for exact Rem references.',
431
915
  },
432
916
  },
433
917
  required: ['parentRemId', 'content'],
@@ -460,6 +944,77 @@ export const UPDATE_TAGS_TOOL = {
460
944
  },
461
945
  outputSchema: UPDATE_NOTE_TOOL.outputSchema,
462
946
  };
947
+ export const SET_PROPERTY_TOOL = {
948
+ name: 'remnote_set_property',
949
+ description: 'Set or clear a tag/table property value on a Rem by exact IDs. The bridge verifies the property belongs to the supplied tag/table Rem, adds the tag idempotently, then writes the property value. For select properties, pass the option Rem ID as value.kind="rem_reference".',
950
+ inputSchema: {
951
+ type: 'object',
952
+ properties: {
953
+ remId: { type: 'string', description: 'The Rem ID whose tag property should be set' },
954
+ tagRemId: {
955
+ type: 'string',
956
+ description: 'Exact tag/table Rem ID that owns the property',
957
+ },
958
+ propertyRemId: {
959
+ type: 'string',
960
+ description: 'Exact property Rem ID under the tag/table Rem',
961
+ },
962
+ value: {
963
+ oneOf: [
964
+ {
965
+ type: 'object',
966
+ properties: {
967
+ kind: { type: 'string', const: 'text' },
968
+ text: {
969
+ type: 'string',
970
+ description: 'Plain text or markdown property value. Use [[id:<remId>]] for exact Rem references.',
971
+ },
972
+ },
973
+ required: ['kind', 'text'],
974
+ additionalProperties: false,
975
+ },
976
+ {
977
+ type: 'object',
978
+ properties: {
979
+ kind: { type: 'string', const: 'rem_reference' },
980
+ remId: {
981
+ type: 'string',
982
+ description: 'Referenced Rem ID; use select-option Rem IDs here too',
983
+ },
984
+ },
985
+ required: ['kind', 'remId'],
986
+ additionalProperties: false,
987
+ },
988
+ {
989
+ type: 'object',
990
+ properties: {
991
+ kind: { type: 'string', const: 'clear' },
992
+ },
993
+ required: ['kind'],
994
+ additionalProperties: false,
995
+ },
996
+ ],
997
+ description: 'Property value. Use text for plain values, rem_reference for Rem references and select options, or clear to remove the value.',
998
+ },
999
+ },
1000
+ required: ['remId', 'tagRemId', 'propertyRemId', 'value'],
1001
+ additionalProperties: false,
1002
+ },
1003
+ outputSchema: {
1004
+ type: 'object',
1005
+ properties: {
1006
+ remId: { type: 'string', description: 'The Rem ID whose property was set' },
1007
+ tagRemId: { type: 'string', description: 'Exact tag/table Rem ID' },
1008
+ propertyRemId: { type: 'string', description: 'Exact property Rem ID' },
1009
+ valueKind: {
1010
+ type: 'string',
1011
+ enum: ['text', 'rem_reference', 'clear'],
1012
+ description: 'The value payload kind applied by the bridge',
1013
+ },
1014
+ },
1015
+ required: ['remId', 'tagRemId', 'propertyRemId', 'valueKind'],
1016
+ },
1017
+ };
463
1018
  export const APPEND_JOURNAL_TOOL = {
464
1019
  name: 'remnote_append_journal',
465
1020
  description: "Append content to today's daily document in RemNote with optional exact tag Rem IDs. Recommended preflight once per session: remnote_status.",
@@ -468,7 +1023,7 @@ export const APPEND_JOURNAL_TOOL = {
468
1023
  properties: {
469
1024
  content: {
470
1025
  type: 'string',
471
- description: "Content to append to today's daily document (markdown supported)",
1026
+ description: "Content to append to today's daily document (markdown supported). Use [[id:<remId>]] for exact Rem references.",
472
1027
  },
473
1028
  timestamp: { type: 'boolean', description: 'Include timestamp (default: true)' },
474
1029
  tagRemIds: {
@@ -510,6 +1065,11 @@ export const STATUS_TOOL = {
510
1065
  connected: { type: 'boolean', description: 'Whether bridge plugin is currently connected' },
511
1066
  serverVersion: { type: 'string', description: 'MCP server version' },
512
1067
  pluginVersion: { type: 'string', description: 'Connected bridge plugin version' },
1068
+ capabilities: {
1069
+ type: 'array',
1070
+ items: { type: 'string' },
1071
+ description: 'Bridge protocol capabilities advertised during handshake',
1072
+ },
513
1073
  version_warning: {
514
1074
  type: 'string',
515
1075
  description: 'Compatibility warning when server/bridge versions differ',
@@ -634,7 +1194,8 @@ export const PLAYBOOK_TOOL = {
634
1194
  orientation: {
635
1195
  type: 'object',
636
1196
  properties: {
637
- includeContent: { type: 'string' },
1197
+ contentMode: { type: 'string' },
1198
+ view: { type: 'string' },
638
1199
  depth: { type: 'number' },
639
1200
  childLimit: { type: 'number' },
640
1201
  },
@@ -670,21 +1231,27 @@ export const ALL_TOOLS = [
670
1231
  SEARCH_TOOL,
671
1232
  SEARCH_BY_TAG_TOOL,
672
1233
  READ_NOTE_TOOL,
1234
+ GET_MEDIA_TOOL,
1235
+ LIST_CHILDREN_TOOL,
673
1236
  UPDATE_NOTE_TOOL,
1237
+ SET_DOCUMENT_STATUS_TOOL,
1238
+ MOVE_NOTE_TOOL,
674
1239
  INSERT_CHILDREN_TOOL,
675
1240
  REPLACE_CHILDREN_TOOL,
676
1241
  UPDATE_TAGS_TOOL,
1242
+ SET_PROPERTY_TOOL,
677
1243
  APPEND_JOURNAL_TOOL,
678
1244
  PLAYBOOK_TOOL,
679
1245
  STATUS_TOOL,
680
1246
  READ_TABLE_TOOL,
681
1247
  ];
682
- export function registerAllTools(server, wsServer, logger) {
1248
+ export function registerAllTools(server, wsServer, logger, mediaRoots = []) {
683
1249
  const toolLogger = logger.child({ context: 'tools' });
684
1250
  async function buildStatusResult() {
685
1251
  const connected = wsServer.isConnected();
686
1252
  const serverVersion = wsServer.getServerVersion();
687
1253
  const bridgeVersion = wsServer.getBridgeVersion();
1254
+ const bridgeCapabilities = wsServer.getBridgeCapabilities?.() ?? [];
688
1255
  if (!connected) {
689
1256
  return { connected: false, serverVersion, message: 'RemNote plugin not connected' };
690
1257
  }
@@ -701,6 +1268,7 @@ export function registerAllTools(server, wsServer, logger) {
701
1268
  connected: true,
702
1269
  serverVersion,
703
1270
  ...statusObj,
1271
+ capabilities: bridgeCapabilities,
704
1272
  ...(versionWarning ? { version_warning: versionWarning } : {}),
705
1273
  };
706
1274
  }
@@ -711,6 +1279,7 @@ export function registerAllTools(server, wsServer, logger) {
711
1279
  toolLogger.debug({ tool: toolName, args: request.params.arguments }, 'Executing tool');
712
1280
  try {
713
1281
  let result;
1282
+ let nativeToolResult;
714
1283
  switch (toolName) {
715
1284
  case 'remnote_create_note': {
716
1285
  const args = CreateNoteSchema.parse(request.params.arguments);
@@ -724,7 +1293,8 @@ export function registerAllTools(server, wsServer, logger) {
724
1293
  }
725
1294
  case 'remnote_search_by_tag': {
726
1295
  const args = SearchByTagSchema.parse(request.params.arguments);
727
- result = await wsServer.sendRequest('search_by_tag', args);
1296
+ const { timeoutMs, ...payload } = args;
1297
+ result = await wsServer.sendRequest('search_by_tag', payload, timeoutMs);
728
1298
  break;
729
1299
  }
730
1300
  case 'remnote_read_note': {
@@ -732,16 +1302,62 @@ export function registerAllTools(server, wsServer, logger) {
732
1302
  result = await wsServer.sendRequest('read_note', args);
733
1303
  break;
734
1304
  }
1305
+ case 'remnote_get_media': {
1306
+ const args = GetMediaSchema.parse(request.params.arguments);
1307
+ if (!wsServer.hasBridgeCapability(MEDIA_CAPABILITY)) {
1308
+ throw new Error(`Bridge capability/version mismatch: connected bridge does not advertise ${MEDIA_CAPABILITY}`);
1309
+ }
1310
+ const locator = parseMediaLocator(await wsServer.sendRequest('get_media_locator', {
1311
+ remId: args.remId,
1312
+ field: args.field,
1313
+ mediaId: args.mediaId,
1314
+ }));
1315
+ if (locator.remId !== args.remId ||
1316
+ locator.field !== args.field ||
1317
+ locator.mediaId !== args.mediaId) {
1318
+ throw new Error('Invalid media locator payload received from bridge: request identity mismatch');
1319
+ }
1320
+ const media = await resolveManagedImage(locator, mediaRoots, args.maxInlineBytes);
1321
+ const metadata = {
1322
+ ...media.metadata,
1323
+ mimeType: media.mimeType,
1324
+ sizeBytes: media.sizeBytes,
1325
+ };
1326
+ nativeToolResult = {
1327
+ structuredContent: metadata,
1328
+ content: [
1329
+ { type: 'image', data: media.data, mimeType: media.mimeType },
1330
+ { type: 'text', text: JSON.stringify(metadata) },
1331
+ ],
1332
+ };
1333
+ result = metadata;
1334
+ break;
1335
+ }
1336
+ case 'remnote_list_children': {
1337
+ const args = ListChildrenSchema.parse(request.params.arguments);
1338
+ result = await wsServer.sendRequest('list_children', args);
1339
+ break;
1340
+ }
735
1341
  case 'remnote_update_note': {
736
1342
  const args = UpdateNoteSchema.parse(request.params.arguments);
737
1343
  result = await wsServer.sendRequest('update_note', args);
738
1344
  break;
739
1345
  }
1346
+ case 'remnote_set_document_status': {
1347
+ const args = SetDocumentStatusSchema.parse(request.params.arguments);
1348
+ result = await wsServer.sendRequest('set_document_status', args);
1349
+ break;
1350
+ }
740
1351
  case 'remnote_insert_children': {
741
1352
  const args = InsertChildrenSchema.parse(request.params.arguments);
742
1353
  result = await wsServer.sendRequest('insert_children', args);
743
1354
  break;
744
1355
  }
1356
+ case 'remnote_move_note': {
1357
+ const args = MoveNoteSchema.parse(request.params.arguments);
1358
+ result = await wsServer.sendRequest('move_note', args);
1359
+ break;
1360
+ }
745
1361
  case 'remnote_replace_children': {
746
1362
  const args = ReplaceChildrenSchema.parse(request.params.arguments);
747
1363
  result = await wsServer.sendRequest('replace_children', args);
@@ -752,6 +1368,11 @@ export function registerAllTools(server, wsServer, logger) {
752
1368
  result = await wsServer.sendRequest('update_tags', args);
753
1369
  break;
754
1370
  }
1371
+ case 'remnote_set_property': {
1372
+ const args = SetPropertySchema.parse(request.params.arguments);
1373
+ result = await wsServer.sendRequest('set_property', args);
1374
+ break;
1375
+ }
755
1376
  case 'remnote_append_journal': {
756
1377
  const args = AppendJournalSchema.parse(request.params.arguments);
757
1378
  result = await wsServer.sendRequest('append_journal', args);
@@ -769,45 +1390,65 @@ export function registerAllTools(server, wsServer, logger) {
769
1390
  };
770
1391
  }
771
1392
  result = {
772
- playbookVersion: '1.1.0',
773
- summary: 'Use this playbook to navigate RemNote efficiently via remIds, and to apply write-safety checks before mutations.',
1393
+ playbookVersion: '1.8.0',
1394
+ summary: 'Use this playbook to check RemNote connection and write gates, navigate by remId with paged search/read/list workflows, retrieve managed images through capability-gated metadata, choose compact/full output views, and apply safe exact-ID writes including inline [[id:<remId>]] references, tag property values, and dry-run-first document status changes.',
774
1395
  recommendedStatusCheck: {
775
1396
  tool: 'remnote_status',
776
1397
  cadence: 'recommended once per session and before risky writes',
777
- rationale: 'status exposes connection health, version compatibility warnings, and write-policy gates',
1398
+ rationale: 'status exposes connection health, version compatibility warnings, write-policy gates, and bridge capabilities',
778
1399
  },
779
1400
  decisionTree: [
780
1401
  'Need connection/capability context? Call remnote_status first.',
781
- 'Need to orient across the KB? Use remnote_search with includeContent="structured", depth=1, childLimit=500.',
782
- 'Need tagged-note context? Use remnote_search_by_tag with tagRemId; it does not resolve tag names or aliases.',
783
- 'Need to traverse a specific branch? Use remnote_read_note on a chosen remId with includeContent="structured", depth=1, childLimit=500, then recurse by child remIds.',
1402
+ 'Need an embedded RemNote-managed image? Confirm media.images.v1, call remnote_read_note with includeMediaMetadata=true, then call remnote_get_media with the returned remId, field, and mediaId.',
1403
+ 'Need to orient across the KB? Use remnote_search with contentMode="structured", view="compact", depth=1, childLimit=500.',
1404
+ 'Need broad search enumeration? Continue remnote_search or remnote_search_by_tag with nextCursor while hasMore is true.',
1405
+ 'Need to search within a specific branch? Use remnote_search with parentRemId; keep the same parentRemId when continuing with nextCursor.',
1406
+ 'Need tagged-note context/navigation? Use remnote_search_by_tag with tagRemId and default resultMode="context"; inspect matchedRems to see the direct tagged Rems behind each context result.',
1407
+ 'Need hierarchy placement context? Add ancestorDepth, typically 5, to search/read/search_by_tag/list_children; ancestors are direct-parent first.',
1408
+ 'Need strict tag verification? Use remnote_search_by_tag with resultMode="tagged", or verify the exact Rem in matchedRems from context mode.',
1409
+ 'Need a large tag search to finish? Prefer cursor paging first; use remnote_search_by_tag.timeoutMs only as a bounded wait-time escape hatch.',
1410
+ 'Need to traverse a specific branch cheaply? Use remnote_list_children on the parentRemId and page through direct children.',
1411
+ 'Need to read a selected subtree? Use remnote_read_note on a chosen remId with contentMode="structured", depth=1, childLimit=500, then deepen selected branches.',
1412
+ 'Need to follow inline graph references? Inspect inlineRefs on search/read results and structured child nodes for exact target Rem IDs.',
784
1413
  'Need tabular/structured data from an Advanced Table? Use remnote_read_table with either tableTitle or tableRemId. Use propertyFilter to limit columns for large tables.',
785
- 'Need a human-readable summary? Switch to includeContent="markdown" on search/read results.',
786
- 'Need to rename a note? Use remnote_update_note with remId and title only.',
787
- 'Need to create a note? Use remnote_create_note; pass tagRemIds for exact-ID tag assignment.',
788
- 'Need to append to today journal? Use remnote_append_journal; pass tagRemIds when the journal entry should be tagged.',
789
- 'Need to insert children? Use remnote_insert_children with an explicit position.',
790
- 'Need to replace children? Check remnote_status first; remnote_replace_children requires acceptReplaceOperation=true.',
1414
+ 'Need a human-readable summary? Switch to contentMode="markdown" on search/read results.',
1415
+ 'Need to rename a note? Use remnote_update_note with remId and title only; use [[id:<remId>]] inside the title for exact inline Rem references.',
1416
+ 'Need to create a note? Use remnote_create_note; pass tagRemIds for exact-ID tag assignment, [[id:<remId>]] for exact inline Rem references, and asDocument=true when the title/root Rem should be a document.',
1417
+ 'Need to mark an existing Rem as a document? Use remnote_set_document_status dryRun first, include expectedOldRemType for stale-context protection, then rerun with dryRun=false after approval.',
1418
+ 'Need to append to today journal? Use remnote_append_journal; pass tagRemIds when the journal entry should be tagged and [[id:<remId>]] for exact inline Rem references.',
1419
+ 'Need to insert children? Use remnote_insert_children with an explicit position; use [[id:<remId>]] for exact inline Rem references.',
1420
+ 'Need to move a note? Use remnote_move_note dryRun first, include expectedOldParentRemId for stale-context protection, then rerun with dryRun=false after approval.',
1421
+ 'Need to replace children? Check remnote_status first; remnote_replace_children requires acceptReplaceOperation=true and supports [[id:<remId>]] for exact inline Rem references.',
791
1422
  'Need to update tags on an existing note? Use remnote_update_tags with exact tag Rem IDs.',
1423
+ 'Need to set a tag/table property value? Use remnote_set_property with exact remId, tagRemId, propertyRemId, and a text/rem_reference/clear value payload. For select properties, pass the option Rem ID as rem_reference.remId; for inline references in text values, use [[id:<remId>]].',
792
1424
  ],
793
1425
  navigationPresets: {
794
1426
  orientation: NAVIGATION_PRESET,
795
1427
  branchTraversal: NAVIGATION_PRESET,
796
1428
  },
797
1429
  contentModes: {
798
- structured: 'ID-first traversal mode. Returns contentStructured with child remIds for deterministic follow-up reads.',
799
- markdown: 'Summary mode. Returns rendered markdown content for human-facing synthesis, not deterministic child ID traversal.',
1430
+ structured: 'ID-first traversal mode. Returns contentStructured with child remIds and inlineRefs for deterministic follow-up reads.',
1431
+ markdown: 'Summary mode. Returns rendered markdown content for human-facing synthesis; inline Rem references render as [[Target Title]].',
800
1432
  none: 'Metadata-only mode when content is not needed.',
801
1433
  },
1434
+ outputViews: {
1435
+ compact: 'Small metadata surface for broad discovery. Omits tags, aliases, inlineRefs, and diagnostic fields unless another option requires them.',
1436
+ standard: 'Normal discovery metadata for most agent workflows.',
1437
+ full: 'Verbose metadata for diagnostics or detailed audits.',
1438
+ },
802
1439
  writePolicy: {
803
1440
  statusTool: 'remnote_status',
804
1441
  requiredFields: ['acceptWriteOperations', 'acceptReplaceOperation'],
805
1442
  guidance: [
806
1443
  'Create/update/insert/replace/tag/journal writes require acceptWriteOperations=true.',
807
1444
  'remnote_update_note is metadata-only; use insert_children, replace_children, and update_tags for structural or tag writes.',
1445
+ 'remnote_set_document_status changes only document status; it preserves concept/card status and defaults to dryRun=true.',
808
1446
  'remnote_replace_children requires acceptReplaceOperation=true.',
809
1447
  'remnote_insert_children preserves existing child Rem IDs; remnote_replace_children removes them.',
1448
+ 'remnote_move_note preserves the moved Rem ID and subtree; dryRun defaults to true.',
810
1449
  'All production tag writes use exact tag Rem IDs: create_note.tagRemIds, append_journal.tagRemIds, and update_tags add/remove arrays.',
1450
+ 'Markdown-capable write fields support [[id:<remId>]] to create real inline references to existing Rems without name lookup.',
1451
+ 'remnote_set_property writes exact-ID tag/table property values and requires acceptWriteOperations=true.',
811
1452
  ],
812
1453
  },
813
1454
  currentStatus,
@@ -830,6 +1471,9 @@ export function registerAllTools(server, wsServer, logger) {
830
1471
  tool: toolName,
831
1472
  duration_ms: Date.now() - startTime,
832
1473
  }, 'Tool completed');
1474
+ if (nativeToolResult) {
1475
+ return nativeToolResult;
1476
+ }
833
1477
  return {
834
1478
  structuredContent: result,
835
1479
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],