@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cachly-dev/mcp-server",
3
- "version": "0.5.1",
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
- ? '3. Call `smart_recall` with keywords for semantic context search'
3786
- : '3. Call `recall_context` with relevant key patterns like "arch:*" or "file:*"';
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 for meaning-based search`
3789
- : `Layer 3 (Autopilot): This file — upgrade to Speed/Business tier to unlock smart_recall`;
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 Memory — ${project_description}
3791
+ const copilotInstructions = `# cachly AI Brain — ${project_description}
3792
3792
 
3793
- ## 3-Layer AI Memory System
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
- - **Layer 1 — Storage:** cachly instance \`${inst.name}\` (${tier}) — persistent Valkey brain, survives restarts
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
- ## BEFORE every task — check memory first
3797
+ ## 🔴 MANDATORY RULES — Always enforced, no exceptions
3800
3798
 
3801
- 1. Call \`recall_best_solution\` with the task topic to get known solutions
3802
- - \`recall_best_solution("deploy:web")\` → returns working deploy command
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
- 2. Call \`recall_context\` with the area you're working in
3807
- - \`recall_context("arch:*")\` → cached architecture decisions
3808
- - \`recall_context("file:src/auth*")\` → cached file summaries
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
- ${smartRecallNote}
3811
- - \`smart_recall("how does authentication work")\` → returns cached auth notes
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
- 4. If nothing is found, proceed normally — then save (see below)
3871
+ ---
3814
3872
 
3815
- ## AFTER every non-trivial task — save to memory
3873
+ ## Task-type trigger table
3816
3874
 
3817
- 1. Call \`learn_from_attempts\` for deploy/debug/fix tasks:
3818
- - Always set outcome = "success" | "failure" | "partial"
3819
- - Always include what_failed — it prevents future wasted time
3820
- - 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")\`
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
- 2. Call \`remember_context\` for code/architecture findings:
3823
- - \`remember_context("arch:auth", "Uses Keycloak with JWT...", category="architecture")\`
3824
- - \`remember_context("file:src/index.ts", "Entry point, registers routes...", category="file_summary")\`
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: \`${instance_id}\`
3829
- - Instance name: ${inst.name}
3830
- - Tier: ${tier}
3831
- - ${layerNote}
3832
- - Embedding provider: ${providerArg}
3907
+ - **Instance ID:** \`${instance_id}\`
3908
+ - **Instance name:** ${inst.name}
3909
+ - **Tier:** ${tier}
3910
+ - **${layerNote}**
3911
+ - **Embedding provider:** ${providerArg}
3912
+
3913
+ ---
3833
3914
 
3834
- ## Quick reference
3915
+ ## How the 3-layer system works
3835
3916
 
3836
3917
  \`\`\`
3837
- recall_best_solution("topic") # before task
3838
- smart_recall("natural language") # find by meaning
3839
- learn_from_attempts(...) # after task
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
  });