roam-research-mcp 0.17.0 → 0.18.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 +53 -2
- package/build/server/roam-server.js +10 -2
- package/build/tools/schemas.js +33 -0
- package/build/tools/tool-handlers.js +53 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Roam Research MCP Server
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/roam-research-mcp)
|
|
4
|
+
[](https://www.repostatus.org/#wip)
|
|
4
5
|
[](https://opensource.org/licenses/MIT)
|
|
5
6
|
[](https://github.com/2b3pro/roam-research-mcp/blob/main/LICENSE)
|
|
6
7
|
|
|
7
|
-
A Model Context Protocol (MCP) server that provides comprehensive access to Roam Research's API functionality. This server enables AI assistants like Claude to interact with your Roam Research graph through a standardized interface.
|
|
8
|
+
A Model Context Protocol (MCP) server that provides comprehensive access to Roam Research's API functionality. This server enables AI assistants like Claude to interact with your Roam Research graph through a standardized interface. (A WORK-IN-PROGRESS)
|
|
8
9
|
|
|
9
10
|
<a href="https://glama.ai/mcp/servers/fzfznyaflu"><img width="380" height="200" src="https://glama.ai/mcp/servers/fzfznyaflu/badge" alt="Roam Research MCP server" /></a>
|
|
10
11
|
|
|
@@ -27,7 +28,7 @@ npm run build
|
|
|
27
28
|
|
|
28
29
|
## Features
|
|
29
30
|
|
|
30
|
-
The server provides
|
|
31
|
+
The server provides twelve powerful tools for interacting with Roam Research:
|
|
31
32
|
|
|
32
33
|
1. `roam_fetch_page_by_title`: Fetch and read a page's content by title, recursively resolving block references up to 4 levels deep
|
|
33
34
|
2. `roam_create_page`: Create new pages with optional content
|
|
@@ -40,6 +41,7 @@ The server provides eleven powerful tools for interacting with Roam Research:
|
|
|
40
41
|
9. `find_pages_modified_today`: Find all pages that have been modified since midnight today
|
|
41
42
|
10. `roam_search_by_text`: Search for blocks containing specific text across all pages or within a specific page
|
|
42
43
|
11. `roam_update_block`: Update block content with direct text or pattern-based transformations
|
|
44
|
+
12. `roam_search_by_date`: Search for blocks and pages based on creation or modification dates
|
|
43
45
|
|
|
44
46
|
## Setup
|
|
45
47
|
|
|
@@ -419,6 +421,55 @@ Returns:
|
|
|
419
421
|
}
|
|
420
422
|
```
|
|
421
423
|
|
|
424
|
+
### Search By Date
|
|
425
|
+
|
|
426
|
+
Search for blocks and pages based on creation or modification dates:
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
use_mcp_tool roam-research roam_search_by_date {
|
|
430
|
+
"start_date": "2025-01-01",
|
|
431
|
+
"end_date": "2025-01-31",
|
|
432
|
+
"type": "modified",
|
|
433
|
+
"scope": "blocks",
|
|
434
|
+
"include_content": true
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Features:
|
|
439
|
+
|
|
440
|
+
- Search by creation date, modification date, or both
|
|
441
|
+
- Filter blocks, pages, or both
|
|
442
|
+
- Optional date range with start and end dates
|
|
443
|
+
- Include or exclude block/page content in results
|
|
444
|
+
- Sort results by timestamp
|
|
445
|
+
- Efficient date-based filtering using Datalog queries
|
|
446
|
+
|
|
447
|
+
Parameters:
|
|
448
|
+
|
|
449
|
+
- `start_date`: Start date in ISO format (YYYY-MM-DD) (required)
|
|
450
|
+
- `end_date`: End date in ISO format (YYYY-MM-DD) (optional)
|
|
451
|
+
- `type`: Whether to search by 'created', 'modified', or 'both' (required)
|
|
452
|
+
- `scope`: Whether to search 'blocks', 'pages', or 'both' (required)
|
|
453
|
+
- `include_content`: Whether to include the content of matching blocks/pages (optional, default: true)
|
|
454
|
+
|
|
455
|
+
Returns:
|
|
456
|
+
|
|
457
|
+
```json
|
|
458
|
+
{
|
|
459
|
+
"success": true,
|
|
460
|
+
"matches": [
|
|
461
|
+
{
|
|
462
|
+
"uid": "block-or-page-uid",
|
|
463
|
+
"type": "block",
|
|
464
|
+
"time": 1704067200000,
|
|
465
|
+
"content": "Block or page content",
|
|
466
|
+
"page_title": "Page title (for blocks)"
|
|
467
|
+
}
|
|
468
|
+
],
|
|
469
|
+
"message": "Found N matches for the given date range and criteria"
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
422
473
|
### Find Pages Modified Today
|
|
423
474
|
|
|
424
475
|
Find all pages that have been modified since midnight today:
|
|
@@ -18,7 +18,7 @@ export class RoamServer {
|
|
|
18
18
|
this.toolHandlers = new ToolHandlers(this.graph);
|
|
19
19
|
this.server = new Server({
|
|
20
20
|
name: 'roam-research',
|
|
21
|
-
version: '0.
|
|
21
|
+
version: '0.17.0',
|
|
22
22
|
}, {
|
|
23
23
|
capabilities: {
|
|
24
24
|
tools: {
|
|
@@ -34,7 +34,8 @@ export class RoamServer {
|
|
|
34
34
|
roam_search_hierarchy: {},
|
|
35
35
|
find_pages_modified_today: {},
|
|
36
36
|
roam_search_by_text: {},
|
|
37
|
-
roam_update_block: {}
|
|
37
|
+
roam_update_block: {},
|
|
38
|
+
roam_search_by_date: {}
|
|
38
39
|
},
|
|
39
40
|
},
|
|
40
41
|
});
|
|
@@ -142,6 +143,13 @@ export class RoamServer {
|
|
|
142
143
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
143
144
|
};
|
|
144
145
|
}
|
|
146
|
+
case 'roam_search_by_date': {
|
|
147
|
+
const params = request.params.arguments;
|
|
148
|
+
const result = await this.toolHandlers.searchByDate(params);
|
|
149
|
+
return {
|
|
150
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
145
153
|
case 'roam_update_block': {
|
|
146
154
|
const { block_uid, content, transform_pattern } = request.params.arguments;
|
|
147
155
|
let result;
|
package/build/tools/schemas.js
CHANGED
|
@@ -313,5 +313,38 @@ export const toolSchemas = {
|
|
|
313
313
|
{ required: ['transform_pattern'] }
|
|
314
314
|
]
|
|
315
315
|
}
|
|
316
|
+
},
|
|
317
|
+
roam_search_by_date: {
|
|
318
|
+
name: 'roam_search_by_date',
|
|
319
|
+
description: 'Search for blocks or pages based on creation or modification dates',
|
|
320
|
+
inputSchema: {
|
|
321
|
+
type: 'object',
|
|
322
|
+
properties: {
|
|
323
|
+
start_date: {
|
|
324
|
+
type: 'string',
|
|
325
|
+
description: 'Start date in ISO format (YYYY-MM-DD)',
|
|
326
|
+
},
|
|
327
|
+
end_date: {
|
|
328
|
+
type: 'string',
|
|
329
|
+
description: 'Optional: End date in ISO format (YYYY-MM-DD)',
|
|
330
|
+
},
|
|
331
|
+
type: {
|
|
332
|
+
type: 'string',
|
|
333
|
+
enum: ['created', 'modified', 'both'],
|
|
334
|
+
description: 'Whether to search by creation date, modification date, or both',
|
|
335
|
+
},
|
|
336
|
+
scope: {
|
|
337
|
+
type: 'string',
|
|
338
|
+
enum: ['blocks', 'pages', 'both'],
|
|
339
|
+
description: 'Whether to search blocks, pages, or both',
|
|
340
|
+
},
|
|
341
|
+
include_content: {
|
|
342
|
+
type: 'boolean',
|
|
343
|
+
description: 'Whether to include the content of matching blocks/pages',
|
|
344
|
+
default: true,
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
required: ['start_date', 'type', 'scope']
|
|
348
|
+
}
|
|
316
349
|
}
|
|
317
350
|
};
|
|
@@ -900,6 +900,59 @@ export class ToolHandlers {
|
|
|
900
900
|
message: `Found ${matches.length} block(s) containing ${primaryTagFormatted}${nearTagFormatted ? ` near ${nearTagFormatted}` : ''}`
|
|
901
901
|
};
|
|
902
902
|
}
|
|
903
|
+
async searchByDate(params) {
|
|
904
|
+
// Convert dates to timestamps
|
|
905
|
+
const startTimestamp = new Date(`${params.start_date}T00:00:00`).getTime();
|
|
906
|
+
const endTimestamp = params.end_date ? new Date(`${params.end_date}T23:59:59`).getTime() : undefined;
|
|
907
|
+
// Define rule for entity type
|
|
908
|
+
const entityRule = `[
|
|
909
|
+
[(block? ?e)
|
|
910
|
+
[?e :block/string]
|
|
911
|
+
[?e :block/page ?p]
|
|
912
|
+
[?p :node/title]]
|
|
913
|
+
[(page? ?e)
|
|
914
|
+
[?e :node/title]]
|
|
915
|
+
]`;
|
|
916
|
+
// Build query based on cheatsheet pattern
|
|
917
|
+
const timeAttr = params.type === 'created' ? ':create/time' : ':edit/time';
|
|
918
|
+
let queryStr = `[:find ?block-uid ?string ?time ?page-title
|
|
919
|
+
:in $ ?start-ts ${endTimestamp ? '?end-ts' : ''}
|
|
920
|
+
:where
|
|
921
|
+
[?b ${timeAttr} ?time]
|
|
922
|
+
[(>= ?time ?start-ts)]
|
|
923
|
+
${endTimestamp ? '[(<= ?time ?end-ts)]' : ''}
|
|
924
|
+
[?b :block/uid ?block-uid]
|
|
925
|
+
[?b :block/string ?string]
|
|
926
|
+
[?b :block/page ?p]
|
|
927
|
+
[?p :node/title ?page-title]]`;
|
|
928
|
+
// Execute query
|
|
929
|
+
const queryParams = endTimestamp ?
|
|
930
|
+
[startTimestamp, endTimestamp] :
|
|
931
|
+
[startTimestamp];
|
|
932
|
+
const results = await q(this.graph, queryStr, queryParams);
|
|
933
|
+
if (!results || results.length === 0) {
|
|
934
|
+
return {
|
|
935
|
+
success: true,
|
|
936
|
+
matches: [],
|
|
937
|
+
message: 'No matches found for the given date range and criteria'
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
// Process results - now we get [block-uid, string, time, page-title]
|
|
941
|
+
const matches = results.map(([uid, content, time, pageTitle]) => ({
|
|
942
|
+
uid,
|
|
943
|
+
type: 'block',
|
|
944
|
+
time,
|
|
945
|
+
...(params.include_content && { content }),
|
|
946
|
+
page_title: pageTitle
|
|
947
|
+
}));
|
|
948
|
+
// Sort by time
|
|
949
|
+
const sortedMatches = matches.sort((a, b) => b.time - a.time);
|
|
950
|
+
return {
|
|
951
|
+
success: true,
|
|
952
|
+
matches: sortedMatches,
|
|
953
|
+
message: `Found ${sortedMatches.length} matches for the given date range and criteria`
|
|
954
|
+
};
|
|
955
|
+
}
|
|
903
956
|
async addTodos(todos) {
|
|
904
957
|
if (!Array.isArray(todos) || todos.length === 0) {
|
|
905
958
|
throw new McpError(ErrorCode.InvalidRequest, 'todos must be a non-empty array');
|