roam-research-mcp 0.24.1 → 0.24.2

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 CHANGED
@@ -30,6 +30,16 @@ npm run build
30
30
 
31
31
  The server provides powerful tools for interacting with Roam Research:
32
32
 
33
+ - Environment variable handling with .env support
34
+ - Comprehensive input validation
35
+ - Case-insensitive page title matching
36
+ - Recursive block reference resolution
37
+ - Markdown parsing and conversion
38
+ - Daily page integration
39
+ - Detailed debug logging
40
+ - Efficient batch operations
41
+ - Hierarchical outline creation
42
+
33
43
  1. `roam_fetch_page_by_title`: Fetch and read a page's content by title, recursively resolving block references up to 4 levels deep
34
44
  2. `roam_create_page`: Create new pages with optional content
35
45
  3. `roam_create_block`: Create new blocks in a page (defaults to today's daily page)
@@ -38,7 +48,7 @@ The server provides powerful tools for interacting with Roam Research:
38
48
  6. `roam_create_outline`: Create hierarchical outlines with proper nesting and structure
39
49
  7. `roam_search_block_refs`: Search for block references within pages or across the graph
40
50
  8. `roam_search_hierarchy`: Navigate and search through block parent-child relationships
41
- 9. `find_pages_modified_today`: Find all pages that have been modified since midnight today
51
+ 9. `roam_find_pages_modified_today`: Find all pages that have been modified since midnight today
42
52
  10. `roam_search_by_text`: Search for blocks containing specific text across all pages or within a specific page
43
53
  11. `roam_update_block`: Update block content with direct text or pattern-based transformations
44
54
  12. `roam_search_by_date`: Search for blocks and pages based on creation or modification dates
@@ -545,7 +555,7 @@ Returns:
545
555
  Find all pages that have been modified since midnight today:
546
556
 
547
557
  ```typescript
548
- use_mcp_tool roam-research find_pages_modified_today {}
558
+ use_mcp_tool roam-research roam_find_pages_modified_today {}
549
559
  ```
550
560
 
551
561
  Features:
@@ -717,25 +727,39 @@ Each error response includes:
717
727
 
718
728
  ## Development
719
729
 
720
- The server is built with TypeScript and includes:
730
+ ### Building
721
731
 
722
- - Environment variable handling with .env support
723
- - Comprehensive input validation
724
- - Case-insensitive page title matching
725
- - Recursive block reference resolution
726
- - Markdown parsing and conversion
727
- - Daily page integration
728
- - Detailed debug logging
729
- - Efficient batch operations
730
- - Hierarchical outline creation
732
+ To build the server:
733
+
734
+ ```bash
735
+ npm install
736
+ npm run build
737
+ ```
738
+
739
+ This will:
740
+
741
+ 1. Install all required dependencies
742
+ 2. Compile TypeScript to JavaScript
743
+ 3. Make the output file executable
744
+
745
+ You can also use `npm run watch` during development to automatically recompile when files change.
746
+
747
+ ### Testing with MCP Inspector
748
+
749
+ The MCP Inspector is a tool that helps test and debug MCP servers. To test the server:
750
+
751
+ ```bash
752
+ # Inspect with npx:
753
+ npx @modelcontextprotocol/inspector node build/index.js
754
+ ```
731
755
 
732
- To modify or extend the server:
756
+ This will:
733
757
 
734
- 1. Clone the repository
735
- 2. Install dependencies with `npm install`
736
- 3. Make changes to the source files
737
- 4. Build with `npm run build`
738
- 5. Test locally by configuring environment variables
758
+ 1. Start the server in inspector mode
759
+ 2. Provide an interactive interface to:
760
+ - List available tools and resources
761
+ - Execute tools with custom parameters
762
+ - View tool responses and error handling
739
763
 
740
764
  ## License
741
765
 
@@ -17,7 +17,7 @@ export class RoamServer {
17
17
  this.toolHandlers = new ToolHandlers(this.graph);
18
18
  this.server = new Server({
19
19
  name: 'roam-research',
20
- version: '0.17.0',
20
+ version: '0.24.2',
21
21
  }, {
22
22
  capabilities: {
23
23
  tools: {
@@ -33,7 +33,7 @@ export class RoamServer {
33
33
  roam_search_by_status: {},
34
34
  roam_search_block_refs: {},
35
35
  roam_search_hierarchy: {},
36
- find_pages_modified_today: {},
36
+ roam_find_pages_modified_today: {},
37
37
  roam_search_by_text: {},
38
38
  roam_update_block: {},
39
39
  roam_update_blocks: {},
@@ -136,7 +136,7 @@ export class RoamServer {
136
136
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
137
137
  };
138
138
  }
139
- case 'find_pages_modified_today': {
139
+ case 'roam_find_pages_modified_today': {
140
140
  const result = await this.toolHandlers.findPagesModifiedToday();
141
141
  return {
142
142
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
@@ -173,7 +173,8 @@ export class RoamServer {
173
173
  };
174
174
  }
175
175
  case 'roam_recall': {
176
- const result = await this.toolHandlers.recall();
176
+ const { sort_by = 'newest', filter_tag } = request.params.arguments;
177
+ const result = await this.toolHandlers.recall(sort_by, filter_tag);
177
178
  return {
178
179
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
179
180
  };
@@ -66,7 +66,7 @@ export class MemoryOperations {
66
66
  }
67
67
  return { success: true };
68
68
  }
69
- async recall() {
69
+ async recall(sort_by = 'newest', filter_tag) {
70
70
  // Get memories tag from environment
71
71
  var memoriesTag = process.env.MEMORIES_TAG;
72
72
  if (!memoriesTag) {
@@ -76,27 +76,73 @@ export class MemoryOperations {
76
76
  const tagText = memoriesTag
77
77
  .replace(/^#/, '') // Remove leading #
78
78
  .replace(/^\[\[/, '').replace(/\]\]$/, ''); // Remove [[ and ]]
79
- // Get results from tag search
80
- const tagResults = await this.searchOps.searchForTag(tagText);
81
- // Get blocks from the memories page
82
- const pageQuery = `[:find ?string
83
- :in $ ?title
84
- :where [?p :node/title ?title]
85
- [?b :block/page ?p]
86
- [?b :block/string ?string]]`;
87
- const pageResults = await q(this.graph, pageQuery, [tagText]);
88
- // Combine both sets of results and remove the memories tag
89
- const allMemories = [
90
- ...tagResults.matches.map((match) => match.content),
91
- ...pageResults.map(([content]) => content)
92
- ].map(content => content.replace(`${memoriesTag} `, ''));
93
- // Resolve any block references in the combined memories
94
- const resolvedMemories = await Promise.all(allMemories.map(async (content) => resolveRefs(this.graph, content)));
95
- // Remove duplicates
96
- const uniqueMemories = [...new Set(resolvedMemories)];
97
- return {
98
- success: true,
99
- memories: uniqueMemories
100
- };
79
+ try {
80
+ // Get page blocks using query to access actual block content
81
+ const ancestorRule = `[
82
+ [ (ancestor ?b ?a)
83
+ [?a :block/children ?b] ]
84
+ [ (ancestor ?b ?a)
85
+ [?parent :block/children ?b]
86
+ (ancestor ?parent ?a) ]
87
+ ]`;
88
+ // Query to find all blocks on the page
89
+ const pageQuery = `[:find ?string ?time
90
+ :in $ % ?title
91
+ :where
92
+ [?page :node/title ?title]
93
+ [?block :block/string ?string]
94
+ [?block :create/time ?time]
95
+ (ancestor ?block ?page)]`;
96
+ // Execute query
97
+ const pageResults = await q(this.graph, pageQuery, [ancestorRule, tagText]);
98
+ // Process page blocks with sorting
99
+ let pageMemories = pageResults
100
+ .sort(([_, aTime], [__, bTime]) => sort_by === 'newest' ? bTime - aTime : aTime - bTime)
101
+ .map(([content]) => content);
102
+ // Get tagged blocks from across the graph
103
+ const tagResults = await this.searchOps.searchForTag(tagText);
104
+ // Process tagged blocks with sorting
105
+ let taggedMemories = tagResults.matches
106
+ .sort((a, b) => {
107
+ const aTime = a.block_uid ? parseInt(a.block_uid.split('-')[0], 16) : 0;
108
+ const bTime = b.block_uid ? parseInt(b.block_uid.split('-')[0], 16) : 0;
109
+ return sort_by === 'newest' ? bTime - aTime : aTime - bTime;
110
+ })
111
+ .map(match => match.content);
112
+ // Resolve any block references in both sets
113
+ const resolvedPageMemories = await Promise.all(pageMemories.map(async (content) => resolveRefs(this.graph, content)));
114
+ const resolvedTaggedMemories = await Promise.all(taggedMemories.map(async (content) => resolveRefs(this.graph, content)));
115
+ // Combine both sets and remove duplicates while preserving order
116
+ let uniqueMemories = [
117
+ ...resolvedPageMemories,
118
+ ...resolvedTaggedMemories
119
+ ].filter((memory, index, self) => self.indexOf(memory) === index);
120
+ // Format filter tag with exact Roam tag syntax
121
+ const filterTagFormatted = filter_tag ?
122
+ (filter_tag.includes(' ') ? `#[[${filter_tag}]]` : `#${filter_tag}`) : null;
123
+ // Filter by exact tag match if provided
124
+ if (filterTagFormatted) {
125
+ uniqueMemories = uniqueMemories.filter(memory => memory.includes(filterTagFormatted));
126
+ }
127
+ // Format memories tag for removal and clean up memories tag
128
+ const memoriesTagFormatted = tagText.includes(' ') || tagText.includes('/') ? `#[[${tagText}]]` : `#${tagText}`;
129
+ uniqueMemories = uniqueMemories.map(memory => memory.replace(memoriesTagFormatted, '').trim());
130
+ // return {
131
+ // success: true,
132
+ // memories: [
133
+ // `memoriesTag = ${memoriesTag}`,
134
+ // `filter_tag = ${filter_tag}`,
135
+ // `filterTagFormatted = ${filterTagFormatted}`,
136
+ // `memoriesTagFormatted = ${memoriesTagFormatted}`,
137
+ // ]
138
+ // }
139
+ return {
140
+ success: true,
141
+ memories: uniqueMemories
142
+ };
143
+ }
144
+ catch (error) {
145
+ throw new McpError(ErrorCode.InternalError, `Failed to recall memories: ${error.message}`);
146
+ }
101
147
  }
102
148
  }
@@ -8,7 +8,7 @@ export class PageOperations {
8
8
  constructor(graph) {
9
9
  this.graph = graph;
10
10
  }
11
- async findPagesModifiedToday() {
11
+ async findPagesModifiedToday(num_pages = 10) {
12
12
  // Define ancestor rule for traversing block hierarchy
13
13
  const ancestorRule = `[
14
14
  [ (ancestor ?b ?a)
@@ -28,7 +28,8 @@ export class PageOperations {
28
28
  [?page :node/title ?title]
29
29
  (ancestor ?block ?page)
30
30
  [?block :edit/time ?time]
31
- [(> ?time ?start_of_day)]]`, [startOfDay.getTime(), ancestorRule]);
31
+ [(> ?time ?start_of_day)]]
32
+ :limit ${num_pages}`, [startOfDay.getTime(), ancestorRule]);
32
33
  if (!results || results.length === 0) {
33
34
  return {
34
35
  success: true,
@@ -243,13 +243,19 @@ export const toolSchemas = {
243
243
  ]
244
244
  }
245
245
  },
246
- find_pages_modified_today: {
247
- name: 'find_pages_modified_today',
248
- description: 'Find all pages that have been modified today (since midnight).',
246
+ roam_find_pages_modified_today: {
247
+ name: 'roam_find_pages_modified_today',
248
+ description: 'Find pages that have been modified today (since midnight), with limit.',
249
249
  inputSchema: {
250
250
  type: 'object',
251
- properties: {},
252
- required: []
251
+ properties: {
252
+ num_pages: {
253
+ type: 'integer',
254
+ description: 'Number of result pages to retrieve (default: 10)',
255
+ default: 10
256
+ },
257
+ },
258
+ required: ['num_pages']
253
259
  }
254
260
  },
255
261
  roam_search_by_text: {
@@ -399,13 +405,13 @@ export const toolSchemas = {
399
405
  },
400
406
  roam_remember: {
401
407
  name: 'roam_remember',
402
- description: 'Add a memory or piece of information to remember, stored on the daily page with #[[LLM/Memories]] 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).',
408
+ 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).',
403
409
  inputSchema: {
404
410
  type: 'object',
405
411
  properties: {
406
412
  memory: {
407
413
  type: 'string',
408
- description: 'The memory or information to remember'
414
+ description: 'The memory detail or information to remember'
409
415
  },
410
416
  categories: {
411
417
  type: 'array',
@@ -420,11 +426,21 @@ export const toolSchemas = {
420
426
  },
421
427
  roam_recall: {
422
428
  name: 'roam_recall',
423
- description: 'Retrieve all stored memories by searching for blocks tagged with MEMORIES_TAG and content from the page with the same name. Returns a combined, deduplicated list of memories.',
429
+ 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 specified tag and sort by creation date.',
424
430
  inputSchema: {
425
431
  type: 'object',
426
- properties: {},
427
- required: []
432
+ properties: {
433
+ sort_by: {
434
+ type: 'string',
435
+ description: 'Sort order for memories based on creation date',
436
+ enum: ['newest', 'oldest'],
437
+ default: 'newest'
438
+ },
439
+ filter_tag: {
440
+ type: 'string',
441
+ 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)'
442
+ }
443
+ }
428
444
  }
429
445
  },
430
446
  roam_datomic_query: {
@@ -23,8 +23,8 @@ export class ToolHandlers {
23
23
  this.outlineOps = new OutlineOperations(graph);
24
24
  }
25
25
  // Page Operations
26
- async findPagesModifiedToday() {
27
- return this.pageOps.findPagesModifiedToday();
26
+ async findPagesModifiedToday(num_pages = 10) {
27
+ return this.pageOps.findPagesModifiedToday(num_pages);
28
28
  }
29
29
  async createPage(title, content) {
30
30
  return this.pageOps.createPage(title, content);
@@ -70,8 +70,8 @@ export class ToolHandlers {
70
70
  async remember(memory, categories) {
71
71
  return this.memoryOps.remember(memory, categories);
72
72
  }
73
- async recall() {
74
- return this.memoryOps.recall();
73
+ async recall(sort_by = 'newest', filter_tag) {
74
+ return this.memoryOps.recall(sort_by, filter_tag);
75
75
  }
76
76
  // Todo Operations
77
77
  async addTodos(todos) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roam-research-mcp",
3
- "version": "0.24.1",
3
+ "version": "0.24.2",
4
4
  "description": "A Model Context Protocol (MCP) server for Roam Research API integration",
5
5
  "private": false,
6
6
  "repository": {