@everstateai/mcp 1.3.2 → 1.3.3

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.
Files changed (70) hide show
  1. package/dist/index.d.ts +5 -3
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +32 -5
  4. package/dist/index.js.map +1 -1
  5. package/dist/setup/auto-update.d.ts +20 -0
  6. package/dist/setup/auto-update.d.ts.map +1 -0
  7. package/dist/setup/auto-update.js +295 -0
  8. package/dist/setup/auto-update.js.map +1 -0
  9. package/dist/setup/commands/doctor.d.ts +15 -0
  10. package/dist/setup/commands/doctor.d.ts.map +1 -0
  11. package/dist/setup/commands/doctor.js +264 -0
  12. package/dist/setup/commands/doctor.js.map +1 -0
  13. package/dist/setup/commands/repair.d.ts +14 -0
  14. package/dist/setup/commands/repair.d.ts.map +1 -0
  15. package/dist/setup/commands/repair.js +252 -0
  16. package/dist/setup/commands/repair.js.map +1 -0
  17. package/dist/setup/hooks/templates.d.ts +30 -0
  18. package/dist/setup/hooks/templates.d.ts.map +1 -0
  19. package/dist/setup/hooks/templates.js +237 -0
  20. package/dist/setup/hooks/templates.js.map +1 -0
  21. package/dist/setup/types.d.ts +120 -0
  22. package/dist/setup/types.d.ts.map +1 -0
  23. package/dist/setup/types.js +58 -0
  24. package/dist/setup/types.js.map +1 -0
  25. package/dist/setup/validators/api-key.d.ts +8 -0
  26. package/dist/setup/validators/api-key.d.ts.map +1 -0
  27. package/dist/setup/validators/api-key.js +233 -0
  28. package/dist/setup/validators/api-key.js.map +1 -0
  29. package/dist/setup/validators/connectivity.d.ts +8 -0
  30. package/dist/setup/validators/connectivity.d.ts.map +1 -0
  31. package/dist/setup/validators/connectivity.js +150 -0
  32. package/dist/setup/validators/connectivity.js.map +1 -0
  33. package/dist/setup/validators/hooks.d.ts +8 -0
  34. package/dist/setup/validators/hooks.d.ts.map +1 -0
  35. package/dist/setup/validators/hooks.js +431 -0
  36. package/dist/setup/validators/hooks.js.map +1 -0
  37. package/dist/setup/validators/index.d.ts +18 -0
  38. package/dist/setup/validators/index.d.ts.map +1 -0
  39. package/dist/setup/validators/index.js +123 -0
  40. package/dist/setup/validators/index.js.map +1 -0
  41. package/dist/setup/validators/mcp-config.d.ts +8 -0
  42. package/dist/setup/validators/mcp-config.d.ts.map +1 -0
  43. package/dist/setup/validators/mcp-config.js +333 -0
  44. package/dist/setup/validators/mcp-config.js.map +1 -0
  45. package/dist/setup/validators/project.d.ts +8 -0
  46. package/dist/setup/validators/project.d.ts.map +1 -0
  47. package/dist/setup/validators/project.js +202 -0
  48. package/dist/setup/validators/project.js.map +1 -0
  49. package/dist/setup/version.d.ts +58 -0
  50. package/dist/setup/version.d.ts.map +1 -0
  51. package/dist/setup/version.js +262 -0
  52. package/dist/setup/version.js.map +1 -0
  53. package/dist/setup.d.ts.map +1 -1
  54. package/dist/setup.js +207 -27
  55. package/dist/setup.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/index.ts +32 -5
  58. package/src/setup/auto-update.ts +328 -0
  59. package/src/setup/commands/doctor.ts +266 -0
  60. package/src/setup/commands/repair.ts +260 -0
  61. package/src/setup/hooks/templates.ts +239 -0
  62. package/src/setup/types.ts +199 -0
  63. package/src/setup/validators/api-key.ts +218 -0
  64. package/src/setup/validators/connectivity.ts +176 -0
  65. package/src/setup/validators/hooks.ts +447 -0
  66. package/src/setup/validators/index.ts +137 -0
  67. package/src/setup/validators/mcp-config.ts +329 -0
  68. package/src/setup/validators/project.ts +179 -0
  69. package/src/setup/version.ts +267 -0
  70. package/src/setup.ts +229 -27
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Auto-Update System
3
+ *
4
+ * Automatically updates hooks and configuration when the MCP proxy starts.
5
+ * Runs silently in the background without blocking the MCP server.
6
+ *
7
+ * Also checks the Everstate server for available updates (same as the dashboard UI).
8
+ */
9
+
10
+ import * as fs from 'fs';
11
+ import {
12
+ getEverstateDir,
13
+ getClaudeConfigPath,
14
+ HOOK_VERSIONS,
15
+ EVERSTATE_API_URL,
16
+ } from './types.js';
17
+ import {
18
+ readVersionFile,
19
+ writeVersionFile,
20
+ createVersionFile,
21
+ getMcpProxyVersion,
22
+ } from './version.js';
23
+ import { getSyncTodosHook } from './hooks/templates.js';
24
+
25
+ const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
26
+
27
+ interface ServerUpdate {
28
+ id: string;
29
+ version: string;
30
+ category: 'hooks' | 'mcp-proxy' | 'config';
31
+ title: string;
32
+ description: string;
33
+ breaking: boolean;
34
+ }
35
+
36
+ interface UpdateCheckResult {
37
+ needsUpdate: boolean;
38
+ updates: string[];
39
+ errors: string[];
40
+ }
41
+
42
+ /**
43
+ * Check and apply updates silently.
44
+ * Called on MCP server startup.
45
+ */
46
+ export async function checkAndApplyUpdates(): Promise<UpdateCheckResult> {
47
+ const result: UpdateCheckResult = {
48
+ needsUpdate: false,
49
+ updates: [],
50
+ errors: [],
51
+ };
52
+
53
+ try {
54
+ // Read or create version file
55
+ let versionFile = readVersionFile();
56
+ const isFirstRun = !versionFile;
57
+
58
+ if (!versionFile) {
59
+ versionFile = createVersionFile('npx');
60
+ }
61
+
62
+ // Check if we should skip this update check (rate limiting)
63
+ const lastCheck = versionFile.lastUpdateCheck;
64
+ if (lastCheck) {
65
+ const elapsed = Date.now() - new Date(lastCheck).getTime();
66
+ if (elapsed < UPDATE_CHECK_INTERVAL_MS) {
67
+ // Skip - checked recently
68
+ return result;
69
+ }
70
+ }
71
+
72
+ // Update the check timestamp
73
+ versionFile.lastUpdateCheck = new Date().toISOString();
74
+
75
+ // Check hook versions
76
+ const hooksDir = `${getEverstateDir()}/hooks`;
77
+ const syncTodosPath = `${hooksDir}/sync-todos.js`;
78
+
79
+ // Update sync-todos.js if needed
80
+ if (shouldUpdateHook(syncTodosPath, HOOK_VERSIONS.syncTodos, versionFile.components.hooks.syncTodos)) {
81
+ try {
82
+ await updateSyncTodosHook(hooksDir, syncTodosPath);
83
+ versionFile.components.hooks.syncTodos = HOOK_VERSIONS.syncTodos;
84
+ result.updates.push(`sync-todos.js updated to v${HOOK_VERSIONS.syncTodos}`);
85
+ result.needsUpdate = true;
86
+ } catch (error) {
87
+ result.errors.push(`Failed to update sync-todos.js: ${String(error)}`);
88
+ }
89
+ }
90
+
91
+ // Update TodoWrite hook registration if needed
92
+ const hookRegistrationUpdated = await ensureHookRegistration(syncTodosPath);
93
+ if (hookRegistrationUpdated) {
94
+ result.updates.push('TodoWrite hook registration updated');
95
+ result.needsUpdate = true;
96
+ }
97
+
98
+ // Update MCP proxy version in version file
99
+ const currentVersion = getMcpProxyVersion();
100
+ if (versionFile.components.mcpProxy !== currentVersion) {
101
+ versionFile.components.mcpProxy = currentVersion;
102
+ result.updates.push(`MCP proxy version recorded: ${currentVersion}`);
103
+ }
104
+
105
+ // Write updated version file
106
+ writeVersionFile(versionFile);
107
+
108
+ // Check for server-side updates (same as dashboard UI)
109
+ const apiKey = loadApiKey();
110
+ if (apiKey) {
111
+ try {
112
+ const serverUpdates = await checkServerUpdates(apiKey, versionFile.components);
113
+ if (serverUpdates.length > 0) {
114
+ console.error(`[Everstate] ${serverUpdates.length} update(s) available:`);
115
+ for (const update of serverUpdates) {
116
+ console.error(` - ${update.title} (${update.category} v${update.version})`);
117
+ }
118
+ console.error(` Run 'npx @everstateai/mcp doctor' to see details`);
119
+ }
120
+ } catch {
121
+ // Silently ignore server check failures
122
+ }
123
+ }
124
+
125
+ // Log updates to stderr (MCP servers use stderr for logging)
126
+ if (result.updates.length > 0) {
127
+ console.error(`[Everstate] Auto-updated: ${result.updates.join(', ')}`);
128
+ }
129
+
130
+ if (result.errors.length > 0) {
131
+ console.error(`[Everstate] Update errors: ${result.errors.join(', ')}`);
132
+ }
133
+
134
+ } catch (error) {
135
+ result.errors.push(`Auto-update check failed: ${String(error)}`);
136
+ }
137
+
138
+ return result;
139
+ }
140
+
141
+ /**
142
+ * Load API key from file
143
+ */
144
+ function loadApiKey(): string | null {
145
+ const keyPath = `${getEverstateDir()}/api-key`;
146
+ try {
147
+ if (fs.existsSync(keyPath)) {
148
+ return fs.readFileSync(keyPath, 'utf8').trim();
149
+ }
150
+ } catch {}
151
+ return null;
152
+ }
153
+
154
+ /**
155
+ * Check for updates from the Everstate server
156
+ */
157
+ async function checkServerUpdates(
158
+ apiKey: string,
159
+ installedComponents: { mcpProxy: string; hooks: { syncTodos: string } }
160
+ ): Promise<ServerUpdate[]> {
161
+ const response = await fetch(`${EVERSTATE_API_URL}/api/updates/check`, {
162
+ method: 'POST',
163
+ headers: {
164
+ 'Authorization': `Bearer ${apiKey}`,
165
+ 'Content-Type': 'application/json',
166
+ },
167
+ body: JSON.stringify({
168
+ installedVersions: {
169
+ mcpProxy: installedComponents.mcpProxy,
170
+ syncTodos: installedComponents.hooks.syncTodos,
171
+ },
172
+ }),
173
+ });
174
+
175
+ if (!response.ok) {
176
+ return [];
177
+ }
178
+
179
+ const data = await response.json() as { updates?: ServerUpdate[] };
180
+ return data.updates || [];
181
+ }
182
+
183
+ /**
184
+ * Determine if a hook needs updating
185
+ */
186
+ function shouldUpdateHook(
187
+ hookPath: string,
188
+ expectedVersion: string,
189
+ installedVersion: string
190
+ ): boolean {
191
+ // If file doesn't exist, needs update
192
+ if (!fs.existsSync(hookPath)) {
193
+ return true;
194
+ }
195
+
196
+ // Compare versions
197
+ if (compareVersions(installedVersion, expectedVersion) < 0) {
198
+ return true;
199
+ }
200
+
201
+ // Also check the file's embedded version
202
+ const fileVersion = extractHookVersion(hookPath);
203
+ if (!fileVersion || compareVersions(fileVersion, expectedVersion) < 0) {
204
+ return true;
205
+ }
206
+
207
+ return false;
208
+ }
209
+
210
+ /**
211
+ * Extract version from hook file
212
+ */
213
+ function extractHookVersion(filePath: string): string | null {
214
+ try {
215
+ const content = fs.readFileSync(filePath, 'utf8');
216
+ const versionMatch = content.match(/VERSION:\s*(\d+\.\d+\.\d+)/i);
217
+ if (versionMatch) {
218
+ return versionMatch[1];
219
+ }
220
+ // Legacy format
221
+ const hookVMatch = content.match(/Hook\s+v(\d+)/i);
222
+ if (hookVMatch) {
223
+ return `${hookVMatch[1]}.0.0`;
224
+ }
225
+ return null;
226
+ } catch {
227
+ return null;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Compare semver versions
233
+ */
234
+ function compareVersions(a: string, b: string): number {
235
+ const aParts = a.split('.').map(Number);
236
+ const bParts = b.split('.').map(Number);
237
+
238
+ for (let i = 0; i < 3; i++) {
239
+ const aNum = aParts[i] || 0;
240
+ const bNum = bParts[i] || 0;
241
+ if (aNum < bNum) return -1;
242
+ if (aNum > bNum) return 1;
243
+ }
244
+ return 0;
245
+ }
246
+
247
+ /**
248
+ * Update the sync-todos hook
249
+ */
250
+ async function updateSyncTodosHook(hooksDir: string, hookPath: string): Promise<void> {
251
+ // Ensure directory exists
252
+ if (!fs.existsSync(hooksDir)) {
253
+ fs.mkdirSync(hooksDir, { recursive: true });
254
+ }
255
+
256
+ // Get the new hook content
257
+ const hookContent = getSyncTodosHook();
258
+
259
+ // Backup existing hook if present
260
+ if (fs.existsSync(hookPath)) {
261
+ const backupPath = `${hookPath}.backup.${Date.now()}`;
262
+ fs.copyFileSync(hookPath, backupPath);
263
+ }
264
+
265
+ // Write new hook
266
+ fs.writeFileSync(hookPath, hookContent, { mode: 0o755 });
267
+ }
268
+
269
+ /**
270
+ * Ensure TodoWrite hook is registered in ~/.claude.json
271
+ */
272
+ async function ensureHookRegistration(syncTodosPath: string): Promise<boolean> {
273
+ const configPath = getClaudeConfigPath();
274
+
275
+ if (!fs.existsSync(configPath)) {
276
+ return false;
277
+ }
278
+
279
+ try {
280
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
281
+
282
+ if (!config.hooks) {
283
+ config.hooks = {};
284
+ }
285
+ if (!config.hooks.PostToolUse) {
286
+ config.hooks.PostToolUse = [];
287
+ }
288
+
289
+ const postToolUse = config.hooks.PostToolUse as Array<{
290
+ matcher: string;
291
+ hooks: Array<{ type?: string; command?: string; timeout?: number }>;
292
+ }>;
293
+
294
+ // Find or create TodoWrite entry
295
+ let todoWriteEntry = postToolUse.find((e) => e.matcher === 'TodoWrite');
296
+ if (!todoWriteEntry) {
297
+ todoWriteEntry = { matcher: 'TodoWrite', hooks: [] };
298
+ postToolUse.push(todoWriteEntry);
299
+ }
300
+
301
+ // Check if sync-todos hook is registered with correct path
302
+ const expectedCommand = `node ${syncTodosPath}`;
303
+ const existingHook = todoWriteEntry.hooks.find((h) =>
304
+ h.command?.includes('sync-todos')
305
+ );
306
+
307
+ if (!existingHook) {
308
+ // Add new hook
309
+ todoWriteEntry.hooks.push({
310
+ type: 'command',
311
+ command: expectedCommand,
312
+ timeout: 10,
313
+ });
314
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
315
+ return true;
316
+ } else if (existingHook.command !== expectedCommand) {
317
+ // Update existing hook with correct path
318
+ existingHook.command = expectedCommand;
319
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
320
+ return true;
321
+ }
322
+
323
+ return false;
324
+ } catch {
325
+ return false;
326
+ }
327
+ }
328
+
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Doctor Command
3
+ *
4
+ * Diagnoses Everstate installation issues.
5
+ *
6
+ * Usage:
7
+ * npx @everstateai/mcp doctor
8
+ * npx @everstateai/mcp doctor --json
9
+ * npx @everstateai/mcp doctor --verbose
10
+ * npx @everstateai/mcp doctor --component hooks
11
+ */
12
+
13
+ import * as fs from 'fs';
14
+ import {
15
+ ValidationContext,
16
+ ValidationSummary,
17
+ ValidationResult,
18
+ DoctorOptions,
19
+ c,
20
+ getEverstateDir,
21
+ } from '../types.js';
22
+ import { validateAll, validateComponent } from '../validators/index.js';
23
+ import { checkForUpdates, checkServerUpdates, ServerUpdate } from '../version.js';
24
+
25
+ export async function doctor(options: DoctorOptions = {}): Promise<void> {
26
+ const context = buildContext(options);
27
+
28
+ // Run validation
29
+ let summary: ValidationSummary;
30
+
31
+ if (options.component) {
32
+ summary = await validateComponent(options.component, context);
33
+ } else {
34
+ summary = await validateAll(context);
35
+ }
36
+
37
+ // Check for server updates (same as dashboard UI)
38
+ let serverUpdates: ServerUpdate[] = [];
39
+ if (context.apiKey) {
40
+ serverUpdates = await checkServerUpdates(context.apiKey);
41
+ }
42
+
43
+ // Output results
44
+ if (options.json) {
45
+ outputJson(summary, serverUpdates);
46
+ } else {
47
+ outputPretty(summary, options.verbose, serverUpdates);
48
+ }
49
+
50
+ // Exit code based on results
51
+ if (summary.failed > 0) {
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ function buildContext(options: DoctorOptions): ValidationContext {
57
+ const context: ValidationContext = {
58
+ projectDir: options.projectDir || process.cwd(),
59
+ verbose: options.verbose,
60
+ };
61
+
62
+ // Try to load API key for authenticated checks
63
+ const apiKeyPath = `${getEverstateDir()}/api-key`;
64
+ if (fs.existsSync(apiKeyPath)) {
65
+ try {
66
+ context.apiKey = fs.readFileSync(apiKeyPath, 'utf8').trim();
67
+ } catch {
68
+ // API key not readable - will be reported by validator
69
+ }
70
+ }
71
+
72
+ return context;
73
+ }
74
+
75
+ function outputJson(summary: ValidationSummary, serverUpdates: ServerUpdate[]): void {
76
+ console.log(JSON.stringify({ ...summary, serverUpdates }, null, 2));
77
+ }
78
+
79
+ function outputPretty(summary: ValidationSummary, verbose?: boolean, serverUpdates: ServerUpdate[] = []): void {
80
+ // Header
81
+ console.log('');
82
+ console.log(c('bold', '╔═══════════════════════════════════════════════════════════╗'));
83
+ console.log(c('bold', '║ Everstate Health Check ║'));
84
+ console.log(c('bold', '╚═══════════════════════════════════════════════════════════╝'));
85
+ console.log('');
86
+
87
+ // Group results by component
88
+ const grouped = new Map<string, ValidationResult[]>();
89
+ for (const result of summary.results) {
90
+ if (!grouped.has(result.component)) {
91
+ grouped.set(result.component, []);
92
+ }
93
+ grouped.get(result.component)!.push(result);
94
+ }
95
+
96
+ // Output each component
97
+ for (const [component, results] of grouped) {
98
+ console.log(c('bold', component));
99
+
100
+ for (const result of results) {
101
+ const icon = getStatusIcon(result.status);
102
+ const color = getStatusColor(result.status);
103
+ const message = result.message;
104
+
105
+ console.log(` ${icon} ${c(color, result.check)}: ${message}`);
106
+
107
+ // Show details in verbose mode
108
+ if (verbose && result.details) {
109
+ for (const [key, value] of Object.entries(result.details)) {
110
+ console.log(c('dim', ` ${key}: ${JSON.stringify(value)}`));
111
+ }
112
+ }
113
+
114
+ // Show repair action hint for failures
115
+ if (result.status === 'fail' && result.repairAction) {
116
+ console.log(c('dim', ` → ${result.repairAction.description}`));
117
+ }
118
+ }
119
+
120
+ console.log('');
121
+ }
122
+
123
+ // Check for available updates (local version checks)
124
+ const localUpdates = checkForUpdates();
125
+
126
+ // Show both local and server updates
127
+ const hasLocalUpdates = localUpdates.needsUpdate;
128
+ const hasServerUpdates = serverUpdates.length > 0;
129
+
130
+ if (hasLocalUpdates || hasServerUpdates) {
131
+ console.log(c('yellow', 'Available Updates:'));
132
+
133
+ // Local version file updates
134
+ if (hasLocalUpdates) {
135
+ for (const outdated of localUpdates.outdated) {
136
+ console.log(c('yellow', ` ↑ ${outdated}`));
137
+ }
138
+ }
139
+
140
+ // Server-announced updates (same as dashboard UI)
141
+ if (hasServerUpdates) {
142
+ for (const update of serverUpdates) {
143
+ const breakingFlag = update.breaking ? c('red', ' [BREAKING]') : '';
144
+ console.log(c('cyan', ` ★ ${update.title}`) + ` (${update.category} v${update.version})${breakingFlag}`);
145
+ if (verbose) {
146
+ console.log(c('dim', ` ${update.description}`));
147
+ }
148
+ }
149
+ }
150
+
151
+ console.log('');
152
+ }
153
+
154
+ // Summary line
155
+ const parts: string[] = [];
156
+
157
+ if (summary.passed > 0) {
158
+ parts.push(c('green', `${summary.passed} passed`));
159
+ }
160
+ if (summary.warnings > 0) {
161
+ parts.push(c('yellow', `${summary.warnings} warnings`));
162
+ }
163
+ if (summary.failed > 0) {
164
+ parts.push(c('red', `${summary.failed} failed`));
165
+ }
166
+
167
+ console.log(`Status: ${parts.join(', ')}`);
168
+
169
+ // Action hint
170
+ if (summary.failed > 0) {
171
+ console.log('');
172
+ console.log(c('cyan', 'Run `npx @everstateai/mcp repair` to fix issues'));
173
+ } else if (summary.warnings > 0) {
174
+ console.log('');
175
+ console.log(c('dim', 'Run `npx @everstateai/mcp repair --include-warnings` to address warnings'));
176
+ }
177
+
178
+ console.log('');
179
+ }
180
+
181
+ function getStatusIcon(status: string): string {
182
+ switch (status) {
183
+ case 'pass':
184
+ return c('green', '✓');
185
+ case 'warn':
186
+ return c('yellow', '⚠');
187
+ case 'fail':
188
+ return c('red', '✗');
189
+ default:
190
+ return '?';
191
+ }
192
+ }
193
+
194
+ function getStatusColor(status: string): keyof typeof import('../types').COLORS {
195
+ switch (status) {
196
+ case 'pass':
197
+ return 'green';
198
+ case 'warn':
199
+ return 'yellow';
200
+ case 'fail':
201
+ return 'red';
202
+ default:
203
+ return 'reset';
204
+ }
205
+ }
206
+
207
+ // CLI entry point
208
+ export async function doctorCli(args: string[]): Promise<void> {
209
+ const options: DoctorOptions = {};
210
+
211
+ for (let i = 0; i < args.length; i++) {
212
+ const arg = args[i];
213
+
214
+ switch (arg) {
215
+ case '--json':
216
+ options.json = true;
217
+ break;
218
+ case '--verbose':
219
+ case '-v':
220
+ options.verbose = true;
221
+ break;
222
+ case '--component':
223
+ case '-c':
224
+ options.component = args[++i];
225
+ break;
226
+ case '--project':
227
+ case '-p':
228
+ options.projectDir = args[++i];
229
+ break;
230
+ case '--help':
231
+ case '-h':
232
+ printHelp();
233
+ process.exit(0);
234
+ }
235
+ }
236
+
237
+ await doctor(options);
238
+ }
239
+
240
+ function printHelp(): void {
241
+ console.log(`
242
+ Everstate Doctor - Diagnose installation issues
243
+
244
+ Usage:
245
+ npx @everstateai/mcp doctor [options]
246
+
247
+ Options:
248
+ --json Output results as JSON
249
+ --verbose, -v Show additional details
250
+ --component, -c Check only a specific component
251
+ --project, -p Specify project directory
252
+ --help, -h Show this help
253
+
254
+ Components:
255
+ API Key API key file and format
256
+ MCP Configuration Claude Code and Desktop config
257
+ Hooks Hook files and registration
258
+ Project Project-level .everstate.json
259
+ Connectivity API server connectivity
260
+
261
+ Examples:
262
+ npx @everstateai/mcp doctor
263
+ npx @everstateai/mcp doctor --json
264
+ npx @everstateai/mcp doctor --component hooks --verbose
265
+ `);
266
+ }