@cachly-dev/mcp-server 0.5.2 → 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 +74 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/scripts/postinstall.js +27 -0
- package/src/index.ts +75 -1
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
|
@@ -4114,8 +4114,75 @@ const server = new Server(
|
|
|
4114
4114
|
|
|
4115
4115
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
4116
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
|
+
|
|
4117
4172
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
4118
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
|
+
|
|
4119
4186
|
try {
|
|
4120
4187
|
const text = await handleTool(name, (args ?? {}) as Record<string, unknown>);
|
|
4121
4188
|
return { content: [{ type: 'text', text }] };
|
|
@@ -4125,8 +4192,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
4125
4192
|
}
|
|
4126
4193
|
});
|
|
4127
4194
|
|
|
4128
|
-
// Graceful shutdown – close all Redis connections
|
|
4195
|
+
// Graceful shutdown – close all Redis connections + auto-end brain session
|
|
4129
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);
|
|
4130
4204
|
for (const [, client] of pool) await client.quit().catch(() => undefined);
|
|
4131
4205
|
process.exit(0);
|
|
4132
4206
|
});
|