@cachly-dev/mcp-server 0.5.1 → 0.5.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.
- package/README.md +82 -262
- package/dist/index.js +194 -39
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/scripts/postinstall.js +27 -0
- package/src/index.ts +196 -39
package/dist/index.js
CHANGED
|
@@ -3291,62 +3291,144 @@ async function handleTool(name, args) {
|
|
|
3291
3291
|
// Generate copilot-instructions.md content
|
|
3292
3292
|
const tier = inst.tier.toUpperCase();
|
|
3293
3293
|
const smartRecallNote = hasVector
|
|
3294
|
-
? '
|
|
3295
|
-
: '
|
|
3294
|
+
? '- `smart_recall("natural language query")` — semantic search (finds by meaning)'
|
|
3295
|
+
: '- `recall_context("arch:*")` — exact/glob key lookup (upgrade to Speed/Business for smart_recall)';
|
|
3296
3296
|
const layerNote = hasVector
|
|
3297
|
-
? `Layer 3 (Semantic): smart_recall uses pgvector HNSW on your ${tier} instance
|
|
3298
|
-
: `Layer 3 (Autopilot):
|
|
3299
|
-
const copilotInstructions = `# cachly AI
|
|
3297
|
+
? `Layer 3 (Semantic): smart_recall uses pgvector HNSW on your ${tier} instance`
|
|
3298
|
+
: `Layer 3 (Autopilot): this file — upgrade to Speed/Business tier to unlock smart_recall`;
|
|
3299
|
+
const copilotInstructions = `# cachly AI Brain — ${project_description}
|
|
3300
3300
|
|
|
3301
|
-
|
|
3301
|
+
> AI memory system powered by [cachly.dev](https://cachly.dev). Works with GitHub Copilot, Claude Code, Cursor, Windsurf, Continue.dev, and any MCP-compatible editor.
|
|
3302
3302
|
|
|
3303
|
-
|
|
3304
|
-
- **Layer 2 — Tools:** learn_from_attempts · recall_best_solution · smart_recall — the memory API
|
|
3305
|
-
- **Layer 3 — Autopilot (this file):** instructs the AI to run memory tools automatically
|
|
3303
|
+
---
|
|
3306
3304
|
|
|
3307
|
-
##
|
|
3305
|
+
## 🔴 MANDATORY RULES — Always enforced, no exceptions
|
|
3308
3306
|
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
- \`recall_best_solution("debug:redis-tls")\` → returns root cause + fix
|
|
3312
|
-
- If a solution exists, use it — do NOT re-discover from scratch
|
|
3307
|
+
### Rule 1 — Session Start (FIRST thing in every window)
|
|
3308
|
+
Call \`session_start\` **before reading any source files or making any changes**:
|
|
3313
3309
|
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3310
|
+
\`\`\`
|
|
3311
|
+
session_start(
|
|
3312
|
+
instance_id = "${instance_id}",
|
|
3313
|
+
focus = "<what you plan to work on today>",
|
|
3314
|
+
)
|
|
3315
|
+
\`\`\`
|
|
3316
|
+
|
|
3317
|
+
Returns: last session summary, handoff from previous window, recent lessons, open failures, brain health.
|
|
3318
|
+
|
|
3319
|
+
**If \`session_start\` is unavailable**, fall back:
|
|
3320
|
+
1. \`smart_recall("recent lessons <topic>")\`
|
|
3321
|
+
2. \`recall_best_solution("<topic>")\`
|
|
3322
|
+
|
|
3323
|
+
### Rule 2 — Learn After Every Fix/Deploy/Discovery (AUTOMATIC — no prompting needed)
|
|
3324
|
+
After **every** bug fix, deployment, failed attempt, or discovery — call \`learn_from_attempts\` immediately:
|
|
3325
|
+
|
|
3326
|
+
\`\`\`
|
|
3327
|
+
learn_from_attempts(
|
|
3328
|
+
instance_id = "${instance_id}",
|
|
3329
|
+
topic = "category:keyword", # deploy:web | fix:auth | debug:db | infra:docker
|
|
3330
|
+
outcome = "success" | "failure" | "partial",
|
|
3331
|
+
what_worked = "exact approach or command that worked",
|
|
3332
|
+
what_failed = "what did NOT work (prevents future wasted time)",
|
|
3333
|
+
severity = "critical" | "major" | "minor",
|
|
3334
|
+
commands = ["exact command that worked"],
|
|
3335
|
+
file_paths = ["path/to/changed/file"],
|
|
3336
|
+
)
|
|
3337
|
+
\`\`\`
|
|
3338
|
+
|
|
3339
|
+
**These events MUST automatically trigger \`learn_from_attempts\`:**
|
|
3340
|
+
- ✅ Successfully deployed → \`topic="deploy:<component>", outcome="success"\`
|
|
3341
|
+
- ✅ Fixed a bug → \`topic="fix:<description>", outcome="success"\`
|
|
3342
|
+
- ✅ Discovered root cause → \`topic="debug:<area>", outcome="partial"\`
|
|
3343
|
+
- ✅ Tried something that failed → \`topic="<area>", outcome="failure", what_failed="..."\`
|
|
3344
|
+
- ✅ Found a config quirk → \`topic="infra:<topic>", outcome="success"\`
|
|
3345
|
+
- ✅ Ran a working command → store it in \`commands=[...]\`
|
|
3346
|
+
|
|
3347
|
+
### Rule 3 — Session Handoff (when closing window or task list > 3 items)
|
|
3348
|
+
\`\`\`
|
|
3349
|
+
session_handoff(
|
|
3350
|
+
instance_id = "${instance_id}",
|
|
3351
|
+
completed_tasks = ["what was done"],
|
|
3352
|
+
remaining_tasks = ["what is still to do"],
|
|
3353
|
+
files_changed = [{"path": "src/...", "status": "modified", "description": "what changed"}],
|
|
3354
|
+
instructions = "IMPORTANT: what the next window MUST know to not break things",
|
|
3355
|
+
context_summary = "current state of the system",
|
|
3356
|
+
)
|
|
3357
|
+
\`\`\`
|
|
3358
|
+
|
|
3359
|
+
The next \`session_start\` automatically includes this handoff. The new window sees exactly what's done, what remains, and what NOT to break.
|
|
3317
3360
|
|
|
3318
|
-
|
|
3319
|
-
|
|
3361
|
+
### Rule 4 — Session End (LAST thing before closing)
|
|
3362
|
+
\`\`\`
|
|
3363
|
+
session_end(
|
|
3364
|
+
instance_id = "${instance_id}",
|
|
3365
|
+
summary = "What was accomplished (2-3 sentences with specifics)",
|
|
3366
|
+
files_changed = ["list", "of", "changed", "files"],
|
|
3367
|
+
lessons_learned = <number of learn_from_attempts calls made>,
|
|
3368
|
+
)
|
|
3369
|
+
\`\`\`
|
|
3320
3370
|
|
|
3321
|
-
|
|
3371
|
+
> \`session_end\` **automatically extracts and stores lessons** from the summary text — no manual calls needed.
|
|
3322
3372
|
|
|
3323
|
-
|
|
3373
|
+
### Rule 5 — Code Index Invalidation (AUTOMATIC)
|
|
3374
|
+
- \`index_project\` tracks MD5 hashes per file — **skips unchanged files automatically**
|
|
3375
|
+
- After code changes: next \`index_project\` run re-indexes only modified files
|
|
3376
|
+
- After delete/rename: run \`index_project\` again to sync
|
|
3377
|
+
- No manual invalidation needed
|
|
3324
3378
|
|
|
3325
|
-
|
|
3326
|
-
- Always set outcome = "success" | "failure" | "partial"
|
|
3327
|
-
- Always include what_failed — it prevents future wasted time
|
|
3328
|
-
- Example: \`learn_from_attempts(topic="deploy:api", outcome="success", what_worked="nohup docker compose up -d --build", what_failed="direct docker compose hangs on SSH timeout")\`
|
|
3379
|
+
---
|
|
3329
3380
|
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3381
|
+
## Task-type trigger table
|
|
3382
|
+
|
|
3383
|
+
| You're about to... | Call BEFORE | Call AFTER |
|
|
3384
|
+
|---|---|---|
|
|
3385
|
+
| Deploy anything | \`recall_best_solution("deploy:<component>")\` | \`learn_from_attempts(topic="deploy:...")\` |
|
|
3386
|
+
| Fix a bug | \`recall_best_solution("fix:<area>")\` | \`learn_from_attempts(topic="fix:...")\` |
|
|
3387
|
+
| Add a feature | \`session_start(focus="feat:<area>")\` | \`learn_from_attempts(topic="feat:...")\` |
|
|
3388
|
+
| Infra/server work | \`recall_best_solution("infra:<topic>")\` | \`learn_from_attempts(topic="infra:...")\` |
|
|
3389
|
+
| Debug an issue | \`smart_recall("<error message or symptom>")\` | \`learn_from_attempts(topic="debug:...")\` |
|
|
3390
|
+
|
|
3391
|
+
---
|
|
3392
|
+
|
|
3393
|
+
## Available Brain Tools
|
|
3394
|
+
|
|
3395
|
+
| Tool | When to use |
|
|
3396
|
+
|------|-------------|
|
|
3397
|
+
| \`session_start\` | **FIRST** — mandatory at start of every session |
|
|
3398
|
+
| \`session_end\` | **LAST** — mandatory at end, auto-learns from summary |
|
|
3399
|
+
| \`session_handoff\` | When closing window with remaining tasks |
|
|
3400
|
+
| \`learn_from_attempts\` | **AUTOMATIC** after every fix/deploy/discovery |
|
|
3401
|
+
| \`recall_best_solution\` | Before any non-trivial task |
|
|
3402
|
+
| \`remember_context\` | After analyzing code — save findings for future sessions |
|
|
3403
|
+
${smartRecallNote ? `| \`smart_recall\` | Search brain by meaning/keywords |\n` : ''}\
|
|
3404
|
+
| \`recall_context\` | Get exact key (supports glob: \`arch:*\`, \`file:*\`) |
|
|
3405
|
+
| \`brain_search\` | BM25+ full-text search over all brain data |
|
|
3406
|
+
| \`auto_learn_session\` | Batch-learn from a list of observations (optional) |
|
|
3407
|
+
| \`index_project\` | Index source files (smart hash, skips unchanged files) |
|
|
3408
|
+
| \`list_remembered\` | See what's cached in the brain |
|
|
3409
|
+
| \`forget_context\` | Remove stale context |
|
|
3410
|
+
|
|
3411
|
+
---
|
|
3333
3412
|
|
|
3334
3413
|
## Instance Details
|
|
3335
3414
|
|
|
3336
|
-
- Instance ID
|
|
3337
|
-
- Instance name
|
|
3338
|
-
- Tier
|
|
3339
|
-
-
|
|
3340
|
-
- Embedding provider
|
|
3415
|
+
- **Instance ID:** \`${instance_id}\`
|
|
3416
|
+
- **Instance name:** ${inst.name}
|
|
3417
|
+
- **Tier:** ${tier}
|
|
3418
|
+
- **${layerNote}**
|
|
3419
|
+
- **Embedding provider:** ${providerArg}
|
|
3420
|
+
|
|
3421
|
+
---
|
|
3341
3422
|
|
|
3342
|
-
##
|
|
3423
|
+
## How the 3-layer system works
|
|
3343
3424
|
|
|
3344
3425
|
\`\`\`
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
remember_context("key", "content") # save analysis
|
|
3426
|
+
Layer 1 — Storage: Your cachly Valkey instance (${inst.name}) — persists forever
|
|
3427
|
+
Layer 2 — Tools: learn_from_attempts · recall_best_solution · brain_search · session_start/end
|
|
3428
|
+
Layer 3 — Autopilot: This file — AI reads it and runs tools automatically every session
|
|
3349
3429
|
\`\`\`
|
|
3430
|
+
|
|
3431
|
+
Result: Your AI **never solves the same problem twice** and always picks up exactly where it left off. 🚀
|
|
3350
3432
|
`;
|
|
3351
3433
|
const lines = [
|
|
3352
3434
|
`🧠 **cachly AI Memory Setup Complete**`,
|
|
@@ -3524,8 +3606,74 @@ async function handleBulkLockStream(name, args) {
|
|
|
3524
3606
|
// ── Server setup ──────────────────────────────────────────────────────────────
|
|
3525
3607
|
const server = new Server({ name: 'cachly-mcp', version: '0.4.0' }, { capabilities: { tools: {} } });
|
|
3526
3608
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
3609
|
+
// ── Auto-session management ───────────────────────────────────────────────────
|
|
3610
|
+
// Transparently starts a Brain session on the first tool call of a process
|
|
3611
|
+
// and saves a session_end summary on SIGTERM/exit so users never have to call
|
|
3612
|
+
// session_start/session_end manually.
|
|
3613
|
+
let _autoSessionInstanceId = null;
|
|
3614
|
+
let _autoSessionStarted = false;
|
|
3615
|
+
let _autoSessionToolCount = 0;
|
|
3616
|
+
async function autoStartSession(instanceId) {
|
|
3617
|
+
if (_autoSessionStarted)
|
|
3618
|
+
return;
|
|
3619
|
+
_autoSessionStarted = true;
|
|
3620
|
+
_autoSessionInstanceId = instanceId;
|
|
3621
|
+
try {
|
|
3622
|
+
await handleTool('session_start', { instance_id: instanceId, focus: 'auto (MCP session)' });
|
|
3623
|
+
}
|
|
3624
|
+
catch { /* non-fatal — session tracking is a best-effort feature */ }
|
|
3625
|
+
// Auto-index the project if it hasn't been indexed in the last 24h.
|
|
3626
|
+
// This is the main lever for growing token savings: more indexed code →
|
|
3627
|
+
// more semantic cache hits → fewer LLM calls for repeated questions.
|
|
3628
|
+
if (process.env.CACHLY_AUTO_INDEX !== 'false') {
|
|
3629
|
+
try {
|
|
3630
|
+
const redis = await getConnection(instanceId);
|
|
3631
|
+
const lastIndexed = await redis.get(`cachly:index:last_indexed:${instanceId}`);
|
|
3632
|
+
const staleMs = 24 * 60 * 60 * 1000;
|
|
3633
|
+
const isStale = !lastIndexed || (Date.now() - parseInt(lastIndexed, 10)) > staleMs;
|
|
3634
|
+
if (isStale) {
|
|
3635
|
+
// Mark as indexing now to prevent concurrent re-runs.
|
|
3636
|
+
await redis.set(`cachly:index:last_indexed:${instanceId}`, String(Date.now()), 'EX', 90000);
|
|
3637
|
+
// Run in background — don't block the first tool call.
|
|
3638
|
+
handleTool('index_project', {
|
|
3639
|
+
instance_id: instanceId,
|
|
3640
|
+
dir: process.cwd(),
|
|
3641
|
+
max_files: 150,
|
|
3642
|
+
ttl: 86400 * 7, // cache for 7 days
|
|
3643
|
+
namespace: 'cachly:sem:code',
|
|
3644
|
+
}).catch(() => undefined);
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
catch { /* never block the session on indexing errors */ }
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
async function autoEndSession() {
|
|
3651
|
+
if (!_autoSessionStarted || !_autoSessionInstanceId)
|
|
3652
|
+
return;
|
|
3653
|
+
_autoSessionStarted = false;
|
|
3654
|
+
try {
|
|
3655
|
+
await handleTool('session_end', {
|
|
3656
|
+
instance_id: _autoSessionInstanceId,
|
|
3657
|
+
summary: `Auto-ended after ${_autoSessionToolCount} tool call(s). Session was started automatically.`,
|
|
3658
|
+
files_changed: [],
|
|
3659
|
+
lessons_learned: 0,
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3662
|
+
catch { /* non-fatal */ }
|
|
3663
|
+
}
|
|
3527
3664
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
3528
3665
|
const { name, arguments: args } = request.params;
|
|
3666
|
+
// Auto-start brain session on first tool call that has an instance_id.
|
|
3667
|
+
// Skip session management tools to avoid recursion.
|
|
3668
|
+
const sessionTools = new Set(['session_start', 'session_end', 'auto_learn_session']);
|
|
3669
|
+
if (!sessionTools.has(name) && !_autoSessionStarted) {
|
|
3670
|
+
const instanceId = args?.instance_id;
|
|
3671
|
+
if (instanceId) {
|
|
3672
|
+
await autoStartSession(instanceId).catch(() => undefined);
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
if (!sessionTools.has(name))
|
|
3676
|
+
_autoSessionToolCount++;
|
|
3529
3677
|
try {
|
|
3530
3678
|
const text = await handleTool(name, (args ?? {}));
|
|
3531
3679
|
return { content: [{ type: 'text', text }] };
|
|
@@ -3536,8 +3684,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3536
3684
|
throw new McpError(ErrorCode.InternalError, err.message);
|
|
3537
3685
|
}
|
|
3538
3686
|
});
|
|
3539
|
-
// Graceful shutdown – close all Redis connections
|
|
3687
|
+
// Graceful shutdown – close all Redis connections + auto-end brain session
|
|
3540
3688
|
process.on('SIGTERM', async () => {
|
|
3689
|
+
await autoEndSession().catch(() => undefined);
|
|
3690
|
+
for (const [, client] of pool)
|
|
3691
|
+
await client.quit().catch(() => undefined);
|
|
3692
|
+
process.exit(0);
|
|
3693
|
+
});
|
|
3694
|
+
process.on('SIGINT', async () => {
|
|
3695
|
+
await autoEndSession().catch(() => undefined);
|
|
3541
3696
|
for (const [, client] of pool)
|
|
3542
3697
|
await client.quit().catch(() => undefined);
|
|
3543
3698
|
process.exit(0);
|