roam-research-mcp 1.4.0 → 2.4.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.
- package/README.md +360 -31
- package/build/Roam_Markdown_Cheatsheet.md +30 -12
- package/build/cli/batch/resolver.js +138 -0
- package/build/cli/batch/translator.js +363 -0
- package/build/cli/batch/types.js +4 -0
- package/build/cli/commands/batch.js +352 -0
- package/build/cli/commands/get.js +161 -0
- package/build/cli/commands/refs.js +135 -0
- package/build/cli/commands/rename.js +58 -0
- package/build/cli/commands/save.js +498 -0
- package/build/cli/commands/search.js +240 -0
- package/build/cli/commands/status.js +91 -0
- package/build/cli/commands/update.js +151 -0
- package/build/cli/roam.js +35 -0
- package/build/cli/utils/graph.js +56 -0
- package/build/cli/utils/output.js +122 -0
- package/build/config/environment.js +70 -34
- package/build/config/graph-registry.js +221 -0
- package/build/config/graph-registry.test.js +30 -0
- package/build/search/block-ref-search.js +34 -7
- package/build/search/status-search.js +5 -4
- package/build/server/roam-server.js +98 -53
- package/build/shared/validation.js +10 -5
- package/build/tools/helpers/refs.js +50 -31
- package/build/tools/operations/blocks.js +38 -1
- package/build/tools/operations/memory.js +51 -5
- package/build/tools/operations/pages.js +186 -111
- package/build/tools/operations/search/index.js +5 -1
- package/build/tools/operations/todos.js +1 -1
- package/build/tools/schemas.js +121 -41
- package/build/tools/tool-handlers.js +9 -2
- package/build/utils/helpers.js +22 -0
- package/package.json +11 -7
- package/build/cli/import-markdown.js +0 -98
package/build/tools/schemas.js
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
// Tool definitions and input schemas for Roam Research MCP server
|
|
2
|
+
/**
|
|
3
|
+
* Multi-graph parameters that are added to all tool schemas
|
|
4
|
+
* These enable targeting specific graphs and providing write confirmation
|
|
5
|
+
*/
|
|
6
|
+
const multiGraphParams = {
|
|
7
|
+
graph: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
description: 'Target graph key from ROAM_GRAPHS config. Defaults to ROAM_DEFAULT_GRAPH. Only needed in multi-graph mode.'
|
|
10
|
+
},
|
|
11
|
+
write_key: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'Write confirmation key. Required for write operations on non-default graphs when write_key is configured.'
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Helper to add multi-graph params to a schema's properties
|
|
18
|
+
*/
|
|
19
|
+
function withMultiGraphParams(properties) {
|
|
20
|
+
return {
|
|
21
|
+
...properties,
|
|
22
|
+
...multiGraphParams
|
|
23
|
+
};
|
|
24
|
+
}
|
|
2
25
|
export const toolSchemas = {
|
|
3
26
|
roam_add_todo: {
|
|
4
27
|
name: 'roam_add_todo',
|
|
5
28
|
description: 'Add a list of todo items as individual blocks to today\'s daily page in Roam. Each item becomes its own actionable block with todo status.\nNOTE on Roam-flavored markdown: For direct linking: use [[link]] syntax. For aliased linking, use [alias]([[link]]) syntax. Do not concatenate words in links/hashtags - correct: #[[multiple words]] #self-esteem (for typically hyphenated words).\nIMPORTANT: Before using this tool, ensure that you have loaded into context the \'Roam Markdown Cheatsheet\' resource.',
|
|
6
29
|
inputSchema: {
|
|
7
30
|
type: 'object',
|
|
8
|
-
properties: {
|
|
31
|
+
properties: withMultiGraphParams({
|
|
9
32
|
todos: {
|
|
10
33
|
type: 'array',
|
|
11
34
|
items: {
|
|
@@ -14,7 +37,7 @@ export const toolSchemas = {
|
|
|
14
37
|
},
|
|
15
38
|
description: 'List of todo items to add'
|
|
16
39
|
}
|
|
17
|
-
},
|
|
40
|
+
}),
|
|
18
41
|
required: ['todos'],
|
|
19
42
|
},
|
|
20
43
|
},
|
|
@@ -23,7 +46,7 @@ export const toolSchemas = {
|
|
|
23
46
|
description: 'Fetch page by title. Returns content in the specified format.',
|
|
24
47
|
inputSchema: {
|
|
25
48
|
type: 'object',
|
|
26
|
-
properties: {
|
|
49
|
+
properties: withMultiGraphParams({
|
|
27
50
|
title: {
|
|
28
51
|
type: 'string',
|
|
29
52
|
description: 'Title of the page. For date pages, use ordinal date formats such as January 2nd, 2025'
|
|
@@ -34,7 +57,7 @@ export const toolSchemas = {
|
|
|
34
57
|
default: 'raw',
|
|
35
58
|
description: "Format output as markdown or JSON. 'markdown' returns as string; 'raw' returns JSON string of the page's blocks"
|
|
36
59
|
}
|
|
37
|
-
},
|
|
60
|
+
}),
|
|
38
61
|
required: ['title']
|
|
39
62
|
},
|
|
40
63
|
},
|
|
@@ -43,7 +66,7 @@ export const toolSchemas = {
|
|
|
43
66
|
description: 'Create a new standalone page in Roam with optional content, including structured outlines and tables, using explicit nesting levels and headings (H1-H3). This is the preferred method for creating a new page with an outline in a single step. Best for:\n- Creating foundational concept pages that other pages will link to/from\n- Establishing new topic areas that need their own namespace\n- Setting up reference materials or documentation\n- Making permanent collections of information\n- Creating pages with mixed text and table content in one call.\n**Efficiency Tip:** This tool batches page and content creation efficiently. For adding content to existing pages, use `roam_process_batch_actions` instead.\nIMPORTANT: Before using this tool, ensure that you have loaded into context the \'Roam Markdown Cheatsheet\' resource.',
|
|
44
67
|
inputSchema: {
|
|
45
68
|
type: 'object',
|
|
46
|
-
properties: {
|
|
69
|
+
properties: withMultiGraphParams({
|
|
47
70
|
title: {
|
|
48
71
|
type: 'string',
|
|
49
72
|
description: 'Title of the new page',
|
|
@@ -104,7 +127,7 @@ export const toolSchemas = {
|
|
|
104
127
|
required: ['level']
|
|
105
128
|
}
|
|
106
129
|
},
|
|
107
|
-
},
|
|
130
|
+
}),
|
|
108
131
|
required: ['title'],
|
|
109
132
|
},
|
|
110
133
|
},
|
|
@@ -113,7 +136,7 @@ export const toolSchemas = {
|
|
|
113
136
|
description: 'Add a structured outline to an existing page or block (by title text or uid), with customizable nesting levels. To create a new page with an outline, use the `roam_create_page` tool instead. The `outline` parameter defines *new* blocks to be created. To nest content under an *existing* block, provide its UID or exact text in `block_text_uid`, and ensure the `outline` array contains only the child blocks with levels relative to that parent. Including the parent block\'s text in the `outline` array will create a duplicate block. Best for:\n- Adding supplementary structured content to existing pages\n- Creating temporary or working outlines (meeting notes, brainstorms)\n- Organizing thoughts or research under a specific topic\n- Breaking down subtopics or components of a larger concept\nBest for simpler, contiguous hierarchical content. For complex nesting (e.g., tables) or granular control over block placement, consider `roam_process_batch_actions` instead.\n**API Usage Note:** This tool performs verification queries after creation. For large outlines (10+ items) or when rate limits are a concern, consider using `roam_process_batch_actions` instead to minimize API calls.\nIMPORTANT: Before using this tool, ensure that you have loaded into context the \'Roam Markdown Cheatsheet\' resource.',
|
|
114
137
|
inputSchema: {
|
|
115
138
|
type: 'object',
|
|
116
|
-
properties: {
|
|
139
|
+
properties: withMultiGraphParams({
|
|
117
140
|
page_title_uid: {
|
|
118
141
|
type: 'string',
|
|
119
142
|
description: 'Title or UID of the page (UID is preferred for accuracy). Leave blank to use the default daily page.'
|
|
@@ -153,7 +176,7 @@ export const toolSchemas = {
|
|
|
153
176
|
required: ['text', 'level']
|
|
154
177
|
}
|
|
155
178
|
}
|
|
156
|
-
},
|
|
179
|
+
}),
|
|
157
180
|
required: ['outline']
|
|
158
181
|
}
|
|
159
182
|
},
|
|
@@ -162,7 +185,7 @@ export const toolSchemas = {
|
|
|
162
185
|
description: 'Import nested markdown content into Roam under a specific block. Can locate the parent block by UID (preferred) or by exact string match within a specific page. If a `parent_string` is provided and the block does not exist, it will be created. Returns a nested structure of the created blocks.\n**API Usage Note:** This tool fetches the full nested structure after import for verification. For large imports or when rate limits are a concern, consider using `roam_process_batch_actions` with pre-structured actions instead.\nIMPORTANT: Before using this tool, ensure that you have loaded into context the \'Roam Markdown Cheatsheet\' resource.',
|
|
163
186
|
inputSchema: {
|
|
164
187
|
type: 'object',
|
|
165
|
-
properties: {
|
|
188
|
+
properties: withMultiGraphParams({
|
|
166
189
|
content: {
|
|
167
190
|
type: 'string',
|
|
168
191
|
description: 'Nested markdown content to import'
|
|
@@ -189,7 +212,7 @@ export const toolSchemas = {
|
|
|
189
212
|
enum: ['first', 'last'],
|
|
190
213
|
default: 'first'
|
|
191
214
|
}
|
|
192
|
-
},
|
|
215
|
+
}),
|
|
193
216
|
required: ['content']
|
|
194
217
|
}
|
|
195
218
|
},
|
|
@@ -198,7 +221,7 @@ export const toolSchemas = {
|
|
|
198
221
|
description: 'Search for blocks containing a specific tag and optionally filter by blocks that also contain another tag nearby or exclude blocks with a specific tag. This tool supports pagination via the `limit` and `offset` parameters. Use this tool to search for memories tagged with the MEMORIES_TAG.',
|
|
199
222
|
inputSchema: {
|
|
200
223
|
type: 'object',
|
|
201
|
-
properties: {
|
|
224
|
+
properties: withMultiGraphParams({
|
|
202
225
|
primary_tag: {
|
|
203
226
|
type: 'string',
|
|
204
227
|
description: 'The main tag to search for (without the [[ ]] brackets)',
|
|
@@ -226,7 +249,7 @@ export const toolSchemas = {
|
|
|
226
249
|
description: 'Optional: The number of results to skip before returning matches. Useful for pagination. Defaults to 0.',
|
|
227
250
|
default: 0
|
|
228
251
|
}
|
|
229
|
-
},
|
|
252
|
+
}),
|
|
230
253
|
required: ['primary_tag']
|
|
231
254
|
}
|
|
232
255
|
},
|
|
@@ -235,7 +258,7 @@ export const toolSchemas = {
|
|
|
235
258
|
description: 'Search for blocks with a specific status (TODO/DONE) across all pages or within a specific page.',
|
|
236
259
|
inputSchema: {
|
|
237
260
|
type: 'object',
|
|
238
|
-
properties: {
|
|
261
|
+
properties: withMultiGraphParams({
|
|
239
262
|
status: {
|
|
240
263
|
type: 'string',
|
|
241
264
|
description: 'Status to search for (TODO or DONE)',
|
|
@@ -253,25 +276,29 @@ export const toolSchemas = {
|
|
|
253
276
|
type: 'string',
|
|
254
277
|
description: 'Optional: Comma-separated list of terms to filter results by exclusion (matches content or page title)'
|
|
255
278
|
}
|
|
256
|
-
},
|
|
279
|
+
}),
|
|
257
280
|
required: ['status']
|
|
258
281
|
}
|
|
259
282
|
},
|
|
260
283
|
roam_search_block_refs: {
|
|
261
284
|
name: 'roam_search_block_refs',
|
|
262
|
-
description: 'Search for block references within a page or across the entire graph. Can search for references to a specific block or find all block references.',
|
|
285
|
+
description: 'Search for block references within a page or across the entire graph. Can search for references to a specific block, a page title, or find all block references.',
|
|
263
286
|
inputSchema: {
|
|
264
287
|
type: 'object',
|
|
265
|
-
properties: {
|
|
288
|
+
properties: withMultiGraphParams({
|
|
266
289
|
block_uid: {
|
|
267
290
|
type: 'string',
|
|
268
|
-
description: 'Optional: UID of the block to find references to'
|
|
291
|
+
description: 'Optional: UID of the block to find references to (searches for ((uid)) patterns in text)'
|
|
292
|
+
},
|
|
293
|
+
title: {
|
|
294
|
+
type: 'string',
|
|
295
|
+
description: 'Optional: Page title to find references to (uses :block/refs for [[page]] and #tag links)'
|
|
269
296
|
},
|
|
270
297
|
page_title_uid: {
|
|
271
298
|
type: 'string',
|
|
272
299
|
description: 'Optional: Title or UID of the page to search in (UID is preferred for accuracy). If not provided, searches across all pages.'
|
|
273
300
|
}
|
|
274
|
-
}
|
|
301
|
+
})
|
|
275
302
|
}
|
|
276
303
|
},
|
|
277
304
|
roam_search_hierarchy: {
|
|
@@ -279,7 +306,7 @@ export const toolSchemas = {
|
|
|
279
306
|
description: 'Search for parent or child blocks in the block hierarchy. Can search up or down the hierarchy from a given block.',
|
|
280
307
|
inputSchema: {
|
|
281
308
|
type: 'object',
|
|
282
|
-
properties: {
|
|
309
|
+
properties: withMultiGraphParams({
|
|
283
310
|
parent_uid: {
|
|
284
311
|
type: 'string',
|
|
285
312
|
description: 'Optional: UID of the block to find children of'
|
|
@@ -298,7 +325,7 @@ export const toolSchemas = {
|
|
|
298
325
|
minimum: 1,
|
|
299
326
|
maximum: 10
|
|
300
327
|
}
|
|
301
|
-
}
|
|
328
|
+
})
|
|
302
329
|
// Note: Validation for either parent_uid or child_uid is handled in the server code
|
|
303
330
|
}
|
|
304
331
|
},
|
|
@@ -307,7 +334,7 @@ export const toolSchemas = {
|
|
|
307
334
|
description: 'Find pages that have been modified today (since midnight), with pagination and sorting options.',
|
|
308
335
|
inputSchema: {
|
|
309
336
|
type: 'object',
|
|
310
|
-
properties: {
|
|
337
|
+
properties: withMultiGraphParams({
|
|
311
338
|
limit: {
|
|
312
339
|
type: 'integer',
|
|
313
340
|
description: 'The maximum number of pages to retrieve (default: 50). Use -1 for no limit, but be aware that very large result sets can impact performance.',
|
|
@@ -324,7 +351,7 @@ export const toolSchemas = {
|
|
|
324
351
|
enum: ['asc', 'desc'],
|
|
325
352
|
default: 'desc'
|
|
326
353
|
}
|
|
327
|
-
}
|
|
354
|
+
})
|
|
328
355
|
}
|
|
329
356
|
},
|
|
330
357
|
roam_search_by_text: {
|
|
@@ -332,7 +359,7 @@ export const toolSchemas = {
|
|
|
332
359
|
description: 'Search for blocks containing specific text across all pages or within a specific page. This tool supports pagination via the `limit` and `offset` parameters.',
|
|
333
360
|
inputSchema: {
|
|
334
361
|
type: 'object',
|
|
335
|
-
properties: {
|
|
362
|
+
properties: withMultiGraphParams({
|
|
336
363
|
text: {
|
|
337
364
|
type: 'string',
|
|
338
365
|
description: 'The text to search for'
|
|
@@ -356,7 +383,7 @@ export const toolSchemas = {
|
|
|
356
383
|
description: 'Optional: The number of results to skip before returning matches. Useful for pagination. Defaults to 0.',
|
|
357
384
|
default: 0
|
|
358
385
|
}
|
|
359
|
-
},
|
|
386
|
+
}),
|
|
360
387
|
required: ['text']
|
|
361
388
|
}
|
|
362
389
|
},
|
|
@@ -365,7 +392,7 @@ export const toolSchemas = {
|
|
|
365
392
|
description: 'Search for blocks or pages based on creation or modification dates. Not for daily pages with ordinal date titles.',
|
|
366
393
|
inputSchema: {
|
|
367
394
|
type: 'object',
|
|
368
|
-
properties: {
|
|
395
|
+
properties: withMultiGraphParams({
|
|
369
396
|
start_date: {
|
|
370
397
|
type: 'string',
|
|
371
398
|
description: 'Start date in ISO format (YYYY-MM-DD)',
|
|
@@ -389,7 +416,7 @@ export const toolSchemas = {
|
|
|
389
416
|
description: 'Whether to include the content of matching blocks/pages',
|
|
390
417
|
default: true,
|
|
391
418
|
}
|
|
392
|
-
},
|
|
419
|
+
}),
|
|
393
420
|
required: ['start_date', 'type', 'scope']
|
|
394
421
|
}
|
|
395
422
|
},
|
|
@@ -398,7 +425,7 @@ export const toolSchemas = {
|
|
|
398
425
|
description: 'Provides the content of the Roam Markdown Cheatsheet resource, optionally concatenated with custom instructions if CUSTOM_INSTRUCTIONS_PATH is set.',
|
|
399
426
|
inputSchema: {
|
|
400
427
|
type: 'object',
|
|
401
|
-
properties: {},
|
|
428
|
+
properties: withMultiGraphParams({}),
|
|
402
429
|
required: [],
|
|
403
430
|
},
|
|
404
431
|
},
|
|
@@ -407,7 +434,7 @@ export const toolSchemas = {
|
|
|
407
434
|
description: 'Add a memory or piece of information to remember, stored on the daily page with MEMORIES_TAG tag and optional categories. \nNOTE on Roam-flavored markdown: For direct linking: use [[link]] syntax. For aliased linking, use [alias]([[link]]) syntax. Do not concatenate words in links/hashtags - correct: #[[multiple words]] #self-esteem (for typically hyphenated words).\nIMPORTANT: Before using this tool, ensure that you have loaded into context the \'Roam Markdown Cheatsheet\' resource.',
|
|
408
435
|
inputSchema: {
|
|
409
436
|
type: 'object',
|
|
410
|
-
properties: {
|
|
437
|
+
properties: withMultiGraphParams({
|
|
411
438
|
memory: {
|
|
412
439
|
type: 'string',
|
|
413
440
|
description: 'The memory detail or information to remember'
|
|
@@ -418,8 +445,16 @@ export const toolSchemas = {
|
|
|
418
445
|
type: 'string'
|
|
419
446
|
},
|
|
420
447
|
description: 'Optional categories to tag the memory with (will be converted to Roam tags)'
|
|
448
|
+
},
|
|
449
|
+
heading: {
|
|
450
|
+
type: 'string',
|
|
451
|
+
description: 'Optional heading text to nest the memory under (e.g., "Memories" or "## LLM Memories"). If the heading does not exist on the daily page, it will be created. Ignored if parent_uid is provided.'
|
|
452
|
+
},
|
|
453
|
+
parent_uid: {
|
|
454
|
+
type: 'string',
|
|
455
|
+
description: 'Optional UID of a specific block to nest the memory under. Takes precedence over heading parameter.'
|
|
421
456
|
}
|
|
422
|
-
},
|
|
457
|
+
}),
|
|
423
458
|
required: ['memory']
|
|
424
459
|
}
|
|
425
460
|
},
|
|
@@ -428,7 +463,7 @@ export const toolSchemas = {
|
|
|
428
463
|
description: 'Retrieve all stored memories on page titled MEMORIES_TAG, or tagged block content with the same name. Returns a combined, deduplicated list of memories. Optionally filter blcoks with a specific tag and sort by creation date.',
|
|
429
464
|
inputSchema: {
|
|
430
465
|
type: 'object',
|
|
431
|
-
properties: {
|
|
466
|
+
properties: withMultiGraphParams({
|
|
432
467
|
sort_by: {
|
|
433
468
|
type: 'string',
|
|
434
469
|
description: 'Sort order for memories based on creation date',
|
|
@@ -439,7 +474,7 @@ export const toolSchemas = {
|
|
|
439
474
|
type: 'string',
|
|
440
475
|
description: 'Include only memories with a specific filter tag. For single word tags use format "tag", for multi-word tags use format "tag word" (without brackets)'
|
|
441
476
|
}
|
|
442
|
-
}
|
|
477
|
+
})
|
|
443
478
|
}
|
|
444
479
|
},
|
|
445
480
|
roam_datomic_query: {
|
|
@@ -447,7 +482,7 @@ export const toolSchemas = {
|
|
|
447
482
|
description: 'Execute a custom Datomic query on the Roam graph for advanced data retrieval beyond the available search tools. This provides direct access to Roam\'s query engine. Note: Roam graph is case-sensitive.\n\n__Optimal Use Cases for `roam_datomic_query`:__\n- __Advanced Filtering (including Regex):__ Use for scenarios requiring complex filtering, including regex matching on results post-query, which Datalog does not natively support for all data types. It can fetch broader results for client-side post-processing.\n- __Highly Complex Boolean Logic:__ Ideal for intricate combinations of "AND", "OR", and "NOT" conditions across multiple terms or attributes.\n- __Arbitrary Sorting Criteria:__ The go-to for highly customized sorting needs beyond default options.\n- __Proximity Search:__ For advanced search capabilities involving proximity, which are difficult to implement efficiently with simpler tools.\n\nList of some of Roam\'s data model Namespaces and Attributes: ancestor (descendants), attrs (lookup), block (children, heading, open, order, page, parents, props, refs, string, text-align, uid), children (view-type), create (email, time), descendant (ancestors), edit (email, seen-by, time), entity (attrs), log (id), node (title), page (uid, title), refs (text).\nPredicates (clojure.string/includes?, clojure.string/starts-with?, clojure.string/ends-with?, <, >, <=, >=, =, not=, !=).\nAggregates (distinct, count, sum, max, min, avg, limit).\nTips: Use :block/parents for all ancestor levels, :block/children for direct descendants only; combine clojure.string for complex matching, use distinct to deduplicate, leverage Pull patterns for hierarchies, handle case-sensitivity carefully, and chain ancestry rules for multi-level queries.',
|
|
448
483
|
inputSchema: {
|
|
449
484
|
type: 'object',
|
|
450
|
-
properties: {
|
|
485
|
+
properties: withMultiGraphParams({
|
|
451
486
|
query: {
|
|
452
487
|
type: 'string',
|
|
453
488
|
description: 'The Datomic query to execute (in Datalog syntax). Example: `[:find ?block-string :where [?block :block/string ?block-string] (or [(clojure.string/includes? ?block-string "hypnosis")] [(clojure.string/includes? ?block-string "trance")] [(clojure.string/includes? ?block-string "suggestion")]) :limit 25]`'
|
|
@@ -474,7 +509,7 @@ export const toolSchemas = {
|
|
|
474
509
|
},
|
|
475
510
|
description: 'Optional: An array of field paths (e.g., ["block_string", "page_title"]) within each Datomic result object to apply the regex filter to. If not provided, the regex is applied to the stringified full result.'
|
|
476
511
|
}
|
|
477
|
-
},
|
|
512
|
+
}),
|
|
478
513
|
required: ['query']
|
|
479
514
|
}
|
|
480
515
|
},
|
|
@@ -483,7 +518,7 @@ export const toolSchemas = {
|
|
|
483
518
|
description: '**RATE LIMIT EFFICIENT:** This is the most API-efficient tool for multiple block operations. Combine all create/update/delete operations into a single call whenever possible. For intensive page updates or revisions, prefer this tool over multiple sequential calls.\n\nExecutes a sequence of low-level block actions (create, update, move, delete) in a single, non-transactional batch. Actions are executed in the provided order.\n\n**UID Placeholders for Nested Blocks:** Use `{{uid:name}}` syntax for parent-child references within the same batch. The server generates proper random UIDs and returns a `uid_map` showing placeholder→UID mappings. Example: `{ uid: "{{uid:parent1}}", string: "Parent" }` then `{ location: { "parent-uid": "{{uid:parent1}}" }, string: "Child" }`. Response includes `{ success: true, uid_map: { "parent1": "Xk7mN2pQ9" } }`.\n\nFor actions on existing blocks, a valid block UID is required. Note: Roam-flavored markdown, including block embedding with `((UID))` syntax, is supported within the `string` property for `create-block` and `update-block` actions. For actions on existing blocks or within a specific page context, it is often necessary to first obtain valid page or block UIDs. Tools like `roam_fetch_page_by_title` or other search tools can be used to retrieve these UIDs before executing batch actions. For simpler, sequential outlines, `roam_create_outline` is often more suitable.\nIMPORTANT: Before using this tool, ensure that you have loaded into context the \'Roam Markdown Cheatsheet\' resource.',
|
|
484
519
|
inputSchema: {
|
|
485
520
|
type: 'object',
|
|
486
|
-
properties: {
|
|
521
|
+
properties: withMultiGraphParams({
|
|
487
522
|
actions: {
|
|
488
523
|
type: 'array',
|
|
489
524
|
description: 'An array of action objects to execute in order.',
|
|
@@ -540,7 +575,7 @@ export const toolSchemas = {
|
|
|
540
575
|
required: ['action']
|
|
541
576
|
}
|
|
542
577
|
}
|
|
543
|
-
},
|
|
578
|
+
}),
|
|
544
579
|
required: ['actions']
|
|
545
580
|
}
|
|
546
581
|
},
|
|
@@ -549,7 +584,7 @@ export const toolSchemas = {
|
|
|
549
584
|
description: 'Fetch a block by its UID along with its hierarchical children down to a specified depth. Returns a nested object structure containing the block\'s UID, text, order, and an array of its children.',
|
|
550
585
|
inputSchema: {
|
|
551
586
|
type: 'object',
|
|
552
|
-
properties: {
|
|
587
|
+
properties: withMultiGraphParams({
|
|
553
588
|
block_uid: {
|
|
554
589
|
type: 'string',
|
|
555
590
|
description: 'The UID of the block to fetch.'
|
|
@@ -560,7 +595,7 @@ export const toolSchemas = {
|
|
|
560
595
|
minimum: 0,
|
|
561
596
|
maximum: 10
|
|
562
597
|
}
|
|
563
|
-
},
|
|
598
|
+
}),
|
|
564
599
|
required: ['block_uid']
|
|
565
600
|
},
|
|
566
601
|
},
|
|
@@ -569,7 +604,7 @@ export const toolSchemas = {
|
|
|
569
604
|
description: 'Create a table in Roam with specified headers and rows. This tool abstracts the complex nested structure that Roam tables require, making it much easier to create properly formatted tables.\n\n**Why use this tool:**\n- Roam tables require precise nested block structures that are error-prone to create manually\n- Automatically handles the {{[[table]]}} container and nested column structure\n- Validates row/column consistency before execution\n- Converts empty cells to spaces (required by Roam)\n\n**Example:** A table with headers ["", "Column A", "Column B"] and rows [{label: "Row 1", cells: ["A1", "B1"]}] creates a 2x3 table.\nIMPORTANT: Before using this tool, ensure that you have loaded into context the \'Roam Markdown Cheatsheet\' resource.',
|
|
570
605
|
inputSchema: {
|
|
571
606
|
type: 'object',
|
|
572
|
-
properties: {
|
|
607
|
+
properties: withMultiGraphParams({
|
|
573
608
|
parent_uid: {
|
|
574
609
|
type: 'string',
|
|
575
610
|
description: 'The UID of the parent block or page where the table should be created.'
|
|
@@ -608,16 +643,39 @@ export const toolSchemas = {
|
|
|
608
643
|
required: ['label', 'cells']
|
|
609
644
|
}
|
|
610
645
|
}
|
|
611
|
-
},
|
|
646
|
+
}),
|
|
612
647
|
required: ['parent_uid', 'headers', 'rows']
|
|
613
648
|
}
|
|
614
649
|
},
|
|
650
|
+
roam_move_block: {
|
|
651
|
+
name: 'roam_move_block',
|
|
652
|
+
description: 'Move a block to a new location (different parent or position). This is a convenience wrapper around `roam_process_batch_actions` for single block moves.',
|
|
653
|
+
inputSchema: {
|
|
654
|
+
type: 'object',
|
|
655
|
+
properties: withMultiGraphParams({
|
|
656
|
+
block_uid: {
|
|
657
|
+
type: 'string',
|
|
658
|
+
description: 'The UID of the block to move'
|
|
659
|
+
},
|
|
660
|
+
parent_uid: {
|
|
661
|
+
type: 'string',
|
|
662
|
+
description: 'The UID of the new parent block or page'
|
|
663
|
+
},
|
|
664
|
+
order: {
|
|
665
|
+
type: ['integer', 'string'],
|
|
666
|
+
description: 'Position under the new parent. Can be a number (0-based index) or "first"/"last". Defaults to "last".',
|
|
667
|
+
default: 'last'
|
|
668
|
+
}
|
|
669
|
+
}),
|
|
670
|
+
required: ['block_uid', 'parent_uid']
|
|
671
|
+
}
|
|
672
|
+
},
|
|
615
673
|
roam_update_page_markdown: {
|
|
616
674
|
name: 'roam_update_page_markdown',
|
|
617
675
|
description: 'Update an existing page with new markdown content using smart diff. Preserves block UIDs where possible and generates minimal changes. This is ideal for:\n- Syncing external markdown files to Roam\n- AI-assisted content updates that preserve references\n- Batch content modifications without losing block references\n\n**How it works:**\n1. Fetches existing page blocks\n2. Matches new content to existing blocks by text similarity\n3. Generates minimal create/update/move/delete operations\n4. Preserves UIDs for matched blocks (keeping references intact)\n\nIMPORTANT: Before using this tool, ensure that you have loaded into context the \'Roam Markdown Cheatsheet\' resource.',
|
|
618
676
|
inputSchema: {
|
|
619
677
|
type: 'object',
|
|
620
|
-
properties: {
|
|
678
|
+
properties: withMultiGraphParams({
|
|
621
679
|
title: {
|
|
622
680
|
type: 'string',
|
|
623
681
|
description: 'Title of the page to update'
|
|
@@ -631,8 +689,30 @@ export const toolSchemas = {
|
|
|
631
689
|
description: 'If true, returns the planned actions without executing them. Useful for previewing changes.',
|
|
632
690
|
default: false
|
|
633
691
|
}
|
|
634
|
-
},
|
|
692
|
+
}),
|
|
635
693
|
required: ['title', 'markdown']
|
|
636
694
|
}
|
|
637
695
|
},
|
|
696
|
+
roam_rename_page: {
|
|
697
|
+
name: 'roam_rename_page',
|
|
698
|
+
description: 'Rename a page by changing its title. Identifies the page by current title or UID.',
|
|
699
|
+
inputSchema: {
|
|
700
|
+
type: 'object',
|
|
701
|
+
properties: withMultiGraphParams({
|
|
702
|
+
old_title: {
|
|
703
|
+
type: 'string',
|
|
704
|
+
description: 'Current title of the page to rename (use this OR uid, not both)'
|
|
705
|
+
},
|
|
706
|
+
uid: {
|
|
707
|
+
type: 'string',
|
|
708
|
+
description: 'UID of the page to rename (use this OR old_title, not both)'
|
|
709
|
+
},
|
|
710
|
+
new_title: {
|
|
711
|
+
type: 'string',
|
|
712
|
+
description: 'New title for the page'
|
|
713
|
+
}
|
|
714
|
+
}),
|
|
715
|
+
required: ['new_title']
|
|
716
|
+
}
|
|
717
|
+
},
|
|
638
718
|
};
|
|
@@ -39,6 +39,9 @@ export class ToolHandlers {
|
|
|
39
39
|
async fetchBlockWithChildren(block_uid, depth) {
|
|
40
40
|
return this.blockRetrievalOps.fetchBlockWithChildren(block_uid, depth);
|
|
41
41
|
}
|
|
42
|
+
async moveBlock(block_uid, parent_uid, order = 'last') {
|
|
43
|
+
return this.blockOps.moveBlock(block_uid, parent_uid, order);
|
|
44
|
+
}
|
|
42
45
|
// Search Operations
|
|
43
46
|
async searchByStatus(status, page_title_uid, include, exclude) {
|
|
44
47
|
return this.searchOps.searchByStatus(status, page_title_uid, include, exclude);
|
|
@@ -64,8 +67,8 @@ export class ToolHandlers {
|
|
|
64
67
|
return handler.execute();
|
|
65
68
|
}
|
|
66
69
|
// Memory Operations
|
|
67
|
-
async remember(memory, categories) {
|
|
68
|
-
return this.memoryOps.remember(memory, categories);
|
|
70
|
+
async remember(memory, categories, heading, parent_uid) {
|
|
71
|
+
return this.memoryOps.remember(memory, categories, heading, parent_uid);
|
|
69
72
|
}
|
|
70
73
|
async recall(sort_by = 'newest', filter_tag) {
|
|
71
74
|
return this.memoryOps.recall(sort_by, filter_tag);
|
|
@@ -93,6 +96,10 @@ export class ToolHandlers {
|
|
|
93
96
|
async updatePageMarkdown(title, markdown, dryRun = false) {
|
|
94
97
|
return this.pageOps.updatePageMarkdown(title, markdown, dryRun);
|
|
95
98
|
}
|
|
99
|
+
// Page Rename
|
|
100
|
+
async renamePage(params) {
|
|
101
|
+
return this.pageOps.renamePage(params);
|
|
102
|
+
}
|
|
96
103
|
async getRoamMarkdownCheatsheet() {
|
|
97
104
|
if (this.cachedCheatsheet) {
|
|
98
105
|
return this.cachedCheatsheet;
|
package/build/utils/helpers.js
CHANGED
|
@@ -17,3 +17,25 @@ export function formatRoamDate(date) {
|
|
|
17
17
|
const year = date.getFullYear();
|
|
18
18
|
return `${month} ${day}${getOrdinalSuffix(day)}, ${year}`;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve relative date keywords to Roam date format.
|
|
22
|
+
* Returns the original string if not a recognized keyword.
|
|
23
|
+
*/
|
|
24
|
+
export function resolveRelativeDate(input) {
|
|
25
|
+
const lower = input.toLowerCase().trim();
|
|
26
|
+
const today = new Date();
|
|
27
|
+
switch (lower) {
|
|
28
|
+
case 'today':
|
|
29
|
+
return formatRoamDate(today);
|
|
30
|
+
case 'yesterday':
|
|
31
|
+
const yesterday = new Date(today);
|
|
32
|
+
yesterday.setDate(today.getDate() - 1);
|
|
33
|
+
return formatRoamDate(yesterday);
|
|
34
|
+
case 'tomorrow':
|
|
35
|
+
const tomorrow = new Date(today);
|
|
36
|
+
tomorrow.setDate(today.getDate() + 1);
|
|
37
|
+
return formatRoamDate(tomorrow);
|
|
38
|
+
default:
|
|
39
|
+
return input;
|
|
40
|
+
}
|
|
41
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roam-research-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "MCP server and CLI for Roam Research",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -23,31 +23,35 @@
|
|
|
23
23
|
"type": "module",
|
|
24
24
|
"bin": {
|
|
25
25
|
"roam-research-mcp": "build/index.js",
|
|
26
|
-
"roam
|
|
26
|
+
"roam": "build/cli/roam.js"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"build"
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build": "echo \"Using custom instructions: .roam/${CUSTOM_INSTRUCTIONS_PREFIX}custom-instructions.md\" && tsc && cat Roam_Markdown_Cheatsheet.md .roam/${CUSTOM_INSTRUCTIONS_PREFIX}custom-instructions.md > build/Roam_Markdown_Cheatsheet.md && chmod 755 build/index.js build/cli/
|
|
32
|
+
"build": "echo \"Using custom instructions: .roam/${CUSTOM_INSTRUCTIONS_PREFIX}custom-instructions.md\" && tsc && cat Roam_Markdown_Cheatsheet.md .roam/${CUSTOM_INSTRUCTIONS_PREFIX}custom-instructions.md > build/Roam_Markdown_Cheatsheet.md && chmod 755 build/index.js build/cli/roam.js",
|
|
33
33
|
"clean": "rm -rf build",
|
|
34
34
|
"watch": "tsc --watch",
|
|
35
35
|
"inspector": "npx @modelcontextprotocol/inspector build/index.js",
|
|
36
36
|
"start": "node build/index.js",
|
|
37
37
|
"prepublishOnly": "npm run clean && npm run build",
|
|
38
|
-
"release
|
|
39
|
-
"release:
|
|
40
|
-
"release:
|
|
38
|
+
"release": "standard-version",
|
|
39
|
+
"release:patch": "standard-version --release-as patch",
|
|
40
|
+
"release:minor": "standard-version --release-as minor",
|
|
41
|
+
"release:major": "standard-version --release-as major",
|
|
42
|
+
"release:first": "standard-version --first-release",
|
|
41
43
|
"test": "vitest run",
|
|
42
44
|
"test:watch": "vitest"
|
|
43
45
|
},
|
|
44
46
|
"dependencies": {
|
|
45
47
|
"@modelcontextprotocol/sdk": "^1.13.2",
|
|
46
48
|
"@roam-research/roam-api-sdk": "^0.10.0",
|
|
49
|
+
"commander": "^14.0.2",
|
|
47
50
|
"dotenv": "^16.4.7"
|
|
48
51
|
},
|
|
49
52
|
"devDependencies": {
|
|
50
53
|
"@types/node": "^20.11.24",
|
|
54
|
+
"standard-version": "^9.5.0",
|
|
51
55
|
"ts-node": "^10.9.2",
|
|
52
56
|
"typescript": "^5.3.3",
|
|
53
57
|
"vitest": "^3.2.4"
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { initializeGraph } from '@roam-research/roam-api-sdk';
|
|
3
|
-
import { API_TOKEN, GRAPH_NAME } from '../config/environment.js';
|
|
4
|
-
import { PageOperations } from '../tools/operations/pages.js';
|
|
5
|
-
import { parseMarkdown } from '../markdown-utils.js';
|
|
6
|
-
/**
|
|
7
|
-
* Flatten nested MarkdownNode[] to flat array with absolute levels
|
|
8
|
-
*/
|
|
9
|
-
function flattenNodes(nodes, baseLevel = 1) {
|
|
10
|
-
const result = [];
|
|
11
|
-
for (const node of nodes) {
|
|
12
|
-
result.push({
|
|
13
|
-
text: node.content,
|
|
14
|
-
level: baseLevel,
|
|
15
|
-
...(node.heading_level && { heading: node.heading_level })
|
|
16
|
-
});
|
|
17
|
-
if (node.children.length > 0) {
|
|
18
|
-
result.push(...flattenNodes(node.children, baseLevel + 1));
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return result;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Read all input from stdin
|
|
25
|
-
*/
|
|
26
|
-
async function readStdin() {
|
|
27
|
-
const chunks = [];
|
|
28
|
-
for await (const chunk of process.stdin) {
|
|
29
|
-
chunks.push(chunk);
|
|
30
|
-
}
|
|
31
|
-
return Buffer.concat(chunks).toString('utf-8');
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Show usage help
|
|
35
|
-
*/
|
|
36
|
-
function showUsage() {
|
|
37
|
-
console.error('Usage: roam-import <page-title>');
|
|
38
|
-
console.error('');
|
|
39
|
-
console.error('Reads markdown from stdin and imports to Roam Research.');
|
|
40
|
-
console.error('');
|
|
41
|
-
console.error('Examples:');
|
|
42
|
-
console.error(' cat document.md | roam-import "Meeting Notes"');
|
|
43
|
-
console.error(' pbpaste | roam-import "Ideas"');
|
|
44
|
-
console.error(' echo "- Item 1\\n- Item 2" | roam-import "Quick Note"');
|
|
45
|
-
console.error('');
|
|
46
|
-
console.error('Environment variables required:');
|
|
47
|
-
console.error(' ROAM_API_TOKEN Your Roam Research API token');
|
|
48
|
-
console.error(' ROAM_GRAPH_NAME Your Roam graph name');
|
|
49
|
-
}
|
|
50
|
-
async function main() {
|
|
51
|
-
// Parse CLI arguments
|
|
52
|
-
const args = process.argv.slice(2);
|
|
53
|
-
const pageTitle = args[0];
|
|
54
|
-
if (!pageTitle || pageTitle === '--help' || pageTitle === '-h') {
|
|
55
|
-
showUsage();
|
|
56
|
-
process.exit(pageTitle ? 0 : 1);
|
|
57
|
-
}
|
|
58
|
-
// Check if stdin is a TTY (no input piped)
|
|
59
|
-
if (process.stdin.isTTY) {
|
|
60
|
-
console.error('Error: No input received. Pipe markdown content to this command.');
|
|
61
|
-
console.error('');
|
|
62
|
-
showUsage();
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
// Read markdown from stdin
|
|
66
|
-
const markdownContent = await readStdin();
|
|
67
|
-
if (!markdownContent.trim()) {
|
|
68
|
-
console.error('Error: Empty input received.');
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
// Initialize Roam graph
|
|
72
|
-
const graph = initializeGraph({
|
|
73
|
-
token: API_TOKEN,
|
|
74
|
-
graph: GRAPH_NAME
|
|
75
|
-
});
|
|
76
|
-
// Parse markdown to nodes
|
|
77
|
-
const nodes = parseMarkdown(markdownContent);
|
|
78
|
-
// Flatten nested structure to content blocks
|
|
79
|
-
const contentBlocks = flattenNodes(nodes);
|
|
80
|
-
if (contentBlocks.length === 0) {
|
|
81
|
-
console.error('Error: No content blocks parsed from input.');
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
// Create page with content
|
|
85
|
-
const pageOps = new PageOperations(graph);
|
|
86
|
-
const result = await pageOps.createPage(pageTitle, contentBlocks);
|
|
87
|
-
if (result.success) {
|
|
88
|
-
console.log(`Created page '${pageTitle}' (uid: ${result.uid})`);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
console.error(`Failed to create page '${pageTitle}'`);
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
main().catch((error) => {
|
|
96
|
-
console.error(`Error: ${error.message}`);
|
|
97
|
-
process.exit(1);
|
|
98
|
-
});
|