@cachly-dev/mcp-server 0.5.39 → 0.5.41
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 +148 -147
- package/dist/index.js +88 -51
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +57 -24
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cachly-dev/mcp-server",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.41",
|
|
4
4
|
"mcpName": "io.github.cachly-dev/mcp-server",
|
|
5
|
-
"description": "AI memory
|
|
5
|
+
"description": "Claude forgets. cachly doesn't. Persistent AI memory for Claude Code, Cursor & Copilot — one command setup, zero copy-paste.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"cachly-mcp": "./dist/index.js"
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
4
4
|
import { join, relative, extname } from 'node:path';
|
|
5
5
|
/**
|
|
6
|
-
* cachly MCP Server v0.5.
|
|
6
|
+
* cachly MCP Server v0.5.41
|
|
7
7
|
*
|
|
8
8
|
* Exposes cachly.dev as MCP tools so any AI assistant
|
|
9
9
|
* (GitHub Copilot, Claude, Cursor, Windsurf, Continue.dev …) can:
|
|
@@ -696,11 +696,26 @@ interface SemanticSearchResponse {
|
|
|
696
696
|
|
|
697
697
|
// ── Connection pool ───────────────────────────────────────────────────────────
|
|
698
698
|
|
|
699
|
-
/** Reuse Redis connections across tool calls (keyed by instance_id). */
|
|
699
|
+
/** Reuse Redis connections across tool calls (keyed by instance_id). Max 20 entries, LRU eviction. */
|
|
700
700
|
const pool = new Map<string, Redis>();
|
|
701
|
+
const POOL_MAX = 20;
|
|
701
702
|
|
|
702
703
|
async function getConnection(instance_id: string): Promise<Redis> {
|
|
703
|
-
if (pool.has(instance_id))
|
|
704
|
+
if (pool.has(instance_id)) {
|
|
705
|
+
// Move to end (most-recently-used)
|
|
706
|
+
const conn = pool.get(instance_id)!;
|
|
707
|
+
pool.delete(instance_id);
|
|
708
|
+
pool.set(instance_id, conn);
|
|
709
|
+
return conn;
|
|
710
|
+
}
|
|
711
|
+
// Evict oldest entry when pool is full
|
|
712
|
+
if (pool.size >= POOL_MAX) {
|
|
713
|
+
const oldest = pool.keys().next().value;
|
|
714
|
+
if (oldest) {
|
|
715
|
+
pool.get(oldest)?.quit().catch(() => undefined);
|
|
716
|
+
pool.delete(oldest);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
704
719
|
|
|
705
720
|
const inst = await apiFetch<Instance>(`/api/v1/instances/${instance_id}`);
|
|
706
721
|
if (inst.status !== 'running') {
|
|
@@ -2318,7 +2333,8 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
2318
2333
|
await redis.set(`${cacheKey}:meta`, meta);
|
|
2319
2334
|
}
|
|
2320
2335
|
|
|
2321
|
-
// Also index semantically for smart_recall (if vector available)
|
|
2336
|
+
// Also index semantically for smart_recall (if vector available and embedding is configured)
|
|
2337
|
+
if (EMBED_PROVIDER !== 'none') {
|
|
2322
2338
|
const inst = await apiFetch<Instance>(`/api/v1/instances/${instance_id}`);
|
|
2323
2339
|
if (inst.vector_token) {
|
|
2324
2340
|
try {
|
|
@@ -2342,6 +2358,7 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
2342
2358
|
// Embedding optional — continue silently
|
|
2343
2359
|
}
|
|
2344
2360
|
}
|
|
2361
|
+
} // end EMBED_PROVIDER !== 'none'
|
|
2345
2362
|
|
|
2346
2363
|
return [
|
|
2347
2364
|
`🧠 **Context Saved**`,
|
|
@@ -3004,7 +3021,18 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
3004
3021
|
lines.push(`After this session, call \`session_end\` to save a summary.`);
|
|
3005
3022
|
lines.push('');
|
|
3006
3023
|
} else {
|
|
3007
|
-
|
|
3024
|
+
const totalRecalls = lessons.reduce((s, l) => s + (l.recall_count ?? 0), 0);
|
|
3025
|
+
const savedMin = totalRecalls * 15;
|
|
3026
|
+
const savedStr = savedMin >= 60
|
|
3027
|
+
? `~${(savedMin / 60).toFixed(1)}h saved`
|
|
3028
|
+
: savedMin > 0 ? `~${savedMin}min saved` : '';
|
|
3029
|
+
const statsLine = [
|
|
3030
|
+
`${lessons.length} lessons`,
|
|
3031
|
+
ctxCount > 0 ? `${ctxCount} context entries` : '',
|
|
3032
|
+
totalRecalls > 0 ? `${totalRecalls} recalls` : '',
|
|
3033
|
+
savedStr,
|
|
3034
|
+
].filter(Boolean).join(' · ');
|
|
3035
|
+
lines.push(`📊 **Brain:** ${statsLine}`, '');
|
|
3008
3036
|
}
|
|
3009
3037
|
|
|
3010
3038
|
// Focus-relevant lessons
|
|
@@ -3194,20 +3222,23 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
3194
3222
|
|
|
3195
3223
|
type Lesson = { topic: string; what_worked: string; outcome: string; file_paths?: string[] };
|
|
3196
3224
|
const relevant: string[] = [];
|
|
3197
|
-
|
|
3198
|
-
const
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
const
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3225
|
+
if (lessonKeys.length > 0) {
|
|
3226
|
+
const values = await redis.mget(...lessonKeys);
|
|
3227
|
+
for (const raw of values) {
|
|
3228
|
+
if (!raw) continue;
|
|
3229
|
+
let lesson: Lesson;
|
|
3230
|
+
try { lesson = JSON.parse(raw) as Lesson; } catch { continue; }
|
|
3231
|
+
// Match by file_paths stored in lesson OR by topic keywords matching file name
|
|
3232
|
+
const topicWords = lesson.topic.toLowerCase().split(/[:\-_]/);
|
|
3233
|
+
const fileMatches = changed_files.some(f => {
|
|
3234
|
+
const fname = f.split('/').pop()?.replace(/\.[^.]+$/, '').toLowerCase() ?? '';
|
|
3235
|
+
return topicWords.some(w => w.length > 3 && fname.includes(w))
|
|
3236
|
+
|| (lesson.file_paths ?? []).some(lf => f.includes(lf) || lf.includes(f));
|
|
3237
|
+
});
|
|
3238
|
+
if (fileMatches) {
|
|
3239
|
+
const emoji = lesson.outcome === 'success' ? '✅' : '⚠️';
|
|
3240
|
+
relevant.push(` ${emoji} \`${lesson.topic}\` — ${lesson.what_worked.slice(0, 80)}`);
|
|
3241
|
+
}
|
|
3211
3242
|
}
|
|
3212
3243
|
}
|
|
3213
3244
|
|
|
@@ -3248,7 +3279,7 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
3248
3279
|
file_paths: file_paths ?? [],
|
|
3249
3280
|
commands: commands ?? [],
|
|
3250
3281
|
tags: [...(tags ?? []), 'team'],
|
|
3251
|
-
|
|
3282
|
+
ts: new Date().toISOString(),
|
|
3252
3283
|
recall_count: 0,
|
|
3253
3284
|
version: 2,
|
|
3254
3285
|
};
|
|
@@ -3287,10 +3318,12 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
3287
3318
|
author?: string; tags?: string[];
|
|
3288
3319
|
};
|
|
3289
3320
|
let lessons: TeamLesson[] = [];
|
|
3290
|
-
|
|
3291
|
-
const
|
|
3292
|
-
|
|
3293
|
-
|
|
3321
|
+
if (lessonKeys.length > 0) {
|
|
3322
|
+
const values = await redis.mget(...lessonKeys);
|
|
3323
|
+
for (const raw of values) {
|
|
3324
|
+
if (!raw) continue;
|
|
3325
|
+
try { lessons.push(JSON.parse(raw) as TeamLesson); } catch { /* skip */ }
|
|
3326
|
+
}
|
|
3294
3327
|
}
|
|
3295
3328
|
|
|
3296
3329
|
// Filter
|