reflect-mcp 1.0.13 → 1.0.15

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/cli.js CHANGED
@@ -34,55 +34,18 @@ function isPortInUse(port) {
34
34
  });
35
35
  }
36
36
  /**
37
- * Kill any process using the specified port
37
+ * Find a free port starting from startPort.
38
+ * Returns the first available port within maxAttempts tries,
39
+ * or throws if none is found.
38
40
  */
39
- function killProcessOnPort(port) {
40
- try {
41
- let pids;
42
- if (platform === "darwin") {
43
- pids = execSync(`lsof -ti:${port}`, { encoding: "utf-8" }).trim();
44
- }
45
- else {
46
- // Linux: try lsof first, fall back to fuser
47
- try {
48
- pids = execSync(`lsof -ti:${port}`, { encoding: "utf-8" }).trim();
49
- }
50
- catch {
51
- pids = execSync(`fuser ${port}/tcp 2>/dev/null`, { encoding: "utf-8" }).trim();
52
- }
53
- }
54
- if (pids) {
55
- console.log(`⚠️ Port ${port} in use by PID(s): ${pids.replace(/\n/g, ", ")}`);
56
- for (const pid of pids.split(/\s+/)) {
57
- if (pid) {
58
- try {
59
- process.kill(parseInt(pid), "SIGKILL");
60
- }
61
- catch { /* already dead */ }
62
- }
63
- }
64
- console.log(`✅ Killed existing process(es) on port ${port}`);
65
- return true;
66
- }
67
- }
68
- catch {
69
- // No process found on port, or kill failed - that's fine
70
- }
71
- return false;
72
- }
73
- /**
74
- * Ensure the port is free, killing any existing process if needed
75
- */
76
- async function ensurePortFree(port) {
77
- if (await isPortInUse(port)) {
78
- killProcessOnPort(port);
79
- // Wait a moment for the port to be released
80
- await new Promise((resolve) => setTimeout(resolve, 500));
81
- // Check again
82
- if (await isPortInUse(port)) {
83
- throw new Error(`Port ${port} is still in use after attempting to free it`);
41
+ async function findFreePort(startPort, maxAttempts = 10) {
42
+ for (let i = 0; i < maxAttempts; i++) {
43
+ const candidate = startPort + i;
44
+ if (!(await isPortInUse(candidate))) {
45
+ return candidate;
84
46
  }
85
47
  }
48
+ throw new Error(`No free port found in range ${startPort}–${startPort + maxAttempts - 1}`);
86
49
  }
87
50
  const REFLECT_CLIENT_ID = "55798f25d5a24efb95e4174fff3d219e";
88
51
  const platform = os.platform();
@@ -180,9 +143,6 @@ async function install(installArgs) {
180
143
  const nodePath = process.execPath;
181
144
  const cliPath = process.argv[1];
182
145
  console.log("📦 Installing Reflect MCP Server as auto-start service...\n");
183
- // Kill any stale processes on the port
184
- killProcessOnPort(port);
185
- await new Promise((resolve) => setTimeout(resolve, 300));
186
146
  if (platform === "darwin") {
187
147
  installDarwin(nodePath, cliPath, expandedDbPath, port);
188
148
  }
@@ -203,7 +163,7 @@ async function install(installArgs) {
203
163
  "mcpServers": {
204
164
  "reflect": {
205
165
  "command": "npx",
206
- "args": ["-y", "mcp-remote", "--port", "4209", "http://localhost:${port}/mcp"]
166
+ "args": ["-y", "mcp-remote", "http://localhost:${port}/mcp", "--port", "4209"]
207
167
  }
208
168
  }
209
169
  }`);
@@ -401,10 +361,10 @@ function statusLinux() {
401
361
  }
402
362
  async function runServer(serverArgs) {
403
363
  let dbPath;
404
- let port = 3000;
364
+ let requestedPort = 3000;
405
365
  for (let i = 0; i < serverArgs.length; i++) {
406
366
  if (serverArgs[i] === "--port" && serverArgs[i + 1]) {
407
- port = parseInt(serverArgs[++i]);
367
+ requestedPort = parseInt(serverArgs[++i]);
408
368
  }
409
369
  else if (!serverArgs[i].startsWith("--")) {
410
370
  dbPath = serverArgs[i];
@@ -418,22 +378,27 @@ async function runServer(serverArgs) {
418
378
  }
419
379
  dbPath = DEFAULT_DB_PATH;
420
380
  }
421
- // Ensure port is free before starting
381
+ // Find the first free port at or above the requested port.
382
+ // This allows multiple MCP clients to each get their own HTTP server
383
+ // without killing other running instances.
384
+ let port;
422
385
  try {
423
- await ensurePortFree(port);
386
+ port = await findFreePort(requestedPort);
424
387
  }
425
388
  catch (err) {
426
- console.error(`❌ ${err}`);
389
+ console.error(`[reflect-mcp] ${err}`);
427
390
  process.exit(1);
428
391
  }
392
+ if (port !== requestedPort) {
393
+ console.error(`[reflect-mcp] Requested port ${requestedPort} is in use — using port ${port} instead`);
394
+ }
429
395
  try {
430
396
  await startReflectMCPServer({
431
397
  clientId: REFLECT_CLIENT_ID,
432
398
  port,
433
399
  dbPath,
434
400
  });
435
- console.log(`Reflect MCP Server running on http://localhost:${port}`);
436
- console.log(`Database: ${dbPath}`);
401
+ console.error(`[reflect-mcp] HTTP server running on http://localhost:${port}`);
437
402
  }
438
403
  catch (err) {
439
404
  console.error("Failed to start server:", err);
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * PKCE OAuth Proxy (No Client Secret Required)
3
3
  *
4
+ * Updated by Twice 🦸‍♂️
5
+ * Cloning capabilities enabled for multiple MCP clients
6
+ *
4
7
  * This module provides a custom OAuth proxy that uses PKCE for authentication
5
8
  * without requiring a client secret, suitable for public clients.
6
9
  */
@@ -25,6 +28,7 @@ export declare class PKCEOAuthProxy {
25
28
  private tokens;
26
29
  private recentlyExchangedCodes;
27
30
  private cleanupInterval;
31
+ private activeConnections;
28
32
  constructor(options: PKCEOAuthProxyConfig);
29
33
  private loadTokensFromDisk;
30
34
  private saveTokensToDisk;
@@ -85,7 +89,7 @@ export declare class PKCEOAuthProxy {
85
89
  client_name?: string;
86
90
  redirect_uris?: string[];
87
91
  }>;
88
- loadUpstreamTokens(proxyToken: string): TokenData | null;
92
+ loadUpstreamTokens(proxyToken: string): Promise<TokenData | null>;
89
93
  getFirstValidToken(): TokenData | null;
90
94
  private startCleanup;
91
95
  destroy(): void;
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * PKCE OAuth Proxy (No Client Secret Required)
3
3
  *
4
+ * Updated by Twice 🦸‍♂️
5
+ * Cloning capabilities enabled for multiple MCP clients
6
+ *
4
7
  * This module provides a custom OAuth proxy that uses PKCE for authentication
5
8
  * without requiring a client secret, suitable for public clients.
6
9
  */
@@ -10,6 +13,56 @@ import * as path from "path";
10
13
  import * as os from "os";
11
14
  import { OAuthProxyError } from "fastmcp/auth";
12
15
  // ============================================================================
16
+ // Write Queue - Prevents concurrent file I/O operations
17
+ // ============================================================================
18
+ /**
19
+ * Simple in-memory write queue that batches and serializes file writes.
20
+ * This prevents race conditions when multiple clients trigger disk writes simultaneously.
21
+ */
22
+ class WriteQueue {
23
+ queue = [];
24
+ isProcessing = false;
25
+ /**
26
+ * Add a write operation to the queue and wait for it to complete
27
+ */
28
+ async add(writeOperation) {
29
+ return new Promise((resolve, reject) => {
30
+ const operation = async () => {
31
+ try {
32
+ await writeOperation();
33
+ resolve();
34
+ }
35
+ catch (error) {
36
+ reject(error);
37
+ }
38
+ };
39
+ this.queue.push(operation);
40
+ this.processQueue();
41
+ });
42
+ }
43
+ /**
44
+ * Process the queue one operation at a time
45
+ */
46
+ async processQueue() {
47
+ if (this.isProcessing || this.queue.length === 0) {
48
+ return;
49
+ }
50
+ this.isProcessing = true;
51
+ const operation = this.queue.shift();
52
+ try {
53
+ await operation();
54
+ }
55
+ finally {
56
+ this.isProcessing = false;
57
+ // Small delay to batch rapid writes together
58
+ await new Promise(resolve => setTimeout(resolve, 10));
59
+ this.processQueue();
60
+ }
61
+ }
62
+ }
63
+ // Global write queue instance shared across all PKCEProxy instances
64
+ const globalWriteQueue = new WriteQueue();
65
+ // ============================================================================
13
66
  // PKCEOAuthProxy Class
14
67
  // ============================================================================
15
68
  export class PKCEOAuthProxy {
@@ -21,6 +74,8 @@ export class PKCEOAuthProxy {
21
74
  // Track tokens that have been exchanged but allow brief retry window
22
75
  recentlyExchangedCodes = new Map();
23
76
  cleanupInterval = null;
77
+ // Active connections counter for debugging
78
+ activeConnections = 0;
24
79
  constructor(options) {
25
80
  this.config = {
26
81
  baseUrl: options.baseUrl,
@@ -60,8 +115,9 @@ export class PKCEOAuthProxy {
60
115
  console.warn("[PKCEProxy] Failed to load tokens from disk:", error);
61
116
  }
62
117
  }
63
- // Save tokens to disk
64
- saveTokensToDisk() {
118
+ // Save tokens to disk - ASYNC with write queue
119
+ // This prevents blocking the event loop and prevents race conditions
120
+ async saveTokensToDisk() {
65
121
  try {
66
122
  const toStore = {};
67
123
  for (const [key, value] of this.tokens) {
@@ -71,7 +127,11 @@ export class PKCEOAuthProxy {
71
127
  expiresAt: value.expiresAt.toISOString(),
72
128
  };
73
129
  }
74
- fs.writeFileSync(this.config.tokenStoragePath, JSON.stringify(toStore, null, 2));
130
+ // Use the global write queue to serialize this write operation
131
+ await globalWriteQueue.add(async () => {
132
+ await fs.promises.writeFile(this.config.tokenStoragePath, JSON.stringify(toStore, null, 2), "utf-8");
133
+ });
134
+ console.log(`[PKCEProxy] Saved ${Object.keys(toStore).length} tokens to disk`);
75
135
  }
76
136
  catch (error) {
77
137
  console.error("[PKCEProxy] Failed to save tokens to disk:", error);
@@ -106,8 +166,8 @@ export class PKCEOAuthProxy {
106
166
  console.warn("[PKCEProxy] Failed to load transactions from disk:", error);
107
167
  }
108
168
  }
109
- // Save transactions to disk (survives server restarts)
110
- saveTransactionsToDisk() {
169
+ // Save transactions to disk (survives server restarts) - ASYNC with write queue
170
+ async saveTransactionsToDisk() {
111
171
  try {
112
172
  const toStore = {};
113
173
  for (const [key, value] of this.transactions) {
@@ -122,7 +182,11 @@ export class PKCEOAuthProxy {
122
182
  expiresAt: value.expiresAt.toISOString(),
123
183
  };
124
184
  }
125
- fs.writeFileSync(this.config.transactionStoragePath, JSON.stringify(toStore, null, 2));
185
+ // Use the global write queue to serialize this write operation
186
+ await globalWriteQueue.add(async () => {
187
+ await fs.promises.writeFile(this.config.transactionStoragePath, JSON.stringify(toStore, null, 2), "utf-8");
188
+ });
189
+ console.log(`[PKCEProxy] Saved ${Object.keys(toStore).length} transactions to disk`);
126
190
  }
127
191
  catch (error) {
128
192
  console.error("[PKCEProxy] Failed to save transactions to disk:", error);
@@ -164,6 +228,23 @@ export class PKCEOAuthProxy {
164
228
  headers: { "Content-Type": "application/json" },
165
229
  });
166
230
  }
231
+ // If we already have a valid token, skip the full OAuth dance.
232
+ // Issue a proxy code immediately so subsequent clients never need a browser.
233
+ const existingToken = this.getFirstValidToken();
234
+ if (existingToken) {
235
+ console.log("[PKCEProxy] Valid token exists — issuing proxy code directly (skipping OAuth)");
236
+ const proxyCode = this.generateId();
237
+ this.tokens.set(proxyCode, { ...existingToken });
238
+ await this.saveTokensToDisk();
239
+ const clientRedirect = new URL(params.redirect_uri);
240
+ clientRedirect.searchParams.set("code", proxyCode);
241
+ clientRedirect.searchParams.set("state", params.state || "");
242
+ console.log("[PKCEProxy] Redirecting client directly to:", clientRedirect.toString());
243
+ return new Response(null, {
244
+ status: 302,
245
+ headers: { Location: clientRedirect.toString() },
246
+ });
247
+ }
167
248
  // Generate our own PKCE for upstream
168
249
  const pkce = this.generatePKCE();
169
250
  const transactionId = this.generateId();
@@ -179,7 +260,7 @@ export class PKCEOAuthProxy {
179
260
  expiresAt: new Date(Date.now() + 600 * 1000), // 10 minutes
180
261
  };
181
262
  this.transactions.set(transactionId, transaction);
182
- this.saveTransactionsToDisk(); // Persist to survive restarts
263
+ await this.saveTransactionsToDisk(); // Persist to survive restarts (async now)
183
264
  console.log("[PKCEProxy] Created transaction:", transactionId);
184
265
  // Build upstream authorization URL
185
266
  const authUrl = new URL(this.config.authorizationEndpoint);
@@ -228,7 +309,7 @@ export class PKCEOAuthProxy {
228
309
  }
229
310
  if (transaction.expiresAt < new Date()) {
230
311
  this.transactions.delete(state);
231
- this.saveTransactionsToDisk();
312
+ await this.saveTransactionsToDisk();
232
313
  console.error("[PKCEProxy] Transaction expired, created:", transaction.createdAt, "expired:", transaction.expiresAt);
233
314
  return new Response(JSON.stringify({ error: "transaction_expired" }), {
234
315
  status: 400,
@@ -265,14 +346,14 @@ export class PKCEOAuthProxy {
265
346
  refreshToken: tokens.refresh_token,
266
347
  expiresAt: new Date(Date.now() + (tokens.expires_in || 3600) * 1000),
267
348
  });
268
- this.saveTokensToDisk(); // Persist to disk
349
+ await this.saveTokensToDisk(); // Persist to disk (async now)
269
350
  // Redirect back to client with our proxy token
270
351
  const clientRedirect = new URL(transaction.clientCallbackUrl);
271
352
  clientRedirect.searchParams.set("code", proxyToken);
272
353
  clientRedirect.searchParams.set("state", transaction.clientState);
273
354
  // Clean up transaction
274
355
  this.transactions.delete(state);
275
- this.saveTransactionsToDisk();
356
+ await this.saveTransactionsToDisk();
276
357
  console.log("[PKCEProxy] Redirecting to client:", clientRedirect.toString());
277
358
  return new Response(null, {
278
359
  status: 302,
@@ -285,11 +366,12 @@ export class PKCEOAuthProxy {
285
366
  if (!params.code) {
286
367
  throw new OAuthProxyError("invalid_request", "Missing authorization code", 400);
287
368
  }
369
+ console.log(`[PKCEProxy] Exchange requested for code: ${params.code.slice(0, 8)}... (connections: ${this.activeConnections})`);
288
370
  // Check if this code was recently exchanged (retry tolerance)
289
371
  // This allows mcp-remote to retry if the first request timed out but actually succeeded
290
372
  const recentExchange = this.recentlyExchangedCodes.get(params.code);
291
373
  if (recentExchange && recentExchange.expiresAt > new Date()) {
292
- console.log("[PKCEProxy] Returning cached token for retry of code:", params.code.slice(0, 8) + "...");
374
+ console.log(`[PKCEProxy] Returning cached token for retry of code: ${params.code.slice(0, 8)}...`);
293
375
  const tokenData = this.tokens.get(recentExchange.accessToken);
294
376
  if (tokenData) {
295
377
  const expiresIn = Math.floor((tokenData.expiresAt.getTime() - Date.now()) / 1000);
@@ -302,24 +384,26 @@ export class PKCEOAuthProxy {
302
384
  }
303
385
  const tokenData = this.tokens.get(params.code);
304
386
  if (!tokenData) {
305
- console.error("[PKCEProxy] Token not found for code:", params.code);
306
- console.error("[PKCEProxy] Available tokens:", Array.from(this.tokens.keys()).map(k => k.slice(0, 8) + "..."));
307
- console.error("[PKCEProxy] Recently exchanged codes:", Array.from(this.recentlyExchangedCodes.keys()).map(k => k.slice(0, 8) + "..."));
387
+ console.error(`[PKCEProxy] Token not found for code: ${params.code}`);
388
+ console.error(`[PKCEProxy] Available tokens:`, Array.from(this.tokens.keys()).map(k => k.slice(0, 8) + "..."));
389
+ console.error(`[PKCEProxy] Recently exchanged codes:`, Array.from(this.recentlyExchangedCodes.keys()).map(k => k.slice(0, 8) + "..."));
308
390
  throw new OAuthProxyError("invalid_grant", "Invalid or expired authorization code", 400);
309
391
  }
310
392
  // Remove the code but keep track of it for retry tolerance (30 second window)
393
+ // Mark as exchanged BEFORE saving to disk to prevent race conditions
311
394
  this.tokens.delete(params.code);
312
395
  // Generate a new access token for the client
313
396
  const accessToken = this.generateId();
314
397
  this.tokens.set(accessToken, tokenData);
315
- this.saveTokensToDisk(); // Persist to disk
316
- // Store the exchange for retry tolerance (30 seconds)
398
+ // Store the exchange for retry tolerance (30 seconds) - mark as exchanged
317
399
  this.recentlyExchangedCodes.set(params.code, {
318
400
  accessToken,
319
401
  expiresAt: new Date(Date.now() + 30 * 1000),
320
402
  });
403
+ // Now save to disk
404
+ await this.saveTokensToDisk();
321
405
  const expiresIn = Math.floor((tokenData.expiresAt.getTime() - Date.now()) / 1000);
322
- console.log("[PKCEProxy] Issuing access token, expires in:", expiresIn, "seconds");
406
+ console.log(`[PKCEProxy] Issued access token (expires in ${expiresIn}s) for code: ${params.code.slice(0, 8)}...`);
323
407
  return {
324
408
  access_token: accessToken,
325
409
  token_type: "Bearer",
@@ -349,9 +433,17 @@ export class PKCEOAuthProxy {
349
433
  };
350
434
  }
351
435
  // Load upstream tokens for a given proxy token
352
- loadUpstreamTokens(proxyToken) {
436
+ async loadUpstreamTokens(proxyToken) {
353
437
  const data = this.tokens.get(proxyToken);
354
438
  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
+ const validToken = this.getFirstValidToken();
443
+ if (validToken) {
444
+ console.warn("[PKCEProxy] Stale token presented, mapping to current valid token:", proxyToken.slice(0, 8) + "...");
445
+ return validToken;
446
+ }
355
447
  console.warn("[PKCEProxy] Token not found:", proxyToken.slice(0, 8) + "...");
356
448
  console.warn("[PKCEProxy] Total tokens in store:", this.tokens.size);
357
449
  return null;
@@ -360,7 +452,7 @@ export class PKCEOAuthProxy {
360
452
  if (data.expiresAt < now) {
361
453
  console.warn("[PKCEProxy] Token expired:", proxyToken.slice(0, 8) + "...", "expired at:", data.expiresAt, "now:", now);
362
454
  this.tokens.delete(proxyToken);
363
- this.saveTokensToDisk();
455
+ await this.saveTokensToDisk();
364
456
  return null;
365
457
  }
366
458
  const timeRemaining = Math.floor((data.expiresAt.getTime() - now.getTime()) / 1000);
@@ -380,8 +472,8 @@ export class PKCEOAuthProxy {
380
472
  return null;
381
473
  }
382
474
  // Cleanup expired transactions, tokens, and retry cache
383
- startCleanup() {
384
- this.cleanupInterval = setInterval(() => {
475
+ async startCleanup() {
476
+ const cleanup = async () => {
385
477
  const now = new Date();
386
478
  let tokensChanged = false;
387
479
  let transactionsChanged = false;
@@ -405,12 +497,15 @@ export class PKCEOAuthProxy {
405
497
  }
406
498
  }
407
499
  if (tokensChanged) {
408
- this.saveTokensToDisk();
500
+ await this.saveTokensToDisk();
409
501
  }
410
502
  if (transactionsChanged) {
411
- this.saveTransactionsToDisk();
503
+ await this.saveTransactionsToDisk();
412
504
  }
413
- }, 60000); // Every minute
505
+ };
506
+ this.cleanupInterval = setInterval(cleanup, 60000); // Every minute
507
+ // Run cleanup immediately on startup
508
+ await cleanup();
414
509
  }
415
510
  destroy() {
416
511
  if (this.cleanupInterval) {
package/dist/server.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * Reflect MCP Server Factory
3
3
  *
4
+ * Updated by Twice 🦸‍♂️
5
+ * Now handling multiple concurrent clients!
6
+ *
4
7
  * Creates and configures the FastMCP server with PKCE OAuth
5
8
  */
6
9
  export interface ServerConfig {
@@ -9,5 +12,11 @@ export interface ServerConfig {
9
12
  dbPath?: string;
10
13
  }
11
14
  export declare function startReflectMCPServer(config: ServerConfig): Promise<void>;
15
+ /**
16
+ * Start the Reflect MCP server in stdio mode.
17
+ * Used when an HTTP server is already running on the port (e.g. a second MCP client).
18
+ * Reads the cached OAuth token from disk instead of running the full OAuth flow.
19
+ */
20
+ export declare function startReflectMCPServerStdio(config: ServerConfig): Promise<void>;
12
21
  export { PKCEOAuthProxy } from "./pkcehandler.js";
13
22
  export * from "./utils.js";
package/dist/server.js CHANGED
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * Reflect MCP Server Factory
3
3
  *
4
+ * Updated by Twice 🦸‍♂️
5
+ * Now handling multiple concurrent clients!
6
+ *
4
7
  * Creates and configures the FastMCP server with PKCE OAuth
5
8
  */
6
9
  import { FastMCP } from "fastmcp";
@@ -44,7 +47,7 @@ export async function startReflectMCPServer(config) {
44
47
  }
45
48
  const token = authHeader.slice(7);
46
49
  try {
47
- const tokenData = pkceProxy.loadUpstreamTokens(token);
50
+ const tokenData = await pkceProxy.loadUpstreamTokens(token);
48
51
  if (!tokenData) {
49
52
  console.warn("[Auth] Token validation failed for:", token.slice(0, 8) + "... - triggering 401");
50
53
  // Throw Response to trigger re-authentication (per FastMCP docs)
@@ -85,6 +88,43 @@ export async function startReflectMCPServer(config) {
85
88
  transportType: "httpStream",
86
89
  });
87
90
  }
91
+ /**
92
+ * Start the Reflect MCP server in stdio mode.
93
+ * Used when an HTTP server is already running on the port (e.g. a second MCP client).
94
+ * Reads the cached OAuth token from disk instead of running the full OAuth flow.
95
+ */
96
+ export async function startReflectMCPServerStdio(config) {
97
+ const port = config.port || 3000;
98
+ const baseUrl = `http://localhost:${port}`;
99
+ // Instantiate proxy only to read tokens from disk — no HTTP server needed
100
+ const pkceProxy = new PKCEOAuthProxy({
101
+ baseUrl,
102
+ clientId: config.clientId,
103
+ authorizationEndpoint: "https://reflect.app/oauth",
104
+ tokenEndpoint: "https://reflect.app/api/oauth/token",
105
+ scopes: ["read:graph", "write:graph"],
106
+ });
107
+ const server = new FastMCP({
108
+ name: "Reflect MCP Server",
109
+ // For stdio, FastMCP calls authenticate(undefined). We load the token from disk.
110
+ authenticate: async (_request) => {
111
+ const tokenData = pkceProxy.getFirstValidToken();
112
+ if (!tokenData) {
113
+ console.error("[Auth] No valid token on disk. Connect via HTTP mode first to complete OAuth.");
114
+ throw new Error("No valid token. Please authenticate via HTTP mode first.");
115
+ }
116
+ const expiresIn = Math.floor((tokenData.expiresAt.getTime() - Date.now()) / 1000);
117
+ console.error("[Auth] Stdio mode: token loaded from disk, expires in:", expiresIn, "seconds");
118
+ return {
119
+ accessToken: tokenData.accessToken,
120
+ expiresIn,
121
+ };
122
+ },
123
+ version: "1.0.0",
124
+ });
125
+ registerTools(server, config.dbPath);
126
+ await server.start({ transportType: "stdio" });
127
+ }
88
128
  // Also export for programmatic use
89
129
  export { PKCEOAuthProxy } from "./pkcehandler.js";
90
130
  export * from "./utils.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reflect-mcp",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
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",