@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cachly-dev/mcp-server",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "MCP server – manage cachly.dev instances from any AI assistant (Copilot, Claude, Cursor, Windsurf)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
12
|
+
"postinstall": "node scripts/postinstall.js",
|
|
12
13
|
"dev": "tsx src/index.ts",
|
|
13
14
|
"start": "node dist/index.js",
|
|
14
15
|
"test": "vitest run",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Shown once after: npm install @cachly-dev/mcp-server
|
|
3
|
+
|
|
4
|
+
const c = {
|
|
5
|
+
reset: "\x1b[0m",
|
|
6
|
+
bold: "\x1b[1m",
|
|
7
|
+
violet: "\x1b[35m",
|
|
8
|
+
green: "\x1b[32m",
|
|
9
|
+
cyan: "\x1b[36m",
|
|
10
|
+
dim: "\x1b[2m",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const line = `${c.dim}─────────────────────────────────────────────────────${c.reset}`;
|
|
14
|
+
|
|
15
|
+
console.log(`
|
|
16
|
+
${line}
|
|
17
|
+
${c.bold}${c.violet}🧠 Cachly Brain MCP Server${c.reset} ${c.dim}v${process.env.npm_package_version ?? ""}${c.reset}
|
|
18
|
+
|
|
19
|
+
${c.bold}Give your AI assistant persistent memory in 30 seconds:${c.reset}
|
|
20
|
+
|
|
21
|
+
${c.cyan}1.${c.reset} Get a free Brain instance at ${c.bold}cachly.dev/setup-ai${c.reset}
|
|
22
|
+
${c.cyan}2.${c.reset} Add to Claude Code / Cursor / Copilot:
|
|
23
|
+
${c.dim}npx @cachly-dev/mcp-server --key cky_live_... --instance <uuid>${c.reset}
|
|
24
|
+
|
|
25
|
+
${c.green}✅ Free tier · GDPR · German servers · No credit card${c.reset}
|
|
26
|
+
|
|
27
|
+
${line}`);
|
package/src/index.ts
CHANGED
|
@@ -3782,65 +3782,148 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
3782
3782
|
// Generate copilot-instructions.md content
|
|
3783
3783
|
const tier = inst.tier.toUpperCase();
|
|
3784
3784
|
const smartRecallNote = hasVector
|
|
3785
|
-
? '
|
|
3786
|
-
: '
|
|
3785
|
+
? '- `smart_recall("natural language query")` — semantic search (finds by meaning)'
|
|
3786
|
+
: '- `recall_context("arch:*")` — exact/glob key lookup (upgrade to Speed/Business for smart_recall)';
|
|
3787
3787
|
const layerNote = hasVector
|
|
3788
|
-
? `Layer 3 (Semantic): smart_recall uses pgvector HNSW on your ${tier} instance
|
|
3789
|
-
: `Layer 3 (Autopilot):
|
|
3788
|
+
? `Layer 3 (Semantic): smart_recall uses pgvector HNSW on your ${tier} instance`
|
|
3789
|
+
: `Layer 3 (Autopilot): this file — upgrade to Speed/Business tier to unlock smart_recall`;
|
|
3790
3790
|
|
|
3791
|
-
const copilotInstructions = `# cachly AI
|
|
3791
|
+
const copilotInstructions = `# cachly AI Brain — ${project_description}
|
|
3792
3792
|
|
|
3793
|
-
|
|
3793
|
+
> 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.
|
|
3794
3794
|
|
|
3795
|
-
|
|
3796
|
-
- **Layer 2 — Tools:** learn_from_attempts · recall_best_solution · smart_recall — the memory API
|
|
3797
|
-
- **Layer 3 — Autopilot (this file):** instructs the AI to run memory tools automatically
|
|
3795
|
+
---
|
|
3798
3796
|
|
|
3799
|
-
##
|
|
3797
|
+
## 🔴 MANDATORY RULES — Always enforced, no exceptions
|
|
3800
3798
|
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
- \`recall_best_solution("debug:redis-tls")\` → returns root cause + fix
|
|
3804
|
-
- If a solution exists, use it — do NOT re-discover from scratch
|
|
3799
|
+
### Rule 1 — Session Start (FIRST thing in every window)
|
|
3800
|
+
Call \`session_start\` **before reading any source files or making any changes**:
|
|
3805
3801
|
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3802
|
+
\`\`\`
|
|
3803
|
+
session_start(
|
|
3804
|
+
instance_id = "${instance_id}",
|
|
3805
|
+
focus = "<what you plan to work on today>",
|
|
3806
|
+
)
|
|
3807
|
+
\`\`\`
|
|
3808
|
+
|
|
3809
|
+
Returns: last session summary, handoff from previous window, recent lessons, open failures, brain health.
|
|
3810
|
+
|
|
3811
|
+
**If \`session_start\` is unavailable**, fall back:
|
|
3812
|
+
1. \`smart_recall("recent lessons <topic>")\`
|
|
3813
|
+
2. \`recall_best_solution("<topic>")\`
|
|
3814
|
+
|
|
3815
|
+
### Rule 2 — Learn After Every Fix/Deploy/Discovery (AUTOMATIC — no prompting needed)
|
|
3816
|
+
After **every** bug fix, deployment, failed attempt, or discovery — call \`learn_from_attempts\` immediately:
|
|
3817
|
+
|
|
3818
|
+
\`\`\`
|
|
3819
|
+
learn_from_attempts(
|
|
3820
|
+
instance_id = "${instance_id}",
|
|
3821
|
+
topic = "category:keyword", # deploy:web | fix:auth | debug:db | infra:docker
|
|
3822
|
+
outcome = "success" | "failure" | "partial",
|
|
3823
|
+
what_worked = "exact approach or command that worked",
|
|
3824
|
+
what_failed = "what did NOT work (prevents future wasted time)",
|
|
3825
|
+
severity = "critical" | "major" | "minor",
|
|
3826
|
+
commands = ["exact command that worked"],
|
|
3827
|
+
file_paths = ["path/to/changed/file"],
|
|
3828
|
+
)
|
|
3829
|
+
\`\`\`
|
|
3830
|
+
|
|
3831
|
+
**These events MUST automatically trigger \`learn_from_attempts\`:**
|
|
3832
|
+
- ✅ Successfully deployed → \`topic="deploy:<component>", outcome="success"\`
|
|
3833
|
+
- ✅ Fixed a bug → \`topic="fix:<description>", outcome="success"\`
|
|
3834
|
+
- ✅ Discovered root cause → \`topic="debug:<area>", outcome="partial"\`
|
|
3835
|
+
- ✅ Tried something that failed → \`topic="<area>", outcome="failure", what_failed="..."\`
|
|
3836
|
+
- ✅ Found a config quirk → \`topic="infra:<topic>", outcome="success"\`
|
|
3837
|
+
- ✅ Ran a working command → store it in \`commands=[...]\`
|
|
3838
|
+
|
|
3839
|
+
### Rule 3 — Session Handoff (when closing window or task list > 3 items)
|
|
3840
|
+
\`\`\`
|
|
3841
|
+
session_handoff(
|
|
3842
|
+
instance_id = "${instance_id}",
|
|
3843
|
+
completed_tasks = ["what was done"],
|
|
3844
|
+
remaining_tasks = ["what is still to do"],
|
|
3845
|
+
files_changed = [{"path": "src/...", "status": "modified", "description": "what changed"}],
|
|
3846
|
+
instructions = "IMPORTANT: what the next window MUST know to not break things",
|
|
3847
|
+
context_summary = "current state of the system",
|
|
3848
|
+
)
|
|
3849
|
+
\`\`\`
|
|
3850
|
+
|
|
3851
|
+
The next \`session_start\` automatically includes this handoff. The new window sees exactly what's done, what remains, and what NOT to break.
|
|
3852
|
+
|
|
3853
|
+
### Rule 4 — Session End (LAST thing before closing)
|
|
3854
|
+
\`\`\`
|
|
3855
|
+
session_end(
|
|
3856
|
+
instance_id = "${instance_id}",
|
|
3857
|
+
summary = "What was accomplished (2-3 sentences with specifics)",
|
|
3858
|
+
files_changed = ["list", "of", "changed", "files"],
|
|
3859
|
+
lessons_learned = <number of learn_from_attempts calls made>,
|
|
3860
|
+
)
|
|
3861
|
+
\`\`\`
|
|
3862
|
+
|
|
3863
|
+
> \`session_end\` **automatically extracts and stores lessons** from the summary text — no manual calls needed.
|
|
3809
3864
|
|
|
3810
|
-
|
|
3811
|
-
|
|
3865
|
+
### Rule 5 — Code Index Invalidation (AUTOMATIC)
|
|
3866
|
+
- \`index_project\` tracks MD5 hashes per file — **skips unchanged files automatically**
|
|
3867
|
+
- After code changes: next \`index_project\` run re-indexes only modified files
|
|
3868
|
+
- After delete/rename: run \`index_project\` again to sync
|
|
3869
|
+
- No manual invalidation needed
|
|
3812
3870
|
|
|
3813
|
-
|
|
3871
|
+
---
|
|
3814
3872
|
|
|
3815
|
-
##
|
|
3873
|
+
## Task-type trigger table
|
|
3816
3874
|
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3875
|
+
| You're about to... | Call BEFORE | Call AFTER |
|
|
3876
|
+
|---|---|---|
|
|
3877
|
+
| Deploy anything | \`recall_best_solution("deploy:<component>")\` | \`learn_from_attempts(topic="deploy:...")\` |
|
|
3878
|
+
| Fix a bug | \`recall_best_solution("fix:<area>")\` | \`learn_from_attempts(topic="fix:...")\` |
|
|
3879
|
+
| Add a feature | \`session_start(focus="feat:<area>")\` | \`learn_from_attempts(topic="feat:...")\` |
|
|
3880
|
+
| Infra/server work | \`recall_best_solution("infra:<topic>")\` | \`learn_from_attempts(topic="infra:...")\` |
|
|
3881
|
+
| Debug an issue | \`smart_recall("<error message or symptom>")\` | \`learn_from_attempts(topic="debug:...")\` |
|
|
3821
3882
|
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3883
|
+
---
|
|
3884
|
+
|
|
3885
|
+
## Available Brain Tools
|
|
3886
|
+
|
|
3887
|
+
| Tool | When to use |
|
|
3888
|
+
|------|-------------|
|
|
3889
|
+
| \`session_start\` | **FIRST** — mandatory at start of every session |
|
|
3890
|
+
| \`session_end\` | **LAST** — mandatory at end, auto-learns from summary |
|
|
3891
|
+
| \`session_handoff\` | When closing window with remaining tasks |
|
|
3892
|
+
| \`learn_from_attempts\` | **AUTOMATIC** after every fix/deploy/discovery |
|
|
3893
|
+
| \`recall_best_solution\` | Before any non-trivial task |
|
|
3894
|
+
| \`remember_context\` | After analyzing code — save findings for future sessions |
|
|
3895
|
+
${smartRecallNote ? `| \`smart_recall\` | Search brain by meaning/keywords |\n` : ''}\
|
|
3896
|
+
| \`recall_context\` | Get exact key (supports glob: \`arch:*\`, \`file:*\`) |
|
|
3897
|
+
| \`brain_search\` | BM25+ full-text search over all brain data |
|
|
3898
|
+
| \`auto_learn_session\` | Batch-learn from a list of observations (optional) |
|
|
3899
|
+
| \`index_project\` | Index source files (smart hash, skips unchanged files) |
|
|
3900
|
+
| \`list_remembered\` | See what's cached in the brain |
|
|
3901
|
+
| \`forget_context\` | Remove stale context |
|
|
3902
|
+
|
|
3903
|
+
---
|
|
3825
3904
|
|
|
3826
3905
|
## Instance Details
|
|
3827
3906
|
|
|
3828
|
-
- Instance ID
|
|
3829
|
-
- Instance name
|
|
3830
|
-
- Tier
|
|
3831
|
-
-
|
|
3832
|
-
- Embedding provider
|
|
3907
|
+
- **Instance ID:** \`${instance_id}\`
|
|
3908
|
+
- **Instance name:** ${inst.name}
|
|
3909
|
+
- **Tier:** ${tier}
|
|
3910
|
+
- **${layerNote}**
|
|
3911
|
+
- **Embedding provider:** ${providerArg}
|
|
3912
|
+
|
|
3913
|
+
---
|
|
3833
3914
|
|
|
3834
|
-
##
|
|
3915
|
+
## How the 3-layer system works
|
|
3835
3916
|
|
|
3836
3917
|
\`\`\`
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
remember_context("key", "content") # save analysis
|
|
3918
|
+
Layer 1 — Storage: Your cachly Valkey instance (${inst.name}) — persists forever
|
|
3919
|
+
Layer 2 — Tools: learn_from_attempts · recall_best_solution · brain_search · session_start/end
|
|
3920
|
+
Layer 3 — Autopilot: This file — AI reads it and runs tools automatically every session
|
|
3841
3921
|
\`\`\`
|
|
3922
|
+
|
|
3923
|
+
Result: Your AI **never solves the same problem twice** and always picks up exactly where it left off. 🚀
|
|
3842
3924
|
`;
|
|
3843
3925
|
|
|
3926
|
+
|
|
3844
3927
|
const lines: string[] = [
|
|
3845
3928
|
`🧠 **cachly AI Memory Setup Complete**`,
|
|
3846
3929
|
``,
|
|
@@ -4031,8 +4114,75 @@ const server = new Server(
|
|
|
4031
4114
|
|
|
4032
4115
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
4033
4116
|
|
|
4117
|
+
// ── Auto-session management ───────────────────────────────────────────────────
|
|
4118
|
+
// Transparently starts a Brain session on the first tool call of a process
|
|
4119
|
+
// and saves a session_end summary on SIGTERM/exit so users never have to call
|
|
4120
|
+
// session_start/session_end manually.
|
|
4121
|
+
|
|
4122
|
+
let _autoSessionInstanceId: string | null = null;
|
|
4123
|
+
let _autoSessionStarted = false;
|
|
4124
|
+
let _autoSessionToolCount = 0;
|
|
4125
|
+
|
|
4126
|
+
async function autoStartSession(instanceId: string): Promise<void> {
|
|
4127
|
+
if (_autoSessionStarted) return;
|
|
4128
|
+
_autoSessionStarted = true;
|
|
4129
|
+
_autoSessionInstanceId = instanceId;
|
|
4130
|
+
try {
|
|
4131
|
+
await handleTool('session_start', { instance_id: instanceId, focus: 'auto (MCP session)' });
|
|
4132
|
+
} catch { /* non-fatal — session tracking is a best-effort feature */ }
|
|
4133
|
+
|
|
4134
|
+
// Auto-index the project if it hasn't been indexed in the last 24h.
|
|
4135
|
+
// This is the main lever for growing token savings: more indexed code →
|
|
4136
|
+
// more semantic cache hits → fewer LLM calls for repeated questions.
|
|
4137
|
+
if (process.env.CACHLY_AUTO_INDEX !== 'false') {
|
|
4138
|
+
try {
|
|
4139
|
+
const redis = await getConnection(instanceId);
|
|
4140
|
+
const lastIndexed = await redis.get(`cachly:index:last_indexed:${instanceId}`);
|
|
4141
|
+
const staleMs = 24 * 60 * 60 * 1000;
|
|
4142
|
+
const isStale = !lastIndexed || (Date.now() - parseInt(lastIndexed, 10)) > staleMs;
|
|
4143
|
+
if (isStale) {
|
|
4144
|
+
// Mark as indexing now to prevent concurrent re-runs.
|
|
4145
|
+
await redis.set(`cachly:index:last_indexed:${instanceId}`, String(Date.now()), 'EX', 90000);
|
|
4146
|
+
// Run in background — don't block the first tool call.
|
|
4147
|
+
handleTool('index_project', {
|
|
4148
|
+
instance_id: instanceId,
|
|
4149
|
+
dir: process.cwd(),
|
|
4150
|
+
max_files: 150,
|
|
4151
|
+
ttl: 86400 * 7, // cache for 7 days
|
|
4152
|
+
namespace: 'cachly:sem:code',
|
|
4153
|
+
}).catch(() => undefined);
|
|
4154
|
+
}
|
|
4155
|
+
} catch { /* never block the session on indexing errors */ }
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
|
|
4159
|
+
async function autoEndSession(): Promise<void> {
|
|
4160
|
+
if (!_autoSessionStarted || !_autoSessionInstanceId) return;
|
|
4161
|
+
_autoSessionStarted = false;
|
|
4162
|
+
try {
|
|
4163
|
+
await handleTool('session_end', {
|
|
4164
|
+
instance_id: _autoSessionInstanceId,
|
|
4165
|
+
summary: `Auto-ended after ${_autoSessionToolCount} tool call(s). Session was started automatically.`,
|
|
4166
|
+
files_changed: [],
|
|
4167
|
+
lessons_learned: 0,
|
|
4168
|
+
});
|
|
4169
|
+
} catch { /* non-fatal */ }
|
|
4170
|
+
}
|
|
4171
|
+
|
|
4034
4172
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
4035
4173
|
const { name, arguments: args } = request.params;
|
|
4174
|
+
|
|
4175
|
+
// Auto-start brain session on first tool call that has an instance_id.
|
|
4176
|
+
// Skip session management tools to avoid recursion.
|
|
4177
|
+
const sessionTools = new Set(['session_start', 'session_end', 'auto_learn_session']);
|
|
4178
|
+
if (!sessionTools.has(name) && !_autoSessionStarted) {
|
|
4179
|
+
const instanceId = (args as Record<string, unknown>)?.instance_id as string | undefined;
|
|
4180
|
+
if (instanceId) {
|
|
4181
|
+
await autoStartSession(instanceId).catch(() => undefined);
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
if (!sessionTools.has(name)) _autoSessionToolCount++;
|
|
4185
|
+
|
|
4036
4186
|
try {
|
|
4037
4187
|
const text = await handleTool(name, (args ?? {}) as Record<string, unknown>);
|
|
4038
4188
|
return { content: [{ type: 'text', text }] };
|
|
@@ -4042,8 +4192,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4042
4192
|
}
|
|
4043
4193
|
});
|
|
4044
4194
|
|
|
4045
|
-
// Graceful shutdown – close all Redis connections
|
|
4195
|
+
// Graceful shutdown – close all Redis connections + auto-end brain session
|
|
4046
4196
|
process.on('SIGTERM', async () => {
|
|
4197
|
+
await autoEndSession().catch(() => undefined);
|
|
4198
|
+
for (const [, client] of pool) await client.quit().catch(() => undefined);
|
|
4199
|
+
process.exit(0);
|
|
4200
|
+
});
|
|
4201
|
+
|
|
4202
|
+
process.on('SIGINT', async () => {
|
|
4203
|
+
await autoEndSession().catch(() => undefined);
|
|
4047
4204
|
for (const [, client] of pool) await client.quit().catch(() => undefined);
|
|
4048
4205
|
process.exit(0);
|
|
4049
4206
|
});
|