roam-research-mcp 0.25.3 → 0.25.7
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 +49 -19
- package/build/markdown-utils.js +10 -26
- package/build/search/block-ref-search.js +0 -1
- package/build/search/datomic-search.js +0 -1
- package/build/search/hierarchy-search.js +0 -1
- package/build/search/status-search.js +0 -1
- package/build/search/tag-search.js +0 -1
- package/build/search/text-search.js +0 -1
- package/build/search/types.js +0 -1
- package/build/server/roam-server.js +50 -32
- package/build/tools/operations/blocks.js +0 -1
- package/build/tools/operations/memory.js +0 -2
- package/build/tools/operations/outline.js +17 -1
- package/build/tools/operations/pages.js +12 -5
- package/build/tools/operations/search/handlers.js +0 -7
- package/build/tools/operations/search/index.js +0 -1
- package/build/tools/operations/todos.js +0 -1
- package/build/tools/schemas.js +20 -8
- package/build/tools/tool-handlers.js +2 -9
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -26,12 +26,42 @@ npm install
|
|
|
26
26
|
npm run build
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Docker
|
|
30
|
+
|
|
31
|
+
This project can be easily containerized using Docker. A `Dockerfile` is provided at the root of the repository.
|
|
32
|
+
|
|
33
|
+
### Build the Docker Image
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
To build the Docker image, navigate to the project root and run:
|
|
32
36
|
|
|
37
|
+
```bash
|
|
38
|
+
docker build -t roam-research-mcp .
|
|
33
39
|
```
|
|
34
|
-
|
|
40
|
+
|
|
41
|
+
### Run the Docker Container
|
|
42
|
+
|
|
43
|
+
To run the Docker container and map port 3000 (if your application uses it), you must also provide the necessary environment variables. Use the `-e` flag to pass `ROAM_API_TOKEN`, `ROAM_GRAPH_NAME`, and optionally `MEMORIES_TAG`:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
docker run -p 3000:3000 \
|
|
47
|
+
-e ROAM_API_TOKEN="your-api-token" \
|
|
48
|
+
-e ROAM_GRAPH_NAME="your-graph-name" \
|
|
49
|
+
-e MEMORIES_TAG="#[[LLM/Memories]]" \
|
|
50
|
+
roam-research-mcp
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Alternatively, if you have a `.env` file in the project root (which is copied into the Docker image during build), you can use the `--env-file` flag:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
docker run -p 3000:3000 --env-file .env roam-research-mcp
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## To Test
|
|
60
|
+
|
|
61
|
+
Run [MCP Inspector](https://github.com/modelcontextprotocol/inspector) after build using the provided npm script:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm run inspector
|
|
35
65
|
```
|
|
36
66
|
|
|
37
67
|
## Features
|
|
@@ -48,22 +78,22 @@ The server provides powerful tools for interacting with Roam Research:
|
|
|
48
78
|
- Efficient batch operations
|
|
49
79
|
- Hierarchical outline creation
|
|
50
80
|
|
|
51
|
-
1. `roam_fetch_page_by_title`: Fetch
|
|
52
|
-
2. `roam_create_page`: Create new pages with optional content
|
|
53
|
-
3. `roam_create_block`:
|
|
54
|
-
4. `roam_import_markdown`: Import nested markdown content under specific
|
|
55
|
-
5. `roam_add_todo`: Add
|
|
56
|
-
6. `roam_create_outline`:
|
|
57
|
-
7. `roam_search_block_refs`: Search for block references within
|
|
58
|
-
8. `roam_search_hierarchy`:
|
|
59
|
-
9. `roam_find_pages_modified_today`: Find
|
|
60
|
-
10. `roam_search_by_text`: Search for blocks containing specific text
|
|
61
|
-
11. `roam_update_block`: Update
|
|
62
|
-
12. `roam_search_by_date`: Search for blocks
|
|
63
|
-
13. `roam_search_for_tag`: Search for blocks containing specific
|
|
64
|
-
14. `roam_remember`:
|
|
65
|
-
15. `roam_recall`:
|
|
66
|
-
16. `roam_datomic_query`: Execute custom
|
|
81
|
+
1. `roam_fetch_page_by_title`: Fetch page content by title.
|
|
82
|
+
2. `roam_create_page`: Create new pages with optional content and headings.
|
|
83
|
+
3. `roam_create_block`: Add new blocks to an existing page or today's daily note.
|
|
84
|
+
4. `roam_import_markdown`: Import nested markdown content under a specific block.
|
|
85
|
+
5. `roam_add_todo`: Add a list of todo items to today's daily page.
|
|
86
|
+
6. `roam_create_outline`: Add a structured outline to an existing page or block.
|
|
87
|
+
7. `roam_search_block_refs`: Search for block references within a page or across the entire graph.
|
|
88
|
+
8. `roam_search_hierarchy`: Search for parent or child blocks in the block hierarchy.
|
|
89
|
+
9. `roam_find_pages_modified_today`: Find pages that have been modified today (since midnight).
|
|
90
|
+
10. `roam_search_by_text`: Search for blocks containing specific text.
|
|
91
|
+
11. `roam_update_block`: Update a single block identified by its UID.
|
|
92
|
+
12. `roam_search_by_date`: Search for blocks or pages based on creation or modification dates.
|
|
93
|
+
13. `roam_search_for_tag`: Search for blocks containing a specific tag and optionally filter by blocks that also contain another tag nearby.
|
|
94
|
+
14. `roam_remember`: Add a memory or piece of information to remember.
|
|
95
|
+
15. `roam_recall`: Retrieve all stored memories.
|
|
96
|
+
16. `roam_datomic_query`: Execute a custom Datomic query on the Roam graph beyond the available search tools.
|
|
67
97
|
|
|
68
98
|
## Setup
|
|
69
99
|
|
package/build/markdown-utils.js
CHANGED
|
@@ -176,39 +176,23 @@ function parseMarkdown(markdown) {
|
|
|
176
176
|
// Calculate indentation level (2 spaces = 1 level)
|
|
177
177
|
const indentation = line.match(/^\s*/)?.[0].length ?? 0;
|
|
178
178
|
let level = Math.floor(indentation / 2);
|
|
179
|
-
|
|
180
|
-
const { heading_level, content: headingContent } = parseMarkdownHeadingLevel(trimmedLine);
|
|
181
|
-
// Then handle bullet points if not a heading
|
|
182
|
-
let content;
|
|
183
|
-
if (heading_level > 0) {
|
|
184
|
-
content = headingContent; // Use clean heading content without # marks
|
|
185
|
-
level = 0; // Headings start at root level
|
|
186
|
-
stack.length = 1; // Reset stack but keep heading as parent
|
|
187
|
-
// Create heading node
|
|
188
|
-
const node = {
|
|
189
|
-
content,
|
|
190
|
-
level,
|
|
191
|
-
heading_level, // Store heading level in node
|
|
192
|
-
children: []
|
|
193
|
-
};
|
|
194
|
-
rootNodes.push(node);
|
|
195
|
-
stack[0] = node;
|
|
196
|
-
continue; // Skip to next line
|
|
197
|
-
}
|
|
198
|
-
// Handle non-heading content
|
|
179
|
+
let contentToParse;
|
|
199
180
|
const bulletMatch = trimmedLine.match(/^(\s*)[-*+]\s+/);
|
|
200
181
|
if (bulletMatch) {
|
|
201
|
-
//
|
|
202
|
-
content = trimmedLine.substring(bulletMatch[0].length);
|
|
182
|
+
// If it's a bullet point, adjust level based on bullet indentation
|
|
203
183
|
level = Math.floor(bulletMatch[1].length / 2);
|
|
184
|
+
contentToParse = trimmedLine.substring(bulletMatch[0].length); // Content after bullet
|
|
204
185
|
}
|
|
205
186
|
else {
|
|
206
|
-
|
|
187
|
+
contentToParse = trimmedLine; // No bullet, use trimmed line
|
|
207
188
|
}
|
|
208
|
-
//
|
|
189
|
+
// Now, from the content after bullet/initial indentation, check for heading
|
|
190
|
+
const { heading_level, content: finalContent } = parseMarkdownHeadingLevel(contentToParse);
|
|
191
|
+
// Create node
|
|
209
192
|
const node = {
|
|
210
|
-
content,
|
|
211
|
-
level,
|
|
193
|
+
content: finalContent, // Use content after heading parsing
|
|
194
|
+
level, // Use level derived from bullet/indentation
|
|
195
|
+
...(heading_level > 0 && { heading_level }),
|
|
212
196
|
children: []
|
|
213
197
|
};
|
|
214
198
|
// Pop stack until we find the parent level
|
|
@@ -3,7 +3,6 @@ import { BaseSearchHandler } from './types.js';
|
|
|
3
3
|
import { SearchUtils } from './utils.js';
|
|
4
4
|
import { resolveRefs } from '../tools/helpers/refs.js';
|
|
5
5
|
export class BlockRefSearchHandler extends BaseSearchHandler {
|
|
6
|
-
params;
|
|
7
6
|
constructor(graph, params) {
|
|
8
7
|
super(graph);
|
|
9
8
|
this.params = params;
|
|
@@ -3,7 +3,6 @@ import { BaseSearchHandler } from './types.js';
|
|
|
3
3
|
import { SearchUtils } from './utils.js';
|
|
4
4
|
import { resolveRefs } from '../tools/helpers/refs.js';
|
|
5
5
|
export class HierarchySearchHandler extends BaseSearchHandler {
|
|
6
|
-
params;
|
|
7
6
|
constructor(graph, params) {
|
|
8
7
|
super(graph);
|
|
9
8
|
this.params = params;
|
|
@@ -3,7 +3,6 @@ import { BaseSearchHandler } from './types.js';
|
|
|
3
3
|
import { SearchUtils } from './utils.js';
|
|
4
4
|
import { resolveRefs } from '../tools/helpers/refs.js';
|
|
5
5
|
export class StatusSearchHandler extends BaseSearchHandler {
|
|
6
|
-
params;
|
|
7
6
|
constructor(graph, params) {
|
|
8
7
|
super(graph);
|
|
9
8
|
this.params = params;
|
|
@@ -3,7 +3,6 @@ import { BaseSearchHandler } from './types.js';
|
|
|
3
3
|
import { SearchUtils } from './utils.js';
|
|
4
4
|
import { resolveRefs } from '../tools/helpers/refs.js';
|
|
5
5
|
export class TagSearchHandler extends BaseSearchHandler {
|
|
6
|
-
params;
|
|
7
6
|
constructor(graph, params) {
|
|
8
7
|
super(graph);
|
|
9
8
|
this.params = params;
|
|
@@ -3,7 +3,6 @@ import { BaseSearchHandler } from './types.js';
|
|
|
3
3
|
import { SearchUtils } from './utils.js';
|
|
4
4
|
import { resolveRefs } from '../tools/helpers/refs.js';
|
|
5
5
|
export class TextSearchHandler extends BaseSearchHandler {
|
|
6
|
-
params;
|
|
7
6
|
constructor(graph, params) {
|
|
8
7
|
super(graph);
|
|
9
8
|
this.params = params;
|
package/build/search/types.js
CHANGED
|
@@ -5,46 +5,58 @@ import { initializeGraph } from '@roam-research/roam-api-sdk';
|
|
|
5
5
|
import { API_TOKEN, GRAPH_NAME } from '../config/environment.js';
|
|
6
6
|
import { toolSchemas } from '../tools/schemas.js';
|
|
7
7
|
import { ToolHandlers } from '../tools/tool-handlers.js';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { join, dirname } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
// Read package.json to get the version
|
|
14
|
+
const packageJsonPath = join(__dirname, '../../package.json');
|
|
15
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
16
|
+
const serverVersion = packageJson.version;
|
|
8
17
|
export class RoamServer {
|
|
9
|
-
server;
|
|
10
|
-
toolHandlers;
|
|
11
|
-
graph;
|
|
12
18
|
constructor() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
try {
|
|
20
|
+
this.graph = initializeGraph({
|
|
21
|
+
token: API_TOKEN,
|
|
22
|
+
graph: GRAPH_NAME,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
27
|
+
throw new McpError(ErrorCode.InternalError, `Failed to initialize Roam graph: ${errorMessage}`);
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
this.toolHandlers = new ToolHandlers(this.graph);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
34
|
+
throw new McpError(ErrorCode.InternalError, `Failed to initialize tool handlers: ${errorMessage}`);
|
|
35
|
+
}
|
|
36
|
+
// Ensure toolSchemas is not empty before proceeding
|
|
37
|
+
if (Object.keys(toolSchemas).length === 0) {
|
|
38
|
+
throw new McpError(ErrorCode.InternalError, 'No tool schemas defined in src/tools/schemas.ts');
|
|
39
|
+
}
|
|
18
40
|
this.server = new Server({
|
|
19
41
|
name: 'roam-research',
|
|
20
|
-
version:
|
|
42
|
+
version: serverVersion, // Use the version from package.json
|
|
21
43
|
}, {
|
|
22
44
|
capabilities: {
|
|
23
45
|
tools: {
|
|
24
|
-
|
|
25
|
-
roam_recall: {},
|
|
26
|
-
roam_add_todo: {},
|
|
27
|
-
roam_fetch_page_by_title: {},
|
|
28
|
-
roam_create_page: {},
|
|
29
|
-
roam_create_block: {},
|
|
30
|
-
roam_import_markdown: {},
|
|
31
|
-
roam_create_outline: {},
|
|
32
|
-
roam_search_for_tag: {},
|
|
33
|
-
roam_search_by_status: {},
|
|
34
|
-
roam_search_block_refs: {},
|
|
35
|
-
roam_search_hierarchy: {},
|
|
36
|
-
roam_find_pages_modified_today: {},
|
|
37
|
-
roam_search_by_text: {},
|
|
38
|
-
roam_update_block: {},
|
|
39
|
-
roam_update_multiple_blocks: {},
|
|
40
|
-
roam_search_by_date: {},
|
|
41
|
-
roam_datomic_query: {}
|
|
46
|
+
...Object.fromEntries(Object.keys(toolSchemas).map((toolName) => [toolName, {}])),
|
|
42
47
|
},
|
|
43
48
|
},
|
|
44
49
|
});
|
|
45
50
|
this.setupRequestHandlers();
|
|
46
51
|
// Error handling
|
|
47
|
-
this.server.onerror = (error) => {
|
|
52
|
+
this.server.onerror = (error) => {
|
|
53
|
+
// Re-throw as McpError to be caught by the MCP client
|
|
54
|
+
if (error instanceof McpError) {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
58
|
+
throw new McpError(ErrorCode.InternalError, `MCP server internal error: ${errorMessage}`);
|
|
59
|
+
};
|
|
48
60
|
process.on('SIGINT', async () => {
|
|
49
61
|
await this.server.close();
|
|
50
62
|
process.exit(0);
|
|
@@ -67,8 +79,8 @@ export class RoamServer {
|
|
|
67
79
|
};
|
|
68
80
|
}
|
|
69
81
|
case 'roam_fetch_page_by_title': {
|
|
70
|
-
const { title } = request.params.arguments;
|
|
71
|
-
const content = await this.toolHandlers.fetchPageByTitle(title);
|
|
82
|
+
const { title, format } = request.params.arguments;
|
|
83
|
+
const content = await this.toolHandlers.fetchPageByTitle(title, format);
|
|
72
84
|
return {
|
|
73
85
|
content: [{ type: 'text', text: content }],
|
|
74
86
|
};
|
|
@@ -223,7 +235,13 @@ export class RoamServer {
|
|
|
223
235
|
});
|
|
224
236
|
}
|
|
225
237
|
async run() {
|
|
226
|
-
|
|
227
|
-
|
|
238
|
+
try {
|
|
239
|
+
const transport = new StdioServerTransport();
|
|
240
|
+
await this.server.connect(transport);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
244
|
+
throw new McpError(ErrorCode.InternalError, `Failed to connect MCP server: ${errorMessage}`);
|
|
245
|
+
}
|
|
228
246
|
}
|
|
229
247
|
}
|
|
@@ -3,7 +3,6 @@ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
|
3
3
|
import { formatRoamDate } from '../../utils/helpers.js';
|
|
4
4
|
import { parseMarkdown, convertToRoamActions, convertToRoamMarkdown } from '../../markdown-utils.js';
|
|
5
5
|
export class BlockOperations {
|
|
6
|
-
graph;
|
|
7
6
|
constructor(graph) {
|
|
8
7
|
this.graph = graph;
|
|
9
8
|
}
|
|
@@ -4,8 +4,6 @@ import { formatRoamDate } from '../../utils/helpers.js';
|
|
|
4
4
|
import { resolveRefs } from '../helpers/refs.js';
|
|
5
5
|
import { SearchOperations } from './search/index.js';
|
|
6
6
|
export class MemoryOperations {
|
|
7
|
-
graph;
|
|
8
|
-
searchOps;
|
|
9
7
|
constructor(graph) {
|
|
10
8
|
this.graph = graph;
|
|
11
9
|
this.searchOps = new SearchOperations(graph);
|
|
@@ -4,10 +4,24 @@ import { formatRoamDate } from '../../utils/helpers.js';
|
|
|
4
4
|
import { capitalizeWords } from '../helpers/text.js';
|
|
5
5
|
import { parseMarkdown, convertToRoamActions, convertToRoamMarkdown } from '../../markdown-utils.js';
|
|
6
6
|
export class OutlineOperations {
|
|
7
|
-
graph;
|
|
8
7
|
constructor(graph) {
|
|
9
8
|
this.graph = graph;
|
|
10
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Creates an outline structure on a Roam Research page, optionally under a specific block.
|
|
12
|
+
*
|
|
13
|
+
* @param outline - An array of OutlineItem objects, each containing text and a level.
|
|
14
|
+
* Markdown heading syntax (#, ##, ###) in the text will be recognized
|
|
15
|
+
* and converted to Roam headings while preserving the outline's hierarchical
|
|
16
|
+
* structure based on indentation.
|
|
17
|
+
* @param page_title_uid - The title or UID of the page where the outline should be created.
|
|
18
|
+
* If not provided, today's daily page will be used.
|
|
19
|
+
* @param block_text_uid - Optional. The text content or UID of an existing block under which
|
|
20
|
+
* the outline should be inserted. If a text string is provided and
|
|
21
|
+
* no matching block is found, a new block with that text will be created
|
|
22
|
+
* on the page to serve as the parent. If a UID is provided and the block
|
|
23
|
+
* is not found, an error will be thrown.
|
|
24
|
+
*/
|
|
11
25
|
async createOutline(outline, page_title_uid, block_text_uid) {
|
|
12
26
|
// Validate input
|
|
13
27
|
if (!Array.isArray(outline) || outline.length === 0) {
|
|
@@ -210,6 +224,8 @@ export class OutlineOperations {
|
|
|
210
224
|
const markdownContent = validOutline
|
|
211
225
|
.map(item => {
|
|
212
226
|
const indent = ' '.repeat(item.level - 1);
|
|
227
|
+
// If the item text starts with a markdown heading (e.g., #, ##, ###),
|
|
228
|
+
// treat it as a direct heading without adding a bullet or outline indentation.
|
|
213
229
|
return `${indent}- ${item.text?.trim()}`;
|
|
214
230
|
})
|
|
215
231
|
.join('\n');
|
|
@@ -4,7 +4,6 @@ import { capitalizeWords } from '../helpers/text.js';
|
|
|
4
4
|
import { resolveRefs } from '../helpers/refs.js';
|
|
5
5
|
import { convertToRoamActions } from '../../markdown-utils.js';
|
|
6
6
|
export class PageOperations {
|
|
7
|
-
graph;
|
|
8
7
|
constructor(graph) {
|
|
9
8
|
this.graph = graph;
|
|
10
9
|
}
|
|
@@ -38,7 +37,7 @@ export class PageOperations {
|
|
|
38
37
|
};
|
|
39
38
|
}
|
|
40
39
|
// Extract unique page titles
|
|
41
|
-
const uniquePages =
|
|
40
|
+
const uniquePages = Array.from(new Set(results.map(([title]) => title)));
|
|
42
41
|
return {
|
|
43
42
|
success: true,
|
|
44
43
|
pages: uniquePages,
|
|
@@ -127,7 +126,7 @@ export class PageOperations {
|
|
|
127
126
|
}
|
|
128
127
|
return { success: true, uid: pageUid };
|
|
129
128
|
}
|
|
130
|
-
async fetchPageByTitle(title) {
|
|
129
|
+
async fetchPageByTitle(title, format = 'raw') {
|
|
131
130
|
if (!title) {
|
|
132
131
|
throw new McpError(ErrorCode.InvalidRequest, 'title is required');
|
|
133
132
|
}
|
|
@@ -170,6 +169,9 @@ export class PageOperations {
|
|
|
170
169
|
[?parent :block/uid ?parent-uid]]`;
|
|
171
170
|
const blocks = await q(this.graph, blocksQuery, [ancestorRule, title]);
|
|
172
171
|
if (!blocks || blocks.length === 0) {
|
|
172
|
+
if (format === 'raw') {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
173
175
|
return `${title} (no content found)`;
|
|
174
176
|
}
|
|
175
177
|
// Get heading information for blocks that have it
|
|
@@ -226,9 +228,13 @@ export class PageOperations {
|
|
|
226
228
|
});
|
|
227
229
|
};
|
|
228
230
|
sortBlocks(rootBlocks);
|
|
231
|
+
if (format === 'raw') {
|
|
232
|
+
return JSON.stringify(rootBlocks);
|
|
233
|
+
}
|
|
229
234
|
// Convert to markdown with proper nesting
|
|
230
235
|
const toMarkdown = (blocks, level = 0) => {
|
|
231
|
-
return blocks
|
|
236
|
+
return blocks
|
|
237
|
+
.map(block => {
|
|
232
238
|
const indent = ' '.repeat(level);
|
|
233
239
|
let md;
|
|
234
240
|
// Check block heading level and format accordingly
|
|
@@ -245,7 +251,8 @@ export class PageOperations {
|
|
|
245
251
|
md += '\n' + toMarkdown(block.children, level + 1);
|
|
246
252
|
}
|
|
247
253
|
return md;
|
|
248
|
-
})
|
|
254
|
+
})
|
|
255
|
+
.join('\n');
|
|
249
256
|
};
|
|
250
257
|
return `# ${title}\n\n${toMarkdown(rootBlocks)}`;
|
|
251
258
|
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
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
|
-
graph;
|
|
5
4
|
constructor(graph) {
|
|
6
5
|
this.graph = graph;
|
|
7
6
|
}
|
|
8
7
|
}
|
|
9
8
|
// Tag search handler
|
|
10
9
|
export class TagSearchHandlerImpl extends BaseSearchHandler {
|
|
11
|
-
params;
|
|
12
10
|
constructor(graph, params) {
|
|
13
11
|
super(graph);
|
|
14
12
|
this.params = params;
|
|
@@ -20,7 +18,6 @@ export class TagSearchHandlerImpl extends BaseSearchHandler {
|
|
|
20
18
|
}
|
|
21
19
|
// Block reference search handler
|
|
22
20
|
export class BlockRefSearchHandlerImpl extends BaseSearchHandler {
|
|
23
|
-
params;
|
|
24
21
|
constructor(graph, params) {
|
|
25
22
|
super(graph);
|
|
26
23
|
this.params = params;
|
|
@@ -32,7 +29,6 @@ export class BlockRefSearchHandlerImpl extends BaseSearchHandler {
|
|
|
32
29
|
}
|
|
33
30
|
// Hierarchy search handler
|
|
34
31
|
export class HierarchySearchHandlerImpl extends BaseSearchHandler {
|
|
35
|
-
params;
|
|
36
32
|
constructor(graph, params) {
|
|
37
33
|
super(graph);
|
|
38
34
|
this.params = params;
|
|
@@ -44,7 +40,6 @@ export class HierarchySearchHandlerImpl extends BaseSearchHandler {
|
|
|
44
40
|
}
|
|
45
41
|
// Text search handler
|
|
46
42
|
export class TextSearchHandlerImpl extends BaseSearchHandler {
|
|
47
|
-
params;
|
|
48
43
|
constructor(graph, params) {
|
|
49
44
|
super(graph);
|
|
50
45
|
this.params = params;
|
|
@@ -56,7 +51,6 @@ export class TextSearchHandlerImpl extends BaseSearchHandler {
|
|
|
56
51
|
}
|
|
57
52
|
// Status search handler
|
|
58
53
|
export class StatusSearchHandlerImpl extends BaseSearchHandler {
|
|
59
|
-
params;
|
|
60
54
|
constructor(graph, params) {
|
|
61
55
|
super(graph);
|
|
62
56
|
this.params = params;
|
|
@@ -68,7 +62,6 @@ export class StatusSearchHandlerImpl extends BaseSearchHandler {
|
|
|
68
62
|
}
|
|
69
63
|
// Datomic query handler
|
|
70
64
|
export class DatomicSearchHandlerImpl extends BaseSearchHandler {
|
|
71
|
-
params;
|
|
72
65
|
constructor(graph, params) {
|
|
73
66
|
super(graph);
|
|
74
67
|
this.params = params;
|
|
@@ -2,7 +2,6 @@ import { q, createBlock, createPage, batchActions } from '@roam-research/roam-ap
|
|
|
2
2
|
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
import { formatRoamDate } from '../../utils/helpers.js';
|
|
4
4
|
export class TodoOperations {
|
|
5
|
-
graph;
|
|
6
5
|
constructor(graph) {
|
|
7
6
|
this.graph = graph;
|
|
8
7
|
}
|
package/build/tools/schemas.js
CHANGED
|
@@ -20,16 +20,22 @@ export const toolSchemas = {
|
|
|
20
20
|
},
|
|
21
21
|
roam_fetch_page_by_title: {
|
|
22
22
|
name: 'roam_fetch_page_by_title',
|
|
23
|
-
description: '
|
|
23
|
+
description: 'Fetch page by title, defaults to raw JSON string.',
|
|
24
24
|
inputSchema: {
|
|
25
25
|
type: 'object',
|
|
26
26
|
properties: {
|
|
27
27
|
title: {
|
|
28
28
|
type: 'string',
|
|
29
|
-
description: 'Title of the page. For date pages, use ordinal date formats such as January 2nd, 2025'
|
|
29
|
+
description: 'Title of the page. For date pages, use ordinal date formats such as January 2nd, 2025'
|
|
30
30
|
},
|
|
31
|
+
format: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
enum: ['markdown', 'raw'],
|
|
34
|
+
default: 'raw',
|
|
35
|
+
description: "Format output as markdown or JSON. 'markdown' returns as string; 'raw' returns JSON string of the page's blocks"
|
|
36
|
+
}
|
|
31
37
|
},
|
|
32
|
-
required: ['title']
|
|
38
|
+
required: ['title']
|
|
33
39
|
},
|
|
34
40
|
},
|
|
35
41
|
roam_create_page: {
|
|
@@ -112,7 +118,7 @@ export const toolSchemas = {
|
|
|
112
118
|
},
|
|
113
119
|
block_text_uid: {
|
|
114
120
|
type: 'string',
|
|
115
|
-
description: 'A relevant title heading for the outline (or UID, if known) of the block under which outline content will be nested. If blank, content will be nested under the page
|
|
121
|
+
description: 'A relevant title heading for the outline (or UID, if known) of the block under which outline content will be nested. If blank, content will be nested directly under the page. This can be either the text content of the block or its UID.'
|
|
116
122
|
},
|
|
117
123
|
outline: {
|
|
118
124
|
type: 'array',
|
|
@@ -129,6 +135,12 @@ export const toolSchemas = {
|
|
|
129
135
|
description: 'Indentation level (1-10, where 1 is top level)',
|
|
130
136
|
minimum: 1,
|
|
131
137
|
maximum: 10
|
|
138
|
+
},
|
|
139
|
+
heading: {
|
|
140
|
+
type: 'integer',
|
|
141
|
+
description: 'Optional: Heading formatting for this block (1-3)',
|
|
142
|
+
minimum: 1,
|
|
143
|
+
maximum: 3
|
|
132
144
|
}
|
|
133
145
|
},
|
|
134
146
|
required: ['text', 'level']
|
|
@@ -140,7 +152,7 @@ export const toolSchemas = {
|
|
|
140
152
|
},
|
|
141
153
|
roam_import_markdown: {
|
|
142
154
|
name: 'roam_import_markdown',
|
|
143
|
-
description: 'Import nested markdown content into Roam under a specific block. Can locate the parent block by UID or by exact string match within a specific page.',
|
|
155
|
+
description: 'Import nested markdown content into Roam under a specific block. Can locate the parent block by UID (preferred) or by exact string match within a specific page.',
|
|
144
156
|
inputSchema: {
|
|
145
157
|
type: 'object',
|
|
146
158
|
properties: {
|
|
@@ -162,7 +174,7 @@ export const toolSchemas = {
|
|
|
162
174
|
},
|
|
163
175
|
parent_string: {
|
|
164
176
|
type: 'string',
|
|
165
|
-
description: 'Optional: Exact string content of the parent block to add content under (must provide either page_uid or page_title)'
|
|
177
|
+
description: 'Optional: Exact string content of the parent block to add content under (must provide either page_uid (preferred) or page_title)'
|
|
166
178
|
},
|
|
167
179
|
order: {
|
|
168
180
|
type: 'string',
|
|
@@ -302,7 +314,7 @@ export const toolSchemas = {
|
|
|
302
314
|
},
|
|
303
315
|
roam_update_block: {
|
|
304
316
|
name: 'roam_update_block',
|
|
305
|
-
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
|
|
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).',
|
|
306
318
|
inputSchema: {
|
|
307
319
|
type: 'object',
|
|
308
320
|
properties: {
|
|
@@ -410,7 +422,7 @@ export const toolSchemas = {
|
|
|
410
422
|
scope: {
|
|
411
423
|
type: 'string',
|
|
412
424
|
enum: ['blocks', 'pages', 'both'],
|
|
413
|
-
description: 'Whether to search blocks, pages
|
|
425
|
+
description: 'Whether to search blocks, pages',
|
|
414
426
|
},
|
|
415
427
|
include_content: {
|
|
416
428
|
type: 'boolean',
|
|
@@ -6,13 +6,6 @@ import { TodoOperations } from './operations/todos.js';
|
|
|
6
6
|
import { OutlineOperations } from './operations/outline.js';
|
|
7
7
|
import { DatomicSearchHandlerImpl } from './operations/search/handlers.js';
|
|
8
8
|
export class ToolHandlers {
|
|
9
|
-
graph;
|
|
10
|
-
pageOps;
|
|
11
|
-
blockOps;
|
|
12
|
-
searchOps;
|
|
13
|
-
memoryOps;
|
|
14
|
-
todoOps;
|
|
15
|
-
outlineOps;
|
|
16
9
|
constructor(graph) {
|
|
17
10
|
this.graph = graph;
|
|
18
11
|
this.pageOps = new PageOperations(graph);
|
|
@@ -29,8 +22,8 @@ export class ToolHandlers {
|
|
|
29
22
|
async createPage(title, content) {
|
|
30
23
|
return this.pageOps.createPage(title, content);
|
|
31
24
|
}
|
|
32
|
-
async fetchPageByTitle(title) {
|
|
33
|
-
return this.pageOps.fetchPageByTitle(title);
|
|
25
|
+
async fetchPageByTitle(title, format) {
|
|
26
|
+
return this.pageOps.fetchPageByTitle(title, format);
|
|
34
27
|
}
|
|
35
28
|
// Block Operations
|
|
36
29
|
async createBlock(content, page_uid, title, heading) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roam-research-mcp",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.7",
|
|
4
4
|
"description": "A Model Context Protocol (MCP) server for Roam Research API integration",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"build": "tsc && chmod 755 build/index.js",
|
|
32
32
|
"prepare": "npm run build",
|
|
33
33
|
"watch": "tsc --watch",
|
|
34
|
-
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
|
|
34
|
+
"inspector": "npx @modelcontextprotocol/inspector build/index.js",
|
|
35
|
+
"start": "node build/index.js"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"@modelcontextprotocol/sdk": "0.6.0",
|