pi-automem-bridge 0.2.0 → 0.2.2
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 +75 -29
- package/examples/config.advanced.json +61 -59
- package/package.json +61 -58
- package/src/config.ts +262 -251
- package/src/mcp-client.ts +401 -361
- package/src/project-detect.ts +96 -94
- package/src/recall.ts +283 -254
- package/src/tools/memory-tools.ts +307 -307
- package/src/tools/relationship-tools.ts +121 -114
- package/src/write-policy.ts +148 -142
package/src/mcp-client.ts
CHANGED
|
@@ -1,361 +1,401 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* mcp-client.ts - JSON-RPC client for AutoMem MCP sidecar.
|
|
3
|
-
*
|
|
4
|
-
* Reads connection info from pi's mcp.json (url + auth header).
|
|
5
|
-
* All calls go through the MCP tools/call endpoint.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
9
|
-
import { homedir } from "node:os";
|
|
10
|
-
import { resolve } from "node:path";
|
|
11
|
-
import { resolveEnvVars } from "./config";
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Types
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
export interface McpCallResult {
|
|
18
|
-
content: Array<{ type: string; text?: string }>;
|
|
19
|
-
isError?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface McpHealth {
|
|
23
|
-
healthy: boolean;
|
|
24
|
-
memoryCount?: number;
|
|
25
|
-
error?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// MCP config reader
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
confidence?: number;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
1
|
+
/**
|
|
2
|
+
* mcp-client.ts - JSON-RPC client for AutoMem MCP sidecar.
|
|
3
|
+
*
|
|
4
|
+
* Reads connection info from pi's mcp.json (url + auth header).
|
|
5
|
+
* All calls go through the MCP tools/call endpoint.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { resolveEnvVars } from "./config";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Types
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export interface McpCallResult {
|
|
18
|
+
content: Array<{ type: string; text?: string }>;
|
|
19
|
+
isError?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface McpHealth {
|
|
23
|
+
healthy: boolean;
|
|
24
|
+
memoryCount?: number;
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// MCP config reader
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
// Cache the parsed server config per server name to keep readFileSync + JSON
|
|
33
|
+
// parse off the per-turn recall hot path. The cache is validated against a
|
|
34
|
+
// cheap stat signature (mtime + size — the same quick-check make/rsync use), so
|
|
35
|
+
// an in-place mcp.json edit is still picked up even within a single mtime tick.
|
|
36
|
+
// An empty signature (stat failed) never matches, forcing a fresh read.
|
|
37
|
+
interface CachedServerConfig { url: string; auth: string; signature: string }
|
|
38
|
+
let mcpConfigCache: Map<string, CachedServerConfig> = new Map();
|
|
39
|
+
|
|
40
|
+
function loadMcpServerConfig(serverName: string): CachedServerConfig {
|
|
41
|
+
const mcpJsonPath = resolve(homedir(), ".pi", "agent", "mcp.json");
|
|
42
|
+
|
|
43
|
+
if (!existsSync(mcpJsonPath)) {
|
|
44
|
+
throw new Error("mcp.json not found at " + mcpJsonPath);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let signature = "";
|
|
48
|
+
try {
|
|
49
|
+
const st = statSync(mcpJsonPath);
|
|
50
|
+
signature = st.mtimeMs + ":" + st.size;
|
|
51
|
+
} catch (_e) { /* leave signature empty so the cache is bypassed */ }
|
|
52
|
+
|
|
53
|
+
const cached = mcpConfigCache.get(serverName);
|
|
54
|
+
if (cached && signature !== "" && cached.signature === signature) return cached;
|
|
55
|
+
|
|
56
|
+
// The file changed on disk (or the stat failed). The endpoint may now expose
|
|
57
|
+
// different tools, so drop the discovery cache too.
|
|
58
|
+
if (cached) discoveredTools = null;
|
|
59
|
+
|
|
60
|
+
const mcpJson = JSON.parse(readFileSync(mcpJsonPath, "utf8")) as {
|
|
61
|
+
mcpServers?: Record<string, { url: string; headers?: Record<string, string> }>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const server = mcpJson.mcpServers ? mcpJson.mcpServers[serverName] : undefined;
|
|
65
|
+
if (!server) {
|
|
66
|
+
const available = mcpJson.mcpServers ? Object.keys(mcpJson.mcpServers).join(", ") : "(none)";
|
|
67
|
+
throw new Error('MCP server "' + serverName + '" not found. Available: ' + available);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const entry: CachedServerConfig = {
|
|
71
|
+
url: server.url,
|
|
72
|
+
auth: resolveEnvVars(server.headers?.Authorization || ""),
|
|
73
|
+
signature,
|
|
74
|
+
};
|
|
75
|
+
mcpConfigCache.set(serverName, entry);
|
|
76
|
+
return entry;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Response parsing — handles both JSON and text/event-stream (SSE)
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
async function parseJsonRpcResponse(resp: Response): Promise<any> {
|
|
84
|
+
const ct = resp.headers.get("content-type") || "";
|
|
85
|
+
if (ct.includes("text/event-stream")) {
|
|
86
|
+
const text = await resp.text();
|
|
87
|
+
// SSE lines are "data: <json>\n"; find the last non-empty data line
|
|
88
|
+
const dataLine = text
|
|
89
|
+
.split("\n")
|
|
90
|
+
.map(function(l: string) { return l.trim(); })
|
|
91
|
+
.filter(function(l: string) { return l.startsWith("data:") && l.length > 5; })
|
|
92
|
+
.pop();
|
|
93
|
+
if (!dataLine) throw new Error("SSE response contained no data lines");
|
|
94
|
+
return JSON.parse(dataLine.slice(5).trim());
|
|
95
|
+
}
|
|
96
|
+
return resp.json();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// JSON-RPC client
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
let callId = 0;
|
|
104
|
+
let configuredServerName = process.env.AUTOMEM_MCP_SERVER || "automem";
|
|
105
|
+
|
|
106
|
+
export function setAutoMemMcpServerName(serverName: string | undefined): void {
|
|
107
|
+
if (serverName && serverName.trim()) {
|
|
108
|
+
const newName = serverName.trim();
|
|
109
|
+
if (newName !== configuredServerName) {
|
|
110
|
+
discoveredTools = null;
|
|
111
|
+
mcpConfigCache = new Map();
|
|
112
|
+
configuredServerName = newName;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getAutoMemMcpServerName(): string {
|
|
118
|
+
return process.env.AUTOMEM_MCP_SERVER || configuredServerName || "automem";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function mcpCall(
|
|
122
|
+
tool: string,
|
|
123
|
+
args: Record<string, unknown>,
|
|
124
|
+
timeoutMs: number = 30000,
|
|
125
|
+
): Promise<McpCallResult> {
|
|
126
|
+
const serverName = getAutoMemMcpServerName();
|
|
127
|
+
const cfg = loadMcpServerConfig(serverName);
|
|
128
|
+
|
|
129
|
+
const body = {
|
|
130
|
+
jsonrpc: "2.0",
|
|
131
|
+
id: ++callId,
|
|
132
|
+
method: "tools/call",
|
|
133
|
+
params: { name: tool, arguments: args },
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const resp = await fetch(cfg.url, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/json",
|
|
144
|
+
...(cfg.auth ? { Authorization: cfg.auth } : {}),
|
|
145
|
+
Accept: "application/json, text/event-stream",
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify(body),
|
|
148
|
+
signal: controller.signal,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (!resp.ok) {
|
|
152
|
+
const text = await resp.text().catch(function() { return ""; });
|
|
153
|
+
throw new Error("MCP HTTP " + resp.status + ": " + text.slice(0, 200));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const payload = (await parseJsonRpcResponse(resp)) as {
|
|
157
|
+
result?: McpCallResult;
|
|
158
|
+
error?: { code: number; message: string };
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (payload.error) {
|
|
162
|
+
throw new Error("MCP error: " + payload.error.message);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const result = payload.result || { content: [] };
|
|
166
|
+
// A tool-level failure is reported as HTTP 200 with isError:true (no
|
|
167
|
+
// JSON-RPC error). Surface it as a thrown error so callers don't treat a
|
|
168
|
+
// failed health check / store / update as success.
|
|
169
|
+
if (result.isError) {
|
|
170
|
+
const errText = result.content && result.content[0] ? result.content[0].text : undefined;
|
|
171
|
+
throw new Error("MCP tool error: " + (errText || "tool reported isError"));
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
} finally {
|
|
175
|
+
clearTimeout(timeout);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Tool discovery cache
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
let discoveredTools: Map<string, string> | null = null;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Discover available tools from the MCP server via tools/list.
|
|
187
|
+
* Returns a Map of normalized tool name → actual tool name.
|
|
188
|
+
* Cached after first call.
|
|
189
|
+
*/
|
|
190
|
+
export async function discoverTools(): Promise<Map<string, string>> {
|
|
191
|
+
// Load config first — if mcp.json changed on disk, loadMcpServerConfig drops
|
|
192
|
+
// the discovery cache, so this must run before the early return below.
|
|
193
|
+
const serverName = getAutoMemMcpServerName();
|
|
194
|
+
const cfg = loadMcpServerConfig(serverName);
|
|
195
|
+
|
|
196
|
+
if (discoveredTools) return discoveredTools;
|
|
197
|
+
|
|
198
|
+
const body = {
|
|
199
|
+
jsonrpc: "2.0",
|
|
200
|
+
id: ++callId,
|
|
201
|
+
method: "tools/list",
|
|
202
|
+
params: {},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const controller = new AbortController();
|
|
206
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const resp = await fetch(cfg.url, {
|
|
210
|
+
method: "POST",
|
|
211
|
+
headers: {
|
|
212
|
+
"Content-Type": "application/json",
|
|
213
|
+
...(cfg.auth ? { Authorization: cfg.auth } : {}),
|
|
214
|
+
Accept: "application/json, text/event-stream",
|
|
215
|
+
},
|
|
216
|
+
body: JSON.stringify(body),
|
|
217
|
+
signal: controller.signal,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (!resp.ok) {
|
|
221
|
+
throw new Error("MCP tools/list HTTP " + resp.status);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const payload = (await parseJsonRpcResponse(resp)) as {
|
|
225
|
+
result?: { tools?: Array<{ name: string }> };
|
|
226
|
+
error?: { code: number; message: string };
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
if (payload.error) {
|
|
230
|
+
throw new Error("MCP tools/list error: " + payload.error.message);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const tools = payload.result?.tools || [];
|
|
234
|
+
const map = new Map<string, string>();
|
|
235
|
+
for (const t of tools) {
|
|
236
|
+
map.set(t.name.toLowerCase(), t.name);
|
|
237
|
+
// Also index without automem_ prefix for fuzzy matching
|
|
238
|
+
if (t.name.toLowerCase().startsWith("automem_")) {
|
|
239
|
+
map.set(t.name.toLowerCase().replace("automem_", ""), t.name);
|
|
240
|
+
}
|
|
241
|
+
// Also index with automem_ prefix for reverse lookups
|
|
242
|
+
map.set("automem_" + t.name.toLowerCase(), t.name);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
discoveredTools = map;
|
|
246
|
+
console.log("[automem] discovered tools: " + Array.from(new Set(map.values())).join(", "));
|
|
247
|
+
return map;
|
|
248
|
+
} catch (err) {
|
|
249
|
+
console.warn("[automem] tools/list failed, using default tool names: " + err);
|
|
250
|
+
// Fallback: use actual server tool names (no automem_ prefix)
|
|
251
|
+
discoveredTools = new Map<string, string>([
|
|
252
|
+
["recall_memory", "recall_memory"],
|
|
253
|
+
["automem_recall_memory", "recall_memory"],
|
|
254
|
+
["check_database_health", "check_database_health"],
|
|
255
|
+
["automem_check_database_health", "check_database_health"],
|
|
256
|
+
["store_memory", "store_memory"],
|
|
257
|
+
["automem_store_memory", "store_memory"],
|
|
258
|
+
["associate_memories", "associate_memories"],
|
|
259
|
+
["automem_associate_memories", "associate_memories"],
|
|
260
|
+
["update_memory", "update_memory"],
|
|
261
|
+
["automem_update_memory", "update_memory"],
|
|
262
|
+
["delete_memory", "delete_memory"],
|
|
263
|
+
["automem_delete_memory", "delete_memory"],
|
|
264
|
+
]);
|
|
265
|
+
return discoveredTools;
|
|
266
|
+
} finally {
|
|
267
|
+
clearTimeout(timeout);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Resolve a logical tool name to the actual server tool name.
|
|
273
|
+
* e.g. "recall_memory" → the actual server tool name discovered from tools/list.
|
|
274
|
+
*/
|
|
275
|
+
export function resolveToolName(logicalName: string): string {
|
|
276
|
+
if (!discoveredTools) return logicalName;
|
|
277
|
+
const key = logicalName.toLowerCase();
|
|
278
|
+
return discoveredTools.get(key) || logicalName;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// AutoMem-specific wrappers
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
|
|
285
|
+
export async function automemRecall(
|
|
286
|
+
query: string,
|
|
287
|
+
options?: {
|
|
288
|
+
limit?: number;
|
|
289
|
+
tags?: string[];
|
|
290
|
+
tagMode?: "any" | "all";
|
|
291
|
+
contextTypes?: string[];
|
|
292
|
+
expandRelations?: boolean;
|
|
293
|
+
expandEntities?: boolean;
|
|
294
|
+
},
|
|
295
|
+
timeoutMs?: number,
|
|
296
|
+
): Promise<McpCallResult> {
|
|
297
|
+
const args: Record<string, unknown> = {
|
|
298
|
+
query,
|
|
299
|
+
limit: options && options.limit ? options.limit : 8,
|
|
300
|
+
tags: options && options.tags ? options.tags : [],
|
|
301
|
+
tag_mode: options && options.tagMode ? options.tagMode : "any",
|
|
302
|
+
expand_relations: options ? !!options.expandRelations : false,
|
|
303
|
+
expand_entities: options ? !!options.expandEntities : false,
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
if (options && options.contextTypes && options.contextTypes.length > 0) {
|
|
307
|
+
args.context_types = options.contextTypes;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return mcpCall(resolveToolName("recall_memory"), args, timeoutMs);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export async function automemHealth(): Promise<McpHealth> {
|
|
314
|
+
try {
|
|
315
|
+
const result = await mcpCall(resolveToolName("check_database_health"), {});
|
|
316
|
+
const text = result.content && result.content[0] ? result.content[0].text : undefined;
|
|
317
|
+
if (text) {
|
|
318
|
+
try {
|
|
319
|
+
const parsed = JSON.parse(text);
|
|
320
|
+
const count = parsed.memory_count !== undefined
|
|
321
|
+
? parsed.memory_count
|
|
322
|
+
: (parsed.count !== undefined ? parsed.count : parsed.memories);
|
|
323
|
+
return {
|
|
324
|
+
healthy: true,
|
|
325
|
+
memoryCount: typeof count === "number" ? count : undefined,
|
|
326
|
+
};
|
|
327
|
+
} catch (_e) {
|
|
328
|
+
return { healthy: true };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return { healthy: true };
|
|
332
|
+
} catch (err) {
|
|
333
|
+
return { healthy: false, error: String(err) };
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export async function automemStore(
|
|
338
|
+
content: string,
|
|
339
|
+
type: string,
|
|
340
|
+
tags: string[],
|
|
341
|
+
options?: {
|
|
342
|
+
source?: string;
|
|
343
|
+
confidence?: number;
|
|
344
|
+
importance?: number;
|
|
345
|
+
metadata?: Record<string, unknown>;
|
|
346
|
+
},
|
|
347
|
+
): Promise<McpCallResult> {
|
|
348
|
+
const meta: Record<string, unknown> = {};
|
|
349
|
+
if (options && options.source) meta.source = options.source;
|
|
350
|
+
if (options && options.metadata) Object.assign(meta, options.metadata);
|
|
351
|
+
|
|
352
|
+
return mcpCall(resolveToolName("store_memory"), {
|
|
353
|
+
content,
|
|
354
|
+
type,
|
|
355
|
+
tags,
|
|
356
|
+
confidence: options?.confidence ?? 0.8,
|
|
357
|
+
importance: options?.importance ?? 0.5,
|
|
358
|
+
metadata: Object.keys(meta).length > 0 ? meta : undefined,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export async function automemAssociate(
|
|
363
|
+
memory1Id: string,
|
|
364
|
+
memory2Id: string,
|
|
365
|
+
relationship: string,
|
|
366
|
+
strength: number = 0.5,
|
|
367
|
+
): Promise<McpCallResult> {
|
|
368
|
+
return mcpCall(resolveToolName("associate_memories"), {
|
|
369
|
+
memory1_id: memory1Id,
|
|
370
|
+
memory2_id: memory2Id,
|
|
371
|
+
type: relationship,
|
|
372
|
+
strength,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export async function automemUpdate(
|
|
377
|
+
memoryId: string,
|
|
378
|
+
updates: {
|
|
379
|
+
content?: string;
|
|
380
|
+
type?: string;
|
|
381
|
+
tags?: string[];
|
|
382
|
+
importance?: number;
|
|
383
|
+
confidence?: number;
|
|
384
|
+
metadata?: Record<string, unknown>;
|
|
385
|
+
},
|
|
386
|
+
): Promise<McpCallResult> {
|
|
387
|
+
const args: Record<string, unknown> = { memory_id: memoryId };
|
|
388
|
+
if (updates.content !== undefined) args.content = updates.content;
|
|
389
|
+
if (updates.type !== undefined) args.type = updates.type;
|
|
390
|
+
if (updates.tags !== undefined) args.tags = updates.tags;
|
|
391
|
+
if (updates.importance !== undefined) args.importance = updates.importance;
|
|
392
|
+
if (updates.confidence !== undefined) args.confidence = updates.confidence;
|
|
393
|
+
if (updates.metadata !== undefined) args.metadata = updates.metadata;
|
|
394
|
+
return mcpCall(resolveToolName("update_memory"), args);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export async function automemDelete(memoryId: string): Promise<McpCallResult> {
|
|
398
|
+
return mcpCall(resolveToolName("delete_memory"), {
|
|
399
|
+
memory_id: memoryId,
|
|
400
|
+
});
|
|
401
|
+
}
|