reflect-mcp 1.0.15 → 1.0.17
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/pkcehandler.d.ts +3 -2
- package/dist/pkcehandler.js +99 -57
- package/dist/server.js +5 -8
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +10 -1
- package/dist/utils.js +11 -7
- package/package.json +2 -2
package/dist/pkcehandler.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export declare class PKCEOAuthProxy {
|
|
|
28
28
|
private tokens;
|
|
29
29
|
private recentlyExchangedCodes;
|
|
30
30
|
private cleanupInterval;
|
|
31
|
+
private pendingAuthTransaction;
|
|
31
32
|
private activeConnections;
|
|
32
33
|
constructor(options: PKCEOAuthProxyConfig);
|
|
33
34
|
private loadTokensFromDisk;
|
|
@@ -66,7 +67,6 @@ export declare class PKCEOAuthProxy {
|
|
|
66
67
|
}): Promise<{
|
|
67
68
|
access_token: string;
|
|
68
69
|
token_type: string;
|
|
69
|
-
expires_in: number;
|
|
70
70
|
refresh_token?: string;
|
|
71
71
|
scope?: string;
|
|
72
72
|
}>;
|
|
@@ -78,7 +78,6 @@ export declare class PKCEOAuthProxy {
|
|
|
78
78
|
}): Promise<{
|
|
79
79
|
access_token: string;
|
|
80
80
|
token_type: string;
|
|
81
|
-
expires_in: number;
|
|
82
81
|
refresh_token?: string;
|
|
83
82
|
}>;
|
|
84
83
|
registerClient(request: {
|
|
@@ -89,6 +88,8 @@ export declare class PKCEOAuthProxy {
|
|
|
89
88
|
client_name?: string;
|
|
90
89
|
redirect_uris?: string[];
|
|
91
90
|
}>;
|
|
91
|
+
private validateUpstreamToken;
|
|
92
|
+
invalidateUpstreamToken(accessToken: string): Promise<void>;
|
|
92
93
|
loadUpstreamTokens(proxyToken: string): Promise<TokenData | null>;
|
|
93
94
|
getFirstValidToken(): TokenData | null;
|
|
94
95
|
private startCleanup;
|
package/dist/pkcehandler.js
CHANGED
|
@@ -74,6 +74,8 @@ export class PKCEOAuthProxy {
|
|
|
74
74
|
// Track tokens that have been exchanged but allow brief retry window
|
|
75
75
|
recentlyExchangedCodes = new Map();
|
|
76
76
|
cleanupInterval = null;
|
|
77
|
+
// Debounce: track pending auth so concurrent requests don't each open a browser
|
|
78
|
+
pendingAuthTransaction = null;
|
|
77
79
|
// Active connections counter for debugging
|
|
78
80
|
activeConnections = 0;
|
|
79
81
|
constructor(options) {
|
|
@@ -98,15 +100,11 @@ export class PKCEOAuthProxy {
|
|
|
98
100
|
const data = fs.readFileSync(this.config.tokenStoragePath, "utf-8");
|
|
99
101
|
const stored = JSON.parse(data);
|
|
100
102
|
for (const [key, value] of Object.entries(stored)) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
refreshToken: value.refreshToken,
|
|
107
|
-
expiresAt,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
103
|
+
this.tokens.set(key, {
|
|
104
|
+
accessToken: value.accessToken,
|
|
105
|
+
refreshToken: value.refreshToken,
|
|
106
|
+
expiresAt: new Date(value.expiresAt),
|
|
107
|
+
});
|
|
110
108
|
}
|
|
111
109
|
console.log(`[PKCEProxy] Loaded ${this.tokens.size} tokens from disk`);
|
|
112
110
|
}
|
|
@@ -245,6 +243,15 @@ export class PKCEOAuthProxy {
|
|
|
245
243
|
headers: { Location: clientRedirect.toString() },
|
|
246
244
|
});
|
|
247
245
|
}
|
|
246
|
+
// Debounce: if another request already started auth, reuse its redirect
|
|
247
|
+
// instead of opening yet another browser tab
|
|
248
|
+
if (this.pendingAuthTransaction && this.pendingAuthTransaction.expiresAt > new Date()) {
|
|
249
|
+
console.log("[PKCEProxy] Auth already in progress — reusing pending transaction:", this.pendingAuthTransaction.transactionId.slice(0, 8) + "...");
|
|
250
|
+
return new Response(null, {
|
|
251
|
+
status: 302,
|
|
252
|
+
headers: { Location: this.pendingAuthTransaction.authUrl },
|
|
253
|
+
});
|
|
254
|
+
}
|
|
248
255
|
// Generate our own PKCE for upstream
|
|
249
256
|
const pkce = this.generatePKCE();
|
|
250
257
|
const transactionId = this.generateId();
|
|
@@ -271,6 +278,12 @@ export class PKCEOAuthProxy {
|
|
|
271
278
|
authUrl.searchParams.set("state", transactionId); // Use transaction ID as state
|
|
272
279
|
authUrl.searchParams.set("code_challenge", pkce.challenge);
|
|
273
280
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
281
|
+
// Store as pending so concurrent requests reuse this instead of opening more browsers
|
|
282
|
+
this.pendingAuthTransaction = {
|
|
283
|
+
transactionId,
|
|
284
|
+
authUrl: authUrl.toString(),
|
|
285
|
+
expiresAt: new Date(Date.now() + 60 * 1000), // 60 second debounce window
|
|
286
|
+
};
|
|
274
287
|
console.log("[PKCEProxy] Redirecting to:", authUrl.toString());
|
|
275
288
|
return new Response(null, {
|
|
276
289
|
status: 302,
|
|
@@ -338,21 +351,22 @@ export class PKCEOAuthProxy {
|
|
|
338
351
|
});
|
|
339
352
|
}
|
|
340
353
|
const tokens = await tokenResponse.json();
|
|
341
|
-
console.log("[PKCEProxy]
|
|
354
|
+
console.log("[PKCEProxy] Reflect token response:", JSON.stringify(tokens, null, 2));
|
|
342
355
|
// Generate a proxy token to give to the client
|
|
343
356
|
const proxyToken = this.generateId();
|
|
344
357
|
this.tokens.set(proxyToken, {
|
|
345
358
|
accessToken: tokens.access_token,
|
|
346
359
|
refreshToken: tokens.refresh_token,
|
|
347
|
-
expiresAt: new Date(Date.now() + (tokens.expires_in || 3600) * 1000),
|
|
360
|
+
expiresAt: new Date(Date.now() + (tokens.expires_in || 365 * 24 * 3600) * 1000),
|
|
348
361
|
});
|
|
349
362
|
await this.saveTokensToDisk(); // Persist to disk (async now)
|
|
350
363
|
// Redirect back to client with our proxy token
|
|
351
364
|
const clientRedirect = new URL(transaction.clientCallbackUrl);
|
|
352
365
|
clientRedirect.searchParams.set("code", proxyToken);
|
|
353
366
|
clientRedirect.searchParams.set("state", transaction.clientState);
|
|
354
|
-
// Clean up transaction
|
|
367
|
+
// Clean up transaction and pending auth debounce
|
|
355
368
|
this.transactions.delete(state);
|
|
369
|
+
this.pendingAuthTransaction = null;
|
|
356
370
|
await this.saveTransactionsToDisk();
|
|
357
371
|
console.log("[PKCEProxy] Redirecting to client:", clientRedirect.toString());
|
|
358
372
|
return new Response(null, {
|
|
@@ -378,7 +392,6 @@ export class PKCEOAuthProxy {
|
|
|
378
392
|
return {
|
|
379
393
|
access_token: recentExchange.accessToken,
|
|
380
394
|
token_type: "Bearer",
|
|
381
|
-
expires_in: expiresIn > 0 ? expiresIn : 3600,
|
|
382
395
|
};
|
|
383
396
|
}
|
|
384
397
|
}
|
|
@@ -402,25 +415,53 @@ export class PKCEOAuthProxy {
|
|
|
402
415
|
});
|
|
403
416
|
// Now save to disk
|
|
404
417
|
await this.saveTokensToDisk();
|
|
405
|
-
|
|
406
|
-
|
|
418
|
+
console.log(`[PKCEProxy] Issued access token for code: ${params.code.slice(0, 8)}...`);
|
|
419
|
+
const refreshToken = `refresh_${this.generateId()}`;
|
|
420
|
+
this.tokens.set(refreshToken, { ...tokenData });
|
|
421
|
+
await this.saveTokensToDisk();
|
|
407
422
|
return {
|
|
408
423
|
access_token: accessToken,
|
|
409
424
|
token_type: "Bearer",
|
|
410
|
-
|
|
411
|
-
// Note: Not returning refresh_token since Reflect doesn't support refresh_token grant
|
|
412
|
-
// This tells the MCP client to re-authenticate via OAuth when the token expires
|
|
425
|
+
refresh_token: refreshToken,
|
|
413
426
|
};
|
|
414
427
|
}
|
|
415
|
-
// Handle refresh token exchange
|
|
416
|
-
// Note: Reflect's API doesn't support standard refresh_token grant
|
|
417
|
-
// We throw an OAuthProxyError to trigger re-authentication via OAuth flow
|
|
418
428
|
async exchangeRefreshToken(params) {
|
|
419
|
-
console.log(
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
429
|
+
console.log(`[PKCEProxy] exchangeRefreshToken called with: ${params.refresh_token.slice(0, 12)}...`);
|
|
430
|
+
const tokenData = this.tokens.get(params.refresh_token);
|
|
431
|
+
if (tokenData) {
|
|
432
|
+
const newAccessToken = this.generateId();
|
|
433
|
+
this.tokens.set(newAccessToken, { ...tokenData });
|
|
434
|
+
const newRefreshToken = `refresh_${this.generateId()}`;
|
|
435
|
+
this.tokens.set(newRefreshToken, { ...tokenData });
|
|
436
|
+
this.tokens.delete(params.refresh_token);
|
|
437
|
+
await this.saveTokensToDisk();
|
|
438
|
+
console.log(`[PKCEProxy] Refreshed silently`);
|
|
439
|
+
return {
|
|
440
|
+
access_token: newAccessToken,
|
|
441
|
+
token_type: "Bearer",
|
|
442
|
+
refresh_token: newRefreshToken,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
// Refresh token not found — fall back to any available token
|
|
446
|
+
const validToken = this.getFirstValidToken();
|
|
447
|
+
if (validToken) {
|
|
448
|
+
console.log("[PKCEProxy] Refresh token not found, using fallback token");
|
|
449
|
+
const newAccessToken = this.generateId();
|
|
450
|
+
this.tokens.set(newAccessToken, { ...validToken });
|
|
451
|
+
const newRefreshToken = `refresh_${this.generateId()}`;
|
|
452
|
+
this.tokens.set(newRefreshToken, { ...validToken });
|
|
453
|
+
this.tokens.delete(params.refresh_token);
|
|
454
|
+
await this.saveTokensToDisk();
|
|
455
|
+
console.log(`[PKCEProxy] Issued token from fallback`);
|
|
456
|
+
return {
|
|
457
|
+
access_token: newAccessToken,
|
|
458
|
+
token_type: "Bearer",
|
|
459
|
+
refresh_token: newRefreshToken,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
// No tokens at all — force browser re-auth
|
|
463
|
+
console.log("[PKCEProxy] No tokens available — forcing re-authentication");
|
|
464
|
+
throw new OAuthProxyError("invalid_grant", "All tokens expired. Please re-authenticate.", 400);
|
|
424
465
|
}
|
|
425
466
|
// Handle /oauth/register (Dynamic Client Registration)
|
|
426
467
|
async registerClient(request) {
|
|
@@ -432,13 +473,40 @@ export class PKCEOAuthProxy {
|
|
|
432
473
|
redirect_uris: request.redirect_uris,
|
|
433
474
|
};
|
|
434
475
|
}
|
|
476
|
+
// Validate an upstream Reflect token by calling the API
|
|
477
|
+
async validateUpstreamToken(tokenData) {
|
|
478
|
+
try {
|
|
479
|
+
const response = await fetch("https://reflect.app/api/users/me", {
|
|
480
|
+
headers: { Authorization: `Bearer ${tokenData.accessToken}` },
|
|
481
|
+
});
|
|
482
|
+
if (response.ok)
|
|
483
|
+
return true;
|
|
484
|
+
console.warn("[PKCEProxy] Upstream token rejected by Reflect API:", response.status);
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
// Network error — don't invalidate, assume token is still good
|
|
489
|
+
console.warn("[PKCEProxy] Network error validating token, keeping it:", error);
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// Invalidate all tokens that share a given upstream access token
|
|
494
|
+
async invalidateUpstreamToken(accessToken) {
|
|
495
|
+
let changed = false;
|
|
496
|
+
for (const [id, token] of this.tokens) {
|
|
497
|
+
if (token.accessToken === accessToken) {
|
|
498
|
+
console.log("[PKCEProxy] Invalidating token with revoked upstream:", id.slice(0, 8) + "...");
|
|
499
|
+
this.tokens.delete(id);
|
|
500
|
+
changed = true;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (changed)
|
|
504
|
+
await this.saveTokensToDisk();
|
|
505
|
+
}
|
|
435
506
|
// Load upstream tokens for a given proxy token
|
|
436
507
|
async loadUpstreamTokens(proxyToken) {
|
|
437
508
|
const data = this.tokens.get(proxyToken);
|
|
438
509
|
if (!data) {
|
|
439
|
-
// Token not found — check if this is a stale/rotated token from a previous session.
|
|
440
|
-
// For a local server, all clients share the same Reflect credentials, so we can
|
|
441
|
-
// fall back to any currently-valid token rather than forcing a re-auth loop.
|
|
442
510
|
const validToken = this.getFirstValidToken();
|
|
443
511
|
if (validToken) {
|
|
444
512
|
console.warn("[PKCEProxy] Stale token presented, mapping to current valid token:", proxyToken.slice(0, 8) + "...");
|
|
@@ -448,26 +516,12 @@ export class PKCEOAuthProxy {
|
|
|
448
516
|
console.warn("[PKCEProxy] Total tokens in store:", this.tokens.size);
|
|
449
517
|
return null;
|
|
450
518
|
}
|
|
451
|
-
const now = new Date();
|
|
452
|
-
if (data.expiresAt < now) {
|
|
453
|
-
console.warn("[PKCEProxy] Token expired:", proxyToken.slice(0, 8) + "...", "expired at:", data.expiresAt, "now:", now);
|
|
454
|
-
this.tokens.delete(proxyToken);
|
|
455
|
-
await this.saveTokensToDisk();
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
const timeRemaining = Math.floor((data.expiresAt.getTime() - now.getTime()) / 1000);
|
|
459
|
-
if (timeRemaining < 300) { // Less than 5 minutes remaining
|
|
460
|
-
console.warn("[PKCEProxy] Token expiring soon:", proxyToken.slice(0, 8) + "...", "remaining:", timeRemaining, "seconds");
|
|
461
|
-
}
|
|
462
519
|
return data;
|
|
463
520
|
}
|
|
464
|
-
// Get first
|
|
521
|
+
// Get first available token (any token in the store)
|
|
465
522
|
getFirstValidToken() {
|
|
466
|
-
const now = new Date();
|
|
467
523
|
for (const [id, token] of this.tokens) {
|
|
468
|
-
|
|
469
|
-
return token;
|
|
470
|
-
}
|
|
524
|
+
return token;
|
|
471
525
|
}
|
|
472
526
|
return null;
|
|
473
527
|
}
|
|
@@ -475,7 +529,6 @@ export class PKCEOAuthProxy {
|
|
|
475
529
|
async startCleanup() {
|
|
476
530
|
const cleanup = async () => {
|
|
477
531
|
const now = new Date();
|
|
478
|
-
let tokensChanged = false;
|
|
479
532
|
let transactionsChanged = false;
|
|
480
533
|
for (const [id, tx] of this.transactions) {
|
|
481
534
|
if (tx.expiresAt < now) {
|
|
@@ -483,22 +536,11 @@ export class PKCEOAuthProxy {
|
|
|
483
536
|
transactionsChanged = true;
|
|
484
537
|
}
|
|
485
538
|
}
|
|
486
|
-
for (const [id, token] of this.tokens) {
|
|
487
|
-
if (token.expiresAt < now) {
|
|
488
|
-
console.log("[PKCEProxy] Cleaning up expired token:", id.slice(0, 8) + "...");
|
|
489
|
-
this.tokens.delete(id);
|
|
490
|
-
tokensChanged = true;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
// Clean up expired retry cache entries
|
|
494
539
|
for (const [code, data] of this.recentlyExchangedCodes) {
|
|
495
540
|
if (data.expiresAt < now) {
|
|
496
541
|
this.recentlyExchangedCodes.delete(code);
|
|
497
542
|
}
|
|
498
543
|
}
|
|
499
|
-
if (tokensChanged) {
|
|
500
|
-
await this.saveTokensToDisk();
|
|
501
|
-
}
|
|
502
544
|
if (transactionsChanged) {
|
|
503
545
|
await this.saveTransactionsToDisk();
|
|
504
546
|
}
|
package/dist/server.js
CHANGED
|
@@ -56,12 +56,10 @@ export async function startReflectMCPServer(config) {
|
|
|
56
56
|
statusText: "Unauthorized - Invalid or expired token",
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
console.log("[Auth] Token validated, expires in:", expiresIn, "seconds");
|
|
59
|
+
console.log("[Auth] Token validated");
|
|
61
60
|
return {
|
|
62
61
|
accessToken: tokenData.accessToken,
|
|
63
62
|
refreshToken: tokenData.refreshToken,
|
|
64
|
-
expiresIn,
|
|
65
63
|
};
|
|
66
64
|
}
|
|
67
65
|
catch (error) {
|
|
@@ -78,12 +76,13 @@ export async function startReflectMCPServer(config) {
|
|
|
78
76
|
},
|
|
79
77
|
version: "1.0.0",
|
|
80
78
|
});
|
|
81
|
-
// Register all tools
|
|
82
|
-
registerTools(server, config.dbPath);
|
|
79
|
+
// Register all tools (pass proxy so tools can invalidate tokens on upstream 401)
|
|
80
|
+
registerTools(server, config.dbPath, pkceProxy);
|
|
83
81
|
// Start server
|
|
84
82
|
await server.start({
|
|
85
83
|
httpStream: {
|
|
86
84
|
port,
|
|
85
|
+
stateless: false,
|
|
87
86
|
},
|
|
88
87
|
transportType: "httpStream",
|
|
89
88
|
});
|
|
@@ -113,11 +112,9 @@ export async function startReflectMCPServerStdio(config) {
|
|
|
113
112
|
console.error("[Auth] No valid token on disk. Connect via HTTP mode first to complete OAuth.");
|
|
114
113
|
throw new Error("No valid token. Please authenticate via HTTP mode first.");
|
|
115
114
|
}
|
|
116
|
-
|
|
117
|
-
console.error("[Auth] Stdio mode: token loaded from disk, expires in:", expiresIn, "seconds");
|
|
115
|
+
console.error("[Auth] Stdio mode: token loaded from disk");
|
|
118
116
|
return {
|
|
119
117
|
accessToken: tokenData.accessToken,
|
|
120
|
-
expiresIn,
|
|
121
118
|
};
|
|
122
119
|
},
|
|
123
120
|
version: "1.0.0",
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -4,4 +4,5 @@
|
|
|
4
4
|
* All tools for interacting with Reflect notes
|
|
5
5
|
*/
|
|
6
6
|
import { FastMCP } from "fastmcp";
|
|
7
|
-
|
|
7
|
+
import type { PKCEOAuthProxy } from "../pkcehandler.js";
|
|
8
|
+
export declare function registerTools(server: FastMCP, dbPath?: string, pkceProxy?: PKCEOAuthProxy): void;
|
package/dist/tools/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import Database from "better-sqlite3";
|
|
8
8
|
import { DEFAULT_DB_PATH, expandPath, stripHtml, formatDate, getDateForTimezone } from "../utils.js";
|
|
9
|
-
export function registerTools(server, dbPath) {
|
|
9
|
+
export function registerTools(server, dbPath, pkceProxy) {
|
|
10
10
|
const resolvedDbPath = expandPath(dbPath || DEFAULT_DB_PATH);
|
|
11
11
|
// Tool: Get all Reflect graphs
|
|
12
12
|
server.addTool({
|
|
@@ -32,6 +32,9 @@ export function registerTools(server, dbPath) {
|
|
|
32
32
|
},
|
|
33
33
|
});
|
|
34
34
|
if (!response.ok) {
|
|
35
|
+
if (response.status === 401 && pkceProxy) {
|
|
36
|
+
await pkceProxy.invalidateUpstreamToken(accessToken);
|
|
37
|
+
}
|
|
35
38
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
36
39
|
}
|
|
37
40
|
const data = await response.json();
|
|
@@ -672,6 +675,9 @@ export function registerTools(server, dbPath) {
|
|
|
672
675
|
}),
|
|
673
676
|
});
|
|
674
677
|
if (!response.ok) {
|
|
678
|
+
if (response.status === 401 && pkceProxy) {
|
|
679
|
+
await pkceProxy.invalidateUpstreamToken(accessToken);
|
|
680
|
+
}
|
|
675
681
|
const errorText = await response.text();
|
|
676
682
|
throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`);
|
|
677
683
|
}
|
|
@@ -690,6 +696,9 @@ export function registerTools(server, dbPath) {
|
|
|
690
696
|
}),
|
|
691
697
|
});
|
|
692
698
|
if (!dailyNoteResponse.ok) {
|
|
699
|
+
if (dailyNoteResponse.status === 401 && pkceProxy) {
|
|
700
|
+
await pkceProxy.invalidateUpstreamToken(accessToken);
|
|
701
|
+
}
|
|
693
702
|
const errorText = await dailyNoteResponse.text();
|
|
694
703
|
console.error(`Failed to append to daily notes: ${dailyNoteResponse.status}, ${errorText}`);
|
|
695
704
|
}
|
package/dist/utils.js
CHANGED
|
@@ -32,31 +32,35 @@ export function findLocalDatabase() {
|
|
|
32
32
|
}
|
|
33
33
|
// Search for database files in the File System directory
|
|
34
34
|
// Structure is typically: File System/XXX/t/XX/XXXXXXXX
|
|
35
|
+
// Reflect may have multiple database partitions (000, 001, etc.)
|
|
36
|
+
// so we find all SQLite files and return the most recently modified one.
|
|
35
37
|
try {
|
|
36
38
|
const entries = fs.readdirSync(basePath);
|
|
39
|
+
let bestPath = null;
|
|
40
|
+
let bestMtime = 0;
|
|
37
41
|
for (const entry of entries) {
|
|
38
42
|
const entryPath = path.join(basePath, entry);
|
|
39
43
|
const tPath = path.join(entryPath, "t");
|
|
40
44
|
if (fs.existsSync(tPath) && fs.statSync(tPath).isDirectory()) {
|
|
41
|
-
// Look for subdirectories in t/
|
|
42
45
|
const tEntries = fs.readdirSync(tPath);
|
|
43
46
|
for (const tEntry of tEntries) {
|
|
44
47
|
const subPath = path.join(tPath, tEntry);
|
|
45
48
|
if (fs.statSync(subPath).isDirectory()) {
|
|
46
|
-
// Look for database files (8-char hex names)
|
|
47
49
|
const dbFiles = fs.readdirSync(subPath);
|
|
48
50
|
for (const dbFile of dbFiles) {
|
|
49
51
|
const dbPath = path.join(subPath, dbFile);
|
|
50
|
-
// Check if it's a valid SQLite database (starts with SQLite header)
|
|
51
52
|
if (fs.statSync(dbPath).isFile()) {
|
|
52
53
|
try {
|
|
53
54
|
const header = Buffer.alloc(16);
|
|
54
55
|
const fd = fs.openSync(dbPath, 'r');
|
|
55
56
|
fs.readSync(fd, header, 0, 16, 0);
|
|
56
57
|
fs.closeSync(fd);
|
|
57
|
-
// SQLite files start with "SQLite format 3"
|
|
58
58
|
if (header.toString('utf8', 0, 15) === 'SQLite format 3') {
|
|
59
|
-
|
|
59
|
+
const mtime = fs.statSync(dbPath).mtimeMs;
|
|
60
|
+
if (mtime > bestMtime) {
|
|
61
|
+
bestMtime = mtime;
|
|
62
|
+
bestPath = dbPath;
|
|
63
|
+
}
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
catch {
|
|
@@ -68,11 +72,11 @@ export function findLocalDatabase() {
|
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
}
|
|
75
|
+
return bestPath;
|
|
71
76
|
}
|
|
72
77
|
catch {
|
|
73
78
|
return null;
|
|
74
79
|
}
|
|
75
|
-
return null;
|
|
76
80
|
}
|
|
77
81
|
/**
|
|
78
82
|
* Gets the default database path, searching for it if not provided.
|
|
@@ -84,7 +88,7 @@ export function getDefaultDbPath() {
|
|
|
84
88
|
return found;
|
|
85
89
|
}
|
|
86
90
|
if (os.platform() === "darwin") {
|
|
87
|
-
return expandPath("~/Library/Application Support/Reflect/File System/
|
|
91
|
+
return expandPath("~/Library/Application Support/Reflect/File System/001/t/00/00000000");
|
|
88
92
|
}
|
|
89
93
|
return "";
|
|
90
94
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reflect-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
40
40
|
"better-sqlite3": "^11.10.0",
|
|
41
41
|
"fastmcp": "^3.25.4",
|
|
42
|
-
"reflect-mcp": "^1.0.
|
|
42
|
+
"reflect-mcp": "^1.0.16",
|
|
43
43
|
"zod": "^4.1.13"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|