@mnemoverse/mcp-memory-server 0.3.2 → 0.3.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.
@@ -1,28 +0,0 @@
1
- {
2
- "$comment": "SINGLE SOURCE OF TRUTH for all distribution channel configs. Edit this file → run `npm run generate:configs` → all 9+ artifacts regenerate. CI verifies committed artifacts match source.",
3
- "name": "mnemoverse",
4
- "displayName": "Mnemoverse Memory",
5
- "description": "Shared AI memory across Claude, Cursor, VS Code, ChatGPT. Write once, recall anywhere.",
6
- "type": "stdio",
7
- "command": "npx",
8
- "args": ["-y", "@mnemoverse/mcp-memory-server@latest"],
9
- "env": {
10
- "MNEMOVERSE_API_KEY": {
11
- "value": "mk_live_YOUR_KEY",
12
- "description": "Your Mnemoverse API key (starts with mk_live_). Get one free at https://console.mnemoverse.com",
13
- "required": true,
14
- "secret": true
15
- }
16
- },
17
- "package": {
18
- "registry": "npm",
19
- "name": "@mnemoverse/mcp-memory-server"
20
- },
21
- "metadata": {
22
- "homepage": "https://mnemoverse.com/docs/api/mcp-server",
23
- "repository": "https://github.com/mnemoverse/mcp-memory-server",
24
- "license": "MIT",
25
- "author": "Mnemoverse",
26
- "tags": ["memory", "persistent", "ai-agents", "cross-tool", "oauth"]
27
- }
28
- }
package/src/index.ts DELETED
@@ -1,464 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { createRequire } from "node:module";
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { z } from "zod";
7
-
8
- // Version is read at runtime from package.json so there is exactly one place
9
- // to bump on each release. Works both from `dist/` during local dev and from
10
- // `node_modules/@mnemoverse/mcp-memory-server/dist/` after an npm install.
11
- const require = createRequire(import.meta.url);
12
- const pkg = require("../package.json") as { version: string };
13
-
14
- const API_URL =
15
- process.env.MNEMOVERSE_API_URL || "https://core.mnemoverse.com/api/v1";
16
- const API_KEY = process.env.MNEMOVERSE_API_KEY || "";
17
-
18
- // Hard cap on tool result size — required by Claude Connectors Directory
19
- // (https://support.claude.com/en/articles/12922490-remote-mcp-server-submission-guide).
20
- // Approximate token count = chars / 4. Cap at 24,000 tokens to leave headroom under the 25K limit.
21
- const MAX_RESULT_CHARS = 24_000 * 4;
22
-
23
- if (!API_KEY) {
24
- console.error(
25
- "Error: MNEMOVERSE_API_KEY environment variable is required.\n" +
26
- "Get your free key at https://console.mnemoverse.com",
27
- );
28
- process.exit(1);
29
- }
30
-
31
- /**
32
- * Fetch from the Mnemoverse core API with authentication.
33
- *
34
- * Generic so call sites can declare the expected response shape:
35
- *
36
- * const r = await apiFetch<{ stored: boolean; atom_id: string }>("/memory/write", { ... });
37
- *
38
- * Handles 204 No Content and empty bodies defensively — FastAPI DELETE
39
- * handlers may switch to 204 in the future even though today they return
40
- * a JSON body.
41
- *
42
- * @throws Error with message `Mnemoverse API error {status}: {body}` on non-2xx.
43
- */
44
- async function apiFetch<T = unknown>(
45
- path: string,
46
- options: RequestInit = {},
47
- ): Promise<T> {
48
- const res = await fetch(`${API_URL}${path}`, {
49
- ...options,
50
- headers: {
51
- "Content-Type": "application/json",
52
- "X-Api-Key": API_KEY,
53
- ...((options.headers as Record<string, string>) || {}),
54
- },
55
- });
56
-
57
- if (!res.ok) {
58
- const text = await res.text();
59
- throw new Error(`Mnemoverse API error ${res.status}: ${text}`);
60
- }
61
-
62
- // 204 No Content or empty body — return an empty object cast as T so
63
- // call sites using optional chaining still work without crashing.
64
- if (res.status === 204 || res.headers.get("content-length") === "0") {
65
- return {} as T;
66
- }
67
-
68
- return (await res.json()) as T;
69
- }
70
-
71
- /**
72
- * Truncate a result string to MAX_RESULT_CHARS, appending a notice if truncated.
73
- * Required by Claude Connectors Directory submission policy.
74
- *
75
- * Defensive against splitting UTF-16 surrogate pairs: if the character right
76
- * before the cut point is a high surrogate (U+D800–U+DBFF), drop it so the
77
- * result stays well-formed. Otherwise an emoji or non-BMP character at the
78
- * boundary can produce a lone surrogate and corrupt downstream JSON encoding.
79
- */
80
- function capResult(text: string): string {
81
- if (text.length <= MAX_RESULT_CHARS) return text;
82
- let truncated = text.slice(0, MAX_RESULT_CHARS - 200);
83
- const lastCode = truncated.charCodeAt(truncated.length - 1);
84
- if (lastCode >= 0xd800 && lastCode <= 0xdbff) {
85
- truncated = truncated.slice(0, -1);
86
- }
87
- return (
88
- truncated +
89
- `\n\n[…truncated to fit 25K token limit. Use a more specific query or smaller top_k to see all results.]`
90
- );
91
- }
92
-
93
- // --- Server setup ---
94
-
95
- const server = new McpServer({
96
- name: "mnemoverse-memory",
97
- version: pkg.version,
98
- });
99
-
100
- // --- Tool: memory_write ---
101
-
102
- server.registerTool(
103
- "memory_write",
104
- {
105
- description:
106
- "Store a memory that should persist across sessions — preferences, decisions, lessons learned, project facts, people and roles. Memories you store are also accessible from Claude, ChatGPT, Cursor, VS Code, and any other AI tool the user connects to Mnemoverse — write once, recall everywhere. Call this PROACTIVELY whenever the user states a preference, makes a decision, or you learn something important. Don't wait to be asked — if it's worth remembering, store it now.",
107
- inputSchema: {
108
- content: z
109
- .string()
110
- .min(1)
111
- .max(10000)
112
- .describe("The memory to store — what happened, what was learned"),
113
- concepts: z
114
- .array(z.string())
115
- .optional()
116
- .describe(
117
- "Key concepts for linking related memories (e.g. ['deploy', 'friday', 'staging'])",
118
- ),
119
- domain: z
120
- .string()
121
- .optional()
122
- .describe(
123
- "Namespace to organize memories (e.g. 'engineering', 'user:alice', 'project:acme')",
124
- ),
125
- },
126
- annotations: {
127
- title: "Store Memory",
128
- readOnlyHint: false,
129
- destructiveHint: false,
130
- idempotentHint: false,
131
- openWorldHint: true,
132
- },
133
- },
134
- async ({ content, concepts, domain }) => {
135
- const r = await apiFetch<{
136
- stored?: boolean;
137
- atom_id?: string | null;
138
- importance?: number;
139
- reason?: string;
140
- }>("/memory/write", {
141
- method: "POST",
142
- body: JSON.stringify({
143
- content,
144
- concepts: concepts || [],
145
- domain: domain || "general",
146
- }),
147
- });
148
-
149
- const importance = (r?.importance ?? 0).toFixed(2);
150
-
151
- if (r?.stored) {
152
- return {
153
- content: [
154
- {
155
- type: "text" as const,
156
- text: `Stored (importance: ${importance}). ID: ${r.atom_id ?? "unknown"}`,
157
- },
158
- ],
159
- };
160
- }
161
- return {
162
- content: [
163
- {
164
- type: "text" as const,
165
- text: `Filtered — ${r?.reason ?? "unknown reason"} (importance: ${importance})`,
166
- },
167
- ],
168
- };
169
- },
170
- );
171
-
172
- // --- Tool: memory_read ---
173
-
174
- server.registerTool(
175
- "memory_read",
176
- {
177
- description:
178
- "ALWAYS call this tool first when the user asks a question about preferences, past decisions, project setup, people, or anything that might have been discussed before. This is your long-term memory — it persists across sessions and tools (Claude, ChatGPT, Cursor, VS Code, and any other AI tool the user connects). Search by natural language query. If you have any doubt whether you know something — check memory first.",
179
- inputSchema: {
180
- query: z
181
- .string()
182
- .min(1)
183
- .max(5000)
184
- .describe("Natural language query — what are you looking for?"),
185
- top_k: z
186
- .number()
187
- .int()
188
- .min(1)
189
- .max(50)
190
- .optional()
191
- .describe("Max results to return (default: 5)"),
192
- domain: z
193
- .string()
194
- .optional()
195
- .describe("Filter by domain namespace"),
196
- },
197
- annotations: {
198
- title: "Search Memories",
199
- readOnlyHint: true,
200
- destructiveHint: false,
201
- idempotentHint: true,
202
- openWorldHint: true,
203
- },
204
- },
205
- async ({ query, top_k, domain }) => {
206
- const r = await apiFetch<{
207
- items?: Array<{
208
- content?: string;
209
- relevance?: number;
210
- concepts?: string[];
211
- domain?: string;
212
- }>;
213
- search_time_ms?: number;
214
- }>("/memory/read", {
215
- method: "POST",
216
- body: JSON.stringify({
217
- query,
218
- top_k: top_k || 5,
219
- domain: domain || undefined,
220
- include_associations: true,
221
- }),
222
- });
223
-
224
- const items = Array.isArray(r?.items) ? r.items : [];
225
-
226
- if (items.length === 0) {
227
- return {
228
- content: [
229
- { type: "text" as const, text: "No memories found for this query." },
230
- ],
231
- };
232
- }
233
-
234
- const lines = items.map((item, i) => {
235
- const relevance = ((item?.relevance ?? 0) * 100).toFixed(0);
236
- const content = item?.content ?? "(empty)";
237
- const concepts = Array.isArray(item?.concepts) && item.concepts.length > 0
238
- ? ` (${item.concepts.join(", ")})`
239
- : "";
240
- return `${i + 1}. [${relevance}%] ${content}${concepts}`;
241
- });
242
-
243
- const searchMs = (r?.search_time_ms ?? 0).toFixed(0);
244
- const text = lines.join("\n\n") + `\n\n(${searchMs}ms)`;
245
-
246
- return {
247
- content: [
248
- {
249
- type: "text" as const,
250
- text: capResult(text),
251
- },
252
- ],
253
- };
254
- },
255
- );
256
-
257
- // --- Tool: memory_feedback ---
258
-
259
- server.registerTool(
260
- "memory_feedback",
261
- {
262
- description:
263
- "Report whether a retrieved memory was helpful. Positive feedback makes memories easier to find next time across all tools. Negative feedback lets them fade. Call this after using memories from memory_read.",
264
- inputSchema: {
265
- atom_ids: z
266
- .array(z.string())
267
- .min(1)
268
- .describe("IDs of memories to give feedback on (from memory_read results)"),
269
- outcome: z
270
- .number()
271
- .min(-1)
272
- .max(1)
273
- .describe("How helpful was this? 1.0 = very helpful, 0 = neutral, -1.0 = harmful/wrong"),
274
- },
275
- annotations: {
276
- title: "Rate Memory Helpfulness",
277
- readOnlyHint: false,
278
- // Feedback permanently mutates the memory's valence and importance
279
- // scores on the backend — per MCP spec, that is a destructive update
280
- // to the stored state (cf. ToolAnnotations.destructiveHint), even
281
- // though the caller intends it as quality signal rather than delete.
282
- destructiveHint: true,
283
- idempotentHint: false,
284
- openWorldHint: true,
285
- },
286
- },
287
- async ({ atom_ids, outcome }) => {
288
- const r = await apiFetch<{ updated_count?: number }>("/memory/feedback", {
289
- method: "POST",
290
- body: JSON.stringify({ atom_ids, outcome }),
291
- });
292
-
293
- const count = r?.updated_count ?? 0;
294
-
295
- return {
296
- content: [
297
- {
298
- type: "text" as const,
299
- text: `Feedback recorded for ${count} memor${count === 1 ? "y" : "ies"}.`,
300
- },
301
- ],
302
- };
303
- },
304
- );
305
-
306
- // --- Tool: memory_stats ---
307
-
308
- server.registerTool(
309
- "memory_stats",
310
- {
311
- description:
312
- "Get memory statistics — how many memories are stored, which domains exist, average quality scores. These memories are shared with all AI tools the user has connected to Mnemoverse. Useful for understanding the current state of memory.",
313
- inputSchema: {},
314
- annotations: {
315
- title: "Memory Statistics",
316
- readOnlyHint: true,
317
- destructiveHint: false,
318
- idempotentHint: true,
319
- openWorldHint: true,
320
- },
321
- },
322
- async () => {
323
- const r = await apiFetch<{
324
- total_atoms?: number;
325
- episodes?: number;
326
- prototypes?: number;
327
- hebbian_edges?: number;
328
- domains?: string[];
329
- avg_valence?: number;
330
- avg_importance?: number;
331
- }>("/memory/stats");
332
-
333
- const domains = Array.isArray(r?.domains) && r.domains.length > 0
334
- ? r.domains.join(", ")
335
- : "general";
336
-
337
- const text = [
338
- `Memories: ${r?.total_atoms ?? 0} (${r?.episodes ?? 0} episodes, ${r?.prototypes ?? 0} prototypes)`,
339
- `Associations: ${r?.hebbian_edges ?? 0} Hebbian edges`,
340
- `Domains: ${domains}`,
341
- `Avg quality: valence ${(r?.avg_valence ?? 0).toFixed(2)}, importance ${(r?.avg_importance ?? 0).toFixed(2)}`,
342
- ].join("\n");
343
-
344
- return { content: [{ type: "text" as const, text }] };
345
- },
346
- );
347
-
348
- // --- Tool: memory_delete ---
349
-
350
- server.registerTool(
351
- "memory_delete",
352
- {
353
- description:
354
- "Permanently delete a single memory by its atom_id. Use when the user explicitly asks to forget something specific, or when you stored a wrong fact that needs correcting. The deletion is irreversible — the memory is gone for good. For broad cleanup of an entire topic, prefer memory_delete_domain.",
355
- inputSchema: {
356
- atom_id: z
357
- .string()
358
- .min(1)
359
- .describe(
360
- "The atom_id of the memory to delete (from memory_read results — each item has an id)",
361
- ),
362
- },
363
- annotations: {
364
- title: "Delete a Memory",
365
- readOnlyHint: false,
366
- destructiveHint: true,
367
- idempotentHint: true,
368
- openWorldHint: true,
369
- },
370
- },
371
- async ({ atom_id }) => {
372
- // Core API returns { deleted: <count>, atom_id }. count == 0 means
373
- // the atom didn't exist (or was already removed). count >= 1 means
374
- // it was deleted.
375
- const r = await apiFetch<{ deleted?: number; atom_id?: string }>(
376
- `/memory/atoms/${encodeURIComponent(atom_id)}`,
377
- { method: "DELETE" },
378
- );
379
-
380
- if (!r?.deleted) {
381
- return {
382
- content: [
383
- {
384
- type: "text" as const,
385
- text: `No memory found with id ${atom_id}.`,
386
- },
387
- ],
388
- };
389
- }
390
-
391
- return {
392
- content: [
393
- {
394
- type: "text" as const,
395
- text: `Deleted memory ${atom_id}.`,
396
- },
397
- ],
398
- };
399
- },
400
- );
401
-
402
- // --- Tool: memory_delete_domain ---
403
-
404
- server.registerTool(
405
- "memory_delete_domain",
406
- {
407
- description:
408
- "Permanently delete ALL memories in a given domain. Use when the user wants to clean up an entire topic — e.g., 'forget everything about project X' or 'wipe my benchmark experiments'. The deletion is irreversible. List domains first with memory_stats to confirm the exact name. Refuse to call this without an explicit user request — it is much more destructive than memory_delete.",
409
- inputSchema: {
410
- domain: z
411
- .string()
412
- .min(1)
413
- .max(200)
414
- .describe(
415
- "The domain namespace to wipe (e.g., 'project:old', 'experiments-2025'). Must match exactly.",
416
- ),
417
- confirm: z
418
- .literal(true)
419
- .describe(
420
- "Must be exactly true to proceed. Acts as a safety interlock against accidental invocation.",
421
- ),
422
- },
423
- annotations: {
424
- title: "Delete an Entire Memory Domain",
425
- readOnlyHint: false,
426
- destructiveHint: true,
427
- idempotentHint: true,
428
- openWorldHint: true,
429
- },
430
- },
431
- // The `confirm: z.literal(true)` in the input schema is the safety
432
- // interlock — Zod rejects any call without confirm === true before it
433
- // reaches this handler, so no runtime re-check is needed here.
434
- async ({ domain }) => {
435
- const r = await apiFetch<{ deleted?: number; domain?: string }>(
436
- `/memory/domain/${encodeURIComponent(domain)}`,
437
- { method: "DELETE" },
438
- );
439
-
440
- const count = r?.deleted ?? 0;
441
- const domainName = r?.domain ?? domain;
442
-
443
- return {
444
- content: [
445
- {
446
- type: "text" as const,
447
- text: `Deleted ${count} ${count === 1 ? "memory" : "memories"} from domain "${domainName}".`,
448
- },
449
- ],
450
- };
451
- },
452
- );
453
-
454
- // --- Start ---
455
-
456
- async function main() {
457
- const transport = new StdioServerTransport();
458
- await server.connect(transport);
459
- }
460
-
461
- main().catch((err) => {
462
- console.error("MCP server error:", err);
463
- process.exit(1);
464
- });
package/tsconfig.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "Node16",
5
- "moduleResolution": "Node16",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "declaration": true,
12
- "sourceMap": true
13
- },
14
- "include": ["src/**/*"]
15
- }