@cachly-dev/mcp-server 0.9.3 → 0.9.5
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/dist/index.js +171 -10
- package/dist/index.js.map +1 -1
- package/package.json +14 -5
- package/server.json +4 -4
- package/smithery.yaml +13 -13
- package/src/index.ts +174 -10
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cachly-dev/mcp-server",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"mcpName": "io.github.cachly-dev/cachly-mcp",
|
|
5
|
-
"description": "AI memory
|
|
5
|
+
"description": "Persistent AI memory for Claude Code, Cursor, Copilot & Windsurf — 63 MCP tools. Causal Knowledge Graph, predictive failure detection, continuous learning from git commits + CI, federated knowledge sharing. Your AI learns from every bug fix and never re-researches the same problem.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"cachly-mcp": "dist/index.js"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"license": "Apache-2.0",
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|
|
37
|
-
"url": "git+https://github.com/cachly-dev/cachly
|
|
37
|
+
"url": "git+https://github.com/cachly-dev/cachly.git"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public",
|
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
"windsurf",
|
|
52
52
|
"continue",
|
|
53
53
|
"ai-memory",
|
|
54
|
+
"ai-brain",
|
|
55
|
+
"persistent-memory",
|
|
54
56
|
"brain",
|
|
55
57
|
"sessions",
|
|
56
58
|
"lessons-learned",
|
|
@@ -58,15 +60,22 @@
|
|
|
58
60
|
"team-knowledge",
|
|
59
61
|
"ambient-git",
|
|
60
62
|
"memory-crystals",
|
|
63
|
+
"causal-knowledge-graph",
|
|
64
|
+
"continuous-learning",
|
|
65
|
+
"predictive-ai",
|
|
61
66
|
"cachly",
|
|
62
67
|
"redis",
|
|
63
68
|
"valkey",
|
|
64
69
|
"cache",
|
|
65
70
|
"ai",
|
|
66
|
-
"llm"
|
|
71
|
+
"llm",
|
|
72
|
+
"knowledge-base",
|
|
73
|
+
"developer-productivity",
|
|
74
|
+
"ckg",
|
|
75
|
+
"mcp-server"
|
|
67
76
|
],
|
|
68
77
|
"homepage": "https://cachly.dev/docs/mcp",
|
|
69
78
|
"bugs": {
|
|
70
|
-
"url": "https://github.com/cachly-dev/cachly
|
|
79
|
+
"url": "https://github.com/cachly-dev/cachly/issues"
|
|
71
80
|
}
|
|
72
81
|
}
|
package/server.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.cachly-dev/cachly-mcp",
|
|
4
|
-
"description": "AI memory
|
|
4
|
+
"description": "Persistent AI memory for Claude Code, Cursor, Copilot & Windsurf. Your AI learns from every bug fix and never re-researches the same problem. 63 MCP tools — Causal Knowledge Graph, predictive failure detection, continuous learning from git commits, federated knowledge sharing.",
|
|
5
5
|
"repository": {
|
|
6
|
-
"url": "https://github.com/cachly-dev/cachly
|
|
6
|
+
"url": "https://github.com/cachly-dev/cachly",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.9.5",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"name": "@cachly-dev/mcp-server",
|
|
14
14
|
"identifier": "@cachly-dev/mcp-server",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.9.5",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
package/smithery.yaml
CHANGED
|
@@ -5,32 +5,32 @@ startCommand:
|
|
|
5
5
|
properties:
|
|
6
6
|
CACHLY_JWT:
|
|
7
7
|
type: string
|
|
8
|
-
title: "API Key"
|
|
9
|
-
description: "
|
|
8
|
+
title: "API Key (optional — sign in via browser on first use)"
|
|
9
|
+
description: "Leave blank for zero-config: on your first tool call the server opens a browser sign-in (OAuth Device Flow) and auto-provisions your Brain instance. Or pre-fill with your API key from cachly.dev/setup-ai."
|
|
10
10
|
examples:
|
|
11
11
|
- "cky_live_abc123..."
|
|
12
12
|
CACHLY_BRAIN_INSTANCE_ID:
|
|
13
13
|
type: string
|
|
14
|
-
title: "Brain Instance ID"
|
|
15
|
-
description: "
|
|
14
|
+
title: "Brain Instance ID (optional)"
|
|
15
|
+
description: "Only needed if you have multiple Brain instances. Leave blank — the server auto-detects your active instance from the API key."
|
|
16
16
|
pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
|
17
17
|
examples:
|
|
18
18
|
- "9d4077aa-bfa2-468b-89cd-0a8d8f3ec483"
|
|
19
19
|
CACHLY_API_URL:
|
|
20
20
|
type: string
|
|
21
21
|
title: "API URL (optional)"
|
|
22
|
-
description: "
|
|
22
|
+
description: "Leave blank unless you self-host Cachly."
|
|
23
23
|
default: "https://api.cachly.dev"
|
|
24
|
-
required:
|
|
25
|
-
- CACHLY_JWT
|
|
26
|
-
- CACHLY_BRAIN_INSTANCE_ID
|
|
24
|
+
required: []
|
|
27
25
|
commandFunction: |-
|
|
28
26
|
(config) => ({
|
|
29
27
|
command: "npx",
|
|
30
28
|
args: ["-y", "@cachly-dev/mcp-server@latest"],
|
|
31
|
-
env:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
env: Object.fromEntries(
|
|
30
|
+
Object.entries({
|
|
31
|
+
CACHLY_JWT: config.CACHLY_JWT,
|
|
32
|
+
CACHLY_BRAIN_INSTANCE_ID: config.CACHLY_BRAIN_INSTANCE_ID,
|
|
33
|
+
CACHLY_API_URL: config.CACHLY_API_URL ?? "https://api.cachly.dev",
|
|
34
|
+
}).filter(([, v]) => v != null && v !== "")
|
|
35
|
+
)
|
|
36
36
|
})
|
package/src/index.ts
CHANGED
|
@@ -53,9 +53,128 @@ import { Redis } from 'ioredis';
|
|
|
53
53
|
// ── Config ────────────────────────────────────────────────────────────────────
|
|
54
54
|
|
|
55
55
|
const API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
|
|
56
|
-
|
|
56
|
+
let JWT = process.env.CACHLY_JWT ?? '';
|
|
57
57
|
const EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
|
|
58
|
-
const CURRENT_VERSION = '0.9.
|
|
58
|
+
const CURRENT_VERSION = '0.9.5';
|
|
59
|
+
|
|
60
|
+
// ── Default Instance Resolution (for Smithery & single-credential setups) ────
|
|
61
|
+
// When CACHLY_BRAIN_INSTANCE_ID is set, tools can omit the instance_id parameter.
|
|
62
|
+
// When neither is set, we auto-fetch the first running instance once per process.
|
|
63
|
+
let _defaultInstanceId: string = process.env.CACHLY_BRAIN_INSTANCE_ID ?? '';
|
|
64
|
+
let _defaultInstanceFetched = false;
|
|
65
|
+
|
|
66
|
+
async function resolveDefaultInstanceId(): Promise<string> {
|
|
67
|
+
if (_defaultInstanceId) return _defaultInstanceId;
|
|
68
|
+
if (_defaultInstanceFetched) return '';
|
|
69
|
+
_defaultInstanceFetched = true;
|
|
70
|
+
if (!JWT) return '';
|
|
71
|
+
try {
|
|
72
|
+
const res = await fetch(`${API_URL}/api/v1/instances`, {
|
|
73
|
+
headers: { Authorization: `Bearer ${JWT}`, Accept: 'application/json' },
|
|
74
|
+
signal: AbortSignal.timeout(5000),
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok) return '';
|
|
77
|
+
const data = await res.json() as { data?: Array<{ id: string; status: string }> };
|
|
78
|
+
const running = (data?.data ?? []).filter(i => i.status === 'running');
|
|
79
|
+
if (running.length > 0) { _defaultInstanceId = running[0].id; return _defaultInstanceId; }
|
|
80
|
+
} catch { /* non-fatal — caller will surface missing instance_id error */ }
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Zero-Credential Device Flow (for Smithery & zero-config installs) ─────────
|
|
85
|
+
// When no CACHLY_JWT is set, the server starts an OAuth Device Flow on first tool
|
|
86
|
+
// call. The user visits a short URL, enters a code, and the server polls for the
|
|
87
|
+
// token in the background. After auth, it auto-provisions an instance if needed.
|
|
88
|
+
// State is kept in-memory (works because Smithery keeps one process per session).
|
|
89
|
+
interface DeviceFlowState {
|
|
90
|
+
deviceCode: string;
|
|
91
|
+
userCode: string;
|
|
92
|
+
verifyUrl: string;
|
|
93
|
+
pollInterval: number; // ms
|
|
94
|
+
deadline: number; // epoch ms
|
|
95
|
+
polling: boolean;
|
|
96
|
+
}
|
|
97
|
+
let _deviceFlow: DeviceFlowState | null = null;
|
|
98
|
+
|
|
99
|
+
async function startDeviceFlow(): Promise<DeviceFlowState | null> {
|
|
100
|
+
const AUTH_BASE = 'https://auth.cachly.dev/realms/cachly/protocol/openid-connect';
|
|
101
|
+
const CLIENT_ID = 'cachly-cli';
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch(`${AUTH_BASE}/auth/device`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
106
|
+
body: `client_id=${CLIENT_ID}&scope=openid`,
|
|
107
|
+
signal: AbortSignal.timeout(8000),
|
|
108
|
+
});
|
|
109
|
+
if (!res.ok) return null;
|
|
110
|
+
const data = await res.json() as {
|
|
111
|
+
device_code: string; user_code: string;
|
|
112
|
+
verification_uri_complete: string; interval: number;
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
deviceCode: data.device_code,
|
|
116
|
+
userCode: data.user_code,
|
|
117
|
+
verifyUrl: data.verification_uri_complete,
|
|
118
|
+
pollInterval: (data.interval ?? 5) * 1000,
|
|
119
|
+
deadline: Date.now() + 10 * 60 * 1000, // 10 min
|
|
120
|
+
polling: false,
|
|
121
|
+
};
|
|
122
|
+
} catch { return null; }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function pollDeviceFlow(flow: DeviceFlowState): Promise<'pending' | 'expired' | 'done'> {
|
|
126
|
+
if (Date.now() > flow.deadline) return 'expired';
|
|
127
|
+
const AUTH_BASE = 'https://auth.cachly.dev/realms/cachly/protocol/openid-connect';
|
|
128
|
+
const CLIENT_ID = 'cachly-cli';
|
|
129
|
+
try {
|
|
130
|
+
const res = await fetch(`${AUTH_BASE}/token`, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
133
|
+
body: `client_id=${CLIENT_ID}&grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=${flow.deviceCode}`,
|
|
134
|
+
signal: AbortSignal.timeout(8000),
|
|
135
|
+
});
|
|
136
|
+
const data = await res.json() as { access_token?: string; error?: string };
|
|
137
|
+
if (data.access_token) {
|
|
138
|
+
// Exchange Keycloak JWT → long-lived API key
|
|
139
|
+
let apiKey = data.access_token;
|
|
140
|
+
try {
|
|
141
|
+
const keyRes = await fetch(`${API_URL}/api/v1/api-keys`, {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
|
|
144
|
+
body: JSON.stringify({ name: 'cachly-mcp-smithery', scope: 'read_write' }),
|
|
145
|
+
signal: AbortSignal.timeout(8000),
|
|
146
|
+
});
|
|
147
|
+
if (keyRes.ok) {
|
|
148
|
+
const keyBody = await keyRes.json() as { key: string };
|
|
149
|
+
if (keyBody.key) apiKey = keyBody.key;
|
|
150
|
+
}
|
|
151
|
+
} catch { /* use raw JWT as fallback */ }
|
|
152
|
+
JWT = apiKey;
|
|
153
|
+
_deviceFlow = null;
|
|
154
|
+
// Auto-provision instance
|
|
155
|
+
_defaultInstanceFetched = false;
|
|
156
|
+
await resolveDefaultInstanceId();
|
|
157
|
+
if (!_defaultInstanceId) {
|
|
158
|
+
// Try auto-provision
|
|
159
|
+
try {
|
|
160
|
+
const autoRes = await fetch(`${API_URL}/api/v1/instances/auto`, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: { Authorization: `Bearer ${JWT}`, 'Content-Type': 'application/json' },
|
|
163
|
+
signal: AbortSignal.timeout(10000),
|
|
164
|
+
});
|
|
165
|
+
if (autoRes.ok) {
|
|
166
|
+
const body = await autoRes.json() as { instance?: { id: string }; instance_id?: string };
|
|
167
|
+
const id = body.instance?.id ?? body.instance_id;
|
|
168
|
+
if (id) _defaultInstanceId = id;
|
|
169
|
+
}
|
|
170
|
+
} catch { /* non-fatal */ }
|
|
171
|
+
}
|
|
172
|
+
return 'done';
|
|
173
|
+
}
|
|
174
|
+
if (data.error === 'slow_down') flow.pollInterval = Math.min(flow.pollInterval + 2000, 15000);
|
|
175
|
+
return 'pending';
|
|
176
|
+
} catch { return 'pending'; }
|
|
177
|
+
}
|
|
59
178
|
/**
|
|
60
179
|
* ┌──────────────────────────────────────────────────────────────────────┐
|
|
61
180
|
* │ EMBEDDING PROVIDER — pluggable, client-side first │
|
|
@@ -3851,26 +3970,71 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
3851
3970
|
// Guard: if no JWT, return actionable onboarding message instead of HTTP 401
|
|
3852
3971
|
if (!JWT) {
|
|
3853
3972
|
void sendAnonymousTelemetry(name);
|
|
3973
|
+
|
|
3974
|
+
// ── Zero-credential device flow ─────────────────────────────────────
|
|
3975
|
+
// 1st call: start device flow, return code + URL
|
|
3976
|
+
// 2nd+ calls: poll for token; once authenticated, proceed transparently
|
|
3977
|
+
if (_deviceFlow) {
|
|
3978
|
+
const result = await pollDeviceFlow(_deviceFlow);
|
|
3979
|
+
if (result === 'done') {
|
|
3980
|
+
// Auth complete — re-enter handleTool with now-valid JWT
|
|
3981
|
+
return handleTool(name, args);
|
|
3982
|
+
}
|
|
3983
|
+
if (result === 'expired') {
|
|
3984
|
+
_deviceFlow = null;
|
|
3985
|
+
return '⌛ **Authentication timed out.** Please call any tool again to restart the sign-in flow.';
|
|
3986
|
+
}
|
|
3987
|
+
// Still pending
|
|
3988
|
+
return [
|
|
3989
|
+
'⏳ **Waiting for authentication...**',
|
|
3990
|
+
'',
|
|
3991
|
+
`Sign in at: **${_deviceFlow.verifyUrl}**`,
|
|
3992
|
+
`Enter code: **${_deviceFlow.userCode}**`,
|
|
3993
|
+
'',
|
|
3994
|
+
'Once you complete sign-in in your browser, call this tool again and it will proceed automatically.',
|
|
3995
|
+
].join('\n');
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3998
|
+
// No pending flow — start a new one
|
|
3999
|
+
const flow = await startDeviceFlow();
|
|
4000
|
+
if (flow) {
|
|
4001
|
+
_deviceFlow = flow;
|
|
4002
|
+
return [
|
|
4003
|
+
'🧠 **cachly AI Brain — One-click sign in**',
|
|
4004
|
+
'',
|
|
4005
|
+
'1. Open this URL in your browser (it may open automatically):',
|
|
4006
|
+
` **${flow.verifyUrl}**`,
|
|
4007
|
+
'',
|
|
4008
|
+
`2. Enter this code if prompted: **${flow.userCode}**`,
|
|
4009
|
+
'',
|
|
4010
|
+
'3. After sign-in, call this tool again — it will proceed automatically.',
|
|
4011
|
+
'',
|
|
4012
|
+
'✨ Free tier includes: 1 Brain instance, persistent memory, 63 MCP tools.',
|
|
4013
|
+
' No credit card required.',
|
|
4014
|
+
].join('\n');
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
// Device flow unavailable (network issue) — fall back to manual setup
|
|
3854
4018
|
return [
|
|
3855
4019
|
'🧠 **cachly AI Brain — Setup required**',
|
|
3856
4020
|
'',
|
|
3857
|
-
'
|
|
3858
|
-
'',
|
|
3859
|
-
'**Step 1:** Register for free (no credit card):',
|
|
3860
|
-
' 👉 https://cachly.dev/setup-ai',
|
|
3861
|
-
'',
|
|
3862
|
-
'**Step 2:** Run the setup wizard in your terminal:',
|
|
4021
|
+
'Run the setup wizard once in your terminal:',
|
|
3863
4022
|
' ```',
|
|
3864
4023
|
' npx @cachly-dev/mcp-server@latest setup',
|
|
3865
4024
|
' ```',
|
|
3866
4025
|
'',
|
|
3867
|
-
'
|
|
3868
|
-
'and writes the config file with your credentials.',
|
|
4026
|
+
'Or get your API key at: https://cachly.dev/setup-ai',
|
|
3869
4027
|
'',
|
|
3870
4028
|
'✨ Free tier includes: 1 Brain instance, persistent memory, semantic search.',
|
|
3871
4029
|
].join('\n');
|
|
3872
4030
|
}
|
|
3873
4031
|
|
|
4032
|
+
// Auto-resolve instance_id from env / API when not provided in args
|
|
4033
|
+
if (!args.instance_id) {
|
|
4034
|
+
const defaultId = await resolveDefaultInstanceId();
|
|
4035
|
+
if (defaultId) args = { ...args, instance_id: defaultId };
|
|
4036
|
+
}
|
|
4037
|
+
|
|
3874
4038
|
// Delegate v0.2 bulk/lock/stream tools first
|
|
3875
4039
|
const bulkResult = await handleBulkLockStream(name, args);
|
|
3876
4040
|
if (bulkResult !== null) return bulkResult;
|