reflect-mcp 1.0.7 → 1.0.8
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/dist/tools/index.js +110 -0
- package/package.json +1 -1
package/dist/tools/index.js
CHANGED
|
@@ -509,6 +509,116 @@ export function registerTools(server, dbPath) {
|
|
|
509
509
|
}
|
|
510
510
|
},
|
|
511
511
|
});
|
|
512
|
+
// Tool: Search notes by subject (fuzzy search with multi-word support)
|
|
513
|
+
server.addTool({
|
|
514
|
+
name: "search_notes",
|
|
515
|
+
description: "Search for notes by subject/title using fuzzy matching. Returns notes whose subjects match the search query.",
|
|
516
|
+
parameters: z.object({
|
|
517
|
+
query: z.string().describe("The search query to match against note subjects"),
|
|
518
|
+
graph_id: z.string().default("rapheal-brain").describe("The graph ID to search in"),
|
|
519
|
+
limit: z.number().default(10).describe("Maximum number of notes to return"),
|
|
520
|
+
include_content: z.boolean().default(false).describe("Whether to search in note content as well as subjects"),
|
|
521
|
+
}),
|
|
522
|
+
execute: async (args) => {
|
|
523
|
+
const { query, graph_id, limit, include_content } = args;
|
|
524
|
+
try {
|
|
525
|
+
const dbFile = resolvedDbPath;
|
|
526
|
+
const db = new Database(dbFile, { readonly: true });
|
|
527
|
+
// Split query into terms, treating dots, dashes, underscores as word separators
|
|
528
|
+
// Filter out empty strings and very short terms
|
|
529
|
+
const searchTerms = query
|
|
530
|
+
.toLowerCase()
|
|
531
|
+
.split(/[\s.,\-_\/\\]+/)
|
|
532
|
+
.filter(term => term.length >= 2);
|
|
533
|
+
if (searchTerms.length === 0) {
|
|
534
|
+
db.close();
|
|
535
|
+
return {
|
|
536
|
+
content: [
|
|
537
|
+
{
|
|
538
|
+
type: "text",
|
|
539
|
+
text: JSON.stringify({ error: "Search query too short or empty", query }),
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
// Build dynamic WHERE clause - notes must contain ALL search terms
|
|
545
|
+
// in either subject or content (if include_content is true)
|
|
546
|
+
const whereConditions = [];
|
|
547
|
+
const params = [];
|
|
548
|
+
for (const term of searchTerms) {
|
|
549
|
+
if (include_content) {
|
|
550
|
+
whereConditions.push(`(LOWER(subject) LIKE ? OR LOWER(documentText) LIKE ?)`);
|
|
551
|
+
params.push(`%${term}%`, `%${term}%`);
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
whereConditions.push(`LOWER(subject) LIKE ?`);
|
|
555
|
+
params.push(`%${term}%`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// Build relevance scoring - higher score for more matches in subject
|
|
559
|
+
const relevanceParts = [];
|
|
560
|
+
for (const term of searchTerms) {
|
|
561
|
+
relevanceParts.push(`CASE WHEN LOWER(subject) LIKE '%${term}%' THEN 20 ELSE 0 END`);
|
|
562
|
+
if (include_content) {
|
|
563
|
+
relevanceParts.push(`CASE WHEN LOWER(documentText) LIKE '%${term}%' THEN 5 ELSE 0 END`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
// Bonus for exact phrase match
|
|
567
|
+
const fullQuery = query.toLowerCase();
|
|
568
|
+
relevanceParts.push(`CASE WHEN LOWER(subject) = '${fullQuery}' THEN 100 ELSE 0 END`);
|
|
569
|
+
relevanceParts.push(`CASE WHEN LOWER(subject) LIKE '${fullQuery}%' THEN 50 ELSE 0 END`);
|
|
570
|
+
relevanceParts.push(`CASE WHEN LOWER(subject) LIKE '%${fullQuery}%' THEN 30 ELSE 0 END`);
|
|
571
|
+
const relevanceExpr = relevanceParts.join(' + ');
|
|
572
|
+
const sql = `
|
|
573
|
+
SELECT id, subject, documentText, tags, editedAt, createdAt, isDaily,
|
|
574
|
+
(${relevanceExpr}) as relevance
|
|
575
|
+
FROM notes
|
|
576
|
+
WHERE isDeleted = 0
|
|
577
|
+
AND graphId = ?
|
|
578
|
+
AND (${whereConditions.join(' AND ')})
|
|
579
|
+
ORDER BY relevance DESC, editedAt DESC
|
|
580
|
+
LIMIT ?
|
|
581
|
+
`;
|
|
582
|
+
const stmt = db.prepare(sql);
|
|
583
|
+
const results = stmt.all(graph_id, ...params, limit);
|
|
584
|
+
db.close();
|
|
585
|
+
const notes = results.map((row) => ({
|
|
586
|
+
id: row.id,
|
|
587
|
+
subject: row.subject,
|
|
588
|
+
preview: row.documentText?.slice(0, 200) || "",
|
|
589
|
+
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
590
|
+
editedAt: formatDate(row.editedAt),
|
|
591
|
+
isDaily: row.isDaily === 1,
|
|
592
|
+
relevance: row.relevance,
|
|
593
|
+
}));
|
|
594
|
+
return {
|
|
595
|
+
content: [
|
|
596
|
+
{
|
|
597
|
+
type: "text",
|
|
598
|
+
text: JSON.stringify({
|
|
599
|
+
query,
|
|
600
|
+
searchTerms,
|
|
601
|
+
graph_id,
|
|
602
|
+
searchedContent: include_content,
|
|
603
|
+
count: notes.length,
|
|
604
|
+
notes
|
|
605
|
+
}, null, 2),
|
|
606
|
+
},
|
|
607
|
+
],
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
catch (e) {
|
|
611
|
+
return {
|
|
612
|
+
content: [
|
|
613
|
+
{
|
|
614
|
+
type: "text",
|
|
615
|
+
text: JSON.stringify({ error: String(e) }),
|
|
616
|
+
},
|
|
617
|
+
],
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
});
|
|
512
622
|
// Tool: Create a new note in Reflect via API
|
|
513
623
|
server.addTool({
|
|
514
624
|
name: "create_note",
|