@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 +26 -3
- package/dist/index.js +462 -12
- package/dist/index.js.map +1 -1
- package/dist/setup/auto-update.d.ts.map +1 -1
- package/dist/setup/auto-update.js +223 -0
- package/dist/setup/auto-update.js.map +1 -1
- package/dist/setup/hooks/templates.d.ts +18 -1
- package/dist/setup/hooks/templates.d.ts.map +1 -1
- package/dist/setup/hooks/templates.js +381 -41
- package/dist/setup/hooks/templates.js.map +1 -1
- package/dist/setup/types.d.ts +2 -0
- package/dist/setup/types.d.ts.map +1 -1
- package/dist/setup/types.js +3 -1
- package/dist/setup/types.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +98 -8
- package/dist/setup.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +503 -13
- package/src/setup/auto-update.ts +263 -0
- package/src/setup/hooks/templates.ts +398 -41
- package/src/setup/types.ts +3 -1
- package/src/setup.ts +113 -8
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. **
|
|
182
|
-
3. **On tool
|
|
183
|
-
4. **
|
|
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:
|
|
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
|
|
262
|
-
if (
|
|
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" &&
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
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 (
|
|
297
|
-
return
|
|
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
|
-
|
|
719
|
+
cachedVersionInfo = { version: data.version, changelog: data.changelog };
|
|
310
720
|
lastNpmCheck = now;
|
|
311
|
-
return
|
|
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);
|