@everstateai/mcp 1.3.12 → 1.3.14

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