@everstateai/mcp 1.3.12 → 1.3.13

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 CHANGED
@@ -178,15 +178,38 @@ npx @everstateai/mcp --version # Check installed version
178
178
  This proxy is intentionally minimal. It:
179
179
 
180
180
  1. **On startup**: Auto-detects your project from `.everstate.json` or API
181
- 2. **On tool list**: Fetches tool definitions from the Everstate API
182
- 3. **On tool call**: Forwards the call to the Everstate API with your projectId
183
- 4. **Returns**: Responses to Claude/Cursor/etc.
181
+ 2. **Auto-session**: First tool call auto-starts a session (no explicit `sync()` needed)
182
+ 3. **On tool list**: Fetches tool definitions from the Everstate API (5 core tools by default)
183
+ 4. **On tool call**: Forwards the call to the Everstate API with your projectId
184
+ 5. **Returns**: Responses to Claude/Cursor/etc.
184
185
 
185
186
  All business logic, tool schemas, and data operations live server-side. Benefits:
186
187
 
187
188
  - **Instant updates** - New features without npm updates
188
189
  - **Feature gating** - Tools enabled by your API key tier
189
190
  - **Security** - All authentication and metering server-side
191
+ - **Auto-session detection** - Proxy tracks idle time, auto-syncs after 5 minutes of inactivity
192
+
193
+ ## Core Tools (5)
194
+
195
+ | Tool | Purpose |
196
+ |------|---------|
197
+ | `sync` | Start session, load context (auto-called) |
198
+ | `done` | End session with summary (auto-called on session end) |
199
+ | `recall` | Search past sessions, decisions, gotchas |
200
+ | `log` | Record progress, decisions, gotchas, blockers |
201
+ | `everstate` | Meta-tool for 100+ actions via `namespace.action` |
202
+
203
+ ## Automatic Features
204
+
205
+ These work via hooks installed during setup:
206
+
207
+ | Feature | Hook | Description |
208
+ |---------|------|-------------|
209
+ | Auto-progress | PostToolUse (Edit/Write/Bash) | File edits and commands logged automatically |
210
+ | Gotcha warnings | PreToolUse (Edit/Write) | Relevant gotchas surface before editing flagged files |
211
+ | Task sync | PostToolUse (TodoWrite) | TodoWrite list syncs with dashboard |
212
+ | Auto-done | Stop/SessionEnd | Session auto-saved when conversation ends |
190
213
 
191
214
  ## Troubleshooting
192
215
 
package/dist/index.js CHANGED
@@ -135,6 +135,22 @@ async function runMcpServer() {
135
135
  const INITIAL_PROJECT_ID = process.env.EVERSTATE_PROJECT_ID;
136
136
  // Session state - persists projectId across tool calls within this proxy instance
137
137
  let sessionProjectId = INITIAL_PROJECT_ID;
138
+ // Auto-session state: track last tool call time and whether sync has been called
139
+ let lastToolCallTime = 0;
140
+ let sessionSynced = false;
141
+ const AUTO_SYNC_IDLE_MS = 5 * 60 * 1000; // 5 minutes
142
+ // Context window tracking
143
+ // Claude's context window is ~200K tokens (~800K chars).
144
+ // We track cumulative MCP response sizes as a proxy for context growth.
145
+ // MCP responses are typically the largest context consumers since user messages
146
+ // and Claude's own output are smaller relative to tool results.
147
+ const CONTEXT_WINDOW_CHARS = 800_000; // ~200K tokens * 4 chars/token
148
+ const CONTEXT_WARNING_THRESHOLD = 0.75; // 75%
149
+ const CONTEXT_CRITICAL_THRESHOLD = 0.90; // 90%
150
+ let cumulativeResponseChars = 0;
151
+ let toolCallCount = 0;
152
+ let contextWarningIssued = false;
153
+ let contextCriticalIssued = false;
138
154
  if (!API_KEY) {
139
155
  console.error("EVERSTATE_API_KEY environment variable is required");
140
156
  console.error("");
@@ -199,6 +215,236 @@ async function runMcpServer() {
199
215
  console.error("[Everstate] Could not auto-detect project:", error);
200
216
  }
201
217
  }
218
+ function readBatchConfig() {
219
+ let dir = process.cwd();
220
+ const root = path.parse(dir).root;
221
+ while (dir !== root) {
222
+ const configPath = path.join(dir, ".everstate.json");
223
+ if (fs.existsSync(configPath)) {
224
+ try {
225
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
226
+ if (config.batchSync) {
227
+ const interval = config.batchSync.intervalSeconds ?? 30;
228
+ const MAX_INTERVAL = 300; // 5 minutes max — prevents stale data loss on crash
229
+ return {
230
+ enabled: config.batchSync.enabled ?? false,
231
+ intervalSeconds: Math.min(Math.max(interval, 5), MAX_INTERVAL),
232
+ };
233
+ }
234
+ }
235
+ catch { /* continue */ }
236
+ }
237
+ dir = path.dirname(dir);
238
+ }
239
+ return { enabled: false, intervalSeconds: 30 };
240
+ }
241
+ const batchConfig = readBatchConfig();
242
+ const BUFFER_DIR = path.join(process.cwd(), ".claude");
243
+ const BUFFER_PATH = path.join(BUFFER_DIR, ".batch-buffer.json");
244
+ const MAX_RETRIES = 3;
245
+ const MAX_BUFFER_SIZE = 200;
246
+ const BUFFER_STALE_MS = 60 * 60 * 1000; // 1 hour
247
+ function emptyBuffer() {
248
+ return { version: 1, lastFlushAt: null, lastFlushResult: null, entries: [], failed: [] };
249
+ }
250
+ function readBuffer() {
251
+ try {
252
+ if (fs.existsSync(BUFFER_PATH)) {
253
+ const buf = JSON.parse(fs.readFileSync(BUFFER_PATH, "utf8"));
254
+ // Discard stale entries on startup
255
+ const cutoff = Date.now() - BUFFER_STALE_MS;
256
+ buf.entries = buf.entries.filter(e => new Date(e.queuedAt).getTime() > cutoff);
257
+ buf.failed = (buf.failed || []).slice(-50);
258
+ return buf;
259
+ }
260
+ }
261
+ catch { /* corrupted file, start fresh */ }
262
+ return emptyBuffer();
263
+ }
264
+ function writeBuffer(buffer) {
265
+ try {
266
+ if (!fs.existsSync(BUFFER_DIR))
267
+ fs.mkdirSync(BUFFER_DIR, { recursive: true });
268
+ const tmpPath = BUFFER_PATH + ".tmp";
269
+ fs.writeFileSync(tmpPath, JSON.stringify(buffer, null, 2));
270
+ fs.renameSync(tmpPath, BUFFER_PATH);
271
+ }
272
+ catch (err) {
273
+ console.error("[Everstate] Failed to write buffer:", err);
274
+ }
275
+ }
276
+ function addToBuffer(toolName, args) {
277
+ const buffer = readBuffer();
278
+ if (buffer.entries.length >= MAX_BUFFER_SIZE) {
279
+ // Drop oldest entries
280
+ const dropped = buffer.entries.splice(0, buffer.entries.length - MAX_BUFFER_SIZE + 1);
281
+ console.error(`[Everstate] Buffer full, dropped ${dropped.length} oldest entries`);
282
+ }
283
+ buffer.entries.push({
284
+ id: `buf_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
285
+ queuedAt: new Date().toISOString(),
286
+ toolName,
287
+ arguments: args,
288
+ retryCount: 0,
289
+ });
290
+ writeBuffer(buffer);
291
+ }
292
+ // Tools that MUST execute immediately (return data the agent needs)
293
+ const IMMEDIATE_TOOLS = new Set([
294
+ "sync", "done", "recall", "status",
295
+ "ask_user", "check_user_response",
296
+ "everstate_list_projects", "everstate_switch_project", "everstate_current_project",
297
+ "list_projects", "switch_project", "current_project",
298
+ ]);
299
+ // Meta-tool action prefixes that must execute immediately (reads/interactive)
300
+ const IMMEDIATE_META_PREFIXES = [
301
+ "help", "session.", "search.", "context.", "heal.", "quality.",
302
+ "google.", "inbound.", "files.", "git.", "team.",
303
+ "work.get_", "work.list_", "work.search_", "work.get_portfolio",
304
+ "decisions.list", "decisions.get",
305
+ "gotchas.list", "gotchas.get",
306
+ "admin.get_", "admin.list_", "admin.check_", "admin.validate_", "admin.export_",
307
+ "project.", "progress.list", "progress.get",
308
+ ];
309
+ function isBatchable(toolName, args) {
310
+ if (IMMEDIATE_TOOLS.has(toolName))
311
+ return false;
312
+ if (toolName === "everstate") {
313
+ const action = args.action || "";
314
+ for (const prefix of IMMEDIATE_META_PREFIXES) {
315
+ if (action === prefix || action.startsWith(prefix))
316
+ return false;
317
+ }
318
+ return true;
319
+ }
320
+ // Core tools: log is batchable, everything else is immediate by default
321
+ if (toolName === "log")
322
+ return true;
323
+ return false;
324
+ }
325
+ function generateSyntheticResponse(toolName, args) {
326
+ if (toolName === "log") {
327
+ const msg = args.message || "";
328
+ const type = args.type || "progress";
329
+ const labels = {
330
+ achievement: "Achievement logged",
331
+ progress: "Progress recorded",
332
+ note: "Note saved",
333
+ warning: "Warning recorded",
334
+ };
335
+ return `${labels[type] || "Logged"} (queued for sync): ${msg.substring(0, 120)}${msg.length > 120 ? "..." : ""}`;
336
+ }
337
+ if (toolName === "everstate") {
338
+ const action = args.action || "";
339
+ return `Queued for sync: ${action}`;
340
+ }
341
+ return `Queued for sync: ${toolName}`;
342
+ }
343
+ async function flushBuffer() {
344
+ const buffer = readBuffer();
345
+ if (buffer.entries.length === 0)
346
+ return;
347
+ const count = buffer.entries.length;
348
+ console.error(`[Everstate] Flushing ${count} buffered items...`);
349
+ let success = 0;
350
+ let failed = 0;
351
+ const remaining = [];
352
+ const failedEntries = [...(buffer.failed || [])];
353
+ // Try batch endpoint first
354
+ try {
355
+ const res = await fetch(`${API_BASE}/mcp/batch-execute`, {
356
+ method: "POST",
357
+ headers: {
358
+ Authorization: `Bearer ${API_KEY}`,
359
+ "Content-Type": "application/json",
360
+ },
361
+ body: JSON.stringify({
362
+ calls: buffer.entries.map(e => ({
363
+ name: e.toolName,
364
+ arguments: e.arguments,
365
+ })),
366
+ }),
367
+ });
368
+ if (res.ok) {
369
+ success = count;
370
+ }
371
+ else if (res.status === 404) {
372
+ // Batch endpoint not deployed yet — fall back to individual calls
373
+ for (const entry of buffer.entries) {
374
+ try {
375
+ const r = await fetch(`${API_BASE}/mcp/execute`, {
376
+ method: "POST",
377
+ headers: {
378
+ Authorization: `Bearer ${API_KEY}`,
379
+ "Content-Type": "application/json",
380
+ },
381
+ body: JSON.stringify({
382
+ name: entry.toolName,
383
+ arguments: entry.arguments,
384
+ }),
385
+ });
386
+ if (r.ok) {
387
+ success++;
388
+ }
389
+ else {
390
+ entry.retryCount++;
391
+ if (entry.retryCount >= MAX_RETRIES) {
392
+ failedEntries.push(entry);
393
+ failed++;
394
+ }
395
+ else {
396
+ remaining.push(entry);
397
+ }
398
+ }
399
+ }
400
+ catch {
401
+ entry.retryCount++;
402
+ if (entry.retryCount >= MAX_RETRIES) {
403
+ failedEntries.push(entry);
404
+ failed++;
405
+ }
406
+ else {
407
+ remaining.push(entry);
408
+ }
409
+ }
410
+ }
411
+ }
412
+ else {
413
+ // Server error — keep all entries for retry
414
+ for (const entry of buffer.entries) {
415
+ entry.retryCount++;
416
+ if (entry.retryCount >= MAX_RETRIES) {
417
+ failedEntries.push(entry);
418
+ failed++;
419
+ }
420
+ else {
421
+ remaining.push(entry);
422
+ }
423
+ }
424
+ }
425
+ }
426
+ catch {
427
+ // Network error — keep all entries for retry
428
+ for (const entry of buffer.entries) {
429
+ entry.retryCount++;
430
+ if (entry.retryCount >= MAX_RETRIES) {
431
+ failedEntries.push(entry);
432
+ failed++;
433
+ }
434
+ else {
435
+ remaining.push(entry);
436
+ }
437
+ }
438
+ }
439
+ buffer.entries = remaining;
440
+ buffer.failed = failedEntries.slice(-50);
441
+ buffer.lastFlushAt = new Date().toISOString();
442
+ buffer.lastFlushResult = { success, failed };
443
+ writeBuffer(buffer);
444
+ if (success > 0 || failed > 0) {
445
+ console.error(`[Everstate] Flush: ${success} sent, ${failed} failed, ${remaining.length} retrying`);
446
+ }
447
+ }
202
448
  // Create MCP server
203
449
  const server = new Server({ name: "everstate", version: "1.3.0" }, { capabilities: { tools: {} } });
204
450
  // Fetch tool definitions from cloud
@@ -228,6 +474,54 @@ async function runMcpServer() {
228
474
  if (sessionProjectId && !enrichedArgs.projectId && !hasProjectIdInParams) {
229
475
  enrichedArgs.projectId = sessionProjectId;
230
476
  }
477
+ // Auto-session: trigger sync if this is the first tool call or idle > 5 minutes
478
+ const now = Date.now();
479
+ const isIdle = lastToolCallTime > 0 && (now - lastToolCallTime) > AUTO_SYNC_IDLE_MS;
480
+ const needsSync = !sessionSynced || isIdle;
481
+ const isSyncCall = name === "sync" || name === "done";
482
+ // Flush buffer before auto-sync (idle means buffered items are sitting stale)
483
+ if (needsSync && isIdle && batchConfig.enabled) {
484
+ await flushBuffer();
485
+ }
486
+ if (needsSync && !isSyncCall && sessionProjectId) {
487
+ try {
488
+ console.error(`[Everstate] Auto-sync: ${!sessionSynced ? 'first tool call' : 'idle > 5min'}`);
489
+ await fetch(`${API_BASE}/mcp/execute`, {
490
+ method: "POST",
491
+ headers: {
492
+ Authorization: `Bearer ${API_KEY}`,
493
+ "Content-Type": "application/json",
494
+ },
495
+ body: JSON.stringify({
496
+ name: "sync",
497
+ arguments: { projectId: sessionProjectId, mode: "minimal" },
498
+ }),
499
+ });
500
+ sessionSynced = true;
501
+ }
502
+ catch {
503
+ // Non-critical: don't block tool call if auto-sync fails
504
+ console.error("[Everstate] Auto-sync failed, continuing...");
505
+ }
506
+ }
507
+ if (isSyncCall) {
508
+ sessionSynced = true;
509
+ }
510
+ lastToolCallTime = now;
511
+ // Force-flush buffer before done() so session summary reflects all progress
512
+ if (name === "done" && batchConfig.enabled) {
513
+ await flushBuffer();
514
+ }
515
+ // Batch sync: buffer batchable write operations instead of calling API immediately
516
+ if (batchConfig.enabled && isBatchable(name, enrichedArgs)) {
517
+ const syntheticResponse = generateSyntheticResponse(name, enrichedArgs);
518
+ addToBuffer(name, enrichedArgs);
519
+ const bufSize = readBuffer().entries.length;
520
+ console.error(`[Everstate] Batched: ${name} (buffer: ${bufSize} items)`);
521
+ return {
522
+ content: [{ type: "text", text: syntheticResponse }],
523
+ };
524
+ }
231
525
  const res = await fetch(`${API_BASE}/mcp/execute`, {
232
526
  method: "POST",
233
527
  headers: {
@@ -237,12 +531,84 @@ async function runMcpServer() {
237
531
  body: JSON.stringify({ name, arguments: enrichedArgs }),
238
532
  });
239
533
  if (!res.ok) {
534
+ // Read the response body for the actual error message
535
+ let errorDetail = `${res.status} ${res.statusText}`;
536
+ try {
537
+ const errorBody = await res.json();
538
+ const msg = errorBody?.content?.[0]?.text;
539
+ if (msg)
540
+ errorDetail = msg;
541
+ }
542
+ catch {
543
+ // Body not JSON, use status text
544
+ }
240
545
  return {
241
- content: [{ type: "text", text: `API error: ${res.status} ${res.statusText}` }],
546
+ content: [{ type: "text", text: errorDetail }],
242
547
  isError: true,
243
548
  };
244
549
  }
245
550
  const result = (await res.json());
551
+ // Track context growth from MCP responses
552
+ toolCallCount++;
553
+ const responseText = result.content?.map((c) => c.text || '').join('') || '';
554
+ cumulativeResponseChars += responseText.length;
555
+ // Also count the request args as they're in context too
556
+ const requestChars = JSON.stringify(enrichedArgs).length;
557
+ cumulativeResponseChars += requestChars;
558
+ const contextUsage = cumulativeResponseChars / CONTEXT_WINDOW_CHARS;
559
+ const contextPercent = Math.round(contextUsage * 100);
560
+ // Force-flush buffer when context is running low — session may end abruptly
561
+ if (contextUsage >= CONTEXT_WARNING_THRESHOLD && batchConfig.enabled) {
562
+ flushBuffer().catch(() => { }); // Non-blocking
563
+ }
564
+ // Append context warning to tool response
565
+ if (contextUsage >= CONTEXT_CRITICAL_THRESHOLD && !contextCriticalIssued) {
566
+ contextCriticalIssued = true;
567
+ const warning = `\n\n---\n` +
568
+ `**CONTEXT CRITICAL (${contextPercent}% estimated usage)**\n\n` +
569
+ `This session's context window is nearly full. You should:\n` +
570
+ `1. Call \`done\` with a detailed summary and next steps\n` +
571
+ `2. Tell the user to start a new session — Everstate will restore context automatically\n\n` +
572
+ `Continuing will lead to context compaction which loses conversation detail.\n` +
573
+ `---`;
574
+ if (result.content?.[0]?.text) {
575
+ result.content[0].text += warning;
576
+ }
577
+ }
578
+ else if (contextUsage >= CONTEXT_WARNING_THRESHOLD && !contextWarningIssued) {
579
+ contextWarningIssued = true;
580
+ const warning = `\n\n---\n` +
581
+ `**CONTEXT WARNING (${contextPercent}% estimated usage)**\n\n` +
582
+ `This session is consuming context quickly (~${toolCallCount} tool calls, ~${Math.round(cumulativeResponseChars / 4000)}K tokens estimated).\n` +
583
+ `Consider wrapping up soon. When ready:\n` +
584
+ `1. Call \`done\` with a summary of what was accomplished and what's next\n` +
585
+ `2. Suggest the user start a new session — Everstate will pick up where you left off\n` +
586
+ `---`;
587
+ if (result.content?.[0]?.text) {
588
+ result.content[0].text += warning;
589
+ }
590
+ // Log the warning to Everstate for analytics
591
+ try {
592
+ await fetch(`${API_BASE}/mcp/execute`, {
593
+ method: "POST",
594
+ headers: {
595
+ Authorization: `Bearer ${API_KEY}`,
596
+ "Content-Type": "application/json",
597
+ },
598
+ body: JSON.stringify({
599
+ name: "log",
600
+ arguments: {
601
+ projectId: sessionProjectId,
602
+ type: "warning",
603
+ message: `Context window at ${contextPercent}% estimated usage after ${toolCallCount} tool calls (~${Math.round(cumulativeResponseChars / 4)} tokens in MCP responses)`,
604
+ },
605
+ }),
606
+ });
607
+ }
608
+ catch {
609
+ // Non-critical
610
+ }
611
+ }
246
612
  // Update session state based on tool results
247
613
  if (name === "switch_project" || name === "everstate_switch_project") {
248
614
  const data = result.data || result.project;
@@ -258,20 +624,52 @@ async function runMcpServer() {
258
624
  // For sync calls, check for npm package updates and prepend notice
259
625
  if (name === "sync" && result.content?.[0]?.text) {
260
626
  try {
261
- const latestVersion = await checkNpmVersion();
262
- if (latestVersion) {
627
+ const versionInfo = await checkNpmVersion();
628
+ if (versionInfo) {
263
629
  const { getMcpProxyVersion: getVersion } = await import("./setup/version.js");
264
630
  const currentVersion = getVersion();
265
- if (currentVersion !== "0.0.0" && latestVersion !== currentVersion && compareVersionStrings(currentVersion, latestVersion) < 0) {
266
- const updateNotice = `> **Update available:** @everstateai/mcp ${currentVersion} → ${latestVersion}\n` +
267
- `> Run \`npx @everstateai/mcp update-all\` to update.\n\n`;
631
+ if (currentVersion !== "0.0.0" && versionInfo.version !== currentVersion && compareVersionStrings(currentVersion, versionInfo.version) < 0) {
632
+ // Build update notice with optional changelog
633
+ let updateNotice = `> **Update available:** @everstateai/mcp ${currentVersion} ${versionInfo.version}\n`;
634
+ if (versionInfo.changelog) {
635
+ updateNotice += `> _What's new: ${versionInfo.changelog}_\n`;
636
+ }
637
+ updateNotice += `> Auto-updating in background... (disable with \`everstate({ action: "admin.settings", params: { autoUpdate: false } })\`)\n\n`;
268
638
  result.content[0].text = updateNotice + result.content[0].text;
639
+ // Trigger auto-update in background (non-blocking)
640
+ autoUpdateNpmPackage(versionInfo.version).catch(() => { });
269
641
  }
270
642
  }
271
643
  }
272
644
  catch {
273
645
  // Non-critical, continue without update notice
274
646
  }
647
+ // Check for MEMORY.md coexistence and add note if found
648
+ try {
649
+ const fs = await import('fs');
650
+ const path = await import('path');
651
+ const projectDir = process.cwd();
652
+ const memoryPaths = [
653
+ path.join(projectDir, 'MEMORY.md'),
654
+ path.join(projectDir, '.claude', 'MEMORY.md'),
655
+ ];
656
+ const hasMemoryMd = memoryPaths.some(p => fs.existsSync(p));
657
+ if (hasMemoryMd && result.content?.[0]?.text) {
658
+ const memoryNote = `> **Note:** MEMORY.md detected. MEMORY.md = your personal notes (local). Everstate = shared project memory (cloud, cross-tool). They complement each other.\n\n`;
659
+ // Insert after any update notice but before the main content
660
+ const text = result.content[0].text;
661
+ const headerMatch = text.match(/^(# Everstate Session Sync\n\n)/);
662
+ if (headerMatch) {
663
+ result.content[0].text = headerMatch[1] + memoryNote + text.slice(headerMatch[1].length);
664
+ }
665
+ else {
666
+ result.content[0].text = memoryNote + text;
667
+ }
668
+ }
669
+ }
670
+ catch {
671
+ // Non-critical
672
+ }
275
673
  }
276
674
  return result;
277
675
  });
@@ -286,15 +684,27 @@ async function runMcpServer() {
286
684
  const transport = new StdioServerTransport();
287
685
  await server.connect(transport);
288
686
  console.error("Everstate MCP Proxy connected to", API_BASE);
687
+ // Start batch sync flush timer if enabled
688
+ if (batchConfig.enabled) {
689
+ console.error(`[Everstate] Batch sync: ON (flush every ${batchConfig.intervalSeconds}s)`);
690
+ setInterval(async () => {
691
+ try {
692
+ await flushBuffer();
693
+ }
694
+ catch (err) {
695
+ console.error("[Everstate] Flush error:", err);
696
+ }
697
+ }, batchConfig.intervalSeconds * 1000);
698
+ }
289
699
  }
290
- // Cache npm version check (once per MCP server lifetime, max every 4 hours)
291
- let cachedLatestVersion = null;
700
+ let cachedVersionInfo = null;
292
701
  let lastNpmCheck = 0;
293
702
  const NPM_CHECK_INTERVAL = 4 * 60 * 60 * 1000; // 4 hours
703
+ let autoUpdateInProgress = false;
294
704
  async function checkNpmVersion() {
295
705
  const now = Date.now();
296
- if (cachedLatestVersion && now - lastNpmCheck < NPM_CHECK_INTERVAL) {
297
- return cachedLatestVersion;
706
+ if (cachedVersionInfo && now - lastNpmCheck < NPM_CHECK_INTERVAL) {
707
+ return cachedVersionInfo;
298
708
  }
299
709
  try {
300
710
  const controller = new AbortController();
@@ -306,9 +716,9 @@ async function checkNpmVersion() {
306
716
  if (res.ok) {
307
717
  const data = (await res.json());
308
718
  if (data.version) {
309
- cachedLatestVersion = data.version;
719
+ cachedVersionInfo = { version: data.version, changelog: data.changelog };
310
720
  lastNpmCheck = now;
311
- return data.version;
721
+ return cachedVersionInfo;
312
722
  }
313
723
  }
314
724
  }
@@ -317,6 +727,46 @@ async function checkNpmVersion() {
317
727
  }
318
728
  return null;
319
729
  }
730
+ /**
731
+ * Auto-update the npm package if enabled and a new version is available.
732
+ * This runs in the background and doesn't block tool calls.
733
+ */
734
+ async function autoUpdateNpmPackage(latestVersion) {
735
+ if (autoUpdateInProgress)
736
+ return;
737
+ // Check if auto-update is disabled
738
+ const fs = await import('fs');
739
+ const path = await import('path');
740
+ const settingsPath = path.join(process.env.HOME || '', '.everstate', 'settings.json');
741
+ try {
742
+ if (fs.existsSync(settingsPath)) {
743
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
744
+ if (settings.autoUpdate === false) {
745
+ return; // User has disabled auto-updates
746
+ }
747
+ }
748
+ }
749
+ catch {
750
+ // If we can't read settings, proceed with auto-update
751
+ }
752
+ autoUpdateInProgress = true;
753
+ console.error(`[Everstate] Auto-updating to v${latestVersion}...`);
754
+ try {
755
+ const { execSync } = await import('child_process');
756
+ // Use npm to update the package globally or locally depending on how it's installed
757
+ execSync('npm install -g @everstateai/mcp@latest', {
758
+ stdio: 'pipe',
759
+ timeout: 60000, // 1 minute timeout
760
+ });
761
+ console.error(`[Everstate] Successfully updated to v${latestVersion}. Restart your AI tool to use the new version.`);
762
+ }
763
+ catch (error) {
764
+ console.error(`[Everstate] Auto-update failed: ${String(error)}. Run 'npx @everstateai/mcp update-all' manually.`);
765
+ }
766
+ finally {
767
+ autoUpdateInProgress = false;
768
+ }
769
+ }
320
770
  function compareVersionStrings(a, b) {
321
771
  const aParts = a.split(".").map(Number);
322
772
  const bParts = b.split(".").map(Number);