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 +42 -18
- package/build/search/block-ref-search.js +8 -2
- package/build/search/hierarchy-search.js +11 -7
- package/build/search/status-search.js +8 -2
- package/build/search/tag-search.js +8 -2
- package/build/search/text-search.js +8 -2
- package/build/server/roam-server.js +5 -4
- package/build/tools/operations/memory.js +69 -23
- package/build/tools/operations/pages.js +4 -3
- package/build/tools/operations/search/handlers.js +13 -1
- package/build/tools/operations/search/index.js +5 -5
- package/build/tools/schemas.js +31 -15
- package/build/tools/tool-handlers.js +4 -4
- package/package.json +1 -1
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. `
|
|
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
|
|
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
|
-
|
|
730
|
+
### Building
|
|
721
731
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
-
|
|
756
|
+
This will:
|
|
733
757
|
|
|
734
|
-
1.
|
|
735
|
-
2.
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
|
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(
|
|
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
|
|
89
|
-
//
|
|
90
|
-
const matches =
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
41
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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 '
|
|
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
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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)]]
|
|
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, '
|
|
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
|
|
9
|
-
|
|
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;
|
package/build/tools/schemas.js
CHANGED
|
@@ -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
|
|
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: '
|
|
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
|
-
|
|
247
|
-
name: '
|
|
248
|
-
description: 'Find
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
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) {
|