@cachly-dev/mcp-server 0.1.0 → 0.2.0
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/CHANGELOG.md +22 -2
- package/README.md +48 -65
- package/dist/index.js +196 -62
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +213 -65
package/src/index.ts
CHANGED
|
@@ -3228,95 +3228,243 @@ process.on('unhandledRejection', (reason) => {
|
|
|
3228
3228
|
process.stderr.write(`[cachly-mcp] unhandledRejection: ${msg}\n`);
|
|
3229
3229
|
});
|
|
3230
3230
|
|
|
3231
|
+
// ── CLI helpers ───────────────────────────────────────────────────────────────
|
|
3232
|
+
|
|
3233
|
+
const EDITOR_FILES: Record<string, string> = {
|
|
3234
|
+
claude: '.mcp.json',
|
|
3235
|
+
cursor: '.cursor/mcp.json',
|
|
3236
|
+
windsurf: '.windsurf/mcp.json',
|
|
3237
|
+
copilot: '.vscode/mcp.json',
|
|
3238
|
+
continue: '.continue/config.json',
|
|
3239
|
+
};
|
|
3240
|
+
|
|
3241
|
+
const CLAUDE_MD_MARKER_START = '<!-- cachly-brain-start -->';
|
|
3242
|
+
const CLAUDE_MD_MARKER_END = '<!-- cachly-brain-end -->';
|
|
3243
|
+
|
|
3244
|
+
function buildMcpConfig(apiKey: string, instanceId: string, editor: string): string {
|
|
3245
|
+
if (editor === 'continue') {
|
|
3246
|
+
return JSON.stringify({
|
|
3247
|
+
experimental: {
|
|
3248
|
+
modelContextProtocolServers: [{
|
|
3249
|
+
transport: { type: 'stdio', command: 'npx', args: ['-y', '@cachly-dev/mcp-server@latest'] },
|
|
3250
|
+
env: { CACHLY_API_URL: 'https://api.cachly.dev', CACHLY_JWT: apiKey, CACHLY_BRAIN_INSTANCE_ID: instanceId },
|
|
3251
|
+
}],
|
|
3252
|
+
},
|
|
3253
|
+
}, null, 2);
|
|
3254
|
+
}
|
|
3255
|
+
return JSON.stringify({
|
|
3256
|
+
mcpServers: {
|
|
3257
|
+
cachly: {
|
|
3258
|
+
command: 'npx',
|
|
3259
|
+
args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
3260
|
+
env: { CACHLY_API_URL: 'https://api.cachly.dev', CACHLY_JWT: apiKey, CACHLY_BRAIN_INSTANCE_ID: instanceId },
|
|
3261
|
+
},
|
|
3262
|
+
},
|
|
3263
|
+
}, null, 2);
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
function buildClaudeMdBlock(instanceId: string): string {
|
|
3267
|
+
return `${CLAUDE_MD_MARKER_START}
|
|
3268
|
+
## Cachly AI Brain
|
|
3269
|
+
|
|
3270
|
+
The Cachly MCP server is available as tool \`cachly\`.
|
|
3271
|
+
|
|
3272
|
+
### At the START of every session (one call):
|
|
3273
|
+
\`\`\`
|
|
3274
|
+
session_start(
|
|
3275
|
+
instance_id = "${instanceId}",
|
|
3276
|
+
focus = "<what you plan to work on today>",
|
|
3277
|
+
)
|
|
3278
|
+
\`\`\`
|
|
3279
|
+
|
|
3280
|
+
### At the END of every session:
|
|
3281
|
+
\`\`\`
|
|
3282
|
+
session_end(
|
|
3283
|
+
instance_id = "${instanceId}",
|
|
3284
|
+
summary = "What was accomplished (2-3 sentences)",
|
|
3285
|
+
files_changed = ["src/..."],
|
|
3286
|
+
lessons_learned = 1,
|
|
3287
|
+
)
|
|
3288
|
+
\`\`\`
|
|
3289
|
+
|
|
3290
|
+
### After fixing any bug or learning something new:
|
|
3291
|
+
\`\`\`
|
|
3292
|
+
learn_from_attempts(
|
|
3293
|
+
instance_id = "${instanceId}",
|
|
3294
|
+
topic = "category:keyword",
|
|
3295
|
+
outcome = "success",
|
|
3296
|
+
what_worked = "...",
|
|
3297
|
+
what_failed = "...",
|
|
3298
|
+
severity = "critical" | "major" | "minor",
|
|
3299
|
+
file_paths = ["path/to/file"],
|
|
3300
|
+
commands = ["the command that worked"],
|
|
3301
|
+
tags = ["tag1", "tag2"],
|
|
3302
|
+
)
|
|
3303
|
+
\`\`\`
|
|
3304
|
+
${CLAUDE_MD_MARKER_END}`;
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
async function writeClaudeMd(projectDir: string, instanceId: string): Promise<'written' | 'updated' | 'appended'> {
|
|
3308
|
+
const { writeFile, appendFile, readFile } = await import('node:fs/promises');
|
|
3309
|
+
const { existsSync } = await import('node:fs');
|
|
3310
|
+
const { resolve } = await import('node:path');
|
|
3311
|
+
|
|
3312
|
+
const claudeMdPath = resolve(projectDir, 'CLAUDE.md');
|
|
3313
|
+
const block = '\n' + buildClaudeMdBlock(instanceId) + '\n';
|
|
3314
|
+
|
|
3315
|
+
if (existsSync(claudeMdPath)) {
|
|
3316
|
+
const existing = await readFile(claudeMdPath, 'utf-8');
|
|
3317
|
+
if (existing.includes(CLAUDE_MD_MARKER_START)) {
|
|
3318
|
+
// Idempotent update: replace existing block (new instance-id, refreshed content)
|
|
3319
|
+
const updated = existing.replace(
|
|
3320
|
+
new RegExp(`${CLAUDE_MD_MARKER_START}[\\s\\S]*?${CLAUDE_MD_MARKER_END}`),
|
|
3321
|
+
buildClaudeMdBlock(instanceId)
|
|
3322
|
+
);
|
|
3323
|
+
await writeFile(claudeMdPath, updated, 'utf-8');
|
|
3324
|
+
return 'updated';
|
|
3325
|
+
}
|
|
3326
|
+
await appendFile(claudeMdPath, block, 'utf-8');
|
|
3327
|
+
return 'appended';
|
|
3328
|
+
}
|
|
3329
|
+
await writeFile(claudeMdPath, block.trimStart(), 'utf-8');
|
|
3330
|
+
return 'written';
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3231
3333
|
// ── CLI: cachly init ──────────────────────────────────────────────────────────
|
|
3232
3334
|
// Usage: npx @cachly-dev/mcp-server init --instance-id <id> --api-key <key> [--editor claude|cursor|windsurf|copilot|continue] [--project-dir /path]
|
|
3233
3335
|
|
|
3234
3336
|
if (process.argv[2] === 'init') {
|
|
3235
3337
|
const { writeFile, mkdir } = await import('node:fs/promises');
|
|
3236
|
-
const { existsSync } = await import('node:fs');
|
|
3237
3338
|
const { resolve, dirname } = await import('node:path');
|
|
3238
3339
|
|
|
3239
|
-
// Parse CLI flags
|
|
3240
3340
|
const argv = process.argv.slice(3);
|
|
3241
|
-
const flag = (name: string) => {
|
|
3242
|
-
const i = argv.indexOf(`--${name}`);
|
|
3243
|
-
return i !== -1 ? argv[i + 1] : undefined;
|
|
3244
|
-
};
|
|
3341
|
+
const flag = (name: string) => { const i = argv.indexOf(`--${name}`); return i !== -1 ? argv[i + 1] : undefined; };
|
|
3245
3342
|
|
|
3246
3343
|
const instanceId = flag('instance-id') ?? process.env.CACHLY_BRAIN_INSTANCE_ID;
|
|
3247
|
-
const apiKey
|
|
3248
|
-
const editor
|
|
3344
|
+
const apiKey = flag('api-key') ?? process.env.CACHLY_JWT;
|
|
3345
|
+
const editor = (flag('editor') ?? 'claude').toLowerCase();
|
|
3249
3346
|
const projectDir = resolve(flag('project-dir') ?? '.');
|
|
3250
3347
|
|
|
3251
3348
|
if (!instanceId || !apiKey) {
|
|
3252
3349
|
console.error('\nUsage: npx @cachly-dev/mcp-server@latest init --instance-id <uuid> --api-key <cky_live_...> [--editor claude|cursor|windsurf|copilot|continue] [--project-dir /path]\n');
|
|
3253
|
-
console.error('
|
|
3350
|
+
console.error('Or run interactively (no flags needed): npx @cachly-dev/mcp-server@latest setup\n');
|
|
3351
|
+
console.error('Get your credentials from: https://cachly.dev/setup-ai\n');
|
|
3254
3352
|
process.exit(1);
|
|
3255
3353
|
}
|
|
3256
3354
|
|
|
3257
|
-
const
|
|
3258
|
-
claude: '.mcp.json',
|
|
3259
|
-
cursor: '.cursor/mcp.json',
|
|
3260
|
-
windsurf: '.windsurf/mcp.json',
|
|
3261
|
-
copilot: '.vscode/mcp.json',
|
|
3262
|
-
continue: '.continue/config.json',
|
|
3263
|
-
};
|
|
3264
|
-
const configFile = editorFiles[editor] ?? '.mcp.json';
|
|
3355
|
+
const configFile = EDITOR_FILES[editor] ?? '.mcp.json';
|
|
3265
3356
|
const configPath = resolve(projectDir, configFile);
|
|
3357
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
3358
|
+
await writeFile(configPath, buildMcpConfig(apiKey, instanceId, editor), 'utf-8');
|
|
3359
|
+
console.log(`\n✅ Written: ${configFile}`);
|
|
3266
3360
|
|
|
3267
|
-
//
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3361
|
+
// Always write CLAUDE.md (idempotent — safe to run multiple times)
|
|
3362
|
+
const result = await writeClaudeMd(projectDir, instanceId);
|
|
3363
|
+
const action = result === 'updated' ? '✅ Updated' : result === 'appended' ? '✅ Appended to' : '✅ Written';
|
|
3364
|
+
console.log(`${action}: CLAUDE.md`);
|
|
3365
|
+
|
|
3366
|
+
console.log(`\n🧠 Cachly AI Brain configured for ${editor === 'claude' ? 'Claude Code' : editor}!`);
|
|
3367
|
+
console.log(` Restart your editor — the \`cachly\` MCP tools will appear.\n`);
|
|
3368
|
+
process.exit(0);
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
// ── CLI: cachly setup (interactive — no flags required) ───────────────────────
|
|
3372
|
+
// Usage: npx @cachly-dev/mcp-server setup
|
|
3373
|
+
|
|
3374
|
+
if (process.argv[2] === 'setup') {
|
|
3375
|
+
const { writeFile, mkdir } = await import('node:fs/promises');
|
|
3376
|
+
const { existsSync } = await import('node:fs');
|
|
3377
|
+
const { resolve, dirname } = await import('node:path');
|
|
3378
|
+
const { createInterface } = await import('node:readline');
|
|
3379
|
+
|
|
3380
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3381
|
+
const ask = (q: string): Promise<string> => new Promise(res => rl.question(q, ans => res(ans.trim())));
|
|
3382
|
+
|
|
3383
|
+
console.log('\n🧠 cachly AI Brain — Interactive Setup');
|
|
3384
|
+
console.log('────────────────────────────────────────\n');
|
|
3385
|
+
|
|
3386
|
+
// ── Step 1: API token ──────────────────────────────────────────────────────
|
|
3387
|
+
let token = process.env.CACHLY_JWT ?? '';
|
|
3388
|
+
if (token) {
|
|
3389
|
+
console.log('✓ Using token from CACHLY_JWT env var\n');
|
|
3282
3390
|
} else {
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
3288
|
-
env: {
|
|
3289
|
-
CACHLY_API_URL: 'https://api.cachly.dev',
|
|
3290
|
-
CACHLY_JWT: apiKey,
|
|
3291
|
-
CACHLY_BRAIN_INSTANCE_ID: instanceId,
|
|
3292
|
-
},
|
|
3293
|
-
},
|
|
3294
|
-
},
|
|
3295
|
-
}, null, 2);
|
|
3391
|
+
console.log('Step 1: Get your API token at https://cachly.dev/setup-ai\n');
|
|
3392
|
+
token = await ask(' Paste API token (cky_live_...): ');
|
|
3393
|
+
if (!token) { console.error('\nToken is required. Aborting.\n'); rl.close(); process.exit(1); }
|
|
3394
|
+
console.log('');
|
|
3296
3395
|
}
|
|
3297
3396
|
|
|
3298
|
-
//
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
} else {
|
|
3313
|
-
await writeFile(claudeMdPath, claudeMdSnippet.trimStart(), 'utf-8');
|
|
3314
|
-
console.log(`✅ Written: ${claudeMdPath}`);
|
|
3315
|
-
}
|
|
3397
|
+
// ── Step 2: Fetch & pick instance ─────────────────────────────────────────
|
|
3398
|
+
process.stdout.write('⏳ Fetching your instances...');
|
|
3399
|
+
let instances: Array<{ id: string; name: string; status: string; tier: string; region: string }> = [];
|
|
3400
|
+
try {
|
|
3401
|
+
const res = await fetch(`${API_URL}/api/v1/instances`, {
|
|
3402
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
3403
|
+
});
|
|
3404
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
3405
|
+
const body = await res.json() as { data: typeof instances };
|
|
3406
|
+
instances = (body.data ?? []).filter(i => i.status === 'running');
|
|
3407
|
+
} catch (e) {
|
|
3408
|
+
console.error(`\n\nFailed to fetch instances: ${(e as Error).message}`);
|
|
3409
|
+
console.error('Check your token and try again.\n');
|
|
3410
|
+
rl.close(); process.exit(1);
|
|
3316
3411
|
}
|
|
3412
|
+
console.log(` found ${instances.length}\n`);
|
|
3317
3413
|
|
|
3318
|
-
|
|
3319
|
-
|
|
3414
|
+
if (instances.length === 0) {
|
|
3415
|
+
console.error('No running instances found. Create one at https://cachly.dev/instances\n');
|
|
3416
|
+
rl.close(); process.exit(1);
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3419
|
+
let instance = instances[0];
|
|
3420
|
+
if (instances.length > 1) {
|
|
3421
|
+
console.log('Step 2: Pick your Brain instance:\n');
|
|
3422
|
+
instances.forEach((inst, idx) =>
|
|
3423
|
+
console.log(` ${idx + 1}. ${inst.name.padEnd(24)} ${inst.tier.padEnd(10)} ${inst.region}`)
|
|
3424
|
+
);
|
|
3425
|
+
const raw = await ask(`\n Pick [1-${instances.length}] (Enter = 1): `);
|
|
3426
|
+
const idx = parseInt(raw || '1', 10) - 1;
|
|
3427
|
+
instance = instances[Math.max(0, Math.min(instances.length - 1, idx))];
|
|
3428
|
+
}
|
|
3429
|
+
console.log(`✓ Instance: ${instance.name} (${instance.id.slice(0, 8)}…)\n`);
|
|
3430
|
+
|
|
3431
|
+
// ── Step 3: Detect editors ────────────────────────────────────────────────
|
|
3432
|
+
const cwd = process.cwd();
|
|
3433
|
+
const detected: string[] = [];
|
|
3434
|
+
// Claude Code: always include (CLAUDE.md is universal)
|
|
3435
|
+
detected.push('claude');
|
|
3436
|
+
if (existsSync(resolve(cwd, '.cursor'))) detected.push('cursor');
|
|
3437
|
+
if (existsSync(resolve(cwd, '.windsurf'))) detected.push('windsurf');
|
|
3438
|
+
if (existsSync(resolve(cwd, '.vscode'))) detected.push('copilot');
|
|
3439
|
+
if (existsSync(resolve(cwd, '.continue'))) detected.push('continue');
|
|
3440
|
+
|
|
3441
|
+
const editorLabel = (e: string) =>
|
|
3442
|
+
({ claude: 'Claude Code', cursor: 'Cursor', windsurf: 'Windsurf', copilot: 'GitHub Copilot', continue: 'Continue.dev' })[e] ?? e;
|
|
3443
|
+
|
|
3444
|
+
console.log(`Step 3: Detected editors: ${detected.map(editorLabel).join(', ')}`);
|
|
3445
|
+
const editorsRaw = await ask(` Configure for which? [all / ${detected.join('/')}] (Enter = all): `);
|
|
3446
|
+
const editorsToSetup = !editorsRaw || editorsRaw.toLowerCase() === 'all'
|
|
3447
|
+
? detected
|
|
3448
|
+
: editorsRaw.split(/[,\s]+/).map(s => s.toLowerCase()).filter(s => EDITOR_FILES[s]);
|
|
3449
|
+
console.log('');
|
|
3450
|
+
|
|
3451
|
+
// ── Step 4: Write editor configs ──────────────────────────────────────────
|
|
3452
|
+
for (const editor of editorsToSetup) {
|
|
3453
|
+
const configFile = EDITOR_FILES[editor] ?? '.mcp.json';
|
|
3454
|
+
const configPath = resolve(cwd, configFile);
|
|
3455
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
3456
|
+
await writeFile(configPath, buildMcpConfig(token, instance.id, editor), 'utf-8');
|
|
3457
|
+
console.log(`✅ Written: ${configFile}`);
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
// ── Step 5: CLAUDE.md (always — idempotent) ───────────────────────────────
|
|
3461
|
+
const mdResult = await writeClaudeMd(cwd, instance.id);
|
|
3462
|
+
const mdLabel = mdResult === 'updated' ? '✅ Updated' : mdResult === 'appended' ? '✅ Appended to' : '✅ Written';
|
|
3463
|
+
console.log(`${mdLabel}: CLAUDE.md`);
|
|
3464
|
+
|
|
3465
|
+
rl.close();
|
|
3466
|
+
console.log(`\n🧠 Done! Restart your editor — the \`cachly\` MCP tools will appear.`);
|
|
3467
|
+
console.log(` Your AI now has persistent memory across every session.\n`);
|
|
3320
3468
|
process.exit(0);
|
|
3321
3469
|
}
|
|
3322
3470
|
|