reflect-mcp 1.0.5 → 1.0.6
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 -0
- package/dist/pkcehandler.js +0 -1
- package/dist/tools/index.js +112 -45
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -125,6 +125,8 @@ npx reflect-mcp uninstall
|
|
|
125
125
|
- Verify database path exists at default location
|
|
126
126
|
- Try specifying custom path: `npx reflect-mcp install /path/to/db`
|
|
127
127
|
|
|
128
|
+
## Demo:
|
|
129
|
+
https://www.loom.com/share/455b1d3eb7184bdea1ae4e8d5904fc53
|
|
128
130
|
## License
|
|
129
131
|
|
|
130
132
|
MIT
|
package/dist/pkcehandler.js
CHANGED
|
@@ -223,7 +223,6 @@ export class PKCEOAuthProxy {
|
|
|
223
223
|
// Handle /oauth/token - exchange proxy code for access token
|
|
224
224
|
// FastMCP expects a TokenResponse object, not a Response
|
|
225
225
|
async exchangeAuthorizationCode(params) {
|
|
226
|
-
console.log("[PKCEProxy] exchangeAuthorizationCode called with code:", params.code?.slice(0, 8) + "...");
|
|
227
226
|
if (!params.code) {
|
|
228
227
|
throw new OAuthProxyError("invalid_request", "Missing authorization code", 400);
|
|
229
228
|
}
|
package/dist/tools/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export function registerTools(server, dbPath) {
|
|
|
11
11
|
// Tool: Get all Reflect graphs
|
|
12
12
|
server.addTool({
|
|
13
13
|
name: "get_graphs",
|
|
14
|
-
description: "Get a list of all Reflect graphs
|
|
14
|
+
description: "Get a list of all Reflect graphs.",
|
|
15
15
|
parameters: z.object({}),
|
|
16
16
|
execute: async (_args, { session }) => {
|
|
17
17
|
if (!session) {
|
|
@@ -59,14 +59,14 @@ export function registerTools(server, dbPath) {
|
|
|
59
59
|
// Tool: Get backlinks for a note from local Reflect SQLite database
|
|
60
60
|
server.addTool({
|
|
61
61
|
name: "get_backlinks",
|
|
62
|
-
description: "Get backlinks for a note from
|
|
62
|
+
description: "Get backlinks for a note from Reflect. Use this tool to get more context about a note after calling the get_note tool.",
|
|
63
63
|
parameters: z.object({
|
|
64
64
|
subject: z.string().describe("The subject/title of the note to get backlinks for"),
|
|
65
|
-
|
|
65
|
+
graph_id: z.string().default("rapheal-brain").describe("The graph ID to search in"),
|
|
66
66
|
limit: z.number().default(10).describe("Maximum number of backlinks to return"),
|
|
67
67
|
}),
|
|
68
68
|
execute: async (args) => {
|
|
69
|
-
const { subject,
|
|
69
|
+
const { subject, graph_id, limit } = args;
|
|
70
70
|
try {
|
|
71
71
|
const dbFile = resolvedDbPath;
|
|
72
72
|
const db = new Database(dbFile, { readonly: true });
|
|
@@ -79,7 +79,7 @@ export function registerTools(server, dbPath) {
|
|
|
79
79
|
ORDER BY bl.updatedAt DESC
|
|
80
80
|
LIMIT ?
|
|
81
81
|
`);
|
|
82
|
-
const results = stmt.all(subject,
|
|
82
|
+
const results = stmt.all(subject, graph_id, limit);
|
|
83
83
|
db.close();
|
|
84
84
|
const backlinks = results.map((row) => ({
|
|
85
85
|
fromSubject: row.from_subject,
|
|
@@ -91,7 +91,7 @@ export function registerTools(server, dbPath) {
|
|
|
91
91
|
content: [
|
|
92
92
|
{
|
|
93
93
|
type: "text",
|
|
94
|
-
text: JSON.stringify({ subject,
|
|
94
|
+
text: JSON.stringify({ subject, graph_id, backlinks }, null, 2),
|
|
95
95
|
},
|
|
96
96
|
],
|
|
97
97
|
};
|
|
@@ -111,13 +111,13 @@ export function registerTools(server, dbPath) {
|
|
|
111
111
|
// Tool: Get recent daily notes
|
|
112
112
|
server.addTool({
|
|
113
113
|
name: "get_daily_notes",
|
|
114
|
-
description: "Get the most recent daily notes from
|
|
114
|
+
description: "Get the most recent daily notes from Reflect.",
|
|
115
115
|
parameters: z.object({
|
|
116
116
|
limit: z.number().default(5).describe("Number of recent daily notes to return"),
|
|
117
|
-
|
|
117
|
+
graph_id: z.string().default("rapheal-brain").describe("The graph ID to search in"),
|
|
118
118
|
}),
|
|
119
119
|
execute: async (args) => {
|
|
120
|
-
const { limit,
|
|
120
|
+
const { limit, graph_id } = args;
|
|
121
121
|
try {
|
|
122
122
|
const dbFile = resolvedDbPath;
|
|
123
123
|
const db = new Database(dbFile, { readonly: true });
|
|
@@ -128,7 +128,7 @@ export function registerTools(server, dbPath) {
|
|
|
128
128
|
ORDER BY dailyDate DESC
|
|
129
129
|
LIMIT ?
|
|
130
130
|
`);
|
|
131
|
-
const rows = stmt.all(
|
|
131
|
+
const rows = stmt.all(graph_id, limit);
|
|
132
132
|
db.close();
|
|
133
133
|
const dailyNotes = rows.map((row) => ({
|
|
134
134
|
id: row.id,
|
|
@@ -137,13 +137,13 @@ export function registerTools(server, dbPath) {
|
|
|
137
137
|
editedAt: formatDate(row.editedAt),
|
|
138
138
|
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
139
139
|
dailyDate: formatDate(row.dailyDate),
|
|
140
|
-
|
|
140
|
+
graph_id: row.graphId,
|
|
141
141
|
}));
|
|
142
142
|
return {
|
|
143
143
|
content: [
|
|
144
144
|
{
|
|
145
145
|
type: "text",
|
|
146
|
-
text: JSON.stringify({
|
|
146
|
+
text: JSON.stringify({ graph_id, count: dailyNotes.length, dailyNotes }, null, 2),
|
|
147
147
|
},
|
|
148
148
|
],
|
|
149
149
|
};
|
|
@@ -163,13 +163,13 @@ export function registerTools(server, dbPath) {
|
|
|
163
163
|
// Tool: Get daily note by date
|
|
164
164
|
server.addTool({
|
|
165
165
|
name: "get_daily_note_by_date",
|
|
166
|
-
description: "Get the daily note for a specific date
|
|
166
|
+
description: "Get the daily note for a specific date.",
|
|
167
167
|
parameters: z.object({
|
|
168
168
|
date: z.string().describe("The date in YYYY-MM-DD format"),
|
|
169
|
-
|
|
169
|
+
graph_id: z.string().default("rapheal-brain").describe("The graph ID to search in"),
|
|
170
170
|
}),
|
|
171
171
|
execute: async (args) => {
|
|
172
|
-
const { date,
|
|
172
|
+
const { date, graph_id } = args;
|
|
173
173
|
try {
|
|
174
174
|
const dbFile = resolvedDbPath;
|
|
175
175
|
const db = new Database(dbFile, { readonly: true });
|
|
@@ -180,14 +180,14 @@ export function registerTools(server, dbPath) {
|
|
|
180
180
|
FROM notes
|
|
181
181
|
WHERE isDaily = 1 AND isDeleted = 0 AND graphId = ? AND dailyDate = ?
|
|
182
182
|
`);
|
|
183
|
-
const result = stmt.get(
|
|
183
|
+
const result = stmt.get(graph_id, dateMs);
|
|
184
184
|
db.close();
|
|
185
185
|
if (!result) {
|
|
186
186
|
return {
|
|
187
187
|
content: [
|
|
188
188
|
{
|
|
189
189
|
type: "text",
|
|
190
|
-
text: JSON.stringify({ error: `No daily note found for ${date}`, date,
|
|
190
|
+
text: JSON.stringify({ error: `No daily note found for ${date}`, date, graph_id }),
|
|
191
191
|
},
|
|
192
192
|
],
|
|
193
193
|
};
|
|
@@ -199,13 +199,13 @@ export function registerTools(server, dbPath) {
|
|
|
199
199
|
editedAt: formatDate(result.editedAt),
|
|
200
200
|
tags: result.tags ? JSON.parse(result.tags) : [],
|
|
201
201
|
dailyDate: formatDate(result.dailyDate),
|
|
202
|
-
|
|
202
|
+
graph_id: result.graph_id,
|
|
203
203
|
};
|
|
204
204
|
return {
|
|
205
205
|
content: [
|
|
206
206
|
{
|
|
207
207
|
type: "text",
|
|
208
|
-
text: JSON.stringify({ date,
|
|
208
|
+
text: JSON.stringify({ date, graph_id, dailyNote }, null, 2),
|
|
209
209
|
},
|
|
210
210
|
],
|
|
211
211
|
};
|
|
@@ -225,14 +225,14 @@ export function registerTools(server, dbPath) {
|
|
|
225
225
|
// Tool: Get notes with most backlinks
|
|
226
226
|
server.addTool({
|
|
227
227
|
name: "get_backlinked_notes",
|
|
228
|
-
description: "Get notes that have at least a minimum number of backlinks from
|
|
228
|
+
description: "Get notes that have at least a minimum number of backlinks from Reflect.",
|
|
229
229
|
parameters: z.object({
|
|
230
230
|
minBacklinks: z.number().default(5).describe("Minimum number of backlinks a note must have"),
|
|
231
231
|
limit: z.number().default(10).describe("Maximum number of notes to return"),
|
|
232
|
-
|
|
232
|
+
graph_id: z.string().default("rapheal-brain").describe("The graph ID to search in"),
|
|
233
233
|
}),
|
|
234
234
|
execute: async (args) => {
|
|
235
|
-
const { minBacklinks, limit,
|
|
235
|
+
const { minBacklinks, limit, graph_id } = args;
|
|
236
236
|
try {
|
|
237
237
|
const dbFile = resolvedDbPath;
|
|
238
238
|
const db = new Database(dbFile, { readonly: true });
|
|
@@ -246,7 +246,7 @@ export function registerTools(server, dbPath) {
|
|
|
246
246
|
ORDER BY backlink_count DESC
|
|
247
247
|
LIMIT ?
|
|
248
248
|
`);
|
|
249
|
-
const results = stmt.all(
|
|
249
|
+
const results = stmt.all(graph_id, minBacklinks, limit);
|
|
250
250
|
db.close();
|
|
251
251
|
const notes = results.map((row) => ({
|
|
252
252
|
id: row.id,
|
|
@@ -258,7 +258,7 @@ export function registerTools(server, dbPath) {
|
|
|
258
258
|
content: [
|
|
259
259
|
{
|
|
260
260
|
type: "text",
|
|
261
|
-
text: JSON.stringify({
|
|
261
|
+
text: JSON.stringify({ graph_id, minBacklinks, count: notes.length, notes }, null, 2),
|
|
262
262
|
},
|
|
263
263
|
],
|
|
264
264
|
};
|
|
@@ -278,13 +278,13 @@ export function registerTools(server, dbPath) {
|
|
|
278
278
|
// Tool: Get all tags with usage counts
|
|
279
279
|
server.addTool({
|
|
280
280
|
name: "get_tags",
|
|
281
|
-
description: "Get all unique tags with their usage counts from
|
|
281
|
+
description: "Get all unique tags with their usage counts from Reflect.",
|
|
282
282
|
parameters: z.object({
|
|
283
|
-
|
|
283
|
+
graph_id: z.string().default("rapheal-brain").describe("The graph ID to search in"),
|
|
284
284
|
limit: z.number().default(50).describe("Maximum number of tags to return"),
|
|
285
285
|
}),
|
|
286
286
|
execute: async (args) => {
|
|
287
|
-
const {
|
|
287
|
+
const { graph_id, limit } = args;
|
|
288
288
|
try {
|
|
289
289
|
const dbFile = resolvedDbPath;
|
|
290
290
|
const db = new Database(dbFile, { readonly: true });
|
|
@@ -292,7 +292,7 @@ export function registerTools(server, dbPath) {
|
|
|
292
292
|
SELECT tags FROM notes
|
|
293
293
|
WHERE isDeleted = 0 AND graphId = ? AND tags IS NOT NULL AND tags != '[]'
|
|
294
294
|
`);
|
|
295
|
-
const rows = stmt.all(
|
|
295
|
+
const rows = stmt.all(graph_id);
|
|
296
296
|
db.close();
|
|
297
297
|
const tagCounts = {};
|
|
298
298
|
for (const row of rows) {
|
|
@@ -314,7 +314,7 @@ export function registerTools(server, dbPath) {
|
|
|
314
314
|
content: [
|
|
315
315
|
{
|
|
316
316
|
type: "text",
|
|
317
|
-
text: JSON.stringify({
|
|
317
|
+
text: JSON.stringify({ graph_id, totalTags: Object.keys(tagCounts).length, tags: sortedTags }, null, 2),
|
|
318
318
|
},
|
|
319
319
|
],
|
|
320
320
|
};
|
|
@@ -334,14 +334,14 @@ export function registerTools(server, dbPath) {
|
|
|
334
334
|
// Tool: Get notes with a specific tag
|
|
335
335
|
server.addTool({
|
|
336
336
|
name: "get_notes_with_tag",
|
|
337
|
-
description: "Get notes that have a specific tag from
|
|
337
|
+
description: "Get notes that have a specific tag from Reflect.",
|
|
338
338
|
parameters: z.object({
|
|
339
339
|
tag: z.string().describe("The tag to search for"),
|
|
340
|
-
|
|
340
|
+
graph_id: z.string().default("rapheal-brain").describe("The graph ID to search in"),
|
|
341
341
|
limit: z.number().default(20).describe("Maximum number of notes to return"),
|
|
342
342
|
}),
|
|
343
343
|
execute: async (args) => {
|
|
344
|
-
const { tag,
|
|
344
|
+
const { tag, graph_id, limit } = args;
|
|
345
345
|
try {
|
|
346
346
|
const dbFile = resolvedDbPath;
|
|
347
347
|
const db = new Database(dbFile, { readonly: true });
|
|
@@ -352,7 +352,7 @@ export function registerTools(server, dbPath) {
|
|
|
352
352
|
ORDER BY editedAt DESC
|
|
353
353
|
LIMIT ?
|
|
354
354
|
`);
|
|
355
|
-
const results = stmt.all(
|
|
355
|
+
const results = stmt.all(graph_id, `%"${tag}"%`, limit);
|
|
356
356
|
db.close();
|
|
357
357
|
const notes = results.map((row) => ({
|
|
358
358
|
id: row.id,
|
|
@@ -366,7 +366,7 @@ export function registerTools(server, dbPath) {
|
|
|
366
366
|
content: [
|
|
367
367
|
{
|
|
368
368
|
type: "text",
|
|
369
|
-
text: JSON.stringify({ tag,
|
|
369
|
+
text: JSON.stringify({ tag, graph_id, count: notes.length, notes }, null, 2),
|
|
370
370
|
},
|
|
371
371
|
],
|
|
372
372
|
};
|
|
@@ -383,49 +383,116 @@ export function registerTools(server, dbPath) {
|
|
|
383
383
|
}
|
|
384
384
|
},
|
|
385
385
|
});
|
|
386
|
-
// Tool: Get a note by title
|
|
386
|
+
// Tool: Get a note by title (exact match first, then fuzzy fallback)
|
|
387
387
|
server.addTool({
|
|
388
388
|
name: "get_note",
|
|
389
|
-
description: "Get a note by its title (subject) from
|
|
389
|
+
description: "Get a note by its title (subject) from Reflect.",
|
|
390
390
|
parameters: z.object({
|
|
391
391
|
title: z.string().describe("The title/subject of the note to retrieve"),
|
|
392
|
-
|
|
392
|
+
graph_id: z.string().default("rapheal-brain").describe("The graph ID to search in"),
|
|
393
393
|
}),
|
|
394
394
|
execute: async (args) => {
|
|
395
|
-
const { title,
|
|
395
|
+
const { title, graph_id } = args;
|
|
396
|
+
const FUZZY_LIMIT = 3;
|
|
396
397
|
try {
|
|
397
398
|
const dbFile = resolvedDbPath;
|
|
398
399
|
const db = new Database(dbFile, { readonly: true });
|
|
399
|
-
|
|
400
|
+
// Try exact match first
|
|
401
|
+
const exactStmt = db.prepare(`
|
|
400
402
|
SELECT id, subject, documentText, tags, editedAt, createdAt
|
|
401
403
|
FROM notes
|
|
402
404
|
WHERE isDeleted = 0 AND graphId = ? AND subject = ?
|
|
403
405
|
`);
|
|
404
|
-
const
|
|
406
|
+
const exactResult = exactStmt.get(graph_id, title);
|
|
407
|
+
if (exactResult) {
|
|
408
|
+
db.close();
|
|
409
|
+
const note = {
|
|
410
|
+
id: exactResult.id,
|
|
411
|
+
subject: exactResult.subject,
|
|
412
|
+
documentText: exactResult.documentText,
|
|
413
|
+
tags: exactResult.tags ? JSON.parse(exactResult.tags) : [],
|
|
414
|
+
editedAt: formatDate(exactResult.editedAt),
|
|
415
|
+
createdAt: formatDate(exactResult.createdAt),
|
|
416
|
+
};
|
|
417
|
+
return {
|
|
418
|
+
content: [
|
|
419
|
+
{
|
|
420
|
+
type: "text",
|
|
421
|
+
text: JSON.stringify({ title, graph_id, note }, null, 2),
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
// No exact match - try fuzzy search
|
|
427
|
+
const searchTerm = title.toLowerCase();
|
|
428
|
+
const fuzzyStmt = db.prepare(`
|
|
429
|
+
SELECT id, subject, documentText, tags, editedAt, createdAt,
|
|
430
|
+
CASE
|
|
431
|
+
WHEN LOWER(subject) LIKE ? THEN 2
|
|
432
|
+
WHEN LOWER(subject) LIKE ? THEN 1
|
|
433
|
+
ELSE 0
|
|
434
|
+
END as relevance
|
|
435
|
+
FROM notes
|
|
436
|
+
WHERE isDeleted = 0
|
|
437
|
+
AND graphId = ?
|
|
438
|
+
AND (LOWER(subject) LIKE ? OR LOWER(subject) LIKE ?)
|
|
439
|
+
ORDER BY relevance DESC, editedAt DESC
|
|
440
|
+
LIMIT ?
|
|
441
|
+
`);
|
|
442
|
+
const fuzzyResults = fuzzyStmt.all(`${searchTerm}%`, // starts with (score 2)
|
|
443
|
+
`%${searchTerm}%`, // contains (score 1)
|
|
444
|
+
graph_id, `${searchTerm}%`, // WHERE starts with
|
|
445
|
+
`%${searchTerm}%`, // WHERE contains
|
|
446
|
+
FUZZY_LIMIT);
|
|
405
447
|
db.close();
|
|
406
|
-
if (
|
|
448
|
+
if (fuzzyResults.length === 0) {
|
|
407
449
|
return {
|
|
408
450
|
content: [
|
|
409
451
|
{
|
|
410
452
|
type: "text",
|
|
411
|
-
text: JSON.stringify({
|
|
453
|
+
text: JSON.stringify({
|
|
454
|
+
error: `No notes found matching '${title}'`,
|
|
455
|
+
query: title,
|
|
456
|
+
graph_id
|
|
457
|
+
}),
|
|
412
458
|
},
|
|
413
459
|
],
|
|
414
460
|
};
|
|
415
461
|
}
|
|
416
|
-
const
|
|
462
|
+
const notes = fuzzyResults.map((result) => ({
|
|
417
463
|
id: result.id,
|
|
418
464
|
subject: result.subject,
|
|
419
465
|
documentText: result.documentText,
|
|
420
466
|
tags: result.tags ? JSON.parse(result.tags) : [],
|
|
421
467
|
editedAt: formatDate(result.editedAt),
|
|
422
468
|
createdAt: formatDate(result.createdAt),
|
|
423
|
-
};
|
|
469
|
+
}));
|
|
470
|
+
// If only one fuzzy match, return it directly
|
|
471
|
+
if (notes.length === 1) {
|
|
472
|
+
return {
|
|
473
|
+
content: [
|
|
474
|
+
{
|
|
475
|
+
type: "text",
|
|
476
|
+
text: JSON.stringify({
|
|
477
|
+
query: title,
|
|
478
|
+
graph_id,
|
|
479
|
+
note: notes[0],
|
|
480
|
+
matchType: "fuzzy"
|
|
481
|
+
}, null, 2),
|
|
482
|
+
},
|
|
483
|
+
],
|
|
484
|
+
};
|
|
485
|
+
}
|
|
424
486
|
return {
|
|
425
487
|
content: [
|
|
426
488
|
{
|
|
427
489
|
type: "text",
|
|
428
|
-
text: JSON.stringify({
|
|
490
|
+
text: JSON.stringify({
|
|
491
|
+
query: title,
|
|
492
|
+
graph_id,
|
|
493
|
+
matchCount: notes.length,
|
|
494
|
+
notes
|
|
495
|
+
}, null, 2),
|
|
429
496
|
},
|
|
430
497
|
],
|
|
431
498
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reflect-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "MCP server for Reflect Notes - connect your notes to Claude Desktop. Just run: npx reflect-mcp",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"build": "tsc",
|
|
16
16
|
"dev": "tsx src/cli.ts",
|
|
17
17
|
"start": "node dist/cli.js",
|
|
18
|
-
"prepublishOnly": "npm run build"
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"postinstall": "npm rebuild better-sqlite3 || true"
|
|
19
20
|
},
|
|
20
21
|
"keywords": [
|
|
21
22
|
"mcp",
|
|
@@ -36,8 +37,9 @@
|
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
39
|
-
"better-sqlite3": "^11.
|
|
40
|
+
"better-sqlite3": "^11.10.0",
|
|
40
41
|
"fastmcp": "^3.25.4",
|
|
42
|
+
"reflect-mcp": "^1.0.3",
|
|
41
43
|
"zod": "^4.1.13"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|