roam-research-mcp 0.23.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
 
@@ -1,6 +1,7 @@
1
1
  import { q } from '@roam-research/roam-api-sdk';
2
2
  import { BaseSearchHandler } from './types.js';
3
3
  import { SearchUtils } from './utils.js';
4
+ import { resolveRefs } from '../tools/helpers/refs.js';
4
5
  export class BlockRefSearchHandler extends BaseSearchHandler {
5
6
  params;
6
7
  constructor(graph, params) {
@@ -63,10 +64,15 @@ export class BlockRefSearchHandler extends BaseSearchHandler {
63
64
  queryParams = [];
64
65
  }
65
66
  }
66
- const results = await q(this.graph, queryStr, queryParams);
67
+ const rawResults = await q(this.graph, queryStr, queryParams);
68
+ // Resolve block references in content
69
+ const resolvedResults = await Promise.all(rawResults.map(async ([uid, content, pageTitle]) => {
70
+ const resolvedContent = await resolveRefs(this.graph, content);
71
+ return [uid, resolvedContent, pageTitle];
72
+ }));
67
73
  const searchDescription = block_uid
68
74
  ? `referencing block ((${block_uid}))`
69
75
  : 'containing block references';
70
- return SearchUtils.formatSearchResults(results, searchDescription, !targetPageUid);
76
+ return SearchUtils.formatSearchResults(resolvedResults, searchDescription, !targetPageUid);
71
77
  }
72
78
  }
@@ -1,6 +1,7 @@
1
1
  import { q } from '@roam-research/roam-api-sdk';
2
2
  import { BaseSearchHandler } from './types.js';
3
3
  import { SearchUtils } from './utils.js';
4
+ import { resolveRefs } from '../tools/helpers/refs.js';
4
5
  export class HierarchySearchHandler extends BaseSearchHandler {
5
6
  params;
6
7
  constructor(graph, params) {
@@ -85,13 +86,16 @@ export class HierarchySearchHandler extends BaseSearchHandler {
85
86
  queryParams = [ancestorRule, child_uid];
86
87
  }
87
88
  }
88
- const results = await q(this.graph, queryStr, queryParams);
89
- // Format results to include depth information
90
- const matches = results.map(([uid, content, pageTitle, depth]) => ({
91
- block_uid: uid,
92
- content,
93
- depth: depth || 1,
94
- ...(pageTitle && { page_title: pageTitle })
89
+ const rawResults = await q(this.graph, queryStr, queryParams);
90
+ // Resolve block references and format results to include depth information
91
+ const matches = await Promise.all(rawResults.map(async ([uid, content, pageTitle, depth]) => {
92
+ const resolvedContent = await resolveRefs(this.graph, content);
93
+ return {
94
+ block_uid: uid,
95
+ content: resolvedContent,
96
+ depth: depth || 1,
97
+ ...(pageTitle && { page_title: pageTitle })
98
+ };
95
99
  }));
96
100
  const searchDescription = parent_uid
97
101
  ? `descendants of block ${parent_uid}`
@@ -1,6 +1,7 @@
1
1
  import { q } from '@roam-research/roam-api-sdk';
2
2
  import { BaseSearchHandler } from './types.js';
3
3
  import { SearchUtils } from './utils.js';
4
+ import { resolveRefs } from '../tools/helpers/refs.js';
4
5
  export class StatusSearchHandler extends BaseSearchHandler {
5
6
  params;
6
7
  constructor(graph, params) {
@@ -37,7 +38,12 @@ export class StatusSearchHandler extends BaseSearchHandler {
37
38
  [(clojure.string/includes? ?block-str (str "{{[[" ?status "]]}}"))]]`;
38
39
  queryParams = [status];
39
40
  }
40
- const results = await q(this.graph, queryStr, queryParams);
41
- return SearchUtils.formatSearchResults(results, `with status ${status}`, !targetPageUid);
41
+ const rawResults = await q(this.graph, queryStr, queryParams);
42
+ // Resolve block references in content
43
+ const resolvedResults = await Promise.all(rawResults.map(async ([uid, content, pageTitle]) => {
44
+ const resolvedContent = await resolveRefs(this.graph, content);
45
+ return [uid, resolvedContent, pageTitle];
46
+ }));
47
+ return SearchUtils.formatSearchResults(resolvedResults, `with status ${status}`, !targetPageUid);
42
48
  }
43
49
  }
@@ -1,6 +1,7 @@
1
1
  import { q } from '@roam-research/roam-api-sdk';
2
2
  import { BaseSearchHandler } from './types.js';
3
3
  import { SearchUtils } from './utils.js';
4
+ import { resolveRefs } from '../tools/helpers/refs.js';
4
5
  export class TagSearchHandler extends BaseSearchHandler {
5
6
  params;
6
7
  constructor(graph, params) {
@@ -28,8 +29,13 @@ export class TagSearchHandler extends BaseSearchHandler {
28
29
  [?b :block/page ?p]
29
30
  [?p :node/title ?page-title]]`;
30
31
  const queryParams = [primary_tag];
31
- const results = await q(this.graph, queryStr, queryParams);
32
+ const rawResults = await q(this.graph, queryStr, queryParams);
33
+ // Resolve block references in content
34
+ const resolvedResults = await Promise.all(rawResults.map(async ([uid, content, pageTitle]) => {
35
+ const resolvedContent = await resolveRefs(this.graph, content);
36
+ return [uid, resolvedContent, pageTitle];
37
+ }));
32
38
  const searchDescription = `referencing "${primary_tag}"`;
33
- return SearchUtils.formatSearchResults(results, searchDescription, !targetPageUid);
39
+ return SearchUtils.formatSearchResults(resolvedResults, searchDescription, !targetPageUid);
34
40
  }
35
41
  }
@@ -1,6 +1,7 @@
1
1
  import { q } from '@roam-research/roam-api-sdk';
2
2
  import { BaseSearchHandler } from './types.js';
3
3
  import { SearchUtils } from './utils.js';
4
+ import { resolveRefs } from '../tools/helpers/refs.js';
4
5
  export class TextSearchHandler extends BaseSearchHandler {
5
6
  params;
6
7
  constructor(graph, params) {
@@ -24,8 +25,13 @@ export class TextSearchHandler extends BaseSearchHandler {
24
25
  [?b :block/page ?p]
25
26
  [?p :node/title ?page-title]]`;
26
27
  const queryParams = [text];
27
- const results = await q(this.graph, queryStr, queryParams);
28
+ const rawResults = await q(this.graph, queryStr, queryParams);
29
+ // Resolve block references in content
30
+ const resolvedResults = await Promise.all(rawResults.map(async ([uid, content, pageTitle]) => {
31
+ const resolvedContent = await resolveRefs(this.graph, content);
32
+ return [uid, resolvedContent, pageTitle];
33
+ }));
28
34
  const searchDescription = `containing "${text}"`;
29
- return SearchUtils.formatSearchResults(results, searchDescription, !targetPageUid);
35
+ return SearchUtils.formatSearchResults(resolvedResults, searchDescription, !targetPageUid);
30
36
  }
31
37
  }
@@ -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,
@@ -87,7 +88,7 @@ export class PageOperations {
87
88
  // Use import_nested_markdown functionality
88
89
  const convertedContent = convertToRoamMarkdown(content);
89
90
  const nodes = parseMarkdown(convertedContent);
90
- const actions = convertToRoamActions(nodes, pageUid, 'last');
91
+ const actions = convertToRoamActions(nodes, pageUid, 'first');
91
92
  const result = await batchActions(this.graph, {
92
93
  action: 'batch-actions',
93
94
  actions
@@ -1,4 +1,4 @@
1
- import { TagSearchHandler, BlockRefSearchHandler, HierarchySearchHandler, TextSearchHandler, DatomicSearchHandler } from '../../../search/index.js';
1
+ import { TagSearchHandler, BlockRefSearchHandler, HierarchySearchHandler, TextSearchHandler, DatomicSearchHandler, StatusSearchHandler } from '../../../search/index.js';
2
2
  // Base class for all search handlers
3
3
  export class BaseSearchHandler {
4
4
  graph;
@@ -54,6 +54,18 @@ export class TextSearchHandlerImpl extends BaseSearchHandler {
54
54
  return handler.execute();
55
55
  }
56
56
  }
57
+ // Status search handler
58
+ export class StatusSearchHandlerImpl extends BaseSearchHandler {
59
+ params;
60
+ constructor(graph, params) {
61
+ super(graph);
62
+ this.params = params;
63
+ }
64
+ async execute() {
65
+ const handler = new StatusSearchHandler(this.graph, this.params);
66
+ return handler.execute();
67
+ }
68
+ }
57
69
  // Datomic query handler
58
70
  export class DatomicSearchHandlerImpl extends BaseSearchHandler {
59
71
  params;
@@ -1,12 +1,12 @@
1
- import { TagSearchHandlerImpl, BlockRefSearchHandlerImpl, HierarchySearchHandlerImpl, TextSearchHandlerImpl } from './handlers.js';
1
+ import { TagSearchHandlerImpl, BlockRefSearchHandlerImpl, HierarchySearchHandlerImpl, TextSearchHandlerImpl, StatusSearchHandlerImpl } from './handlers.js';
2
2
  export class SearchOperations {
3
3
  graph;
4
4
  constructor(graph) {
5
5
  this.graph = graph;
6
6
  }
7
7
  async searchByStatus(status, page_title_uid, include, exclude) {
8
- const handler = new TagSearchHandlerImpl(this.graph, {
9
- primary_tag: `{{[[${status}]]}}`,
8
+ const handler = new StatusSearchHandlerImpl(this.graph, {
9
+ status,
10
10
  page_title_uid,
11
11
  });
12
12
  const result = await handler.execute();
@@ -14,7 +14,7 @@ export class SearchOperations {
14
14
  let matches = result.matches;
15
15
  if (include) {
16
16
  const includeTerms = include.split(',').map(term => term.trim());
17
- matches = matches.filter(match => {
17
+ matches = matches.filter((match) => {
18
18
  const matchContent = match.content;
19
19
  const matchTitle = match.page_title;
20
20
  const terms = includeTerms;
@@ -24,7 +24,7 @@ export class SearchOperations {
24
24
  }
25
25
  if (exclude) {
26
26
  const excludeTerms = exclude.split(',').map(term => term.trim());
27
- matches = matches.filter(match => {
27
+ matches = matches.filter((match) => {
28
28
  const matchContent = match.content;
29
29
  const matchTitle = match.page_title;
30
30
  const terms = excludeTerms;
@@ -34,7 +34,7 @@ export const toolSchemas = {
34
34
  },
35
35
  roam_create_page: {
36
36
  name: 'roam_create_page',
37
- description: 'Create a new standalone page in Roam from markdown with given title. Best for hierarchical content, reference materials, markdown tables, and topics that deserve their own namespace. Optional initial content will be properly nested as blocks.',
37
+ description: 'Create a new standalone page in Roam with optional content using markdown-style formatting. The nesting structure is inferred from markdown indentation (spaces). 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.',
38
38
  inputSchema: {
39
39
  type: 'object',
40
40
  properties: {
@@ -44,7 +44,7 @@ export const toolSchemas = {
44
44
  },
45
45
  content: {
46
46
  type: 'string',
47
- description: 'Initial content for the page (optional)',
47
+ description: 'Initial content for the page (optional). Each line becomes a separate block. Indentation (using spaces or tabs) determines the nesting level of each block.',
48
48
  },
49
49
  },
50
50
  required: ['title'],
@@ -74,7 +74,7 @@ export const toolSchemas = {
74
74
  },
75
75
  roam_create_outline: {
76
76
  name: 'roam_create_outline',
77
- description: 'Create a structured outline with nested structure in Roam from an array of items with explicit levels. Can be added on a specific page or under a specific block. Ideal for saving a conversation with an LLM response, research, or organizing thoughts.',
77
+ 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',
78
78
  inputSchema: {
79
79
  type: 'object',
80
80
  properties: {
@@ -88,7 +88,7 @@ export const toolSchemas = {
88
88
  },
89
89
  outline: {
90
90
  type: 'array',
91
- description: 'Array of outline items with block text and level',
91
+ description: 'Array of outline items with block text and explicit nesting level',
92
92
  items: {
93
93
  type: 'object',
94
94
  properties: {
@@ -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,16 +426,26 @@ 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: {
431
447
  name: 'roam_datomic_query',
432
- description: 'Execute a custom Datomic query on the Roam graph beyond the available search tools. This provides direct access to Roam\'s query engine for advanced data retrieval. Note: The Roam graph is case-sensitive.\nA list 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).\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
+ description: 'Execute a custom Datomic query on the Roam graph beyond the available search tools. This provides direct access to Roam\'s query engine for advanced data retrieval. Note: Roam graph is case-sensitive.\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.',
433
449
  inputSchema: {
434
450
  type: 'object',
435
451
  properties: {
@@ -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.23.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": {