roam-research-mcp 0.32.0 → 0.32.4
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
|
@@ -73,6 +73,7 @@ docker run -p 3000:3000 -p 8088:8088 -p 8087:8087 \
|
|
|
73
73
|
-e ROAM_API_TOKEN="your-api-token" \
|
|
74
74
|
-e ROAM_GRAPH_NAME="your-graph-name" \
|
|
75
75
|
-e MEMORIES_TAG="#[[LLM/Memories]]" \
|
|
76
|
+
-e CUSTOM_INSTRUCTIONS_PATH="/path/to/your/custom_instructions_file.md" \
|
|
76
77
|
-e HTTP_STREAM_PORT="8088" \
|
|
77
78
|
-e SSE_PORT="8087" \
|
|
78
79
|
roam-research-mcp
|
|
@@ -106,10 +107,11 @@ The server provides powerful tools for interacting with Roam Research:
|
|
|
106
107
|
- Efficient batch operations
|
|
107
108
|
- Hierarchical outline creation
|
|
108
109
|
- Enhanced documentation for Roam Tables in `Roam_Markdown_Cheatsheet.md` for clearer guidance on nesting.
|
|
110
|
+
- Custom instruction appended to the cheat sheet about your specific Roam notes.
|
|
109
111
|
|
|
110
112
|
1. `roam_fetch_page_by_title`: Fetch page content by title. Returns content in the specified format.
|
|
111
113
|
2. `roam_fetch_block_with_children`: Fetch a block by its UID along with its hierarchical children down to a specified depth. Automatically handles `((UID))` formatting.
|
|
112
|
-
3. `roam_create_page`: Create new pages with optional content and headings.
|
|
114
|
+
3. `roam_create_page`: Create new pages with optional content and headings. Now creates a block on the daily page linking to the newly created page.
|
|
113
115
|
4. `roam_import_markdown`: Import nested markdown content under a specific block. (Internally uses `roam_process_batch_actions`.)
|
|
114
116
|
5. `roam_add_todo`: Add a list of todo items to today's daily page. (Internally uses `roam_process_batch_actions`.)
|
|
115
117
|
6. `roam_create_outline`: Add a structured outline to an existing page or block, with support for `children_view_type`. Best for simpler, sequential outlines. For complex nesting (e.g., tables), consider `roam_process_batch_actions`. If `page_title_uid` and `block_text_uid` are both blank, content defaults to the daily page. (Internally uses `roam_process_batch_actions`.)
|
|
@@ -242,6 +244,7 @@ This demonstrates moving a block from one location to another and simultaneously
|
|
|
242
244
|
ROAM_API_TOKEN=your-api-token
|
|
243
245
|
ROAM_GRAPH_NAME=your-graph-name
|
|
244
246
|
MEMORIES_TAG='#[[LLM/Memories]]'
|
|
247
|
+
CUSTOM_INSTRUCTIONS_PATH='/path/to/your/custom_instructions_file.md'
|
|
245
248
|
HTTP_STREAM_PORT=8088 # Or your desired port for HTTP Stream communication
|
|
246
249
|
SSE_PORT=8087 # Or your desired port for SSE communication
|
|
247
250
|
```
|
|
@@ -262,6 +265,7 @@ This demonstrates moving a block from one location to another and simultaneously
|
|
|
262
265
|
"ROAM_API_TOKEN": "your-api-token",
|
|
263
266
|
"ROAM_GRAPH_NAME": "your-graph-name",
|
|
264
267
|
"MEMORIES_TAG": "#[[LLM/Memories]]",
|
|
268
|
+
"CUSTOM_INSTRUCTIONS_PATH": "/path/to/your/custom_instructions_file.md",
|
|
265
269
|
"HTTP_STREAM_PORT": "8088",
|
|
266
270
|
"SSE_PORT": "8087"
|
|
267
271
|
}
|
|
@@ -112,7 +112,12 @@ The provided markdown structure represents a Roam Research Kanban board. It star
|
|
|
112
112
|
This markdown structure allows embedding custom HTML or other content using Hiccup syntax. The `:hiccup` keyword is followed by a Clojure-like vector defining the HTML elements and their attributes in one block. This provides a powerful way to inject dynamic or custom components into your Roam graph. Example: `:hiccup [:iframe {:width "600" :height "400" :src "https://www.example.com"}]`
|
|
113
113
|
|
|
114
114
|
## Specific notes and preferences concerning my Roam Research graph
|
|
115
|
+
### What To Tag
|
|
115
116
|
|
|
116
|
-
|
|
117
|
+
NONE
|
|
118
|
+
|
|
119
|
+
### Don't Include…
|
|
120
|
+
|
|
121
|
+
NONE
|
|
117
122
|
|
|
118
123
|
⭐️📋 END (Cheat Sheet LOADED) < < < 📋⭐️
|
package/build/markdown-utils.js
CHANGED
|
@@ -75,73 +75,69 @@ function convertToRoamMarkdown(text) {
|
|
|
75
75
|
return text;
|
|
76
76
|
}
|
|
77
77
|
function parseMarkdown(markdown) {
|
|
78
|
-
// Convert markdown syntax first
|
|
79
78
|
markdown = convertToRoamMarkdown(markdown);
|
|
80
|
-
const
|
|
79
|
+
const originalLines = markdown.split('\n');
|
|
80
|
+
const processedLines = [];
|
|
81
|
+
// Pre-process lines to handle mid-line code blocks without splice
|
|
82
|
+
for (const line of originalLines) {
|
|
83
|
+
const trimmedLine = line.trimEnd();
|
|
84
|
+
const codeStartIndex = trimmedLine.indexOf('```');
|
|
85
|
+
if (codeStartIndex > 0) {
|
|
86
|
+
const indentationWhitespace = line.match(/^\s*/)?.[0] ?? '';
|
|
87
|
+
processedLines.push(indentationWhitespace + trimmedLine.substring(0, codeStartIndex));
|
|
88
|
+
processedLines.push(indentationWhitespace + trimmedLine.substring(codeStartIndex));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
processedLines.push(line);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
81
94
|
const rootNodes = [];
|
|
82
95
|
const stack = [];
|
|
83
96
|
let inCodeBlock = false;
|
|
84
97
|
let codeBlockContent = '';
|
|
85
98
|
let codeBlockIndentation = 0;
|
|
86
99
|
let codeBlockParentLevel = 0;
|
|
87
|
-
for (let i = 0; i <
|
|
88
|
-
const line =
|
|
100
|
+
for (let i = 0; i < processedLines.length; i++) {
|
|
101
|
+
const line = processedLines[i];
|
|
89
102
|
const trimmedLine = line.trimEnd();
|
|
90
|
-
// Handle code blocks
|
|
91
103
|
if (trimmedLine.match(/^(\s*)```/)) {
|
|
92
104
|
if (!inCodeBlock) {
|
|
93
|
-
// Start of code block
|
|
94
105
|
inCodeBlock = true;
|
|
95
|
-
// Store the opening backticks without indentation
|
|
96
106
|
codeBlockContent = trimmedLine.trimStart() + '\n';
|
|
97
107
|
codeBlockIndentation = line.match(/^\s*/)?.[0].length ?? 0;
|
|
98
|
-
// Save current parent level
|
|
99
108
|
codeBlockParentLevel = stack.length;
|
|
100
109
|
}
|
|
101
110
|
else {
|
|
102
|
-
// End of code block
|
|
103
111
|
inCodeBlock = false;
|
|
104
|
-
// Add closing backticks without indentation
|
|
105
112
|
codeBlockContent += trimmedLine.trimStart();
|
|
106
|
-
|
|
107
|
-
const lines = codeBlockContent.split('\n');
|
|
108
|
-
// Find the first non-empty code line to determine base indentation
|
|
113
|
+
const linesInCodeBlock = codeBlockContent.split('\n');
|
|
109
114
|
let baseIndentation = '';
|
|
110
|
-
let
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const indentMatch = line.match(/^[\t ]*/);
|
|
115
|
+
for (let j = 1; j < linesInCodeBlock.length - 1; j++) {
|
|
116
|
+
const codeLine = linesInCodeBlock[j];
|
|
117
|
+
if (codeLine.trim().length > 0) {
|
|
118
|
+
const indentMatch = codeLine.match(/^[\t ]*/);
|
|
115
119
|
if (indentMatch) {
|
|
116
120
|
baseIndentation = indentMatch[0];
|
|
117
|
-
codeStartIndex = i;
|
|
118
121
|
break;
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
return line.trimStart();
|
|
127
|
-
// Empty lines should be completely trimmed
|
|
128
|
-
if (line.trim().length === 0)
|
|
125
|
+
const processedCodeLines = linesInCodeBlock.map((codeLine, index) => {
|
|
126
|
+
if (index === 0 || index === linesInCodeBlock.length - 1)
|
|
127
|
+
return codeLine.trimStart();
|
|
128
|
+
if (codeLine.trim().length === 0)
|
|
129
129
|
return '';
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return line.slice(baseIndentation.length);
|
|
130
|
+
if (codeLine.startsWith(baseIndentation)) {
|
|
131
|
+
return codeLine.slice(baseIndentation.length);
|
|
133
132
|
}
|
|
134
|
-
|
|
135
|
-
return line.trimStart();
|
|
133
|
+
return codeLine.trimStart();
|
|
136
134
|
});
|
|
137
|
-
// Create node for the entire code block
|
|
138
135
|
const level = Math.floor(codeBlockIndentation / 2);
|
|
139
136
|
const node = {
|
|
140
|
-
content:
|
|
137
|
+
content: processedCodeLines.join('\n'),
|
|
141
138
|
level,
|
|
142
139
|
children: []
|
|
143
140
|
};
|
|
144
|
-
// Restore to code block's parent level
|
|
145
141
|
while (stack.length > codeBlockParentLevel) {
|
|
146
142
|
stack.pop();
|
|
147
143
|
}
|
|
@@ -169,37 +165,30 @@ function parseMarkdown(markdown) {
|
|
|
169
165
|
codeBlockContent += line + '\n';
|
|
170
166
|
continue;
|
|
171
167
|
}
|
|
172
|
-
// Skip truly empty lines (no spaces)
|
|
173
168
|
if (trimmedLine === '') {
|
|
174
169
|
continue;
|
|
175
170
|
}
|
|
176
|
-
// Calculate indentation level (2 spaces = 1 level)
|
|
177
171
|
const indentation = line.match(/^\s*/)?.[0].length ?? 0;
|
|
178
172
|
let level = Math.floor(indentation / 2);
|
|
179
173
|
let contentToParse;
|
|
180
174
|
const bulletMatch = trimmedLine.match(/^(\s*)[-*+]\s+/);
|
|
181
175
|
if (bulletMatch) {
|
|
182
|
-
// If it's a bullet point, adjust level based on bullet indentation
|
|
183
176
|
level = Math.floor(bulletMatch[1].length / 2);
|
|
184
|
-
contentToParse = trimmedLine.substring(bulletMatch[0].length);
|
|
177
|
+
contentToParse = trimmedLine.substring(bulletMatch[0].length);
|
|
185
178
|
}
|
|
186
179
|
else {
|
|
187
|
-
contentToParse = trimmedLine;
|
|
180
|
+
contentToParse = trimmedLine;
|
|
188
181
|
}
|
|
189
|
-
// Now, from the content after bullet/initial indentation, check for heading
|
|
190
182
|
const { heading_level, content: finalContent } = parseMarkdownHeadingLevel(contentToParse);
|
|
191
|
-
// Create node
|
|
192
183
|
const node = {
|
|
193
|
-
content: finalContent,
|
|
194
|
-
level,
|
|
184
|
+
content: finalContent,
|
|
185
|
+
level,
|
|
195
186
|
...(heading_level > 0 && { heading_level }),
|
|
196
187
|
children: []
|
|
197
188
|
};
|
|
198
|
-
// Pop stack until we find the parent level
|
|
199
189
|
while (stack.length > level) {
|
|
200
190
|
stack.pop();
|
|
201
191
|
}
|
|
202
|
-
// Add to appropriate parent
|
|
203
192
|
if (level === 0 || !stack[level - 1]) {
|
|
204
193
|
rootNodes.push(node);
|
|
205
194
|
stack[0] = node;
|
|
@@ -21,6 +21,7 @@ export class OutlineOperations {
|
|
|
21
21
|
* no matching block is found, a new block with that text will be created
|
|
22
22
|
* on the page to serve as the parent. If a UID is provided and the block
|
|
23
23
|
* is not found, an error will be thrown.
|
|
24
|
+
* @returns An object containing success status, page UID, parent UID, and a nested array of created block UIDs.
|
|
24
25
|
*/
|
|
25
26
|
async createOutline(outline, page_title_uid, block_text_uid) {
|
|
26
27
|
// Validate input
|
|
@@ -125,7 +126,6 @@ export class OutlineOperations {
|
|
|
125
126
|
// Exponential backoff
|
|
126
127
|
const delay = initialDelay * Math.pow(2, retry);
|
|
127
128
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
128
|
-
console.log(`Retry ${retry + 1}/${maxRetries} finding block "${blockString}" under "${pageUid}"`);
|
|
129
129
|
}
|
|
130
130
|
throw new McpError(ErrorCode.InternalError, `Failed to find block "${blockString}" under page "${pageUid}" after trying multiple strategies`);
|
|
131
131
|
};
|
|
@@ -162,7 +162,7 @@ export class OutlineOperations {
|
|
|
162
162
|
}
|
|
163
163
|
catch (error) {
|
|
164
164
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
165
|
-
console.log(`Failed to find block on attempt ${retry + 1}: ${errorMessage}`);
|
|
165
|
+
// console.log(`Failed to find block on attempt ${retry + 1}: ${errorMessage}`); // Removed console.log
|
|
166
166
|
if (retry === maxRetries - 1)
|
|
167
167
|
throw error;
|
|
168
168
|
}
|
|
@@ -174,7 +174,7 @@ export class OutlineOperations {
|
|
|
174
174
|
if (isRetry)
|
|
175
175
|
throw error;
|
|
176
176
|
// Otherwise, try one more time with a clean slate
|
|
177
|
-
console.log(`Retrying block creation for "${content}" with fresh attempt`);
|
|
177
|
+
// console.log(`Retrying block creation for "${content}" with fresh attempt`); // Removed console.log
|
|
178
178
|
await new Promise(resolve => setTimeout(resolve, initialDelay * 2));
|
|
179
179
|
return createAndVerifyBlock(content, parentUid, maxRetries, initialDelay, true);
|
|
180
180
|
}
|
|
@@ -183,6 +183,50 @@ export class OutlineOperations {
|
|
|
183
183
|
const isValidUid = (str) => {
|
|
184
184
|
return typeof str === 'string' && str.length === 9;
|
|
185
185
|
};
|
|
186
|
+
// Helper function to fetch a block and its children recursively
|
|
187
|
+
const fetchBlockWithChildren = async (blockUid, level = 1) => {
|
|
188
|
+
const query = `
|
|
189
|
+
[:find ?childUid ?childString ?childOrder
|
|
190
|
+
:in $ ?parentUid
|
|
191
|
+
:where
|
|
192
|
+
[?parentEntity :block/uid ?parentUid]
|
|
193
|
+
[?parentEntity :block/children ?childEntity] ; This ensures direct children
|
|
194
|
+
[?childEntity :block/uid ?childUid]
|
|
195
|
+
[?childEntity :block/string ?childString]
|
|
196
|
+
[?childEntity :block/order ?childOrder]]
|
|
197
|
+
`;
|
|
198
|
+
const blockQuery = `
|
|
199
|
+
[:find ?string
|
|
200
|
+
:in $ ?uid
|
|
201
|
+
:where
|
|
202
|
+
[?e :block/uid ?uid]
|
|
203
|
+
[?e :block/string ?string]]
|
|
204
|
+
`;
|
|
205
|
+
try {
|
|
206
|
+
const blockStringResult = await q(this.graph, blockQuery, [blockUid]);
|
|
207
|
+
if (!blockStringResult || blockStringResult.length === 0) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
const text = blockStringResult[0][0];
|
|
211
|
+
const childrenResults = await q(this.graph, query, [blockUid]);
|
|
212
|
+
const children = [];
|
|
213
|
+
if (childrenResults && childrenResults.length > 0) {
|
|
214
|
+
// Sort children by order
|
|
215
|
+
const sortedChildren = childrenResults.sort((a, b) => a[2] - b[2]);
|
|
216
|
+
for (const childResult of sortedChildren) {
|
|
217
|
+
const childUid = childResult[0];
|
|
218
|
+
const nestedChild = await fetchBlockWithChildren(childUid, level + 1);
|
|
219
|
+
if (nestedChild) {
|
|
220
|
+
children.push(nestedChild);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return { uid: blockUid, text, level, children: children.length > 0 ? children : undefined };
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
throw new McpError(ErrorCode.InternalError, `Failed to fetch block with children for UID "${blockUid}": ${error.message}`);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
186
230
|
// Get or create the parent block
|
|
187
231
|
let targetParentUid;
|
|
188
232
|
if (!block_text_uid) {
|
|
@@ -235,7 +279,9 @@ export class OutlineOperations {
|
|
|
235
279
|
const indent = ' '.repeat(item.level - 1);
|
|
236
280
|
// If the item text starts with a markdown heading (e.g., #, ##, ###),
|
|
237
281
|
// treat it as a direct heading without adding a bullet or outline indentation.
|
|
238
|
-
|
|
282
|
+
// NEW CHANGE: Handle standalone code blocks - do not prepend bullet
|
|
283
|
+
const isCodeBlock = item.text?.startsWith('```') && item.text.endsWith('```') && item.text.includes('\n');
|
|
284
|
+
return isCodeBlock ? `${indent}${item.text?.trim()}` : `${indent}- ${item.text?.trim()}`;
|
|
239
285
|
})
|
|
240
286
|
.join('\n');
|
|
241
287
|
// Convert to Roam markdown format
|
|
@@ -243,13 +289,16 @@ export class OutlineOperations {
|
|
|
243
289
|
// Parse markdown into hierarchical structure
|
|
244
290
|
// We pass the original OutlineItem properties (heading, children_view_type)
|
|
245
291
|
// along with the parsed content to the nodes.
|
|
246
|
-
const nodes = parseMarkdown(convertedContent).map((node, index) =>
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
292
|
+
const nodes = parseMarkdown(convertedContent).map((node, index) => {
|
|
293
|
+
const outlineItem = validOutline[index];
|
|
294
|
+
return {
|
|
295
|
+
...node,
|
|
296
|
+
...(outlineItem?.heading && { heading_level: outlineItem.heading }),
|
|
297
|
+
...(outlineItem?.children_view_type && { children_view_type: outlineItem.children_view_type })
|
|
298
|
+
};
|
|
299
|
+
});
|
|
251
300
|
// Convert nodes to batch actions
|
|
252
|
-
const actions = convertToRoamActions(nodes, targetParentUid, '
|
|
301
|
+
const actions = convertToRoamActions(nodes, targetParentUid, 'last');
|
|
253
302
|
if (actions.length === 0) {
|
|
254
303
|
throw new McpError(ErrorCode.InvalidRequest, 'No valid actions generated from outline');
|
|
255
304
|
}
|
|
@@ -269,13 +318,32 @@ export class OutlineOperations {
|
|
|
269
318
|
throw error;
|
|
270
319
|
throw new McpError(ErrorCode.InternalError, `Failed to create outline: ${error.message}`);
|
|
271
320
|
}
|
|
272
|
-
//
|
|
273
|
-
const
|
|
321
|
+
// Post-creation verification to get actual UIDs for top-level blocks and their children
|
|
322
|
+
const createdBlocks = [];
|
|
323
|
+
// Only query for top-level blocks (level 1) based on the original outline input
|
|
324
|
+
const topLevelOutlineItems = validOutline.filter(item => item.level === 1);
|
|
325
|
+
for (const item of topLevelOutlineItems) {
|
|
326
|
+
try {
|
|
327
|
+
// Assert item.text is a string as it's filtered earlier to be non-undefined and non-empty
|
|
328
|
+
const foundUid = await findBlockWithRetry(targetParentUid, item.text);
|
|
329
|
+
if (foundUid) {
|
|
330
|
+
const nestedBlock = await fetchBlockWithChildren(foundUid);
|
|
331
|
+
if (nestedBlock) {
|
|
332
|
+
createdBlocks.push(nestedBlock);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
// This is a warning because even if one block fails to fetch, others might succeed.
|
|
338
|
+
// The error will be logged but not re-thrown to allow partial success reporting.
|
|
339
|
+
// console.warn(`Could not fetch nested block for "${item.text}": ${error.message}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
274
342
|
return {
|
|
275
343
|
success: true,
|
|
276
344
|
page_uid: targetPageUid,
|
|
277
345
|
parent_uid: targetParentUid,
|
|
278
|
-
created_uids:
|
|
346
|
+
created_uids: createdBlocks
|
|
279
347
|
};
|
|
280
348
|
}
|
|
281
349
|
async importMarkdown(content, page_uid, page_title, parent_uid, parent_string, order = 'first') {
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import { q, createPage as createRoamPage, batchActions } from '@roam-research/roam-api-sdk';
|
|
1
|
+
import { q, createPage as createRoamPage, batchActions, createBlock } from '@roam-research/roam-api-sdk';
|
|
2
2
|
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
3
3
|
import { capitalizeWords } from '../helpers/text.js';
|
|
4
4
|
import { resolveRefs } from '../helpers/refs.js';
|
|
5
5
|
import { convertToRoamActions, convertToRoamMarkdown } from '../../markdown-utils.js';
|
|
6
|
+
// Helper to get ordinal suffix for dates
|
|
7
|
+
function getOrdinalSuffix(day) {
|
|
8
|
+
if (day > 3 && day < 21)
|
|
9
|
+
return 'th'; // Handles 11th, 12th, 13th
|
|
10
|
+
switch (day % 10) {
|
|
11
|
+
case 1: return 'st';
|
|
12
|
+
case 2: return 'nd';
|
|
13
|
+
case 3: return 'rd';
|
|
14
|
+
default: return 'th';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
6
17
|
export class PageOperations {
|
|
7
18
|
constructor(graph) {
|
|
8
19
|
this.graph = graph;
|
|
@@ -124,6 +135,37 @@ export class PageOperations {
|
|
|
124
135
|
throw new McpError(ErrorCode.InternalError, `Failed to add content to page: ${error instanceof Error ? error.message : String(error)}`);
|
|
125
136
|
}
|
|
126
137
|
}
|
|
138
|
+
// Add a link to the created page on today's daily page
|
|
139
|
+
try {
|
|
140
|
+
const today = new Date();
|
|
141
|
+
const day = today.getDate();
|
|
142
|
+
const month = today.toLocaleString('en-US', { month: 'long' });
|
|
143
|
+
const year = today.getFullYear();
|
|
144
|
+
const formattedTodayTitle = `${month} ${day}${getOrdinalSuffix(day)}, ${year}`;
|
|
145
|
+
const dailyPageQuery = `[:find ?uid .
|
|
146
|
+
:where [?e :node/title "${formattedTodayTitle}"]
|
|
147
|
+
[?e :block/uid ?uid]]`;
|
|
148
|
+
const dailyPageResult = await q(this.graph, dailyPageQuery, []);
|
|
149
|
+
const dailyPageUid = dailyPageResult ? String(dailyPageResult) : null;
|
|
150
|
+
if (dailyPageUid) {
|
|
151
|
+
await createBlock(this.graph, {
|
|
152
|
+
action: 'create-block',
|
|
153
|
+
block: {
|
|
154
|
+
string: `Created page: [[${pageTitle}]]`
|
|
155
|
+
},
|
|
156
|
+
location: {
|
|
157
|
+
'parent-uid': dailyPageUid,
|
|
158
|
+
order: 'last'
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.warn(`Could not find daily page with title: ${formattedTodayTitle}. Link to created page not added.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.error(`Failed to add link to daily page: ${error instanceof Error ? error.message : String(error)}`);
|
|
168
|
+
}
|
|
127
169
|
return { success: true, uid: pageUid };
|
|
128
170
|
}
|
|
129
171
|
async fetchPageByTitle(title, format = 'raw') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roam-research-mcp",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.4",
|
|
4
4
|
"description": "A Model Context Protocol (MCP) server for Roam Research API integration",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"build"
|
|
29
29
|
],
|
|
30
30
|
"scripts": {
|
|
31
|
-
"build": "tsc &&
|
|
31
|
+
"build": "tsc && cat Roam_Markdown_Cheatsheet.md .roam/${CUSTOM_INSTRUCTIONS_PREFIX}custom-instructions.md > build/Roam_Markdown_Cheatsheet.md && chmod 755 build/index.js",
|
|
32
32
|
"clean": "rm -rf build",
|
|
33
33
|
"watch": "tsc --watch",
|
|
34
34
|
"inspector": "npx @modelcontextprotocol/inspector build/index.js",
|