roam-research-mcp 0.25.5 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,16 +5,25 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![GitHub](https://img.shields.io/github/license/2b3pro/roam-research-mcp)](https://github.com/2b3pro/roam-research-mcp/blob/main/LICENSE)
7
7
 
8
- A Model Context Protocol (MCP) server that provides comprehensive access to Roam Research's API functionality. This server enables AI assistants like Claude to interact with your Roam Research graph through a standardized interface. (A WORK-IN-PROGRESS, personal project not officially endorsed by Roam Research)
8
+ A Model Context Protocol (MCP) server that provides comprehensive access to Roam Research's API functionality. This server enables AI assistants like Claude to interact with your Roam Research graph through a standardized interface. It supports standard input/output (stdio), HTTP Stream, and Server-Sent Events (SSE) communication. (A WORK-IN-PROGRESS, personal project not officially endorsed by Roam Research)
9
9
 
10
10
  <a href="https://glama.ai/mcp/servers/fzfznyaflu"><img width="380" height="200" src="https://glama.ai/mcp/servers/fzfznyaflu/badge" alt="Roam Research MCP server" /></a>
11
11
 
12
- ## Installation
12
+ ## Installation and Usage
13
13
 
14
- You can install the package globally:
14
+ This MCP server supports three primary communication methods:
15
+
16
+ 1. **Stdio (Standard Input/Output):** Ideal for local inter-process communication, command-line tools, and direct integration with applications running on the same machine. This is the default communication method when running the server directly.
17
+ 2. **HTTP Stream:** Provides network-based communication, suitable for web-based clients, remote applications, or scenarios requiring real-time updates over HTTP. The HTTP Stream endpoint runs on port `8088` by default.
18
+ 3. **SSE (Server-Sent Events):** A transport for legacy clients that require SSE. The SSE endpoint runs on port `8087` by default. (NOTE: ⚠️ DEPRECATED: The SSE Transport has been deprecated as of MCP specification version 2025-03-26. HTTP Stream Transport preferred.)
19
+
20
+ ### Running with Stdio
21
+
22
+ You can install the package globally and run it:
15
23
 
16
24
  ```bash
17
25
  npm install -g roam-research-mcp
26
+ roam-research-mcp
18
27
  ```
19
28
 
20
29
  Or clone the repository and build from source:
@@ -24,14 +33,60 @@ git clone https://github.com/2b3pro/roam-research-mcp.git
24
33
  cd roam-research-mcp
25
34
  npm install
26
35
  npm run build
36
+ npm start
27
37
  ```
28
38
 
29
- ## To Test
39
+ ### Running with HTTP Stream
40
+
41
+ To run the server with HTTP Stream or SSE support, you can either:
42
+
43
+ 1. **Use the default ports:** Run `npm start` after building (as shown above). The server will automatically listen on port `8088` for HTTP Stream and `8087` for SSE.
44
+ 2. **Specify custom ports:** Set the `HTTP_STREAM_PORT` and/or `SSE_PORT` environment variables before starting the server.
45
+
46
+ ```bash
47
+ HTTP_STREAM_PORT=9000 SSE_PORT=9001 npm start
48
+ ```
49
+
50
+ Or, if using a `.env` file, add `HTTP_STREAM_PORT=9000` and/or `SSE_PORT=9001` to it.
51
+
52
+ ## Docker
53
+
54
+ This project can be easily containerized using Docker. A `Dockerfile` is provided at the root of the repository.
30
55
 
31
- Run [MCP Inspector](https://github.com/modelcontextprotocol/inspector) after build…
56
+ ### Build the Docker Image
32
57
 
58
+ To build the Docker image, navigate to the project root and run:
59
+
60
+ ```bash
61
+ docker build -t roam-research-mcp .
33
62
  ```
34
- npx @modelcontextprotocol/inspector node build/index.js
63
+
64
+ ### Run the Docker Container
65
+
66
+ To run the Docker container and map the necessary ports, you must also provide the required environment variables. Use the `-e` flag to pass `ROAM_API_TOKEN`, `ROAM_GRAPH_NAME`, and optionally `MEMORIES_TAG`, `HTTP_STREAM_PORT`, and `SSE_PORT`:
67
+
68
+ ```bash
69
+ docker run -p 3000:3000 -p 8088:8088 -p 8087:8087 \
70
+ -e ROAM_API_TOKEN="your-api-token" \
71
+ -e ROAM_GRAPH_NAME="your-graph-name" \
72
+ -e MEMORIES_TAG="#[[LLM/Memories]]" \
73
+ -e HTTP_STREAM_PORT="8088" \
74
+ -e SSE_PORT="8087" \
75
+ roam-research-mcp
76
+ ```
77
+
78
+ 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:
79
+
80
+ ```bash
81
+ docker run -p 3000:3000 -p 8088:8088 --env-file .env roam-research-mcp
82
+ ```
83
+
84
+ ## To Test
85
+
86
+ Run [MCP Inspector](https://github.com/modelcontextprotocol/inspector) after build using the provided npm script:
87
+
88
+ ```bash
89
+ npm run inspector
35
90
  ```
36
91
 
37
92
  ## Features
@@ -48,22 +103,24 @@ The server provides powerful tools for interacting with Roam Research:
48
103
  - Efficient batch operations
49
104
  - Hierarchical outline creation
50
105
 
51
- 1. `roam_fetch_page_by_title`: Fetch and read a page's content by title, recursively resolving block references up to 4 levels deep
52
- 2. `roam_create_page`: Create new pages with optional content
53
- 3. `roam_create_block`: Create new blocks in a page (defaults to today's daily page)
54
- 4. `roam_import_markdown`: Import nested markdown content under specific blocks
55
- 5. `roam_add_todo`: Add multiple todo items to today's daily page with checkbox syntax
56
- 6. `roam_create_outline`: Create hierarchical outlines with proper nesting and structure
57
- 7. `roam_search_block_refs`: Search for block references within pages or across the graph
58
- 8. `roam_search_hierarchy`: Navigate and search through block parent-child relationships
59
- 9. `roam_find_pages_modified_today`: Find all pages that have been modified since midnight today
60
- 10. `roam_search_by_text`: Search for blocks containing specific text across all pages or within a specific page
61
- 11. `roam_update_block`: Update block content with direct text or pattern-based transformations
62
- 12. `roam_search_by_date`: Search for blocks and pages based on creation or modification dates
63
- 13. `roam_search_for_tag`: Search for blocks containing specific tags with optional filtering by nearby tags
64
- 14. `roam_remember`: Store and categorize memories or information with automatic tagging
65
- 15. `roam_recall`: Recall memories of blocks marked with tag MEMORIES_TAG (see below) or blocks on page title of the same name
66
- 16. `roam_datomic_query`: Execute custom Datalog queries on the Roam graph for advanced data retrieval and analysis
106
+ 1. `roam_fetch_page_by_title`: Fetch page content by title.
107
+ 2. `roam_create_page`: Create new pages with optional content and headings.
108
+ 3. `roam_create_block`: Add new blocks to an existing page or today's daily note.
109
+ 4. `roam_import_markdown`: Import nested markdown content under a specific block.
110
+ 5. `roam_add_todo`: Add a list of todo items to today's daily page.
111
+ 6. `roam_create_outline`: Add a structured outline to an existing page or block.
112
+ 7. `roam_search_block_refs`: Search for block references within a page or across the entire graph.
113
+ 8. `roam_search_hierarchy`: Search for parent or child blocks in the block hierarchy.
114
+ 9. `roam_find_pages_modified_today`: Find pages that have been modified today (since midnight).
115
+ 10. `roam_search_by_text`: Search for blocks containing specific text.
116
+ 11. `roam_update_block`: Update a single block identified by its UID.
117
+ 12. `roam_update_multiple_blocks`: Efficiently update multiple blocks in a single batch operation.
118
+ 13. `roam_search_by_status`: Search for blocks with a specific status (TODO/DONE) across all pages or within a specific page.
119
+ 14. `roam_search_by_date`: Search for blocks or pages based on creation or modification dates.
120
+ 15. `roam_search_for_tag`: Search for blocks containing a specific tag and optionally filter by blocks that also contain another tag nearby.
121
+ 16. `roam_remember`: Add a memory or piece of information to remember.
122
+ 17. `roam_recall`: Retrieve all stored memories.
123
+ 18. `roam_datomic_query`: Execute a custom Datomic query on the Roam graph beyond the available search tools.
67
124
 
68
125
  ## Setup
69
126
 
@@ -83,10 +140,12 @@ The server provides powerful tools for interacting with Roam Research:
83
140
  ROAM_API_TOKEN=your-api-token
84
141
  ROAM_GRAPH_NAME=your-graph-name
85
142
  MEMORIES_TAG='#[[LLM/Memories]]'
143
+ HTTP_STREAM_PORT=8088 # Or your desired port for HTTP Stream communication
144
+ SSE_PORT=8087 # Or your desired port for SSE communication
86
145
  ```
87
146
 
88
147
  Option 2: Using MCP settings (Alternative method)
89
- Add the configuration to your MCP settings file:
148
+ Add the configuration to your MCP settings file. Note that you may need to update the `args` to `["/path/to/roam-research-mcp/build/index.js"]` if you are running the server directly.
90
149
 
91
150
  - For Cline (`~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`):
92
151
  - For Claude desktop app (`~/Library/Application Support/Claude/claude_desktop_config.json`):
@@ -100,7 +159,9 @@ The server provides powerful tools for interacting with Roam Research:
100
159
  "env": {
101
160
  "ROAM_API_TOKEN": "your-api-token",
102
161
  "ROAM_GRAPH_NAME": "your-graph-name",
103
- "MEMORIES_TAG": "#[[LLM/Memories]]"
162
+ "MEMORIES_TAG": "#[[LLM/Memories]]",
163
+ "HTTP_STREAM_PORT": "8088",
164
+ "SSE_PORT": "8087"
104
165
  }
105
166
  }
106
167
  }
@@ -116,599 +177,6 @@ The server provides powerful tools for interacting with Roam Research:
116
177
  npm run build
117
178
  ```
118
179
 
119
- ## Usage
120
-
121
- ### Fetch Page By Title
122
-
123
- Fetch and read a page's content with resolved block references:
124
-
125
- ```typescript
126
- use_mcp_tool roam-research roam_fetch_page_by_title {
127
- "title": "Example Page"
128
- }
129
- ```
130
-
131
- Returns the page content as markdown with:
132
-
133
- - Complete hierarchical structure
134
- - Block references recursively resolved (up to 4 levels deep)
135
- - Proper indentation for nesting levels
136
- - Full markdown formatting
137
-
138
- ### Create Page
139
-
140
- Create a new page with optional content:
141
-
142
- ```typescript
143
- use_mcp_tool roam-research roam_create_page {
144
- "title": "New Page",
145
- "content": "Initial content for the page"
146
- }
147
- ```
148
-
149
- Returns the created page's UID on success.
150
-
151
- ### Create Block
152
-
153
- Add a new block to a page (defaults to today's daily page if neither page_uid nor title provided):
154
-
155
- ```typescript
156
- use_mcp_tool roam-research roam_create_block {
157
- "content": "Block content",
158
- "page_uid": "optional-target-page-uid",
159
- "title": "optional-target-page-title"
160
- }
161
- ```
162
-
163
- You can specify either:
164
-
165
- - `page_uid`: Direct reference to target page
166
- - `title`: Name of target page (will be created if it doesn't exist)
167
- - Neither: Block will be added to today's daily page
168
-
169
- Returns:
170
-
171
- ```json
172
- {
173
- "success": true,
174
- "block_uid": "created-block-uid",
175
- "parent_uid": "parent-page-uid"
176
- }
177
- ```
178
-
179
- ### Create Outline
180
-
181
- Create a hierarchical outline with proper nesting and structure:
182
-
183
- ```typescript
184
- use_mcp_tool roam-research roam_create_outline {
185
- "outline": [
186
- {
187
- "text": "I. Top Level",
188
- "level": 1
189
- },
190
- {
191
- "text": "A. Second Level",
192
- "level": 2
193
- },
194
- {
195
- "text": "1. Third Level",
196
- "level": 3
197
- }
198
- ],
199
- "page_title_uid": "optional-target-page",
200
- "block_text_uid": "optional-header-text"
201
- }
202
- ```
203
-
204
- Features:
205
-
206
- - Create complex outlines with up to 10 levels of nesting
207
- - Validate outline structure and content
208
- - Maintain proper parent-child relationships
209
- - Optional header block for the outline
210
- - Defaults to today's daily page if no page specified
211
- - Efficient batch operations for creating blocks
212
-
213
- Parameters:
214
-
215
- - `outline`: Array of outline items, each with:
216
- - `text`: Content of the outline item (required)
217
- - `level`: Nesting level (1-10, required)
218
- - `page_title_uid`: Target page title or UID (optional, defaults to today's page)
219
- - `block_text_uid`: Header text for the outline (optional)
220
-
221
- Returns:
222
-
223
- ```json
224
- {
225
- "success": true,
226
- "page_uid": "target-page-uid",
227
- "parent_uid": "header-block-uid",
228
- "created_uids": ["uid1", "uid2", ...]
229
- }
230
- ```
231
-
232
- ### Add Todo Items
233
-
234
- Add one or more todo items to today's daily page:
235
-
236
- ```typescript
237
- use_mcp_tool roam-research roam_add_todo {
238
- "todos": [
239
- "First todo item",
240
- "Second todo item",
241
- "Third todo item"
242
- ]
243
- }
244
- ```
245
-
246
- Features:
247
-
248
- - Adds todos with Roam checkbox syntax (`{{TODO}} todo text`)
249
- - Supports adding multiple todos in a single operation
250
- - Uses batch actions for efficiency when adding >10 todos
251
- - Automatically creates today's page if it doesn't exist
252
- - Adds todos as top-level blocks in sequential order
253
-
254
- ### Import Nested Markdown
255
-
256
- Import nested markdown content under a specific block:
257
-
258
- ```typescript
259
- use_mcp_tool roam-research roam_import_markdown {
260
- "content": "- Item 1\n - Subitem A\n - Subitem B\n- Item 2",
261
- "page_uid": "optional-page-uid",
262
- "page_title": "optional-page-title",
263
- "parent_uid": "optional-parent-block-uid",
264
- "parent_string": "optional-exact-block-content",
265
- "order": "first"
266
- }
267
- ```
268
-
269
- Features:
270
-
271
- - Import content under specific blocks:
272
- - Find parent block by UID or exact string match
273
- - Locate blocks within specific pages by title or UID
274
- - Defaults to today's page if no page specified
275
- - Control content placement:
276
- - Add as first or last child of parent block
277
- - Preserve hierarchical structure
278
- - Efficient batch operations for nested content
279
- - Comprehensive return value:
280
- ```json
281
- {
282
- "success": true,
283
- "page_uid": "target-page-uid",
284
- "parent_uid": "parent-block-uid",
285
- "created_uids": ["uid1", "uid2", ...]
286
- }
287
- ```
288
-
289
- Parameters:
290
-
291
- - `content`: Nested markdown content to import
292
- - `page_uid`: UID of the page containing the parent block
293
- - `page_title`: Title of the page containing the parent block (ignored if page_uid provided)
294
- - `parent_uid`: UID of the parent block to add content under
295
- - `parent_string`: Exact string content of the parent block (must provide either page_uid or page_title)
296
- - `order`: Where to add the content ("first" or "last", defaults to "first")
297
-
298
- ### Search Block References
299
-
300
- Search for block references within pages or across the entire graph:
301
-
302
- ```typescript
303
- use_mcp_tool roam-research roam_search_block_refs {
304
- "block_uid": "optional-block-uid",
305
- "page_title_uid": "optional-page-title-or-uid"
306
- }
307
- ```
308
-
309
- Features:
310
-
311
- - Find all references to a specific block
312
- - Search for any block references within a page
313
- - Search across the entire graph
314
- - Supports both direct and indirect references
315
- - Includes block content and location context
316
-
317
- Parameters:
318
-
319
- - `block_uid`: UID of the block to find references to (optional)
320
- - `page_title_uid`: Title or UID of the page to search in (optional)
321
-
322
- Returns:
323
-
324
- ```json
325
- {
326
- "success": true,
327
- "matches": [
328
- {
329
- "block_uid": "referenced-block-uid",
330
- "content": "Block content with ((reference))",
331
- "page_title": "Page containing reference"
332
- }
333
- ],
334
- "message": "Found N block(s) referencing..."
335
- }
336
- ```
337
-
338
- ### Search By Text
339
-
340
- Search for blocks containing specific text across all pages or within a specific page:
341
-
342
- ```typescript
343
- use_mcp_tool roam-research roam_search_by_text {
344
- "text": "search text",
345
- "page_title_uid": "optional-page-title-or-uid",
346
- "case_sensitive": true
347
- }
348
- ```
349
-
350
- Features:
351
-
352
- - Search for any text across all blocks in the graph
353
- - Optional page-scoped search
354
- - Case-sensitive or case-insensitive search
355
- - Returns block content with page context
356
- - Efficient text matching using Datalog queries
357
-
358
- Parameters:
359
-
360
- - `text`: The text to search for (required)
361
- - `page_title_uid`: Title or UID of the page to search in (optional)
362
- - `case_sensitive`: Whether to perform a case-sensitive search (optional, default: true to match Roam's native behavior)
363
-
364
- Returns:
365
-
366
- ```json
367
- {
368
- "success": true,
369
- "matches": [
370
- {
371
- "block_uid": "matching-block-uid",
372
- "content": "Block content containing search text",
373
- "page_title": "Page containing block"
374
- }
375
- ],
376
- "message": "Found N block(s) containing \"search text\""
377
- }
378
- ```
379
-
380
- ### Update Block Content
381
-
382
- Update a block's content using either direct text replacement or pattern-based transformations:
383
-
384
- ```typescript
385
- use_mcp_tool roam-research roam_update_block {
386
- "block_uid": "target-block-uid",
387
- "content": "New block content"
388
- }
389
- ```
390
-
391
- Or use pattern-based transformation:
392
-
393
- ```typescript
394
- use_mcp_tool roam-research roam_update_block {
395
- "block_uid": "target-block-uid",
396
- "transform_pattern": {
397
- "find": "\\bPython\\b",
398
- "replace": "[[Python]]",
399
- "global": true
400
- }
401
- }
402
- ```
403
-
404
- Features:
405
-
406
- - Two update modes:
407
- - Direct content replacement
408
- - Pattern-based transformation using regex
409
- - Verify block existence before updating
410
- - Return updated content in response
411
- - Support for global or single-match replacements
412
- - Preserve block relationships and metadata
413
-
414
- Parameters:
415
-
416
- - `block_uid`: UID of the block to update (required)
417
- - `content`: New content for the block (if using direct replacement)
418
- - `transform_pattern`: Pattern for transforming existing content:
419
- - `find`: Text or regex pattern to find
420
- - `replace`: Text to replace with
421
- - `global`: Whether to replace all occurrences (default: true)
422
-
423
- Returns:
424
-
425
- ```json
426
- {
427
- "success": true,
428
- "content": "Updated block content"
429
- }
430
- ```
431
-
432
- ### Search For Tags
433
-
434
- Search for blocks containing specific tags with optional filtering by nearby tags:
435
-
436
- ```typescript
437
- use_mcp_tool roam-research roam_search_for_tag {
438
- "primary_tag": "Project/Tasks",
439
- "page_title_uid": "optional-page-title-or-uid",
440
- "near_tag": "optional-secondary-tag",
441
- "case_sensitive": true
442
- }
443
- ```
444
-
445
- Features:
446
-
447
- - Search for blocks containing specific tags
448
- - Optional filtering by presence of another tag
449
- - Page-scoped or graph-wide search
450
- - Case-sensitive or case-insensitive search
451
- - Returns block content with page context
452
- - Efficient tag matching using Datalog queries
453
-
454
- Parameters:
455
-
456
- - `primary_tag`: The main tag to search for (required)
457
- - `page_title_uid`: Title or UID of the page to search in (optional)
458
- - `near_tag`: Another tag to filter results by (optional)
459
- - `case_sensitive`: Whether to perform case-sensitive search (optional, default: true to match Roam's native behavior)
460
-
461
- Returns:
462
-
463
- ```json
464
- {
465
- "success": true,
466
- "matches": [
467
- {
468
- "block_uid": "matching-block-uid",
469
- "content": "Block content containing #[[primary_tag]]",
470
- "page_title": "Page containing block"
471
- }
472
- ],
473
- "message": "Found N block(s) referencing \"primary_tag\""
474
- }
475
- ```
476
-
477
- ### Remember Information
478
-
479
- Store memories or important information with automatic tagging and categorization:
480
-
481
- ```typescript
482
- use_mcp_tool roam-research roam_remember {
483
- "memory": "Important information to remember",
484
- "categories": ["Work", "Project/Alpha"]
485
- }
486
- ```
487
-
488
- Features:
489
-
490
- - Store information with #[[LLM/Memories]] tag
491
- - Add optional category tags for organization
492
- - Automatically adds to today's daily page
493
- - Supports multiple categories per memory
494
- - Easy retrieval using roam_search_for_tag
495
- - Maintains chronological order of memories
496
-
497
- Parameters:
498
-
499
- - `memory`: The information to remember (required)
500
- - `categories`: Optional array of categories to tag the memory with
501
-
502
- Returns:
503
-
504
- ```json
505
- {
506
- "success": true,
507
- "block_uid": "created-block-uid",
508
- "content": "Memory content with tags"
509
- }
510
- ```
511
-
512
- ### Search By Date
513
-
514
- Search for blocks and pages based on creation or modification dates:
515
-
516
- ```typescript
517
- use_mcp_tool roam-research roam_search_by_date {
518
- "start_date": "2025-01-01",
519
- "end_date": "2025-01-31",
520
- "type": "modified",
521
- "scope": "blocks",
522
- "include_content": true
523
- }
524
- ```
525
-
526
- Features:
527
-
528
- - Search by creation date, modification date, or both
529
- - Filter blocks, pages, or both
530
- - Optional date range with start and end dates
531
- - Include or exclude block/page content in results
532
- - Sort results by timestamp
533
- - Efficient date-based filtering using Datalog queries
534
-
535
- Parameters:
536
-
537
- - `start_date`: Start date in ISO format (YYYY-MM-DD) (required)
538
- - `end_date`: End date in ISO format (YYYY-MM-DD) (optional)
539
- - `type`: Whether to search by 'created', 'modified', or 'both' (required)
540
- - `scope`: Whether to search 'blocks', 'pages', or 'both' (required)
541
- - `include_content`: Whether to include the content of matching blocks/pages (optional, default: true)
542
-
543
- Returns:
544
-
545
- ```json
546
- {
547
- "success": true,
548
- "matches": [
549
- {
550
- "uid": "block-or-page-uid",
551
- "type": "block",
552
- "time": 1704067200000,
553
- "content": "Block or page content",
554
- "page_title": "Page title (for blocks)"
555
- }
556
- ],
557
- "message": "Found N matches for the given date range and criteria"
558
- }
559
- ```
560
-
561
- ### Find Pages Modified Today
562
-
563
- Find all pages that have been modified since midnight today:
564
-
565
- ```typescript
566
- use_mcp_tool roam-research roam_find_pages_modified_today {}
567
- ```
568
-
569
- Features:
570
-
571
- - Tracks all modifications made to pages since midnight
572
- - Detects changes at any level in the block hierarchy
573
- - Returns unique list of modified page titles
574
- - Includes count of modified pages
575
- - No parameters required
576
-
577
- Returns:
578
-
579
- ```json
580
- {
581
- "success": true,
582
- "pages": ["Page 1", "Page 2"],
583
- "message": "Found 2 page(s) modified today"
584
- }
585
- ```
586
-
587
- ### Execute Datomic Queries
588
-
589
- Execute custom Datalog queries on your Roam graph for advanced data retrieval and analysis:
590
-
591
- ```typescript
592
- use_mcp_tool roam-research roam_datomic_query {
593
- "query": "[:find (count ?p)\n :where [?p :node/title]]",
594
- "inputs": []
595
- }
596
- ```
597
-
598
- Features:
599
-
600
- - Direct access to Roam's query engine
601
- - Support for all Datalog query features:
602
- - Complex pattern matching
603
- - Aggregation functions (count, sum, max, min, avg, distinct)
604
- - String operations (includes?, starts-with?, ends-with?)
605
- - Logical operations (<, >, <=, >=, =, not=)
606
- - Rules for recursive queries
607
- - Case-sensitive and case-insensitive search capabilities
608
- - Efficient querying across the entire graph
609
-
610
- Parameters:
611
-
612
- - `query`: The Datalog query to execute (required)
613
- - `inputs`: Optional array of input parameters for the query
614
-
615
- Returns:
616
-
617
- ```json
618
- {
619
- "success": true,
620
- "matches": [
621
- {
622
- "content": "[result data]",
623
- "block_uid": "",
624
- "page_title": ""
625
- }
626
- ],
627
- "message": "Query executed successfully. Found N results."
628
- }
629
- ```
630
-
631
- Example Queries:
632
-
633
- 1. Count all pages:
634
-
635
- ```clojure
636
- [:find (count ?p)
637
- :where [?p :node/title]]
638
- ```
639
-
640
- 2. Case-insensitive text search:
641
-
642
- ```clojure
643
- [:find ?string ?title
644
- :where
645
- [?b :block/string ?string]
646
- [(clojure.string/lower-case ?string) ?lower]
647
- [(clojure.string/includes? ?lower "search term")]
648
- [?b :block/page ?p]
649
- [?p :node/title ?title]]
650
- ```
651
-
652
- 3. Find blocks modified after a date:
653
-
654
- ```clojure
655
- [:find ?block_ref ?string
656
- :in $ ?start_of_day
657
- :where
658
- [?b :edit/time ?time]
659
- [(> ?time ?start_of_day)]
660
- [?b :block/uid ?block_ref]
661
- [?b :block/string ?string]]
662
- ```
663
-
664
- See Roam_Research_Datalog_Cheatsheet.md for more query examples and syntax documentation.
665
-
666
- ### Search Block Hierarchy
667
-
668
- Navigate and search through block parent-child relationships:
669
-
670
- ```typescript
671
- use_mcp_tool roam-research roam_search_hierarchy {
672
- "parent_uid": "optional-parent-block-uid",
673
- "child_uid": "optional-child-block-uid",
674
- "page_title_uid": "optional-page-title-or-uid",
675
- "max_depth": 3
676
- }
677
- ```
678
-
679
- Features:
680
-
681
- - Search up or down the block hierarchy
682
- - Find children of a specific block
683
- - Find parents of a specific block
684
- - Configure search depth (1-10 levels)
685
- - Optional page scope filtering
686
- - Includes depth information for each result
687
-
688
- Parameters:
689
-
690
- - `parent_uid`: UID of the block to find children of (required if searching down)
691
- - `child_uid`: UID of the block to find parents of (required if searching up)
692
- - `page_title_uid`: Title or UID of the page to search in (optional)
693
- - `max_depth`: How many levels deep to search (optional, default: 1, max: 10)
694
-
695
- Returns:
696
-
697
- ```json
698
- {
699
- "success": true,
700
- "matches": [
701
- {
702
- "block_uid": "related-block-uid",
703
- "content": "Block content",
704
- "depth": 2,
705
- "page_title": "Page containing block"
706
- }
707
- ],
708
- "message": "Found N block(s) as children/parents..."
709
- }
710
- ```
711
-
712
180
  ## Error Handling
713
181
 
714
182
  The server provides comprehensive error handling for common scenarios:
@@ -41,4 +41,6 @@ if (!API_TOKEN || !GRAPH_NAME) {
41
41
  ' ROAM_API_TOKEN=your-api-token\n' +
42
42
  ' ROAM_GRAPH_NAME=your-graph-name');
43
43
  }
44
- export { API_TOKEN, GRAPH_NAME };
44
+ const HTTP_STREAM_PORT = process.env.HTTP_STREAM_PORT || '8088'; // Default to 8080
45
+ const SSE_PORT = process.env.SSE_PORT || '8087'; // Default to 8087
46
+ export { API_TOKEN, GRAPH_NAME, HTTP_STREAM_PORT, SSE_PORT };
@@ -1,59 +1,54 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
3
5
  import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
4
6
  import { initializeGraph } from '@roam-research/roam-api-sdk';
5
- import { API_TOKEN, GRAPH_NAME } from '../config/environment.js';
7
+ import { API_TOKEN, GRAPH_NAME, HTTP_STREAM_PORT, SSE_PORT } from '../config/environment.js';
6
8
  import { toolSchemas } from '../tools/schemas.js';
7
9
  import { ToolHandlers } from '../tools/tool-handlers.js';
10
+ import { readFileSync } from 'node:fs';
11
+ import { join, dirname } from 'node:path';
12
+ import { createServer } from 'node:http';
13
+ import { fileURLToPath } from 'node:url';
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+ // Read package.json to get the version
17
+ const packageJsonPath = join(__dirname, '../../package.json');
18
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
19
+ const serverVersion = packageJson.version;
8
20
  export class RoamServer {
9
21
  constructor() {
10
- this.graph = initializeGraph({
11
- token: API_TOKEN,
12
- graph: GRAPH_NAME,
13
- });
14
- this.toolHandlers = new ToolHandlers(this.graph);
15
- this.server = new Server({
16
- name: 'roam-research',
17
- version: '0.25.5',
18
- }, {
19
- capabilities: {
20
- tools: {
21
- roam_remember: {},
22
- roam_recall: {},
23
- roam_add_todo: {},
24
- roam_fetch_page_by_title: {},
25
- roam_create_page: {},
26
- roam_create_block: {},
27
- roam_import_markdown: {},
28
- roam_create_outline: {},
29
- roam_search_for_tag: {},
30
- roam_search_by_status: {},
31
- roam_search_block_refs: {},
32
- roam_search_hierarchy: {},
33
- roam_find_pages_modified_today: {},
34
- roam_search_by_text: {},
35
- roam_update_block: {},
36
- roam_update_multiple_blocks: {},
37
- roam_search_by_date: {},
38
- roam_datomic_query: {}
39
- },
40
- },
41
- });
42
- this.setupRequestHandlers();
43
- // Error handling
44
- this.server.onerror = (error) => { };
45
- process.on('SIGINT', async () => {
46
- await this.server.close();
47
- process.exit(0);
48
- });
22
+ try {
23
+ this.graph = initializeGraph({
24
+ token: API_TOKEN,
25
+ graph: GRAPH_NAME,
26
+ });
27
+ }
28
+ catch (error) {
29
+ const errorMessage = error instanceof Error ? error.message : String(error);
30
+ throw new McpError(ErrorCode.InternalError, `Failed to initialize Roam graph: ${errorMessage}`);
31
+ }
32
+ try {
33
+ this.toolHandlers = new ToolHandlers(this.graph);
34
+ }
35
+ catch (error) {
36
+ const errorMessage = error instanceof Error ? error.message : String(error);
37
+ throw new McpError(ErrorCode.InternalError, `Failed to initialize tool handlers: ${errorMessage}`);
38
+ }
39
+ // Ensure toolSchemas is not empty before proceeding
40
+ if (Object.keys(toolSchemas).length === 0) {
41
+ throw new McpError(ErrorCode.InternalError, 'No tool schemas defined in src/tools/schemas.ts');
42
+ }
49
43
  }
50
- setupRequestHandlers() {
44
+ // Refactored to accept a Server instance
45
+ setupRequestHandlers(mcpServer) {
51
46
  // List available tools
52
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
47
+ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({
53
48
  tools: Object.values(toolSchemas),
54
49
  }));
55
50
  // Handle tool calls
56
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
51
+ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
57
52
  try {
58
53
  switch (request.params.name) {
59
54
  case 'roam_remember': {
@@ -220,7 +215,116 @@ export class RoamServer {
220
215
  });
221
216
  }
222
217
  async run() {
223
- const transport = new StdioServerTransport();
224
- await this.server.connect(transport);
218
+ try {
219
+ const stdioMcpServer = new Server({
220
+ name: 'roam-research',
221
+ version: serverVersion,
222
+ }, {
223
+ capabilities: {
224
+ tools: {
225
+ ...Object.fromEntries(Object.keys(toolSchemas).map((toolName) => [toolName, {}])),
226
+ },
227
+ },
228
+ });
229
+ this.setupRequestHandlers(stdioMcpServer);
230
+ const stdioTransport = new StdioServerTransport();
231
+ await stdioMcpServer.connect(stdioTransport);
232
+ const httpMcpServer = new Server({
233
+ name: 'roam-research-http', // A distinct name for the HTTP server
234
+ version: serverVersion,
235
+ }, {
236
+ capabilities: {
237
+ tools: {
238
+ ...Object.fromEntries(Object.keys(toolSchemas).map((toolName) => [toolName, {}])),
239
+ },
240
+ },
241
+ });
242
+ this.setupRequestHandlers(httpMcpServer);
243
+ const httpStreamTransport = new StreamableHTTPServerTransport({
244
+ sessionIdGenerator: () => Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15),
245
+ });
246
+ await httpMcpServer.connect(httpStreamTransport);
247
+ const httpServer = createServer(async (req, res) => {
248
+ try {
249
+ await httpStreamTransport.handleRequest(req, res);
250
+ }
251
+ catch (error) {
252
+ // console.error('HTTP Stream Server error:', error);
253
+ if (!res.headersSent) {
254
+ res.writeHead(500, { 'Content-Type': 'application/json' });
255
+ res.end(JSON.stringify({ error: 'Internal Server Error' }));
256
+ }
257
+ }
258
+ });
259
+ httpServer.listen(parseInt(HTTP_STREAM_PORT), () => {
260
+ // console.log(`MCP Roam Research server running HTTP Stream on port ${HTTP_STREAM_PORT}`);
261
+ });
262
+ // SSE Server setup
263
+ const sseMcpServer = new Server({
264
+ name: 'roam-research-sse', // Distinct name for SSE server
265
+ version: serverVersion,
266
+ }, {
267
+ capabilities: {
268
+ tools: {
269
+ ...Object.fromEntries(Object.keys(toolSchemas).map((toolName) => [toolName, {}])),
270
+ },
271
+ },
272
+ });
273
+ this.setupRequestHandlers(sseMcpServer);
274
+ const sseHttpServer = createServer(async (req, res) => {
275
+ const parseBody = (request) => {
276
+ return new Promise((resolve, reject) => {
277
+ let body = '';
278
+ request.on('data', (chunk) => {
279
+ body += chunk.toString();
280
+ });
281
+ request.on('end', () => {
282
+ try {
283
+ resolve(body ? JSON.parse(body) : {});
284
+ }
285
+ catch (error) {
286
+ reject(error);
287
+ }
288
+ });
289
+ request.on('error', reject);
290
+ });
291
+ };
292
+ try {
293
+ if (req.url === '/sse') {
294
+ const sseTransport = new SSEServerTransport('/sse', res);
295
+ await sseMcpServer.connect(sseTransport);
296
+ if (req.method === 'GET') {
297
+ await sseTransport.start();
298
+ }
299
+ else if (req.method === 'POST') {
300
+ const parsedBody = await parseBody(req);
301
+ await sseTransport.handlePostMessage(req, res, parsedBody);
302
+ }
303
+ else {
304
+ res.writeHead(405, { 'Content-Type': 'text/plain' });
305
+ res.end('Method Not Allowed');
306
+ }
307
+ }
308
+ else {
309
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
310
+ res.end('Not Found');
311
+ }
312
+ }
313
+ catch (error) {
314
+ // console.error('SSE HTTP Server error:', error);
315
+ if (!res.headersSent) {
316
+ res.writeHead(500, { 'Content-Type': 'application/json' });
317
+ res.end(JSON.stringify({ error: 'Internal Server Error' }));
318
+ }
319
+ }
320
+ });
321
+ sseHttpServer.listen(parseInt(SSE_PORT), () => {
322
+ // console.log(`MCP Roam Research server running SSE on port ${SSE_PORT}`);
323
+ });
324
+ }
325
+ catch (error) {
326
+ const errorMessage = error instanceof Error ? error.message : String(error);
327
+ throw new McpError(ErrorCode.InternalError, `Failed to connect MCP server: ${errorMessage}`);
328
+ }
225
329
  }
226
330
  }
@@ -19,21 +19,24 @@ export const toolSchemas = {
19
19
  },
20
20
  },
21
21
  roam_fetch_page_by_title: {
22
+ name: 'roam_fetch_page_by_title',
22
23
  description: 'Fetch page by title, defaults to raw JSON string.',
23
- type: 'object',
24
- properties: {
25
- title: {
26
- type: 'string',
27
- description: 'Title of the page. For date pages, use ordinal date formats such as January 2nd, 2025'
24
+ inputSchema: {
25
+ type: 'object',
26
+ properties: {
27
+ title: {
28
+ type: 'string',
29
+ description: 'Title of the page. For date pages, use ordinal date formats such as January 2nd, 2025'
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
+ }
28
37
  },
29
- format: {
30
- type: 'string',
31
- enum: ['markdown', 'raw'],
32
- default: 'raw',
33
- description: "Format output as markdown or JSON. 'markdown' returns as string; 'raw' returns JSON string of the page's blocks"
34
- }
38
+ required: ['title']
35
39
  },
36
- required: ['title']
37
40
  },
38
41
  roam_create_page: {
39
42
  name: 'roam_create_page',
@@ -149,7 +152,7 @@ export const toolSchemas = {
149
152
  },
150
153
  roam_import_markdown: {
151
154
  name: 'roam_import_markdown',
152
- 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.',
153
156
  inputSchema: {
154
157
  type: 'object',
155
158
  properties: {
@@ -171,7 +174,7 @@ export const toolSchemas = {
171
174
  },
172
175
  parent_string: {
173
176
  type: 'string',
174
- 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)'
175
178
  },
176
179
  order: {
177
180
  type: 'string',
@@ -311,7 +314,7 @@ export const toolSchemas = {
311
314
  },
312
315
  roam_update_block: {
313
316
  name: 'roam_update_block',
314
- 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 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).',
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).',
315
318
  inputSchema: {
316
319
  type: 'object',
317
320
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roam-research-mcp",
3
- "version": "0.25.5",
3
+ "version": "0.27.0",
4
4
  "description": "A Model Context Protocol (MCP) server for Roam Research API integration",
5
5
  "private": false,
6
6
  "repository": {
@@ -29,12 +29,12 @@
29
29
  ],
30
30
  "scripts": {
31
31
  "build": "tsc && chmod 755 build/index.js",
32
- "prepare": "npm run build",
33
32
  "watch": "tsc --watch",
34
- "inspector": "npx @modelcontextprotocol/inspector build/index.js"
33
+ "inspector": "npx @modelcontextprotocol/inspector build/index.js",
34
+ "start": "node build/index.js"
35
35
  },
36
36
  "dependencies": {
37
- "@modelcontextprotocol/sdk": "0.6.0",
37
+ "@modelcontextprotocol/sdk": "^1.0.0",
38
38
  "@roam-research/roam-api-sdk": "^0.10.0",
39
39
  "dotenv": "^16.4.7"
40
40
  },