@mnemoverse/mcp-memory-server 0.11.0 → 0.12.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/README.md CHANGED
@@ -325,28 +325,50 @@ saw and diff it against what the server serves today, by version.
325
325
  not have one; once declared, that schema's fields are add-only under this
326
326
  same rule. Where this server's output schema deliberately differs from the
327
327
  hosted connector's, the CHANGELOG entry says so; today that is
328
+ `memory_id` in every output schema, a plain string here and a
329
+ GUID-validated string there (this package's ids are opaque);
328
330
  `memory_list_recent`'s `next_cursor`, optional here (absent when the
329
331
  service sent a continuation token this client will not pass on) and
330
- required there.
332
+ required there; `memory_stats`, which carries five optional fields
333
+ (`episodes`, `prototypes`, `hebbian_edges`, `avg_valence`,
334
+ `avg_importance`) the connector's schema does not declare; the room
335
+ tools (`memory_create_room`, `memory_invite_to_room`, `memory_join_room`,
336
+ `memory_list_rooms`), where several fields the connector marks required
337
+ are optional here (`name`, `scope`, `already_member`, and the invite's
338
+ `code`, `scope`, `room_address` and `expires_at`), because a value core
339
+ did not send is an honest outcome here rather than a placeholder; and
340
+ `memory_list_rooms` and `vault_list`, which drop a row with no usable
341
+ identity (`room_id`, `address`, `role`; `alias`, `context`) from the data,
342
+ reported once on stderr, instead of emitting empty strings into required
343
+ fields.
331
344
  - **Removing or renaming a tool or a tool's input parameter, or dropping or
332
345
  renaming a declared annotation field,** is announced one MINOR ahead: the
333
346
  tool (or parameter) stays, its description says
334
347
  `deprecated since x.y, removed in x.z`, and the change lands only in the
335
348
  announced version, with its CHANGELOG line. A renamed parameter is accepted
336
349
  under both names until then (0.11: `memory_feedback`'s `atom_ids` became
337
- `memory_ids`). A rename is announced by naming
350
+ `memory_ids`; its removal, first announced for 0.12, lands in 0.13, since
351
+ 0.12 shipped sooner than that announcement assumed). A rename is announced by naming
338
352
  both the old and the new name; the version pair alone does not say what a
339
353
  client should look for. Because a MINOR may add a field but not remove one, a
340
354
  renamed annotation field is declared under both names until the announced
341
355
  version.
342
356
  - Any difference between two servers of the same version is a bug. Report it
343
- with both `tools/list` outputs.
357
+ with both `tools/list` outputs. One exception, by configuration: a server
358
+ built on the `/shared` entry point may ask for its own noun in the three
359
+ descriptions that name the server (`wording.serverNoun`, below), which
360
+ changes those three description strings and nothing else; tool names,
361
+ input and output schemas and annotations never vary by configuration.
344
362
 
345
- The list above is the 0.11 surface: ten tools, each declaring all four hints.
363
+ The list above is the 0.12 surface: ten tools, each declaring all four hints.
346
364
  The hosted connector at `mcp.mnemoverse.com/mcp` serves the same ten.
347
365
 
348
366
  **If the hosted connector stops answering in a session.** A client can keep showing the connector as connected while every call in that session fails with "not connected". Reconnecting it on claude.ai does not revive a session that is already stuck; reconnect from inside the session instead (in Claude Code, `/mcp`, then sign in again). Meanwhile this local server, set up with an API key from the same account as in the Quick Start, reaches the same memory and does not depend on that session's sign-in.
349
367
 
368
+ ### Building a second MCP server on this package
369
+
370
+ `@mnemoverse/mcp-memory-server/shared` is the entry point another server registers these same tools from, instead of keeping its own copy (ADR-025, mnemoverse-core). It exports `registerMemoryTools`/`registerMemoryPrompts`/`registerMemoryResources`, the three typed error classes (`ApiError`, `NetworkError`, `UnreadableBodyError`), `MAX_RESULT_CHARS`/`capResult`, and two optional dependencies a hosted deployment injects to speak in its own voice: `wording` (its own server noun and error vocabulary, including an OAuth mode under which no 401 or 403 explanation names an API key) and `writeAuthor` (vouching for the end user behind a write). See [`docs/shared.md`](./docs/shared.md) for the full contract.
371
+
350
372
  ## Use cases
351
373
 
352
374
  The pattern that pays off first is cross-tool continuity: a decision made while pairing in Claude Code is there when you open Cursor an hour later, and the preference you stated in VS Code holds in a ChatGPT session that evening. Teams use shared rooms the same way — one place where an agent's lessons about a codebase accumulate instead of being re-taught per seat. And because recall re-ranks from feedback, the memories that keep proving useful surface first, which matters once a store grows past what anyone curates by hand.
package/dist/errors.d.ts CHANGED
@@ -76,6 +76,41 @@
76
76
  * outright, see `validatedKeysUrl` below, but this constant stays the
77
77
  * fallback for both). */
78
78
  export declare const KEYS_URL = "https://console.mnemoverse.com/dashboard/keys";
79
+ /**
80
+ * How a server that registers these tools wants its failures worded (STEP4-2,
81
+ * STEP4-3, owner decisions 2026-09-24). Every field is optional, and every
82
+ * field's absence means exactly what this file already does today: the
83
+ * whole point is that a consumer supplying no `wording` at all gets
84
+ * byte-identical text to before this type existed.
85
+ *
86
+ * - `serverNoun`: what the three tool descriptions that name themselves
87
+ * call this deployment ("this server" vs "this connector"). Read by
88
+ * src/tools.ts at registration time; ignored by this module.
89
+ * - `auth`: which credential the CALLER holds, not which one core issued.
90
+ * "api-key" (default) keeps every existing MNEMOVERSE_API_KEY-flavoured
91
+ * sentence. "oauth" is for a server whose user never sees an API key at
92
+ * all (a hosted connector minting a key on their behalf): no 401/403
93
+ * explanation under this mode names MNEMOVERSE_API_KEY, an env var, an
94
+ * MCP config file, or the keys console; each says what an OAuth user can
95
+ * actually do instead (reconnect, sign in again, check the plan, wait
96
+ * for the retry window).
97
+ * - `keysUrl`: replaces {@link KEYS_URL} wherever the api-key vocabulary
98
+ * prints a console URL, for a deployment whose key-management page is
99
+ * not console.mnemoverse.com. Has no effect under `auth: "oauth"`, which
100
+ * prints no console URL at all.
101
+ * - `rawDetail`: whether the wire body's raw tail (STEP4-3; e.g. `Raw
102
+ * detail — Mnemoverse API error 401 on …`) is appended after the
103
+ * guidance. Defaults to `true`, unchanged from every release before this
104
+ * one. Held in reserve for a deployment whose core-side error envelopes
105
+ * are found to echo request content back in `details`: set to `false`
106
+ * only once that check finds something to hide.
107
+ */
108
+ export interface Wording {
109
+ serverNoun?: "this server" | "this connector";
110
+ auth?: "api-key" | "oauth";
111
+ keysUrl?: string;
112
+ rawDetail?: boolean;
113
+ }
79
114
  /** Everything known about one failed call, at the moment it failed. */
80
115
  export interface ApiFailure {
81
116
  /** HTTP status. */
@@ -146,12 +181,16 @@ export declare function parseErrorEnvelope(body: string): ErrorEnvelope;
146
181
  export declare function retryAfterSeconds(header: string | null | undefined): number | undefined;
147
182
  /**
148
183
  * The agent-facing explanation for one failed call, with the raw body kept
149
- * after it.
184
+ * after it (unless `wording.rawDetail === false`, STEP4-3).
150
185
  *
151
186
  * Total by construction: every status reaches a sentence, and the fallback says
152
187
  * that it has no specific guidance instead of inventing some.
188
+ *
189
+ * `wording` (STEP4-2/3, owner 2026-09-24) is optional and, absent, resolves
190
+ * to exactly today's behaviour: every call site before this release passes
191
+ * none, so every existing pin stays byte-identical.
153
192
  */
154
- export declare function explainApiFailure(f: ApiFailure): string;
193
+ export declare function explainApiFailure(f: ApiFailure, wording?: Wording): string;
155
194
  /**
156
195
  * The request never got an answer at all: DNS, connectivity, a host that does
157
196
  * not listen, or this client's own probe deadline firing.
@@ -172,7 +211,7 @@ export declare function explainApiFailure(f: ApiFailure): string;
172
211
  * that case now. Every sentence below asserts that nothing answered, so it may
173
212
  * only be reached when nothing did.
174
213
  */
175
- export declare function explainNetworkFailure(method: string, path: string, cause: unknown): string;
214
+ export declare function explainNetworkFailure(method: string, path: string, cause: unknown, wording?: Wording): string;
176
215
  /** A 2xx-or-not response whose BODY this client could not turn into the JSON
177
216
  * this API speaks. The status is known — a reply arrived — which is the whole
178
217
  * difference between this and {@link ApiFailure} or a transport failure. */
@@ -212,7 +251,7 @@ export interface UnreadableBody {
212
251
  * say whether the operation ran, because a body it cannot read is no evidence
213
252
  * either way. That last clause matters most for a write.
214
253
  */
215
- export declare function explainUnreadableBody(f: UnreadableBody): string;
254
+ export declare function explainUnreadableBody(f: UnreadableBody, wording?: Wording): string;
216
255
  /**
217
256
  * {@link explainUnreadableBody} as an error, so a caller can tell "a reply
218
257
  * arrived and was unreadable" from "no reply arrived" by TYPE rather than by
@@ -222,14 +261,25 @@ export declare class UnreadableBodyError extends Error {
222
261
  readonly status: number;
223
262
  readonly method: string;
224
263
  readonly path: string;
225
- constructor(f: UnreadableBody);
264
+ /** The bytes that failed to parse, when they were read at all. Kept so
265
+ * {@link withWording} re-renders the same arm of the explanation. */
266
+ readonly bodyPreview: string | undefined;
267
+ /** `wording` (STEP4-2/3): optional, and absent (every call site before this
268
+ * release) reproduces today's message exactly: see {@link explainUnreadableBody}. */
269
+ constructor(f: UnreadableBody, wording?: Wording);
270
+ /** The same failure, explained under `wording`: see {@link ApiError.withWording}. */
271
+ withWording(wording: Wording | undefined): UnreadableBodyError;
226
272
  }
227
273
  /** {@link explainNetworkFailure} as an error, so `instanceof` can tell a
228
274
  * transport failure from an HTTP one without reading either message. */
229
275
  export declare class NetworkError extends Error {
230
276
  readonly method: string;
231
277
  readonly path: string;
232
- constructor(method: string, path: string, cause: unknown);
278
+ /** `wording` (STEP4-2/3): optional, and absent (every call site before this
279
+ * release) reproduces today's message exactly: see {@link explainNetworkFailure}. */
280
+ constructor(method: string, path: string, cause: unknown, wording?: Wording);
281
+ /** The same failure, explained under `wording`: see {@link ApiError.withWording}. */
282
+ withWording(wording: Wording | undefined): NetworkError;
233
283
  }
234
284
  /**
235
285
  * A failed API call, as an ERROR OBJECT rather than a parsed string.
@@ -249,7 +299,22 @@ export declare class ApiError extends Error {
249
299
  readonly path: string;
250
300
  /** The engine's envelope, already parsed — so a caller never re-parses. */
251
301
  readonly envelope: ErrorEnvelope;
252
- constructor(f: ApiFailure);
302
+ /** The `Retry-After` header as it arrived, when the response carried one.
303
+ * Kept so {@link withWording} re-renders a 429 with its retry sentence. */
304
+ readonly retryAfter: string | null | undefined;
305
+ /** `wording` (STEP4-2/3): optional, and absent (every call site before this
306
+ * release) reproduces today's message exactly: see {@link explainApiFailure}. */
307
+ constructor(f: ApiFailure, wording?: Wording);
308
+ /**
309
+ * The same failure, explained under `wording`: a new instance of this
310
+ * class whose message is what the constructor would have produced with
311
+ * that `wording`, every field and {@link isBare404} unchanged. The message
312
+ * is a pure function of the failure and the wording, so re-rendering an
313
+ * instance under the wording it was built with gives the same text back.
314
+ * This is how `MemoryToolDeps.wording` reaches an error the consumer's
315
+ * `apiFetch` built without it: see {@link rewordFailure}.
316
+ */
317
+ withWording(wording: Wording | undefined): ApiError;
253
318
  /**
254
319
  * A 404 the ENGINE did not answer: silence, or the router's literal
255
320
  * defaults. This is what a path the deployment does not serve looks like
@@ -269,3 +334,21 @@ export declare class ApiError extends Error {
269
334
  */
270
335
  get isBare404(): boolean;
271
336
  }
337
+ /**
338
+ * Re-explain one of this module's three errors under `wording`; hand
339
+ * anything else back untouched.
340
+ *
341
+ * This is how `MemoryToolDeps.wording` reaches the error text (STEP4-2): a
342
+ * consumer's `apiFetch` constructs `ApiError`, `NetworkError` and
343
+ * `UnreadableBodyError` however it likes, and `registerMemoryTools` passes
344
+ * every rejection through here with the `wording` it was given, so the
345
+ * consumer states its wording once, on `deps`, and the constructors need no
346
+ * second copy. Re-rendering is idempotent (see {@link ApiError.withWording}),
347
+ * so an `apiFetch` that does pass the same `wording` to the constructors
348
+ * gets the same text either way; one that passed a different wording gets
349
+ * `deps.wording`, the one source of truth for this registration. A
350
+ * rejection that is none of the three classes (a plain `Error`, an
351
+ * `McpError`) is returned as is: it carries no wording to apply, and its
352
+ * identity may matter to whoever threw it.
353
+ */
354
+ export declare function rewordFailure(e: unknown, wording: Wording | undefined): unknown;