roam-research-mcp 0.25.7 → 0.30.1
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 +185 -615
- package/build/config/environment.js +3 -1
- package/build/markdown-utils.js +2 -1
- package/build/server/roam-server.js +122 -70
- package/build/tools/operations/batch.js +37 -0
- package/build/tools/operations/memory.js +16 -7
- package/build/tools/operations/outline.js +33 -14
- package/build/tools/operations/todos.js +16 -37
- package/build/tools/schemas.js +82 -127
- package/build/tools/tool-handlers.js +6 -9
- package/build/utils/net.js +38 -0
- package/package.json +2 -3
package/build/tools/schemas.js
CHANGED
|
@@ -78,34 +78,6 @@ export const toolSchemas = {
|
|
|
78
78
|
required: ['title'],
|
|
79
79
|
},
|
|
80
80
|
},
|
|
81
|
-
roam_create_block: {
|
|
82
|
-
name: 'roam_create_block',
|
|
83
|
-
description: 'Add new block to an existing Roam page. If no page specified, adds to today\'s daily note. Best for capturing immediate thoughts, additions to discussions, or content that doesn\'t warrant its own page. Can specify page by title or UID.\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).',
|
|
84
|
-
inputSchema: {
|
|
85
|
-
type: 'object',
|
|
86
|
-
properties: {
|
|
87
|
-
content: {
|
|
88
|
-
type: 'string',
|
|
89
|
-
description: 'Content of the block',
|
|
90
|
-
},
|
|
91
|
-
page_uid: {
|
|
92
|
-
type: 'string',
|
|
93
|
-
description: 'Optional: UID of the page to add block to',
|
|
94
|
-
},
|
|
95
|
-
title: {
|
|
96
|
-
type: 'string',
|
|
97
|
-
description: 'Optional: Title of the page to add block to (defaults to today\'s date if neither page_uid nor title provided)',
|
|
98
|
-
},
|
|
99
|
-
heading: {
|
|
100
|
-
type: 'integer',
|
|
101
|
-
description: 'Optional: Heading formatting for this block (1-3)',
|
|
102
|
-
minimum: 1,
|
|
103
|
-
maximum: 3
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
required: ['content'],
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
81
|
roam_create_outline: {
|
|
110
82
|
name: 'roam_create_outline',
|
|
111
83
|
description: 'Add a structured outline to an existing page or block (by title text or uid), with customizable nesting levels. 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',
|
|
@@ -114,11 +86,11 @@ export const toolSchemas = {
|
|
|
114
86
|
properties: {
|
|
115
87
|
page_title_uid: {
|
|
116
88
|
type: 'string',
|
|
117
|
-
description: 'Title
|
|
89
|
+
description: 'Title or UID of the page (UID is preferred for accuracy). Leave blank to use the default daily page.'
|
|
118
90
|
},
|
|
119
91
|
block_text_uid: {
|
|
120
92
|
type: 'string',
|
|
121
|
-
description: '
|
|
93
|
+
description: 'The text content or UID of the block to nest the outline under (UID is preferred for accuracy). If blank, content is nested directly under the page.'
|
|
122
94
|
},
|
|
123
95
|
outline: {
|
|
124
96
|
type: 'array',
|
|
@@ -141,6 +113,11 @@ export const toolSchemas = {
|
|
|
141
113
|
description: 'Optional: Heading formatting for this block (1-3)',
|
|
142
114
|
minimum: 1,
|
|
143
115
|
maximum: 3
|
|
116
|
+
},
|
|
117
|
+
children_view_type: {
|
|
118
|
+
type: 'string',
|
|
119
|
+
description: 'Optional: The view type for children of this block ("bullet", "document", or "numbered")',
|
|
120
|
+
enum: ["bullet", "document", "numbered"]
|
|
144
121
|
}
|
|
145
122
|
},
|
|
146
123
|
required: ['text', 'level']
|
|
@@ -162,19 +139,19 @@ export const toolSchemas = {
|
|
|
162
139
|
},
|
|
163
140
|
page_uid: {
|
|
164
141
|
type: 'string',
|
|
165
|
-
description: 'Optional: UID of the page containing the parent block'
|
|
142
|
+
description: 'Optional: UID of the page containing the parent block (preferred for accuracy).'
|
|
166
143
|
},
|
|
167
144
|
page_title: {
|
|
168
145
|
type: 'string',
|
|
169
|
-
description: 'Optional: Title of the page containing the parent block (
|
|
146
|
+
description: 'Optional: Title of the page containing the parent block (used if page_uid is not provided).'
|
|
170
147
|
},
|
|
171
148
|
parent_uid: {
|
|
172
149
|
type: 'string',
|
|
173
|
-
description: 'Optional: UID of the parent block to add content under'
|
|
150
|
+
description: 'Optional: UID of the parent block to add content under (preferred for accuracy).'
|
|
174
151
|
},
|
|
175
152
|
parent_string: {
|
|
176
153
|
type: 'string',
|
|
177
|
-
description: 'Optional: Exact string content of the parent block to add content under (
|
|
154
|
+
description: 'Optional: Exact string content of the parent block to add content under (used if parent_uid is not provided; requires page_uid or page_title).'
|
|
178
155
|
},
|
|
179
156
|
order: {
|
|
180
157
|
type: 'string',
|
|
@@ -198,7 +175,7 @@ export const toolSchemas = {
|
|
|
198
175
|
},
|
|
199
176
|
page_title_uid: {
|
|
200
177
|
type: 'string',
|
|
201
|
-
description: 'Optional: Title or UID of the page to search in. Defaults to today\'s daily page if not provided',
|
|
178
|
+
description: 'Optional: Title or UID of the page to search in (UID is preferred for accuracy). Defaults to today\'s daily page if not provided.',
|
|
202
179
|
},
|
|
203
180
|
near_tag: {
|
|
204
181
|
type: 'string',
|
|
@@ -221,7 +198,7 @@ export const toolSchemas = {
|
|
|
221
198
|
},
|
|
222
199
|
page_title_uid: {
|
|
223
200
|
type: 'string',
|
|
224
|
-
description: 'Optional: Title or UID of the page to search in. If not provided, searches across all pages'
|
|
201
|
+
description: 'Optional: Title or UID of the page to search in (UID is preferred for accuracy). If not provided, searches across all pages.'
|
|
225
202
|
},
|
|
226
203
|
include: {
|
|
227
204
|
type: 'string',
|
|
@@ -247,7 +224,7 @@ export const toolSchemas = {
|
|
|
247
224
|
},
|
|
248
225
|
page_title_uid: {
|
|
249
226
|
type: 'string',
|
|
250
|
-
description: 'Optional: Title or UID of the page to search in. If not provided, searches across all pages'
|
|
227
|
+
description: 'Optional: Title or UID of the page to search in (UID is preferred for accuracy). If not provided, searches across all pages.'
|
|
251
228
|
}
|
|
252
229
|
}
|
|
253
230
|
}
|
|
@@ -268,7 +245,7 @@ export const toolSchemas = {
|
|
|
268
245
|
},
|
|
269
246
|
page_title_uid: {
|
|
270
247
|
type: 'string',
|
|
271
|
-
description: 'Optional: Title or UID of the page to search in'
|
|
248
|
+
description: 'Optional: Title or UID of the page to search in (UID is preferred for accuracy).'
|
|
272
249
|
},
|
|
273
250
|
max_depth: {
|
|
274
251
|
type: 'integer',
|
|
@@ -306,100 +283,12 @@ export const toolSchemas = {
|
|
|
306
283
|
},
|
|
307
284
|
page_title_uid: {
|
|
308
285
|
type: 'string',
|
|
309
|
-
description: 'Optional: Title or UID of the page to search in. If not provided, searches across all pages'
|
|
286
|
+
description: 'Optional: Title or UID of the page to search in (UID is preferred for accuracy). If not provided, searches across all pages.'
|
|
310
287
|
}
|
|
311
288
|
},
|
|
312
289
|
required: ['text']
|
|
313
290
|
}
|
|
314
291
|
},
|
|
315
|
-
roam_update_block: {
|
|
316
|
-
name: 'roam_update_block',
|
|
317
|
-
description: 'Update a single block identified by its UID. Use this for individual block updates when you need to either replace the entire content or apply a transform pattern to modify specific parts of the content.\nNOTE 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).',
|
|
318
|
-
inputSchema: {
|
|
319
|
-
type: 'object',
|
|
320
|
-
properties: {
|
|
321
|
-
block_uid: {
|
|
322
|
-
type: 'string',
|
|
323
|
-
description: 'UID of the block to update'
|
|
324
|
-
},
|
|
325
|
-
content: {
|
|
326
|
-
type: 'string',
|
|
327
|
-
description: 'New content for the block. If not provided, transform_pattern will be used.'
|
|
328
|
-
},
|
|
329
|
-
transform_pattern: {
|
|
330
|
-
type: 'object',
|
|
331
|
-
description: 'Pattern to transform the current content. Used if content is not provided.',
|
|
332
|
-
properties: {
|
|
333
|
-
find: {
|
|
334
|
-
type: 'string',
|
|
335
|
-
description: 'Text or regex pattern to find'
|
|
336
|
-
},
|
|
337
|
-
replace: {
|
|
338
|
-
type: 'string',
|
|
339
|
-
description: 'Text to replace with'
|
|
340
|
-
},
|
|
341
|
-
global: {
|
|
342
|
-
type: 'boolean',
|
|
343
|
-
description: 'Whether to replace all occurrences',
|
|
344
|
-
default: true
|
|
345
|
-
}
|
|
346
|
-
},
|
|
347
|
-
required: ['find', 'replace']
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
required: ['block_uid']
|
|
351
|
-
// Note: Validation for either content or transform_pattern is handled in the server code
|
|
352
|
-
}
|
|
353
|
-
},
|
|
354
|
-
roam_update_multiple_blocks: {
|
|
355
|
-
name: 'roam_update_multiple_blocks',
|
|
356
|
-
description: 'Efficiently update multiple blocks in a single batch operation. Use this when you need to update several blocks at once to avoid making multiple separate API calls. Each block in the batch can independently either have its content replaced or transformed using a pattern.\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).',
|
|
357
|
-
inputSchema: {
|
|
358
|
-
type: 'object',
|
|
359
|
-
properties: {
|
|
360
|
-
updates: {
|
|
361
|
-
type: 'array',
|
|
362
|
-
description: 'Array of block updates to perform',
|
|
363
|
-
items: {
|
|
364
|
-
type: 'object',
|
|
365
|
-
properties: {
|
|
366
|
-
block_uid: {
|
|
367
|
-
type: 'string',
|
|
368
|
-
description: 'UID of the block to update'
|
|
369
|
-
},
|
|
370
|
-
content: {
|
|
371
|
-
type: 'string',
|
|
372
|
-
description: 'New content for the block. If not provided, transform will be used.'
|
|
373
|
-
},
|
|
374
|
-
transform: {
|
|
375
|
-
type: 'object',
|
|
376
|
-
description: 'Pattern to transform the current content. Used if content is not provided.',
|
|
377
|
-
properties: {
|
|
378
|
-
find: {
|
|
379
|
-
type: 'string',
|
|
380
|
-
description: 'Text or regex pattern to find'
|
|
381
|
-
},
|
|
382
|
-
replace: {
|
|
383
|
-
type: 'string',
|
|
384
|
-
description: 'Text to replace with'
|
|
385
|
-
},
|
|
386
|
-
global: {
|
|
387
|
-
type: 'boolean',
|
|
388
|
-
description: 'Whether to replace all occurrences',
|
|
389
|
-
default: true
|
|
390
|
-
}
|
|
391
|
-
},
|
|
392
|
-
required: ['find', 'replace']
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
required: ['block_uid']
|
|
396
|
-
// Note: Validation for either content or transform is handled in the server code
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
},
|
|
400
|
-
required: ['updates']
|
|
401
|
-
}
|
|
402
|
-
},
|
|
403
292
|
roam_search_by_date: {
|
|
404
293
|
name: 'roam_search_by_date',
|
|
405
294
|
description: 'Search for blocks or pages based on creation or modification dates. Not for daily pages with ordinal date titles.',
|
|
@@ -493,5 +382,71 @@ export const toolSchemas = {
|
|
|
493
382
|
},
|
|
494
383
|
required: ['query']
|
|
495
384
|
}
|
|
385
|
+
},
|
|
386
|
+
roam_process_batch_actions: {
|
|
387
|
+
name: 'roam_process_batch_actions',
|
|
388
|
+
description: 'Executes a sequence of low-level block actions (create, update, move, delete) in a single, non-transactional batch. Actions are executed in the provided order. For creating nested blocks, you can use a temporary client-side UID in a parent block and refer to it in a child block within the same batch. For actions on existing blocks, a valid block UID is required.',
|
|
389
|
+
inputSchema: {
|
|
390
|
+
type: 'object',
|
|
391
|
+
properties: {
|
|
392
|
+
actions: {
|
|
393
|
+
type: 'array',
|
|
394
|
+
description: 'An array of action objects to execute in order.',
|
|
395
|
+
items: {
|
|
396
|
+
type: 'object',
|
|
397
|
+
properties: {
|
|
398
|
+
"action": {
|
|
399
|
+
type: 'string',
|
|
400
|
+
description: 'The specific action to perform.',
|
|
401
|
+
enum: ['create-block', 'update-block', 'move-block', 'delete-block']
|
|
402
|
+
},
|
|
403
|
+
"uid": {
|
|
404
|
+
type: 'string',
|
|
405
|
+
description: 'The UID of the block to target for "update-block", "move-block", or "delete-block" actions.'
|
|
406
|
+
},
|
|
407
|
+
"string": {
|
|
408
|
+
type: 'string',
|
|
409
|
+
description: 'The content for the block, used in "create-block" and "update-block" actions.'
|
|
410
|
+
},
|
|
411
|
+
"open": {
|
|
412
|
+
type: "boolean",
|
|
413
|
+
description: "Optional: Sets the open/closed state of a block, used in 'update-block' or 'create-block'. Defaults to true."
|
|
414
|
+
},
|
|
415
|
+
"heading": {
|
|
416
|
+
type: "integer",
|
|
417
|
+
description: "Optional: The heading level (1, 2, or 3) for 'create-block' or 'update-block'.",
|
|
418
|
+
enum: [1, 2, 3]
|
|
419
|
+
},
|
|
420
|
+
"text-align": {
|
|
421
|
+
type: "string",
|
|
422
|
+
description: "Optional: The text alignment for 'create-block' or 'update-block'.",
|
|
423
|
+
enum: ["left", "center", "right", "justify"]
|
|
424
|
+
},
|
|
425
|
+
"children-view-type": {
|
|
426
|
+
type: "string",
|
|
427
|
+
description: "Optional: The view type for children of the block, for 'create-block' or 'update-block'.",
|
|
428
|
+
enum: ["bullet", "document", "numbered"]
|
|
429
|
+
},
|
|
430
|
+
"location": {
|
|
431
|
+
type: 'object',
|
|
432
|
+
description: 'Specifies where to place a block, used in "create-block" and "move-block" actions.',
|
|
433
|
+
properties: {
|
|
434
|
+
"parent-uid": {
|
|
435
|
+
type: 'string',
|
|
436
|
+
description: 'The UID of the parent block or page.'
|
|
437
|
+
},
|
|
438
|
+
"order": {
|
|
439
|
+
type: ['number', 'string'],
|
|
440
|
+
description: 'The position of the block under its parent (0 for top, "last" for bottom).'
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
required: ['action']
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
required: ['actions']
|
|
450
|
+
}
|
|
496
451
|
}
|
|
497
452
|
};
|
|
@@ -4,6 +4,7 @@ import { SearchOperations } from './operations/search/index.js';
|
|
|
4
4
|
import { MemoryOperations } from './operations/memory.js';
|
|
5
5
|
import { TodoOperations } from './operations/todos.js';
|
|
6
6
|
import { OutlineOperations } from './operations/outline.js';
|
|
7
|
+
import { BatchOperations } from './operations/batch.js';
|
|
7
8
|
import { DatomicSearchHandlerImpl } from './operations/search/handlers.js';
|
|
8
9
|
export class ToolHandlers {
|
|
9
10
|
constructor(graph) {
|
|
@@ -14,6 +15,7 @@ export class ToolHandlers {
|
|
|
14
15
|
this.memoryOps = new MemoryOperations(graph);
|
|
15
16
|
this.todoOps = new TodoOperations(graph);
|
|
16
17
|
this.outlineOps = new OutlineOperations(graph);
|
|
18
|
+
this.batchOps = new BatchOperations(graph);
|
|
17
19
|
}
|
|
18
20
|
// Page Operations
|
|
19
21
|
async findPagesModifiedToday(max_num_pages = 50) {
|
|
@@ -26,15 +28,6 @@ export class ToolHandlers {
|
|
|
26
28
|
return this.pageOps.fetchPageByTitle(title, format);
|
|
27
29
|
}
|
|
28
30
|
// Block Operations
|
|
29
|
-
async createBlock(content, page_uid, title, heading) {
|
|
30
|
-
return this.blockOps.createBlock(content, page_uid, title, heading);
|
|
31
|
-
}
|
|
32
|
-
async updateBlock(block_uid, content, transform) {
|
|
33
|
-
return this.blockOps.updateBlock(block_uid, content, transform);
|
|
34
|
-
}
|
|
35
|
-
async updateBlocks(updates) {
|
|
36
|
-
return this.blockOps.updateBlocks(updates);
|
|
37
|
-
}
|
|
38
31
|
// Search Operations
|
|
39
32
|
async searchByStatus(status, page_title_uid, include, exclude) {
|
|
40
33
|
return this.searchOps.searchByStatus(status, page_title_uid, include, exclude);
|
|
@@ -77,4 +70,8 @@ export class ToolHandlers {
|
|
|
77
70
|
async importMarkdown(content, page_uid, page_title, parent_uid, parent_string, order = 'first') {
|
|
78
71
|
return this.outlineOps.importMarkdown(content, page_uid, page_title, parent_uid, parent_string, order);
|
|
79
72
|
}
|
|
73
|
+
// Batch Operations
|
|
74
|
+
async processBatch(actions) {
|
|
75
|
+
return this.batchOps.processBatch(actions);
|
|
76
|
+
}
|
|
80
77
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
/**
|
|
3
|
+
* Checks if a given port is currently in use.
|
|
4
|
+
* @param port The port to check.
|
|
5
|
+
* @returns A promise that resolves to true if the port is in use, and false otherwise.
|
|
6
|
+
*/
|
|
7
|
+
export function isPortInUse(port) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
const server = createServer();
|
|
10
|
+
server.once('error', (err) => {
|
|
11
|
+
if (err.code === 'EADDRINUSE') {
|
|
12
|
+
resolve(true);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
// Handle other errors if necessary, but for this check, we assume other errors mean the port is available.
|
|
16
|
+
resolve(false);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
server.once('listening', () => {
|
|
20
|
+
server.close();
|
|
21
|
+
resolve(false);
|
|
22
|
+
});
|
|
23
|
+
server.listen(port);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Finds an available port, starting from a given port and incrementing by a specified amount.
|
|
28
|
+
* @param startPort The port to start checking from.
|
|
29
|
+
* @param incrementBy The amount to increment the port by if it's in use. Defaults to 2.
|
|
30
|
+
* @returns A promise that resolves to an available port number.
|
|
31
|
+
*/
|
|
32
|
+
export async function findAvailablePort(startPort, incrementBy = 2) {
|
|
33
|
+
let port = startPort;
|
|
34
|
+
while (await isPortInUse(port)) {
|
|
35
|
+
port += incrementBy;
|
|
36
|
+
}
|
|
37
|
+
return port;
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roam-research-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.1",
|
|
4
4
|
"description": "A Model Context Protocol (MCP) server for Roam Research API integration",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -29,13 +29,12 @@
|
|
|
29
29
|
],
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsc && chmod 755 build/index.js",
|
|
32
|
-
"prepare": "npm run build",
|
|
33
32
|
"watch": "tsc --watch",
|
|
34
33
|
"inspector": "npx @modelcontextprotocol/inspector build/index.js",
|
|
35
34
|
"start": "node build/index.js"
|
|
36
35
|
},
|
|
37
36
|
"dependencies": {
|
|
38
|
-
"@modelcontextprotocol/sdk": "0.
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
39
38
|
"@roam-research/roam-api-sdk": "^0.10.0",
|
|
40
39
|
"dotenv": "^16.4.7"
|
|
41
40
|
},
|