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,17 +3,19 @@ export const FALLBACK_TOOLS = [
3
3
  {
4
4
  name: 'remnote_create_note',
5
5
  description:
6
- '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.',
6
+ '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.',
7
7
  inputSchema: {
8
8
  type: 'object',
9
9
  properties: {
10
10
  title: {
11
11
  type: 'string',
12
- description: 'The title of the note (optional if content is provided)',
12
+ description:
13
+ 'The title of the note (optional if content is provided). Supports exact Rem references as [[id:<remId>]].',
13
14
  },
14
15
  content: {
15
16
  type: 'string',
16
- description: 'Content as plain text, child bullets or hierarchical markdown',
17
+ description:
18
+ 'Content as plain text, child bullets, or hierarchical markdown. Use [[id:<remId>]] for exact Rem references.',
17
19
  },
18
20
  parentId: {
19
21
  type: 'string',
@@ -26,6 +28,11 @@ export const FALLBACK_TOOLS = [
26
28
  },
27
29
  description: 'Exact tag Rem IDs to apply',
28
30
  },
31
+ asDocument: {
32
+ type: 'boolean',
33
+ description:
34
+ 'Mark the created title/root Rem as a document while preserving any concept/card status',
35
+ },
29
36
  },
30
37
  required: [],
31
38
  additionalProperties: false,
@@ -34,7 +41,7 @@ export const FALLBACK_TOOLS = [
34
41
  {
35
42
  name: 'remnote_search',
36
43
  description:
37
- '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.',
44
+ '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.',
38
45
  inputSchema: {
39
46
  type: 'object',
40
47
  properties: {
@@ -42,16 +49,35 @@ export const FALLBACK_TOOLS = [
42
49
  type: 'string',
43
50
  description: 'Search query text',
44
51
  },
52
+ parentRemId: {
53
+ type: 'string',
54
+ minLength: 1,
55
+ description:
56
+ "Optional non-empty Rem ID. Scope the search to within this Rem's subtree. The Rem itself is excluded from results.",
57
+ },
45
58
  limit: {
46
59
  type: 'number',
47
60
  description: 'Maximum results (1-150, default: 50)',
48
61
  },
49
- includeContent: {
62
+ cursor: {
63
+ type: 'string',
64
+ description: 'Opaque cursor returned by a previous remnote_search response',
65
+ },
66
+ contentMode: {
50
67
  type: 'string',
51
68
  enum: ['none', 'markdown', 'structured'],
52
69
  description:
53
70
  'Content rendering mode: "none" omits content (default), "markdown" renders child subtree as indented markdown, "structured" returns nested child objects with remIds',
54
71
  },
72
+ view: {
73
+ type: 'string',
74
+ enum: ['compact', 'standard', 'full'],
75
+ description: 'Output detail level: compact, standard, or full',
76
+ },
77
+ ancestorDepth: {
78
+ type: 'number',
79
+ description: 'Number of parent Rems to include, direct parent first (0-20, default: 0)',
80
+ },
55
81
  depth: {
56
82
  type: 'number',
57
83
  description:
@@ -72,7 +98,7 @@ export const FALLBACK_TOOLS = [
72
98
  {
73
99
  name: 'remnote_search_by_tag',
74
100
  description:
75
- '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.',
101
+ '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.',
76
102
  inputSchema: {
77
103
  type: 'object',
78
104
  properties: {
@@ -80,16 +106,31 @@ export const FALLBACK_TOOLS = [
80
106
  type: 'string',
81
107
  description: 'Exact tag Rem ID to search',
82
108
  },
109
+ resultMode: {
110
+ type: 'string',
111
+ enum: ['context', 'tagged'],
112
+ description:
113
+ '"context" returns resolved ancestor context targets with matchedRems (default); "tagged" returns directly tagged Rems with context metadata',
114
+ },
83
115
  limit: {
84
116
  type: 'number',
85
117
  description: 'Maximum results (1-150, default: 50)',
86
118
  },
87
- includeContent: {
119
+ contentMode: {
88
120
  type: 'string',
89
121
  enum: ['none', 'markdown', 'structured'],
90
122
  description:
91
123
  'Content rendering mode: "none" omits content (default), "markdown" renders child subtree as indented markdown, "structured" returns nested child objects with remIds',
92
124
  },
125
+ view: {
126
+ type: 'string',
127
+ enum: ['compact', 'standard', 'full'],
128
+ description: 'Output detail level: compact, standard, or full',
129
+ },
130
+ ancestorDepth: {
131
+ type: 'number',
132
+ description: 'Number of parent Rems to include, direct parent first (0-20, default: 0)',
133
+ },
93
134
  depth: {
94
135
  type: 'number',
95
136
  description:
@@ -103,6 +144,15 @@ export const FALLBACK_TOOLS = [
103
144
  type: 'number',
104
145
  description: 'Maximum character length for rendered content (default: 3000)',
105
146
  },
147
+ cursor: {
148
+ type: 'string',
149
+ description: 'Opaque cursor returned by a previous remnote_search_by_tag response',
150
+ },
151
+ timeoutMs: {
152
+ type: 'number',
153
+ description:
154
+ 'Per-call bridge wait timeout in milliseconds (1-60000, default: 15000). Does not cancel plugin-side work.',
155
+ },
106
156
  },
107
157
  required: ['tagRemId'],
108
158
  },
@@ -110,7 +160,7 @@ export const FALLBACK_TOOLS = [
110
160
  {
111
161
  name: 'remnote_read_note',
112
162
  description:
113
- '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.',
163
+ '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.',
114
164
  inputSchema: {
115
165
  type: 'object',
116
166
  properties: {
@@ -122,12 +172,21 @@ export const FALLBACK_TOOLS = [
122
172
  type: 'number',
123
173
  description: 'Depth of child hierarchy to render (0-10, default: 5)',
124
174
  },
125
- includeContent: {
175
+ contentMode: {
126
176
  type: 'string',
127
177
  enum: ['none', 'markdown', 'structured'],
128
178
  description:
129
179
  'Content rendering mode: "markdown" renders child subtree (default), "structured" returns nested child objects with remIds, "none" omits content',
130
180
  },
181
+ view: {
182
+ type: 'string',
183
+ enum: ['compact', 'standard', 'full'],
184
+ description: 'Output detail level: compact, standard, or full',
185
+ },
186
+ ancestorDepth: {
187
+ type: 'number',
188
+ description: 'Number of parent Rems to include, direct parent first (0-20, default: 0)',
189
+ },
131
190
  childLimit: {
132
191
  type: 'number',
133
192
  description: 'Maximum children per level (1-500, default: 100)',
@@ -136,13 +195,80 @@ export const FALLBACK_TOOLS = [
136
195
  type: 'number',
137
196
  description: 'Maximum character length for rendered content (default: 100000)',
138
197
  },
198
+ includeMediaMetadata: {
199
+ type: 'boolean',
200
+ description:
201
+ 'Include ordered image metadata from the root Rem text and backText fields (default: false)',
202
+ },
139
203
  },
140
204
  required: ['remId'],
141
205
  },
142
206
  },
207
+ {
208
+ name: 'remnote_get_media',
209
+ description:
210
+ '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.',
211
+ inputSchema: {
212
+ type: 'object',
213
+ properties: {
214
+ remId: {
215
+ type: 'string',
216
+ description: 'Root Rem ID containing the image',
217
+ },
218
+ field: {
219
+ type: 'string',
220
+ enum: ['text', 'backText'],
221
+ description: 'Rich-text field containing the image',
222
+ },
223
+ mediaId: {
224
+ type: 'string',
225
+ description: 'Stable media ID returned by remnote_read_note',
226
+ },
227
+ maxInlineBytes: {
228
+ type: 'number',
229
+ description: 'Maximum bytes to inline (default 5 MiB, hard maximum 10 MiB)',
230
+ },
231
+ },
232
+ required: ['remId', 'field', 'mediaId'],
233
+ additionalProperties: false,
234
+ },
235
+ },
236
+ {
237
+ name: 'remnote_list_children',
238
+ description:
239
+ 'List direct child Rems under a parent without rendering a subtree. Use for cheap hierarchy traversal; page with nextCursor when hasMore is true.',
240
+ inputSchema: {
241
+ type: 'object',
242
+ properties: {
243
+ parentRemId: {
244
+ type: 'string',
245
+ description: 'Parent Rem ID whose direct children to list',
246
+ },
247
+ limit: {
248
+ type: 'number',
249
+ description: 'Maximum direct children (1-150, default: 50)',
250
+ },
251
+ cursor: {
252
+ type: 'string',
253
+ description: 'Opaque cursor returned by a previous remnote_list_children response',
254
+ },
255
+ view: {
256
+ type: 'string',
257
+ enum: ['compact', 'standard', 'full'],
258
+ description: 'Output detail level for child metadata: compact, standard, or full',
259
+ },
260
+ ancestorDepth: {
261
+ type: 'number',
262
+ description: 'Number of parent Rems to include for each child, direct parent first',
263
+ },
264
+ },
265
+ required: ['parentRemId'],
266
+ },
267
+ },
143
268
  {
144
269
  name: 'remnote_update_note',
145
- description: 'Update note metadata in RemNote. Use this tool for title changes only.',
270
+ description:
271
+ 'Update note metadata in RemNote. Use this tool for title changes only. The title supports exact Rem references as [[id:<remId>]].',
146
272
  inputSchema: {
147
273
  type: 'object',
148
274
  properties: {
@@ -152,13 +278,84 @@ export const FALLBACK_TOOLS = [
152
278
  },
153
279
  title: {
154
280
  type: 'string',
155
- description: 'New title',
281
+ description: 'New title. Use [[id:<remId>]] for exact Rem references.',
156
282
  },
157
283
  },
158
284
  required: ['remId', 'title'],
159
285
  additionalProperties: false,
160
286
  },
161
287
  },
288
+ {
289
+ name: 'remnote_set_document_status',
290
+ description:
291
+ '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.',
292
+ inputSchema: {
293
+ type: 'object',
294
+ properties: {
295
+ remId: {
296
+ type: 'string',
297
+ description: 'The Rem ID whose document status should change',
298
+ },
299
+ isDocument: {
300
+ type: 'boolean',
301
+ description: 'Whether the Rem should be marked as a document',
302
+ },
303
+ dryRun: {
304
+ type: 'boolean',
305
+ description:
306
+ 'Preview the document-status change without mutating RemNote (default: true)',
307
+ },
308
+ expectedOldRemType: {
309
+ type: 'string',
310
+ enum: ['document', 'dailyDocument', 'concept', 'descriptor', 'portal', 'text'],
311
+ description:
312
+ 'Optional stale-context guard; reject if current remType differs from this value',
313
+ },
314
+ },
315
+ required: ['remId', 'isDocument'],
316
+ additionalProperties: false,
317
+ },
318
+ },
319
+ {
320
+ name: 'remnote_move_note',
321
+ description:
322
+ '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.',
323
+ inputSchema: {
324
+ type: 'object',
325
+ properties: {
326
+ remId: {
327
+ type: 'string',
328
+ description: 'Rem ID to move',
329
+ },
330
+ newParentRemId: {
331
+ type: 'string',
332
+ description: 'New parent Rem ID',
333
+ },
334
+ position: {
335
+ type: 'string',
336
+ enum: ['first', 'last', 'before', 'after'],
337
+ description: 'Where to place the moved Rem under the new parent (default: last)',
338
+ },
339
+ siblingRemId: {
340
+ type: 'string',
341
+ description: 'Sibling Rem ID required for before/after positioning',
342
+ },
343
+ dryRun: {
344
+ type: 'boolean',
345
+ description: 'Preview without mutating RemNote (default: true)',
346
+ },
347
+ expectedOldParentRemId: {
348
+ type: 'string',
349
+ description: 'Reject if the current direct parent differs from this Rem ID',
350
+ },
351
+ ancestorDepth: {
352
+ type: 'number',
353
+ description: 'Number of parent Rems to include before/after the move',
354
+ },
355
+ },
356
+ required: ['remId', 'newParentRemId'],
357
+ },
358
+ },
162
359
  {
163
360
  name: 'remnote_insert_children',
164
361
  description:
@@ -172,7 +369,8 @@ export const FALLBACK_TOOLS = [
172
369
  },
173
370
  content: {
174
371
  type: 'string',
175
- description: 'Markdown content to insert as child Rems',
372
+ description:
373
+ 'Markdown content to insert as child Rems. Use [[id:<remId>]] for exact Rem references.',
176
374
  },
177
375
  position: {
178
376
  type: 'string',
@@ -202,7 +400,7 @@ export const FALLBACK_TOOLS = [
202
400
  content: {
203
401
  type: 'string',
204
402
  description:
205
- 'Markdown content to use as replacement children; empty string clears all direct children',
403
+ 'Markdown content to use as replacement children; empty string clears all direct children. Use [[id:<remId>]] for exact Rem references.',
206
404
  },
207
405
  },
208
406
  required: ['parentRemId', 'content'],
@@ -241,6 +439,78 @@ export const FALLBACK_TOOLS = [
241
439
  additionalProperties: false,
242
440
  },
243
441
  },
442
+ {
443
+ name: 'remnote_set_property',
444
+ description:
445
+ '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".',
446
+ inputSchema: {
447
+ type: 'object',
448
+ properties: {
449
+ remId: {
450
+ type: 'string',
451
+ description: 'The Rem ID whose tag property should be set',
452
+ },
453
+ tagRemId: {
454
+ type: 'string',
455
+ description: 'Exact tag/table Rem ID that owns the property',
456
+ },
457
+ propertyRemId: {
458
+ type: 'string',
459
+ description: 'Exact property Rem ID under the tag/table Rem',
460
+ },
461
+ value: {
462
+ oneOf: [
463
+ {
464
+ type: 'object',
465
+ properties: {
466
+ kind: {
467
+ type: 'string',
468
+ const: 'text',
469
+ },
470
+ text: {
471
+ type: 'string',
472
+ description:
473
+ 'Plain text or markdown property value. Use [[id:<remId>]] for exact Rem references.',
474
+ },
475
+ },
476
+ required: ['kind', 'text'],
477
+ additionalProperties: false,
478
+ },
479
+ {
480
+ type: 'object',
481
+ properties: {
482
+ kind: {
483
+ type: 'string',
484
+ const: 'rem_reference',
485
+ },
486
+ remId: {
487
+ type: 'string',
488
+ description: 'Referenced Rem ID; use select-option Rem IDs here too',
489
+ },
490
+ },
491
+ required: ['kind', 'remId'],
492
+ additionalProperties: false,
493
+ },
494
+ {
495
+ type: 'object',
496
+ properties: {
497
+ kind: {
498
+ type: 'string',
499
+ const: 'clear',
500
+ },
501
+ },
502
+ required: ['kind'],
503
+ additionalProperties: false,
504
+ },
505
+ ],
506
+ description:
507
+ 'Property value. Use text for plain values, rem_reference for Rem references and select options, or clear to remove the value.',
508
+ },
509
+ },
510
+ required: ['remId', 'tagRemId', 'propertyRemId', 'value'],
511
+ additionalProperties: false,
512
+ },
513
+ },
244
514
  {
245
515
  name: 'remnote_append_journal',
246
516
  description:
@@ -250,7 +520,8 @@ export const FALLBACK_TOOLS = [
250
520
  properties: {
251
521
  content: {
252
522
  type: 'string',
253
- description: "Content to append to today's daily document (markdown supported)",
523
+ description:
524
+ "Content to append to today's daily document (markdown supported). Use [[id:<remId>]] for exact Rem references.",
254
525
  },
255
526
  timestamp: {
256
527
  type: 'boolean',
@@ -19,11 +19,13 @@ const packageJson = require('../package.json');
19
19
  export const SERVER_INFO = { name: 'remnote-mcp-stdio', version: packageJson.version };
20
20
 
21
21
  export function normalizeMcpUrl(value) {
22
- const trimmed = String(value || DEFAULT_MCP_URL).trim();
22
+ const trimmed = String(value || DEFAULT_MCP_URL)
23
+ .trim()
24
+ .replace(/\/+$/, '');
23
25
  if (trimmed.endsWith('/mcp')) {
24
26
  return trimmed;
25
27
  }
26
- return `${trimmed.replace(/\/+$/, '')}/mcp`;
28
+ return `${trimmed}/mcp`;
27
29
  }
28
30
 
29
31
  export function createSdkHttpClient(mcpUrl, clientInfo = SERVER_INFO) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remnote-mcp-server",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "MCP server bridge for RemNote knowledge base",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -63,7 +63,7 @@
63
63
  "homepage": "https://github.com/robert7/remnote-mcp-server#readme",
64
64
  "dependencies": {
65
65
  "@modelcontextprotocol/sdk": "^1.29.0",
66
- "commander": "^14.0.3",
66
+ "commander": "^15.0.0",
67
67
  "express": "^5.2.0",
68
68
  "pino": "^10.3.1",
69
69
  "ws": "^8.20.0",
@@ -72,7 +72,7 @@
72
72
  "devDependencies": {
73
73
  "@eslint/js": "^10.0.1",
74
74
  "@types/express": "^5.0.0",
75
- "@types/node": "^25.5.2",
75
+ "@types/node": "^26.1.1",
76
76
  "@types/ws": "^8.18.1",
77
77
  "@typescript-eslint/eslint-plugin": "^8.58.1",
78
78
  "@typescript-eslint/parser": "^8.58.1",
@@ -87,6 +87,6 @@
87
87
  "vitest": "^4.1.3"
88
88
  },
89
89
  "engines": {
90
- "node": ">=20.19.0"
90
+ "node": ">=22.13.0"
91
91
  }
92
92
  }