@dudamel/mcp-memory 2026.1005.3 → 2026.1015.1
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.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +533 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/stdio-server.js +563 -16
- package/dist/tools/conversations.d.ts +1 -1
- package/dist/tools/conversations.d.ts.map +1 -1
- package/dist/tools/files.d.ts +2 -2
- package/dist/tools/files.d.ts.map +1 -1
- package/dist/tools/memories.d.ts +1 -1
- package/dist/tools/memories.d.ts.map +1 -1
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/util.d.ts +1 -14
- package/dist/util.d.ts.map +1 -1
- package/package.json +5 -4
- package/dist/http-client.d.ts +0 -18
- package/dist/http-client.d.ts.map +0 -1
- package/dist/http-client.js +0 -72
- package/dist/http-client.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/server.js +0 -29
- package/dist/server.js.map +0 -1
- package/dist/stdio-server.js.map +0 -1
- package/dist/tools/conversations.js +0 -36
- package/dist/tools/conversations.js.map +0 -1
- package/dist/tools/files.js +0 -127
- package/dist/tools/files.js.map +0 -1
- package/dist/tools/memories.js +0 -195
- package/dist/tools/memories.js.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/util.js +0 -59
- package/dist/util.js.map +0 -1
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,533 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
|
|
4
|
+
// ../common/dist/http-client.js
|
|
5
|
+
function parseApiErrorBody(body) {
|
|
6
|
+
try {
|
|
7
|
+
const parsed = JSON.parse(body);
|
|
8
|
+
if (typeof parsed !== "object" || parsed === null || !("error" in parsed))
|
|
9
|
+
return void 0;
|
|
10
|
+
const error = parsed.error;
|
|
11
|
+
if (typeof error === "object" && error !== null && typeof error.code === "string" && typeof error.message === "string") {
|
|
12
|
+
return error;
|
|
13
|
+
}
|
|
14
|
+
return void 0;
|
|
15
|
+
} catch {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
var DudamelApiError = class extends Error {
|
|
20
|
+
status;
|
|
21
|
+
statusText;
|
|
22
|
+
body;
|
|
23
|
+
/** Stable machine-readable code from the API error body, or 'UNKNOWN' for non-conforming bodies. */
|
|
24
|
+
code;
|
|
25
|
+
/** Per-field validation errors, when the API provided them. */
|
|
26
|
+
details;
|
|
27
|
+
/** Extra machine-readable data (e.g. `inputsSchema` on execute validation failures). */
|
|
28
|
+
context;
|
|
29
|
+
constructor(status, statusText, body) {
|
|
30
|
+
const apiError = parseApiErrorBody(body);
|
|
31
|
+
const lines = [
|
|
32
|
+
apiError ? `Dudamel API error ${status} ${statusText}: ${apiError.code}: ${apiError.message}` : `Dudamel API error ${status} ${statusText}: ${body}`
|
|
33
|
+
];
|
|
34
|
+
if (apiError?.details?.length) {
|
|
35
|
+
lines.push(...apiError.details.map((d) => ` - ${d.path}: ${d.message}`));
|
|
36
|
+
}
|
|
37
|
+
if (apiError?.context?.["inputsSchema"] !== void 0) {
|
|
38
|
+
lines.push(`Expected inputs schema: ${JSON.stringify(apiError.context["inputsSchema"])}`);
|
|
39
|
+
}
|
|
40
|
+
super(lines.join("\n"));
|
|
41
|
+
this.status = status;
|
|
42
|
+
this.statusText = statusText;
|
|
43
|
+
this.body = body;
|
|
44
|
+
this.name = "DudamelApiError";
|
|
45
|
+
this.code = apiError?.code ?? "UNKNOWN";
|
|
46
|
+
this.details = apiError?.details;
|
|
47
|
+
this.context = apiError?.context;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var DudamelApiClient = class {
|
|
51
|
+
baseUrl;
|
|
52
|
+
headers;
|
|
53
|
+
constructor(baseUrl, apiKey, authScheme) {
|
|
54
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
55
|
+
this.headers = {
|
|
56
|
+
Authorization: `${authScheme ?? "ApiKey"} ${apiKey}`,
|
|
57
|
+
"Content-Type": "application/json"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async get(path, params) {
|
|
61
|
+
const url = this.buildUrl(path, params);
|
|
62
|
+
return this.request(url, { method: "GET" });
|
|
63
|
+
}
|
|
64
|
+
async post(path, body, params) {
|
|
65
|
+
const url = this.buildUrl(path, params);
|
|
66
|
+
return this.request(url, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async put(path, body, params) {
|
|
72
|
+
const url = this.buildUrl(path, params);
|
|
73
|
+
return this.request(url, {
|
|
74
|
+
method: "PUT",
|
|
75
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async delete(path, params) {
|
|
79
|
+
const url = this.buildUrl(path, params);
|
|
80
|
+
return this.request(url, { method: "DELETE" });
|
|
81
|
+
}
|
|
82
|
+
buildUrl(path, params) {
|
|
83
|
+
const url = new URL(path, this.baseUrl);
|
|
84
|
+
if (params) {
|
|
85
|
+
for (const [key, value] of Object.entries(params)) {
|
|
86
|
+
if (value !== void 0 && value !== "") {
|
|
87
|
+
url.searchParams.set(key, value);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return url.toString();
|
|
92
|
+
}
|
|
93
|
+
async request(url, init) {
|
|
94
|
+
const response = await fetch(url, {
|
|
95
|
+
...init,
|
|
96
|
+
headers: this.headers
|
|
97
|
+
});
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
const body = await response.text();
|
|
100
|
+
throw new DudamelApiError(response.status, response.statusText, body);
|
|
101
|
+
}
|
|
102
|
+
const text = await response.text();
|
|
103
|
+
if (!text) {
|
|
104
|
+
return void 0;
|
|
105
|
+
}
|
|
106
|
+
return JSON.parse(text);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// ../common/dist/read-only-filter.js
|
|
111
|
+
function withReadOnlyFilter(server, readOnly) {
|
|
112
|
+
if (!readOnly)
|
|
113
|
+
return server;
|
|
114
|
+
const original = server.registerTool.bind(server);
|
|
115
|
+
const filtered = (...args) => {
|
|
116
|
+
const config = args[1];
|
|
117
|
+
if (config?.annotations?.readOnlyHint === false) {
|
|
118
|
+
return void 0;
|
|
119
|
+
}
|
|
120
|
+
return original(...args);
|
|
121
|
+
};
|
|
122
|
+
return new Proxy(server, {
|
|
123
|
+
get(target, prop, receiver) {
|
|
124
|
+
if (prop === "registerTool")
|
|
125
|
+
return filtered;
|
|
126
|
+
return Reflect.get(target, prop, receiver);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ../common/dist/create-server.js
|
|
132
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
133
|
+
|
|
134
|
+
// ../common/dist/util.js
|
|
135
|
+
function jsonResult(data) {
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function errorResult(error) {
|
|
141
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
142
|
+
return {
|
|
143
|
+
content: [{ type: "text", text: message }],
|
|
144
|
+
isError: true
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/tools/memories.ts
|
|
149
|
+
import { z as z2 } from "zod";
|
|
150
|
+
|
|
151
|
+
// src/util.ts
|
|
152
|
+
import { z } from "zod";
|
|
153
|
+
var AGENT_NAME_FIELD = z.string().optional().describe(
|
|
154
|
+
'The Dudamel agent you are acting as (e.g. "dudamel-expert"). Pass it on every call when you are one \u2014 it is what scopes memories, files and conversations to that agent. Omit it only when you are not acting as any Dudamel agent.'
|
|
155
|
+
);
|
|
156
|
+
function agentNameInput(ctx) {
|
|
157
|
+
return ctx.agentName === null ? { agentName: AGENT_NAME_FIELD } : {};
|
|
158
|
+
}
|
|
159
|
+
function effectiveAgent(ctx, requested) {
|
|
160
|
+
return ctx.agentName ?? (requested?.trim() || null);
|
|
161
|
+
}
|
|
162
|
+
function agentParams(ctx, requested) {
|
|
163
|
+
const agent = effectiveAgent(ctx, requested);
|
|
164
|
+
return agent ? { agent } : {};
|
|
165
|
+
}
|
|
166
|
+
function agentBody(ctx, requested) {
|
|
167
|
+
const agent = effectiveAgent(ctx, requested);
|
|
168
|
+
return agent ? { agentName: agent } : {};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/tools/memories.ts
|
|
172
|
+
var SCOPE_HEADER = "Scope of the memory \u2014 this is WHO WILL BE ABLE TO READ IT. Pick the NARROWEST that fits:\n";
|
|
173
|
+
var RELATIONAL_LINE = "- 'relational': you (this agent) + this one user. Nobody else. The default for a preference or fact about the person you are talking to.\n";
|
|
174
|
+
var PROFILE_LINE = "- 'profile': this one user, across ALL agents \u2014 every agent they talk to sees it. For facts about them that any agent should know (their role, timezone, how to reach them).\n";
|
|
175
|
+
var AGENT_LINE = "- 'agent': you (this agent), across ALL users \u2014 everyone who talks to you sees it. For your own working knowledge; never for something about a particular person.\n";
|
|
176
|
+
var ORG_LINE = "- 'org': EVERY agent and EVERY user in the organisation. The widest reach there is \u2014 only for a fact true of the whole organisation that you would be comfortable showing anyone in it.\n";
|
|
177
|
+
function scopeDescription(unbound) {
|
|
178
|
+
return SCOPE_HEADER + RELATIONAL_LINE + PROFILE_LINE + AGENT_LINE + ORG_LINE + (unbound ? "'relational' and 'agent' need agentName \u2014 without an agent they have no one to belong to. " : "") + "Ask, in order: is this about ONE person (relational/profile) or about nobody in particular (agent/org)? Then: does it really need to be visible beyond this conversation?";
|
|
179
|
+
}
|
|
180
|
+
var AGENT_SCOPES = /* @__PURE__ */ new Set(["relational", "agent"]);
|
|
181
|
+
function registerMemoryTools(server, client, ctx) {
|
|
182
|
+
const unbound = ctx.agentName === null;
|
|
183
|
+
const scopes = z2.enum(["relational", "profile", "agent", "org"]);
|
|
184
|
+
const agentInput = agentNameInput(ctx);
|
|
185
|
+
server.registerTool(
|
|
186
|
+
"list-memories",
|
|
187
|
+
{
|
|
188
|
+
description: "List memories relevant to the current user (summaries only, NOT full content). Returns the union of everything you know that applies here: your notes on this user, their profile, and your/org general knowledge" + (unbound ? " (without agentName: only their profile and org knowledge). " : ". ") + "Call this at the start of an interaction. You MUST call this before creating a memory. Use get-memory to read a full entry.",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
q: z2.string().optional().describe("Keyword filter over summary + content (omit to list all)"),
|
|
191
|
+
scope: scopes.optional().describe("Filter to a single scope"),
|
|
192
|
+
...agentInput
|
|
193
|
+
},
|
|
194
|
+
annotations: { readOnlyHint: true }
|
|
195
|
+
},
|
|
196
|
+
async ({ q, scope, agentName }) => {
|
|
197
|
+
try {
|
|
198
|
+
const params = agentParams(ctx, agentName);
|
|
199
|
+
if (q) params["q"] = q;
|
|
200
|
+
if (scope) params["scope"] = scope;
|
|
201
|
+
const rows = await client.get("/api/chat/memories", params);
|
|
202
|
+
if (Array.isArray(rows)) {
|
|
203
|
+
for (const r of rows) if (r.id) ctx.seenIds.add(r.id);
|
|
204
|
+
}
|
|
205
|
+
ctx.hasListed = true;
|
|
206
|
+
return jsonResult(rows);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
return errorResult(err);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
server.registerTool(
|
|
213
|
+
"get-memory",
|
|
214
|
+
{
|
|
215
|
+
description: "Get the full content of a single memory by id. You MUST call this before updating a memory (so you edit its real content, not blindly). Ids come from list-memories.",
|
|
216
|
+
inputSchema: { id: z2.string().describe("Memory id (from list-memories)"), ...agentInput },
|
|
217
|
+
annotations: { readOnlyHint: true }
|
|
218
|
+
},
|
|
219
|
+
async ({ id, agentName }) => {
|
|
220
|
+
try {
|
|
221
|
+
const memory = await client.get(
|
|
222
|
+
`/api/chat/memories/${id}`,
|
|
223
|
+
agentParams(ctx, agentName)
|
|
224
|
+
);
|
|
225
|
+
if (memory?.id) {
|
|
226
|
+
ctx.fetchedIds.add(memory.id);
|
|
227
|
+
ctx.seenIds.add(memory.id);
|
|
228
|
+
}
|
|
229
|
+
return jsonResult(memory);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
return errorResult(err);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
server.registerTool(
|
|
236
|
+
"create-memory",
|
|
237
|
+
{
|
|
238
|
+
description: "Create a NEW memory. Requires a prior list-memories call this session (to avoid duplicates \u2014 if a similar memory exists, use update-memory instead). Content can be multi-line markdown. A one-line summary is generated automatically; if the summary service is unavailable the memory is NOT saved and you should retry later.",
|
|
239
|
+
inputSchema: {
|
|
240
|
+
scope: scopes.describe(scopeDescription(unbound)),
|
|
241
|
+
content: z2.string().describe("Full memory text (multi-line markdown allowed)"),
|
|
242
|
+
...agentInput
|
|
243
|
+
},
|
|
244
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
245
|
+
},
|
|
246
|
+
async ({ scope, content, agentName }) => {
|
|
247
|
+
if (!ctx.hasListed) {
|
|
248
|
+
return errorResult(
|
|
249
|
+
new Error(
|
|
250
|
+
"Call list-memories first (to check for an existing memory to update instead of duplicating)."
|
|
251
|
+
)
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
if (AGENT_SCOPES.has(scope) && !effectiveAgent(ctx, agentName)) {
|
|
255
|
+
return errorResult(
|
|
256
|
+
new Error(
|
|
257
|
+
`Scope '${scope}' belongs to an agent: pass agentName (the agent you are acting as), or use 'profile' / 'org'.`
|
|
258
|
+
)
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const result = await client.post("/api/chat/memories", {
|
|
263
|
+
...agentBody(ctx, agentName),
|
|
264
|
+
scope,
|
|
265
|
+
content
|
|
266
|
+
});
|
|
267
|
+
if (result?.id) ctx.seenIds.add(result.id);
|
|
268
|
+
return jsonResult(result);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
return errorResult(err);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
server.registerTool(
|
|
275
|
+
"update-memory",
|
|
276
|
+
{
|
|
277
|
+
description: "Update an existing memory's content (its summary is regenerated). You MUST call get-memory on this id first, so you edit its real content. If the summary service is unavailable the memory is NOT updated and you should retry later.",
|
|
278
|
+
inputSchema: {
|
|
279
|
+
id: z2.string().describe("Memory id to update"),
|
|
280
|
+
content: z2.string().describe("New full memory text (replaces the old content)"),
|
|
281
|
+
...agentInput
|
|
282
|
+
},
|
|
283
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
284
|
+
},
|
|
285
|
+
async ({ id, content, agentName }) => {
|
|
286
|
+
if (!ctx.fetchedIds.has(id)) {
|
|
287
|
+
return errorResult(
|
|
288
|
+
new Error(`Call get-memory("${id}") first so you update its real content, not a guess.`)
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
const result = await client.put(
|
|
293
|
+
`/api/chat/memories/${id}`,
|
|
294
|
+
{ content },
|
|
295
|
+
agentParams(ctx, agentName)
|
|
296
|
+
);
|
|
297
|
+
return jsonResult(result);
|
|
298
|
+
} catch (err) {
|
|
299
|
+
return errorResult(err);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
server.registerTool(
|
|
304
|
+
"delete-memory",
|
|
305
|
+
{
|
|
306
|
+
description: "Delete a memory by id. Requires the id to have been seen via list-memories or get-memory.",
|
|
307
|
+
inputSchema: { id: z2.string().describe("Memory id to delete"), ...agentInput },
|
|
308
|
+
annotations: { readOnlyHint: false, destructiveHint: true }
|
|
309
|
+
},
|
|
310
|
+
async ({ id, agentName }) => {
|
|
311
|
+
if (!ctx.seenIds.has(id)) {
|
|
312
|
+
return errorResult(
|
|
313
|
+
new Error(`Cannot delete ${id}: not seen in a prior list/get. Call list-memories first.`)
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
await client.delete(`/api/chat/memories/${id}`, agentParams(ctx, agentName));
|
|
318
|
+
ctx.seenIds.delete(id);
|
|
319
|
+
ctx.fetchedIds.delete(id);
|
|
320
|
+
return jsonResult({ deleted: true, id });
|
|
321
|
+
} catch (err) {
|
|
322
|
+
return errorResult(err);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
server.registerTool(
|
|
327
|
+
"mark-memory-useful",
|
|
328
|
+
{
|
|
329
|
+
description: "Mark a memory as useful now (bumps its recency ranking). Requires the id to have been seen via list-memories or get-memory.",
|
|
330
|
+
inputSchema: { id: z2.string().describe("Memory id to mark as useful"), ...agentInput },
|
|
331
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
332
|
+
},
|
|
333
|
+
async ({ id, agentName }) => {
|
|
334
|
+
if (!ctx.seenIds.has(id)) {
|
|
335
|
+
return errorResult(
|
|
336
|
+
new Error(`Cannot mark ${id}: not seen in a prior list/get. Call list-memories first.`)
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
const result = await client.post(
|
|
341
|
+
`/api/chat/memories/${id}/useful`,
|
|
342
|
+
void 0,
|
|
343
|
+
agentParams(ctx, agentName)
|
|
344
|
+
);
|
|
345
|
+
return jsonResult(result);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
return errorResult(err);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// src/tools/conversations.ts
|
|
354
|
+
import { z as z3 } from "zod";
|
|
355
|
+
function registerConversationTools(server, client, ctx) {
|
|
356
|
+
server.registerTool(
|
|
357
|
+
"list-conversations",
|
|
358
|
+
{
|
|
359
|
+
description: "List your past conversations. Scoped to the current agent when one is in context; otherwise every conversation you have had, with any agent. Returns conversation metadata including ids, timestamps, and summaries.",
|
|
360
|
+
inputSchema: { ...agentNameInput(ctx) },
|
|
361
|
+
annotations: { readOnlyHint: true }
|
|
362
|
+
},
|
|
363
|
+
async ({ agentName }) => {
|
|
364
|
+
try {
|
|
365
|
+
const result = await client.get("/api/chat/conversations", agentParams(ctx, agentName));
|
|
366
|
+
return jsonResult(result);
|
|
367
|
+
} catch (err) {
|
|
368
|
+
return errorResult(err);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
server.registerTool(
|
|
373
|
+
"get-conversation-messages",
|
|
374
|
+
{
|
|
375
|
+
description: "Get the full message history for a specific conversation. Returns all messages in chronological order.",
|
|
376
|
+
inputSchema: {
|
|
377
|
+
conversationId: z3.string().describe("The conversation ID to retrieve messages for")
|
|
378
|
+
},
|
|
379
|
+
annotations: { readOnlyHint: true }
|
|
380
|
+
},
|
|
381
|
+
async ({ conversationId }) => {
|
|
382
|
+
try {
|
|
383
|
+
const result = await client.get(`/api/chat/conversations/${conversationId}/messages`);
|
|
384
|
+
return jsonResult(result);
|
|
385
|
+
} catch (err) {
|
|
386
|
+
return errorResult(err);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/tools/files.ts
|
|
393
|
+
import { z as z4 } from "zod";
|
|
394
|
+
function registerFileTools(server, client, ctx) {
|
|
395
|
+
const agentInput = agentNameInput(ctx);
|
|
396
|
+
server.registerTool(
|
|
397
|
+
"save-file",
|
|
398
|
+
{
|
|
399
|
+
description: "Store a file so it survives this turn. The turn workspace is wiped when the turn ends, so anything you want to keep \u2014 a report, a diff, a generated CSV \u2014 has to be saved here. Returns an id you can hand back to the user or use in a later turn. Content goes base64-encoded.",
|
|
400
|
+
inputSchema: {
|
|
401
|
+
fileName: z4.string().describe('Name for the file, e.g. "informe-q3.csv"'),
|
|
402
|
+
mimeType: z4.string().describe('MIME type, e.g. "text/csv" or "application/pdf"'),
|
|
403
|
+
contentBase64: z4.string().describe("File content, base64-encoded"),
|
|
404
|
+
conversationId: z4.string().optional().describe("Conversation this belongs to. Defaults to the current one."),
|
|
405
|
+
...agentInput
|
|
406
|
+
},
|
|
407
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
408
|
+
},
|
|
409
|
+
async ({ fileName, mimeType, contentBase64, conversationId, agentName }) => {
|
|
410
|
+
try {
|
|
411
|
+
return jsonResult(
|
|
412
|
+
await client.post("/api/chat/files", {
|
|
413
|
+
fileName,
|
|
414
|
+
mimeType,
|
|
415
|
+
contentBase64,
|
|
416
|
+
// The current conversation unless told otherwise, so the common case needs no
|
|
417
|
+
// argument and the file is findable where it was produced.
|
|
418
|
+
conversationId: conversationId ?? ctx.conversationId,
|
|
419
|
+
...agentBody(ctx, agentName)
|
|
420
|
+
})
|
|
421
|
+
);
|
|
422
|
+
} catch (err) {
|
|
423
|
+
return errorResult(err);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
server.registerTool(
|
|
428
|
+
"list-files",
|
|
429
|
+
{
|
|
430
|
+
description: "List stored files for this conversation (default) or for this agent across conversations. Returns metadata only \u2014 ids, names, types, sizes \u2014 not content.",
|
|
431
|
+
inputSchema: {
|
|
432
|
+
conversationId: z4.string().optional().describe("Conversation to list. Defaults to the current one."),
|
|
433
|
+
allConversations: z4.boolean().optional().describe("List everything this agent saved, not just this conversation's"),
|
|
434
|
+
limit: z4.number().optional().describe("Max files to return (default 50)"),
|
|
435
|
+
...agentInput
|
|
436
|
+
},
|
|
437
|
+
annotations: { readOnlyHint: true }
|
|
438
|
+
},
|
|
439
|
+
async ({ conversationId, allConversations, limit, agentName }) => {
|
|
440
|
+
try {
|
|
441
|
+
const params = agentParams(ctx, agentName);
|
|
442
|
+
if (!allConversations) {
|
|
443
|
+
const target = conversationId ?? ctx.conversationId;
|
|
444
|
+
if (target) params.conversationId = target;
|
|
445
|
+
}
|
|
446
|
+
if (limit) params.limit = String(limit);
|
|
447
|
+
return jsonResult(await client.get("/api/chat/files", params));
|
|
448
|
+
} catch (err) {
|
|
449
|
+
return errorResult(err);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
server.registerTool(
|
|
454
|
+
"get-file",
|
|
455
|
+
{
|
|
456
|
+
description: "Read a stored file by id. Small text files come back with their content inline; anything large or binary returns metadata only, and you should pass the id along rather than trying to read the bytes \u2014 they would not fit in your context anyway.",
|
|
457
|
+
inputSchema: {
|
|
458
|
+
id: z4.string().describe("File id from save-file or list-files")
|
|
459
|
+
},
|
|
460
|
+
annotations: { readOnlyHint: true }
|
|
461
|
+
},
|
|
462
|
+
async ({ id }) => {
|
|
463
|
+
try {
|
|
464
|
+
return jsonResult(await client.get(`/api/chat/files/${encodeURIComponent(id)}`));
|
|
465
|
+
} catch (err) {
|
|
466
|
+
return errorResult(err);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
server.registerTool(
|
|
471
|
+
"send-file",
|
|
472
|
+
{
|
|
473
|
+
description: "Hand a stored file to the user in this conversation. Use it after save-file when the user should actually receive the file \u2014 saving alone only keeps it. It is delivered right after your reply, through whatever channel you are talking on; you do not need to know which.",
|
|
474
|
+
inputSchema: {
|
|
475
|
+
id: z4.string().describe("File id from save-file or list-files")
|
|
476
|
+
},
|
|
477
|
+
annotations: { readOnlyHint: false, destructiveHint: false }
|
|
478
|
+
},
|
|
479
|
+
async ({ id }) => {
|
|
480
|
+
try {
|
|
481
|
+
return jsonResult(await client.post(`/api/chat/files/${encodeURIComponent(id)}/send`, {}));
|
|
482
|
+
} catch (err) {
|
|
483
|
+
return errorResult(err);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
);
|
|
487
|
+
server.registerTool(
|
|
488
|
+
"delete-file",
|
|
489
|
+
{
|
|
490
|
+
description: "Delete a stored file permanently. Use it for things you produced and no longer need \u2014 not for files a user uploaded, unless they asked.",
|
|
491
|
+
inputSchema: {
|
|
492
|
+
id: z4.string().describe("File id from save-file or list-files")
|
|
493
|
+
},
|
|
494
|
+
annotations: { readOnlyHint: false, destructiveHint: true }
|
|
495
|
+
},
|
|
496
|
+
async ({ id }) => {
|
|
497
|
+
try {
|
|
498
|
+
await client.delete(`/api/chat/files/${encodeURIComponent(id)}`);
|
|
499
|
+
return jsonResult({ deleted: true, id });
|
|
500
|
+
} catch (err) {
|
|
501
|
+
return errorResult(err);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/server.ts
|
|
508
|
+
function createMemoryServer(config) {
|
|
509
|
+
const server = new McpServer2(
|
|
510
|
+
{ name: "dudamel-memory", version: "0.1.0" },
|
|
511
|
+
{
|
|
512
|
+
capabilities: { tools: {} },
|
|
513
|
+
instructions: "Dudamel Memory MCP \u2014 query and manage your memories and past conversations. Always search before creating or updating a memory to avoid duplicates. Write tools require a prior search in the same session. Files: the turn workspace is wiped when the turn ends, so use save-file for anything that must outlive it."
|
|
514
|
+
}
|
|
515
|
+
);
|
|
516
|
+
const client = new DudamelApiClient(config.apiUrl, config.apiKey, config.authScheme);
|
|
517
|
+
const ctx = {
|
|
518
|
+
userId: config.userId,
|
|
519
|
+
agentName: config.agentName,
|
|
520
|
+
seenIds: /* @__PURE__ */ new Set(),
|
|
521
|
+
fetchedIds: /* @__PURE__ */ new Set(),
|
|
522
|
+
hasListed: false,
|
|
523
|
+
conversationId: config.conversationId
|
|
524
|
+
};
|
|
525
|
+
const host = withReadOnlyFilter(server, config.readOnly ?? false);
|
|
526
|
+
registerMemoryTools(host, client, ctx);
|
|
527
|
+
registerConversationTools(host, client, ctx);
|
|
528
|
+
registerFileTools(host, client, ctx);
|
|
529
|
+
return server;
|
|
530
|
+
}
|
|
531
|
+
export {
|
|
532
|
+
createMemoryServer
|
|
533
|
+
};
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAE/D,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAE/D,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CA+BrE"}
|