roam-research-mcp 0.18.0 → 0.19.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 +2 -2
- package/build/server/roam-server.js +8 -0
- package/build/tools/schemas.js +72 -4
- package/build/tools/tool-handlers.js +165 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -335,7 +335,7 @@ Search for blocks containing specific text across all pages or within a specific
|
|
|
335
335
|
use_mcp_tool roam-research roam_search_by_text {
|
|
336
336
|
"text": "search text",
|
|
337
337
|
"page_title_uid": "optional-page-title-or-uid",
|
|
338
|
-
"case_sensitive":
|
|
338
|
+
"case_sensitive": true
|
|
339
339
|
}
|
|
340
340
|
```
|
|
341
341
|
|
|
@@ -351,7 +351,7 @@ Parameters:
|
|
|
351
351
|
|
|
352
352
|
- `text`: The text to search for (required)
|
|
353
353
|
- `page_title_uid`: Title or UID of the page to search in (optional)
|
|
354
|
-
- `case_sensitive`: Whether to perform a case-sensitive search (optional, default:
|
|
354
|
+
- `case_sensitive`: Whether to perform a case-sensitive search (optional, default: true to match Roam's native behavior)
|
|
355
355
|
|
|
356
356
|
Returns:
|
|
357
357
|
|
|
@@ -35,6 +35,7 @@ export class RoamServer {
|
|
|
35
35
|
find_pages_modified_today: {},
|
|
36
36
|
roam_search_by_text: {},
|
|
37
37
|
roam_update_block: {},
|
|
38
|
+
roam_update_blocks: {},
|
|
38
39
|
roam_search_by_date: {}
|
|
39
40
|
},
|
|
40
41
|
},
|
|
@@ -166,6 +167,13 @@ export class RoamServer {
|
|
|
166
167
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
167
168
|
};
|
|
168
169
|
}
|
|
170
|
+
case 'roam_update_blocks': {
|
|
171
|
+
const { updates } = request.params.arguments;
|
|
172
|
+
const result = await this.toolHandlers.updateBlocks(updates);
|
|
173
|
+
return {
|
|
174
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
169
177
|
default:
|
|
170
178
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
171
179
|
}
|
package/build/tools/schemas.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export const toolSchemas = {
|
|
3
3
|
roam_add_todo: {
|
|
4
4
|
name: 'roam_add_todo',
|
|
5
|
-
description: 'Add a list of todo items as individual blocks to today\'s daily page in Roam. Each item becomes its own actionable block with todo status.',
|
|
5
|
+
description: 'Add a list of todo items as individual blocks to today\'s daily page in Roam. Each item becomes its own actionable block with todo status.\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).',
|
|
6
6
|
inputSchema: {
|
|
7
7
|
type: 'object',
|
|
8
8
|
properties: {
|
|
@@ -52,7 +52,7 @@ export const toolSchemas = {
|
|
|
52
52
|
},
|
|
53
53
|
roam_create_block: {
|
|
54
54
|
name: 'roam_create_block',
|
|
55
|
-
description: 'Add a new block to an existing Roam page. If no page specified, adds to today\'s daily note. Best for capturing immediate thoughts, additions to discussions, or content that doesn\'t warrant its own page. Can specify page by title or UID.',
|
|
55
|
+
description: 'Add a new block to an existing Roam page. If no page specified, adds to today\'s daily note. Best for capturing immediate thoughts, additions to discussions, or content that doesn\'t warrant its own page. Can specify page by title or UID.\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).',
|
|
56
56
|
inputSchema: {
|
|
57
57
|
type: 'object',
|
|
58
58
|
properties: {
|
|
@@ -161,6 +161,11 @@ export const toolSchemas = {
|
|
|
161
161
|
near_tag: {
|
|
162
162
|
type: 'string',
|
|
163
163
|
description: 'Optional: Another tag to filter results by - will only return blocks where both tags appear',
|
|
164
|
+
},
|
|
165
|
+
case_sensitive: {
|
|
166
|
+
type: 'boolean',
|
|
167
|
+
description: 'Optional: Whether to perform case-sensitive matching (default: true, matching Roam\'s native behavior)',
|
|
168
|
+
default: true
|
|
164
169
|
}
|
|
165
170
|
},
|
|
166
171
|
required: ['primary_tag']
|
|
@@ -188,6 +193,11 @@ export const toolSchemas = {
|
|
|
188
193
|
exclude: {
|
|
189
194
|
type: 'string',
|
|
190
195
|
description: 'Optional: Comma-separated list of terms to filter results by exclusion (matches content or page title)'
|
|
196
|
+
},
|
|
197
|
+
case_sensitive: {
|
|
198
|
+
type: 'boolean',
|
|
199
|
+
description: 'Optional: Whether to perform case-sensitive matching (default: true, matching Roam\'s native behavior)',
|
|
200
|
+
default: true
|
|
191
201
|
}
|
|
192
202
|
},
|
|
193
203
|
required: ['status']
|
|
@@ -266,7 +276,8 @@ export const toolSchemas = {
|
|
|
266
276
|
},
|
|
267
277
|
case_sensitive: {
|
|
268
278
|
type: 'boolean',
|
|
269
|
-
description: 'Optional: Whether to perform
|
|
279
|
+
description: 'Optional: Whether to perform case-sensitive search (default: true, matching Roam\'s native behavior)',
|
|
280
|
+
default: true
|
|
270
281
|
}
|
|
271
282
|
},
|
|
272
283
|
required: ['text']
|
|
@@ -274,7 +285,7 @@ export const toolSchemas = {
|
|
|
274
285
|
},
|
|
275
286
|
roam_update_block: {
|
|
276
287
|
name: 'roam_update_block',
|
|
277
|
-
description: 'Update the content of an existing block identified by its UID. Can either provide new content directly or use a transform pattern to modify existing content.',
|
|
288
|
+
description: 'Update the content of an existing block identified by its UID. Can either provide new content directly or use a transform pattern to modify existing 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).',
|
|
278
289
|
inputSchema: {
|
|
279
290
|
type: 'object',
|
|
280
291
|
properties: {
|
|
@@ -314,6 +325,58 @@ export const toolSchemas = {
|
|
|
314
325
|
]
|
|
315
326
|
}
|
|
316
327
|
},
|
|
328
|
+
roam_update_blocks: {
|
|
329
|
+
name: 'roam_update_blocks',
|
|
330
|
+
description: 'Update multiple blocks in a single batch operation. Each update can provide either new content directly or a transform pattern.\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).',
|
|
331
|
+
inputSchema: {
|
|
332
|
+
type: 'object',
|
|
333
|
+
properties: {
|
|
334
|
+
updates: {
|
|
335
|
+
type: 'array',
|
|
336
|
+
description: 'Array of block updates to perform',
|
|
337
|
+
items: {
|
|
338
|
+
type: 'object',
|
|
339
|
+
properties: {
|
|
340
|
+
block_uid: {
|
|
341
|
+
type: 'string',
|
|
342
|
+
description: 'UID of the block to update'
|
|
343
|
+
},
|
|
344
|
+
content: {
|
|
345
|
+
type: 'string',
|
|
346
|
+
description: 'New content for the block. If not provided, transform will be used.'
|
|
347
|
+
},
|
|
348
|
+
transform: {
|
|
349
|
+
type: 'object',
|
|
350
|
+
description: 'Pattern to transform the current content. Used if content is not provided.',
|
|
351
|
+
properties: {
|
|
352
|
+
find: {
|
|
353
|
+
type: 'string',
|
|
354
|
+
description: 'Text or regex pattern to find'
|
|
355
|
+
},
|
|
356
|
+
replace: {
|
|
357
|
+
type: 'string',
|
|
358
|
+
description: 'Text to replace with'
|
|
359
|
+
},
|
|
360
|
+
global: {
|
|
361
|
+
type: 'boolean',
|
|
362
|
+
description: 'Whether to replace all occurrences',
|
|
363
|
+
default: true
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
required: ['find', 'replace']
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
required: ['block_uid'],
|
|
370
|
+
oneOf: [
|
|
371
|
+
{ required: ['content'] },
|
|
372
|
+
{ required: ['transform'] }
|
|
373
|
+
]
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
required: ['updates']
|
|
378
|
+
}
|
|
379
|
+
},
|
|
317
380
|
roam_search_by_date: {
|
|
318
381
|
name: 'roam_search_by_date',
|
|
319
382
|
description: 'Search for blocks or pages based on creation or modification dates',
|
|
@@ -342,6 +405,11 @@ export const toolSchemas = {
|
|
|
342
405
|
type: 'boolean',
|
|
343
406
|
description: 'Whether to include the content of matching blocks/pages',
|
|
344
407
|
default: true,
|
|
408
|
+
},
|
|
409
|
+
case_sensitive: {
|
|
410
|
+
type: 'boolean',
|
|
411
|
+
description: 'Optional: Whether to perform case-sensitive matching (default: true, matching Roam\'s native behavior)',
|
|
412
|
+
default: true
|
|
345
413
|
}
|
|
346
414
|
},
|
|
347
415
|
required: ['start_date', 'type', 'scope']
|
|
@@ -157,21 +157,23 @@ export class ToolHandlers {
|
|
|
157
157
|
// Get or create the target page
|
|
158
158
|
const targetPageUid = await findOrCreatePage(page_title_uid || formatRoamDate(new Date()));
|
|
159
159
|
// Helper function to find block with improved relationship checks
|
|
160
|
-
const findBlockWithRetry = async (pageUid, blockString, maxRetries = 5, initialDelay = 1000) => {
|
|
160
|
+
const findBlockWithRetry = async (pageUid, blockString, maxRetries = 5, initialDelay = 1000, case_sensitive = false) => {
|
|
161
161
|
// Try multiple query strategies
|
|
162
162
|
const queries = [
|
|
163
163
|
// Strategy 1: Direct page and string match
|
|
164
164
|
`[:find ?b-uid ?order
|
|
165
165
|
:where [?p :block/uid "${pageUid}"]
|
|
166
166
|
[?b :block/page ?p]
|
|
167
|
-
[?b :block/string
|
|
167
|
+
[?b :block/string ?block-str]
|
|
168
|
+
[(${case_sensitive ? '=' : 'clojure.string/equals-ignore-case'} ?block-str "${blockString}")]
|
|
168
169
|
[?b :block/order ?order]
|
|
169
170
|
[?b :block/uid ?b-uid]]`,
|
|
170
171
|
// Strategy 2: Parent-child relationship
|
|
171
172
|
`[:find ?b-uid ?order
|
|
172
173
|
:where [?p :block/uid "${pageUid}"]
|
|
173
174
|
[?b :block/parents ?p]
|
|
174
|
-
[?b :block/string
|
|
175
|
+
[?b :block/string ?block-str]
|
|
176
|
+
[(${case_sensitive ? '=' : 'clojure.string/equals-ignore-case'} ?block-str "${blockString}")]
|
|
175
177
|
[?b :block/order ?order]
|
|
176
178
|
[?b :block/uid ?b-uid]]`,
|
|
177
179
|
// Strategy 3: Broader page relationship
|
|
@@ -179,7 +181,8 @@ export class ToolHandlers {
|
|
|
179
181
|
:where [?p :block/uid "${pageUid}"]
|
|
180
182
|
[?b :block/page ?page]
|
|
181
183
|
[?p :block/page ?page]
|
|
182
|
-
[?b :block/string
|
|
184
|
+
[?b :block/string ?block-str]
|
|
185
|
+
[(${case_sensitive ? '=' : 'clojure.string/equals-ignore-case'} ?block-str "${blockString}")]
|
|
183
186
|
[?b :block/order ?order]
|
|
184
187
|
[?b :block/uid ?b-uid]]`
|
|
185
188
|
];
|
|
@@ -201,7 +204,7 @@ export class ToolHandlers {
|
|
|
201
204
|
throw new McpError(ErrorCode.InternalError, `Failed to find block "${blockString}" under page "${pageUid}" after trying multiple strategies`);
|
|
202
205
|
};
|
|
203
206
|
// Helper function to create and verify block with improved error handling
|
|
204
|
-
const createAndVerifyBlock = async (content, parentUid, maxRetries = 5, initialDelay = 1000, isRetry = false) => {
|
|
207
|
+
const createAndVerifyBlock = async (content, parentUid, maxRetries = 5, initialDelay = 1000, isRetry = false, case_sensitive = false) => {
|
|
205
208
|
try {
|
|
206
209
|
// Initial delay before any operations
|
|
207
210
|
if (!isRetry) {
|
|
@@ -223,7 +226,7 @@ export class ToolHandlers {
|
|
|
223
226
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
224
227
|
try {
|
|
225
228
|
// Try to find the block using our improved findBlockWithRetry
|
|
226
|
-
return await findBlockWithRetry(parentUid, content);
|
|
229
|
+
return await findBlockWithRetry(parentUid, content, maxRetries, initialDelay, case_sensitive);
|
|
227
230
|
}
|
|
228
231
|
catch (error) {
|
|
229
232
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -241,7 +244,7 @@ export class ToolHandlers {
|
|
|
241
244
|
// Otherwise, try one more time with a clean slate
|
|
242
245
|
console.log(`Retrying block creation for "${content}" with fresh attempt`);
|
|
243
246
|
await new Promise(resolve => setTimeout(resolve, initialDelay * 2));
|
|
244
|
-
return createAndVerifyBlock(content, parentUid, maxRetries, initialDelay, true);
|
|
247
|
+
return createAndVerifyBlock(content, parentUid, maxRetries, initialDelay, true, case_sensitive);
|
|
245
248
|
}
|
|
246
249
|
};
|
|
247
250
|
// Get or create the parent block
|
|
@@ -728,7 +731,110 @@ export class ToolHandlers {
|
|
|
728
731
|
throw new McpError(ErrorCode.InternalError, `Failed to update block: ${error.message}`);
|
|
729
732
|
}
|
|
730
733
|
}
|
|
731
|
-
async
|
|
734
|
+
async updateBlocks(updates) {
|
|
735
|
+
if (!Array.isArray(updates) || updates.length === 0) {
|
|
736
|
+
throw new McpError(ErrorCode.InvalidRequest, 'updates must be a non-empty array');
|
|
737
|
+
}
|
|
738
|
+
// Validate each update has required fields
|
|
739
|
+
updates.forEach((update, index) => {
|
|
740
|
+
if (!update.block_uid) {
|
|
741
|
+
throw new McpError(ErrorCode.InvalidRequest, `Update at index ${index} missing block_uid`);
|
|
742
|
+
}
|
|
743
|
+
if (!update.content && !update.transform) {
|
|
744
|
+
throw new McpError(ErrorCode.InvalidRequest, `Update at index ${index} must have either content or transform`);
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
// Get current content for all blocks
|
|
748
|
+
const blockUids = updates.map(u => u.block_uid);
|
|
749
|
+
const blockQuery = `[:find ?uid ?string
|
|
750
|
+
:in $ [?uid ...]
|
|
751
|
+
:where [?b :block/uid ?uid]
|
|
752
|
+
[?b :block/string ?string]]`;
|
|
753
|
+
const blockResults = await q(this.graph, blockQuery, [blockUids]);
|
|
754
|
+
// Create map of uid -> current content
|
|
755
|
+
const contentMap = new Map();
|
|
756
|
+
blockResults.forEach(([uid, string]) => {
|
|
757
|
+
contentMap.set(uid, string);
|
|
758
|
+
});
|
|
759
|
+
// Prepare batch actions
|
|
760
|
+
const actions = [];
|
|
761
|
+
const results = [];
|
|
762
|
+
for (const update of updates) {
|
|
763
|
+
try {
|
|
764
|
+
const currentContent = contentMap.get(update.block_uid);
|
|
765
|
+
if (!currentContent) {
|
|
766
|
+
results.push({
|
|
767
|
+
block_uid: update.block_uid,
|
|
768
|
+
content: '',
|
|
769
|
+
success: false,
|
|
770
|
+
error: `Block with UID "${update.block_uid}" not found`
|
|
771
|
+
});
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
// Determine new content
|
|
775
|
+
let newContent;
|
|
776
|
+
if (update.content) {
|
|
777
|
+
newContent = update.content;
|
|
778
|
+
}
|
|
779
|
+
else if (update.transform) {
|
|
780
|
+
const regex = new RegExp(update.transform.find, update.transform.global ? 'g' : '');
|
|
781
|
+
newContent = currentContent.replace(regex, update.transform.replace);
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
// This shouldn't happen due to earlier validation
|
|
785
|
+
throw new Error('Invalid update configuration');
|
|
786
|
+
}
|
|
787
|
+
// Add to batch actions
|
|
788
|
+
actions.push({
|
|
789
|
+
action: 'update-block',
|
|
790
|
+
block: {
|
|
791
|
+
uid: update.block_uid,
|
|
792
|
+
string: newContent
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
results.push({
|
|
796
|
+
block_uid: update.block_uid,
|
|
797
|
+
content: newContent,
|
|
798
|
+
success: true
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
catch (error) {
|
|
802
|
+
results.push({
|
|
803
|
+
block_uid: update.block_uid,
|
|
804
|
+
content: contentMap.get(update.block_uid) || '',
|
|
805
|
+
success: false,
|
|
806
|
+
error: error.message
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
// Execute batch update if we have any valid actions
|
|
811
|
+
if (actions.length > 0) {
|
|
812
|
+
try {
|
|
813
|
+
const batchResult = await batchActions(this.graph, {
|
|
814
|
+
action: 'batch-actions',
|
|
815
|
+
actions
|
|
816
|
+
});
|
|
817
|
+
if (!batchResult) {
|
|
818
|
+
throw new Error('Batch update failed');
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
// Mark all previously successful results as failed
|
|
823
|
+
results.forEach(result => {
|
|
824
|
+
if (result.success) {
|
|
825
|
+
result.success = false;
|
|
826
|
+
result.error = `Batch update failed: ${error.message}`;
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return {
|
|
832
|
+
success: results.every(r => r.success),
|
|
833
|
+
results
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
async searchByStatus(status, page_title_uid, include, exclude, case_sensitive = true // Changed to true to match Roam's behavior
|
|
837
|
+
) {
|
|
732
838
|
// Get target page UID if provided
|
|
733
839
|
let targetPageUid;
|
|
734
840
|
if (page_title_uid) {
|
|
@@ -789,16 +895,26 @@ export class ToolHandlers {
|
|
|
789
895
|
...(pageTitle && { page_title: pageTitle })
|
|
790
896
|
};
|
|
791
897
|
});
|
|
792
|
-
// Post-query filtering
|
|
898
|
+
// Post-query filtering with case sensitivity option
|
|
793
899
|
if (include) {
|
|
794
|
-
const includeTerms = include.
|
|
795
|
-
matches = matches.filter(match =>
|
|
796
|
-
|
|
900
|
+
const includeTerms = include.split(',').map(term => term.trim());
|
|
901
|
+
matches = matches.filter(match => {
|
|
902
|
+
const matchContent = case_sensitive ? match.content : match.content.toLowerCase();
|
|
903
|
+
const matchTitle = match.page_title && (case_sensitive ? match.page_title : match.page_title.toLowerCase());
|
|
904
|
+
const terms = case_sensitive ? includeTerms : includeTerms.map(t => t.toLowerCase());
|
|
905
|
+
return terms.some(term => matchContent.includes(case_sensitive ? term : term.toLowerCase()) ||
|
|
906
|
+
(matchTitle && matchTitle.includes(case_sensitive ? term : term.toLowerCase())));
|
|
907
|
+
});
|
|
797
908
|
}
|
|
798
909
|
if (exclude) {
|
|
799
|
-
const excludeTerms = exclude.
|
|
800
|
-
matches = matches.filter(match =>
|
|
801
|
-
|
|
910
|
+
const excludeTerms = exclude.split(',').map(term => term.trim());
|
|
911
|
+
matches = matches.filter(match => {
|
|
912
|
+
const matchContent = case_sensitive ? match.content : match.content.toLowerCase();
|
|
913
|
+
const matchTitle = match.page_title && (case_sensitive ? match.page_title : match.page_title.toLowerCase());
|
|
914
|
+
const terms = case_sensitive ? excludeTerms : excludeTerms.map(t => t.toLowerCase());
|
|
915
|
+
return !terms.some(term => matchContent.includes(case_sensitive ? term : term.toLowerCase()) ||
|
|
916
|
+
(matchTitle && matchTitle.includes(case_sensitive ? term : term.toLowerCase())));
|
|
917
|
+
});
|
|
802
918
|
}
|
|
803
919
|
return {
|
|
804
920
|
success: true,
|
|
@@ -806,7 +922,8 @@ export class ToolHandlers {
|
|
|
806
922
|
message: `Found ${matches.length} block(s) with status ${status}${include ? ` including "${include}"` : ''}${exclude ? ` excluding "${exclude}"` : ''}`
|
|
807
923
|
};
|
|
808
924
|
}
|
|
809
|
-
async searchForTag(primary_tag, page_title_uid, near_tag
|
|
925
|
+
async searchForTag(primary_tag, page_title_uid, near_tag, case_sensitive = true // Changed to true to match Roam's behavior
|
|
926
|
+
) {
|
|
810
927
|
// Ensure tags are properly formatted with #
|
|
811
928
|
const formatTag = (tag) => tag.startsWith('#') ? tag : `#${tag}`;
|
|
812
929
|
const primaryTagFormatted = formatTag(primary_tag);
|
|
@@ -841,8 +958,12 @@ export class ToolHandlers {
|
|
|
841
958
|
[?b :block/page ?p]
|
|
842
959
|
[?b :block/string ?block-str]
|
|
843
960
|
[?b :block/uid ?block-uid]
|
|
844
|
-
[(clojure.string/includes?
|
|
845
|
-
|
|
961
|
+
[(clojure.string/includes?
|
|
962
|
+
${case_sensitive ? '?block-str' : '(clojure.string/lower-case ?block-str)'}
|
|
963
|
+
${case_sensitive ? '?primary-tag' : '(clojure.string/lower-case ?primary-tag)'})]
|
|
964
|
+
[(clojure.string/includes?
|
|
965
|
+
${case_sensitive ? '?block-str' : '(clojure.string/lower-case ?block-str)'}
|
|
966
|
+
${case_sensitive ? '?near-tag' : '(clojure.string/lower-case ?near-tag)'})]`;
|
|
846
967
|
queryParams = [primaryTagFormatted, nearTagFormatted, targetPageUid];
|
|
847
968
|
}
|
|
848
969
|
else {
|
|
@@ -852,7 +973,9 @@ export class ToolHandlers {
|
|
|
852
973
|
[?b :block/page ?p]
|
|
853
974
|
[?b :block/string ?block-str]
|
|
854
975
|
[?b :block/uid ?block-uid]
|
|
855
|
-
[(clojure.string/includes?
|
|
976
|
+
[(clojure.string/includes?
|
|
977
|
+
${case_sensitive ? '?block-str' : '(clojure.string/lower-case ?block-str)'}
|
|
978
|
+
${case_sensitive ? '?primary-tag' : '(clojure.string/lower-case ?primary-tag)'})]`;
|
|
856
979
|
queryParams = [primaryTagFormatted, targetPageUid];
|
|
857
980
|
}
|
|
858
981
|
}
|
|
@@ -865,8 +988,12 @@ export class ToolHandlers {
|
|
|
865
988
|
[?b :block/uid ?block-uid]
|
|
866
989
|
[?b :block/page ?p]
|
|
867
990
|
[?p :node/title ?page-title]
|
|
868
|
-
[(clojure.string/includes?
|
|
869
|
-
|
|
991
|
+
[(clojure.string/includes?
|
|
992
|
+
${case_sensitive ? '?block-str' : '(clojure.string/lower-case ?block-str)'}
|
|
993
|
+
${case_sensitive ? '?primary-tag' : '(clojure.string/lower-case ?primary-tag)'})]
|
|
994
|
+
[(clojure.string/includes?
|
|
995
|
+
${case_sensitive ? '?block-str' : '(clojure.string/lower-case ?block-str)'}
|
|
996
|
+
${case_sensitive ? '?near-tag' : '(clojure.string/lower-case ?near-tag)'})]`;
|
|
870
997
|
queryParams = [primaryTagFormatted, nearTagFormatted];
|
|
871
998
|
}
|
|
872
999
|
else {
|
|
@@ -876,7 +1003,9 @@ export class ToolHandlers {
|
|
|
876
1003
|
[?b :block/uid ?block-uid]
|
|
877
1004
|
[?b :block/page ?p]
|
|
878
1005
|
[?p :node/title ?page-title]
|
|
879
|
-
[(clojure.string/includes?
|
|
1006
|
+
[(clojure.string/includes?
|
|
1007
|
+
${case_sensitive ? '?block-str' : '(clojure.string/lower-case ?block-str)'}
|
|
1008
|
+
${case_sensitive ? '?primary-tag' : '(clojure.string/lower-case ?primary-tag)'})]`;
|
|
880
1009
|
queryParams = [primaryTagFormatted];
|
|
881
1010
|
}
|
|
882
1011
|
}
|
|
@@ -945,6 +1074,20 @@ export class ToolHandlers {
|
|
|
945
1074
|
...(params.include_content && { content }),
|
|
946
1075
|
page_title: pageTitle
|
|
947
1076
|
}));
|
|
1077
|
+
// Apply case sensitivity if content is included
|
|
1078
|
+
if (params.include_content) {
|
|
1079
|
+
const case_sensitive = params.case_sensitive ?? true; // Default to true to match Roam's behavior
|
|
1080
|
+
if (!case_sensitive) {
|
|
1081
|
+
matches.forEach(match => {
|
|
1082
|
+
if (match.content) {
|
|
1083
|
+
match.content = match.content.toLowerCase();
|
|
1084
|
+
}
|
|
1085
|
+
if (match.page_title) {
|
|
1086
|
+
match.page_title = match.page_title.toLowerCase();
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
948
1091
|
// Sort by time
|
|
949
1092
|
const sortedMatches = matches.sort((a, b) => b.time - a.time);
|
|
950
1093
|
return {
|