@cachly-dev/mcp-server 0.10.75 → 0.10.77
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 +74 -0
- package/dist/src/handlers/brain.js +15 -1
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/index.js +110 -37
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/src/index.js
CHANGED
|
@@ -60,10 +60,12 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/
|
|
|
60
60
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
61
61
|
import { Redis } from 'ioredis';
|
|
62
62
|
// ── Config ────────────────────────────────────────────────────────────────────
|
|
63
|
-
|
|
63
|
+
// Mutable so the `setup`/`init` CLI can honor a `--api-url` flag for self-hosting.
|
|
64
|
+
// Reassigned (if at all) before any network call is made.
|
|
65
|
+
let API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
|
|
64
66
|
let JWT = process.env.CACHLY_JWT ?? '';
|
|
65
67
|
const _EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
|
|
66
|
-
const CURRENT_VERSION = '0.10.
|
|
68
|
+
const CURRENT_VERSION = '0.10.77';
|
|
67
69
|
// Max time to wait for a freshly-provisioned instance to become "running" before
|
|
68
70
|
// giving up. Free-tier provisioning in high-latency regions can take 45–90s, so the
|
|
69
71
|
// old hard-coded 25s caused spurious "instance_not_reachable" on first run.
|
|
@@ -1110,13 +1112,26 @@ const EDITOR_FILES = {
|
|
|
1110
1112
|
};
|
|
1111
1113
|
const CLAUDE_MD_MARKER_START = '<!-- cachly-brain-start -->';
|
|
1112
1114
|
const CLAUDE_MD_MARKER_END = '<!-- cachly-brain-end -->';
|
|
1115
|
+
const DEFAULT_API_URL = 'https://api.cachly.dev';
|
|
1116
|
+
// Build the env block written into an editor's MCP config. Self-hosting is
|
|
1117
|
+
// first-class: if the operator runs setup/init against their own backend
|
|
1118
|
+
// (CACHLY_API_URL set to a non-default URL), that URL is baked into the config so
|
|
1119
|
+
// the editor-launched server talks to the self-hosted instance — not api.cachly.dev.
|
|
1120
|
+
// For the default cloud backend we OMIT CACHLY_API_URL to keep configs clean (the
|
|
1121
|
+
// binary already defaults to it).
|
|
1122
|
+
function buildServerEnv(apiKey, instanceId) {
|
|
1123
|
+
const env = { CACHLY_JWT: apiKey, CACHLY_BRAIN_INSTANCE_ID: instanceId };
|
|
1124
|
+
if (API_URL && API_URL !== DEFAULT_API_URL)
|
|
1125
|
+
env.CACHLY_API_URL = API_URL;
|
|
1126
|
+
return env;
|
|
1127
|
+
}
|
|
1113
1128
|
function buildMcpConfig(apiKey, instanceId, editor) {
|
|
1114
1129
|
if (editor === 'continue') {
|
|
1115
1130
|
return JSON.stringify({
|
|
1116
1131
|
experimental: {
|
|
1117
1132
|
modelContextProtocolServers: [{
|
|
1118
1133
|
transport: { type: 'stdio', command: 'npx', args: ['-y', '@cachly-dev/mcp-server@latest'] },
|
|
1119
|
-
env:
|
|
1134
|
+
env: buildServerEnv(apiKey, instanceId),
|
|
1120
1135
|
}],
|
|
1121
1136
|
},
|
|
1122
1137
|
}, null, 2);
|
|
@@ -1128,7 +1143,7 @@ function buildMcpConfig(apiKey, instanceId, editor) {
|
|
|
1128
1143
|
command: {
|
|
1129
1144
|
path: 'npx',
|
|
1130
1145
|
args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
1131
|
-
env:
|
|
1146
|
+
env: buildServerEnv(apiKey, instanceId),
|
|
1132
1147
|
},
|
|
1133
1148
|
settings: {},
|
|
1134
1149
|
},
|
|
@@ -1140,7 +1155,7 @@ function buildMcpConfig(apiKey, instanceId, editor) {
|
|
|
1140
1155
|
cachly: {
|
|
1141
1156
|
command: 'npx',
|
|
1142
1157
|
args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
1143
|
-
env:
|
|
1158
|
+
env: buildServerEnv(apiKey, instanceId),
|
|
1144
1159
|
},
|
|
1145
1160
|
},
|
|
1146
1161
|
}, null, 2);
|
|
@@ -1151,7 +1166,7 @@ async function mergeMcpConfig(configPath, apiKey, instanceId, editor, fsOps) {
|
|
|
1151
1166
|
const cachlyEntry = {
|
|
1152
1167
|
command: 'npx',
|
|
1153
1168
|
args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
1154
|
-
env:
|
|
1169
|
+
env: buildServerEnv(apiKey, instanceId),
|
|
1155
1170
|
};
|
|
1156
1171
|
if (!fsOps.existsSync(configPath))
|
|
1157
1172
|
return buildMcpConfig(apiKey, instanceId, editor);
|
|
@@ -1558,7 +1573,9 @@ if (process.argv[2] === 'join') {
|
|
|
1558
1573
|
env['CACHLY_BRAIN_INSTANCE_ID'] = instance_id;
|
|
1559
1574
|
if (joinJwt && !env['CACHLY_JWT'])
|
|
1560
1575
|
env['CACHLY_JWT'] = joinJwt;
|
|
1561
|
-
|
|
1576
|
+
// Only persist a self-hosted backend URL; leave default-cloud configs clean.
|
|
1577
|
+
if (API_URL !== DEFAULT_API_URL)
|
|
1578
|
+
env['CACHLY_API_URL'] = API_URL;
|
|
1562
1579
|
servers['cachly'] = { ...existing, command: 'npx', args: ['-y', '@cachly-dev/mcp-server@latest'], env };
|
|
1563
1580
|
cfg['mcpServers'] = servers;
|
|
1564
1581
|
await wfJ(ePath, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
@@ -1923,6 +1940,11 @@ if (process.argv[2] === 'init') {
|
|
|
1923
1940
|
let apiKey = flag('api-key') ?? process.env.CACHLY_JWT;
|
|
1924
1941
|
const editor = (flag('editor') ?? 'claude').toLowerCase();
|
|
1925
1942
|
const projectDir = resolve(flag('project-dir') ?? '.');
|
|
1943
|
+
// Self-hosting: --api-url overrides the backend baked into the written config.
|
|
1944
|
+
const apiUrlFlag = flag('api-url');
|
|
1945
|
+
if (apiUrlFlag) {
|
|
1946
|
+
API_URL = apiUrlFlag.replace(/\/+$/, '');
|
|
1947
|
+
}
|
|
1926
1948
|
// Zero-arg path: fall back to credentials saved by a previous `setup`
|
|
1927
1949
|
// (~/.claude/mcp.json). Makes `init` a fast, idempotent re-configuration command
|
|
1928
1950
|
// you can run in any project without copy-pasting tokens.
|
|
@@ -1944,7 +1966,7 @@ if (process.argv[2] === 'init') {
|
|
|
1944
1966
|
catch { /* fall through to the usage error below */ }
|
|
1945
1967
|
}
|
|
1946
1968
|
if (!instanceId || !apiKey) {
|
|
1947
|
-
console.error('\nUsage: npx @cachly-dev/mcp-server@latest init --instance-id <uuid> --api-key <cky_live_...> [--editor claude|cursor|windsurf|copilot|continue] [--project-dir /path]\n');
|
|
1969
|
+
console.error('\nUsage: npx @cachly-dev/mcp-server@latest init --instance-id <uuid> --api-key <cky_live_...> [--editor claude|cursor|windsurf|copilot|continue] [--project-dir /path] [--api-url https://your-self-hosted-backend]\n');
|
|
1948
1970
|
console.error('First time? Run the zero-config wizard (signs you in, auto-provisions): npx @cachly-dev/mcp-server@latest setup\n');
|
|
1949
1971
|
console.error('Get your credentials from: https://cachly.dev/setup-ai\n');
|
|
1950
1972
|
process.exit(1);
|
|
@@ -2108,6 +2130,11 @@ if (process.argv[2] === 'health') {
|
|
|
2108
2130
|
if (!jwt) {
|
|
2109
2131
|
fail('CACHLY_JWT not set — run: npx @cachly-dev/mcp-server@latest setup');
|
|
2110
2132
|
}
|
|
2133
|
+
else if (jwt.startsWith('cky_')) {
|
|
2134
|
+
// Long-lived API key (the credential `setup` provisions). It is not a JWT and
|
|
2135
|
+
// never expires client-side — treat a well-formed key as healthy.
|
|
2136
|
+
ok('Long-lived API key set (cky_…) — no client-side expiry');
|
|
2137
|
+
}
|
|
2111
2138
|
else {
|
|
2112
2139
|
try {
|
|
2113
2140
|
const parts = jwt.split('.');
|
|
@@ -2127,7 +2154,7 @@ if (process.argv[2] === 'health') {
|
|
|
2127
2154
|
}
|
|
2128
2155
|
}
|
|
2129
2156
|
catch {
|
|
2130
|
-
fail('CACHLY_JWT format invalid (expected
|
|
2157
|
+
fail('CACHLY_JWT format invalid (expected a cky_ API key or a 3-part JWT)');
|
|
2131
2158
|
}
|
|
2132
2159
|
}
|
|
2133
2160
|
// ── 2. Brain API reachability ───────────────────────────────────────────────
|
|
@@ -2275,6 +2302,30 @@ if (process.argv[2] === 'health') {
|
|
|
2275
2302
|
else {
|
|
2276
2303
|
warn('.git/hooks/post-commit not found — run: npx @cachly-dev/mcp-server@latest setup');
|
|
2277
2304
|
}
|
|
2305
|
+
// ── 6. Embedding provider (BYOK) ─────────────────────────────────────────────
|
|
2306
|
+
console.log('\n🧬 Embedding provider (semantic search)');
|
|
2307
|
+
{
|
|
2308
|
+
const providerKeys = [
|
|
2309
|
+
['OPENAI_API_KEY', 'openai'], ['GEMINI_API_KEY', 'gemini'],
|
|
2310
|
+
['MISTRAL_API_KEY', 'mistral'], ['COHERE_API_KEY', 'cohere'],
|
|
2311
|
+
['OLLAMA_BASE_URL', 'ollama'],
|
|
2312
|
+
];
|
|
2313
|
+
const explicit = (process.env.CACHLY_EMBED_PROVIDER ?? '').toLowerCase();
|
|
2314
|
+
const byok = providerKeys.find(([envVar]) => process.env[envVar]);
|
|
2315
|
+
if (explicit && explicit !== 'cachly') {
|
|
2316
|
+
const envVar = providerKeys.find(([, p]) => p === explicit)?.[0];
|
|
2317
|
+
if (envVar && process.env[envVar])
|
|
2318
|
+
ok(`BYOK: ${explicit} (key present in ${envVar})`);
|
|
2319
|
+
else
|
|
2320
|
+
warn(`CACHLY_EMBED_PROVIDER=${explicit} but its key env var is not set — semantic search will fail`);
|
|
2321
|
+
}
|
|
2322
|
+
else if (byok) {
|
|
2323
|
+
ok(`BYOK: ${byok[1]} auto-detected (${byok[0]} present)`);
|
|
2324
|
+
}
|
|
2325
|
+
else {
|
|
2326
|
+
ok('Server-side embeddings (cachly) — no key needed, uses your JWT');
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2278
2329
|
// ── Summary ─────────────────────────────────────────────────────────────────
|
|
2279
2330
|
console.log(`\n${'─'.repeat(40)}`);
|
|
2280
2331
|
if (failed === 0) {
|
|
@@ -2294,6 +2345,14 @@ if (process.argv[2] === 'setup') {
|
|
|
2294
2345
|
const { existsSync } = await import('node:fs');
|
|
2295
2346
|
const { resolve, dirname } = await import('node:path');
|
|
2296
2347
|
const { createInterface } = await import('node:readline');
|
|
2348
|
+
const setupStartMs = Date.now();
|
|
2349
|
+
// Self-hosting: `setup --api-url https://cachly.mycorp.internal` points the whole
|
|
2350
|
+
// wizard (auth, provisioning, config writes) at a private backend.
|
|
2351
|
+
const _apiUrlIdx = process.argv.indexOf('--api-url');
|
|
2352
|
+
if (_apiUrlIdx !== -1 && process.argv[_apiUrlIdx + 1]) {
|
|
2353
|
+
API_URL = process.argv[_apiUrlIdx + 1].replace(/\/+$/, '');
|
|
2354
|
+
console.log(`ℹ️ Using self-hosted backend: ${API_URL}\n`);
|
|
2355
|
+
}
|
|
2297
2356
|
sendFunnelEvent('setup_started');
|
|
2298
2357
|
// --yes / -y → non-interactive mode (skips all prompts, picks defaults).
|
|
2299
2358
|
// Also auto-detect a non-interactive stdin (VSCode tasks, CI, piped input):
|
|
@@ -2655,7 +2714,7 @@ if (process.argv[2] === 'setup') {
|
|
|
2655
2714
|
globalConfig.mcpServers['cachly'] = {
|
|
2656
2715
|
command: 'npx',
|
|
2657
2716
|
args: ['-y', '@cachly-dev/mcp-server@latest'],
|
|
2658
|
-
env:
|
|
2717
|
+
env: buildServerEnv(token, instance.id),
|
|
2659
2718
|
};
|
|
2660
2719
|
await writeFile(globalClaudePath, JSON.stringify(globalConfig, null, 2), 'utf-8');
|
|
2661
2720
|
console.log(`✅ Written: ~/.claude/mcp.json (global — works in every project)`);
|
|
@@ -2843,7 +2902,13 @@ if (process.argv[2] === 'setup') {
|
|
|
2843
2902
|
catch { /* non-critical */ }
|
|
2844
2903
|
// Setup reached the end successfully — close the funnel.
|
|
2845
2904
|
JWT = token;
|
|
2846
|
-
|
|
2905
|
+
const setupElapsedS = Math.round((Date.now() - setupStartMs) / 1000);
|
|
2906
|
+
sendFunnelEvent('setup_completed', { instance_id: instance.id, elapsed_s: setupElapsedS });
|
|
2907
|
+
console.log('\n╔══════════════════════════════════════════════════════╗');
|
|
2908
|
+
console.log('║ 🚀 Brain is ready. ║');
|
|
2909
|
+
console.log(`║ ⏱️ Setup completed in ${String(setupElapsedS + 's').padEnd(5)} — restart your editor ║`);
|
|
2910
|
+
console.log('║ and your AI arrives pre-briefed every session. ║');
|
|
2911
|
+
console.log('╚══════════════════════════════════════════════════════╝\n');
|
|
2847
2912
|
rl.close();
|
|
2848
2913
|
// Give the fire-and-forget telemetry a moment to flush before exit.
|
|
2849
2914
|
await new Promise(r => setTimeout(r, 300));
|
|
@@ -2925,37 +2990,45 @@ if (process.argv[2] === 'learn-git') {
|
|
|
2925
2990
|
process.exit(0);
|
|
2926
2991
|
}
|
|
2927
2992
|
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
2928
|
-
//
|
|
2929
|
-
//
|
|
2930
|
-
//
|
|
2993
|
+
// Credential-missing handling at startup. The critical distinction is HOW we were
|
|
2994
|
+
// launched, because writing anything to stdout in stdio-MCP mode corrupts the
|
|
2995
|
+
// JSON-RPC stream and exiting kills the zero-credential device-flow onboarding.
|
|
2996
|
+
// • Human in a real terminal (TTY), no args → show the setup banner, exit 0.
|
|
2997
|
+
// • Editor as an MCP stdio server (non-TTY) → ONE stderr hint, then KEEP RUNNING
|
|
2998
|
+
// so tools/list works and the first tool call starts the browser sign-in.
|
|
2999
|
+
// Skip entirely for CLI subcommands that intentionally run without credentials.
|
|
2931
3000
|
const _cliNoAuthCommands = ['demo', 'share', 'health', 'setup', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade'];
|
|
2932
|
-
if (!JWT && !_cliNoAuthCommands.includes(process.argv[2] ?? '')
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
if (!process.argv[2] && _isMain) {
|
|
3001
|
+
if (!JWT && !_cliNoAuthCommands.includes(process.argv[2] ?? '')) {
|
|
3002
|
+
const runningInTerminal = !process.argv[2] && process.stdout.isTTY === true && _isMain;
|
|
3003
|
+
if (runningInTerminal) {
|
|
3004
|
+
// A human ran `npx @cachly-dev/mcp-server` directly with no credentials.
|
|
3005
|
+
// Show them how to set up, then exit cleanly.
|
|
3006
|
+
const banner = '\n' +
|
|
3007
|
+
'╔══════════════════════════════════════════════════════════════════╗\n' +
|
|
3008
|
+
'║ 🧠 cachly AI Brain — Setup required ║\n' +
|
|
3009
|
+
'╠══════════════════════════════════════════════════════════════════╣\n' +
|
|
3010
|
+
'║ ║\n' +
|
|
3011
|
+
'║ CACHLY_JWT is not set. Get your free credentials at: ║\n' +
|
|
3012
|
+
'║ ║\n' +
|
|
3013
|
+
'║ 👉 https://cachly.dev/setup-ai ║\n' +
|
|
3014
|
+
'║ ║\n' +
|
|
3015
|
+
'║ Then run the interactive setup wizard: ║\n' +
|
|
3016
|
+
'║ ║\n' +
|
|
3017
|
+
'║ npx @cachly-dev/mcp-server@latest setup ║\n' +
|
|
3018
|
+
'║ ║\n' +
|
|
3019
|
+
'║ Free tier — no credit card required. ║\n' +
|
|
3020
|
+
'╚══════════════════════════════════════════════════════════════════╝\n' +
|
|
3021
|
+
'\n';
|
|
2954
3022
|
process.stdout.write(banner);
|
|
2955
3023
|
process.exit(0);
|
|
2956
3024
|
}
|
|
2957
3025
|
else {
|
|
2958
|
-
|
|
3026
|
+
// Launched as an MCP stdio server (or HTTP) without credentials. NEVER touch
|
|
3027
|
+
// stdout here — it carries the JSON-RPC protocol. Emit a single actionable
|
|
3028
|
+
// hint to stderr (shown in the editor's MCP log) and fall through so the
|
|
3029
|
+
// server starts and the zero-credential device flow can run on first tool call.
|
|
3030
|
+
process.stderr.write('\n🧠 cachly: no CACHLY_JWT set yet — call any cachly tool and a 10-second browser sign-in starts automatically.\n' +
|
|
3031
|
+
' (Or run once: npx @cachly-dev/mcp-server@latest setup)\n\n');
|
|
2959
3032
|
}
|
|
2960
3033
|
}
|
|
2961
3034
|
else {
|