@mnemoverse/mcp-memory-server 0.10.1 → 0.11.0

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/render.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * the line are simply omitted.
12
12
  */
13
13
  import { MAX_DOMAIN_TAG_LITERAL, exactLiteral } from "./names.js";
14
- import { parseAsUtc } from "./time.js";
14
+ import { parseAsUtc, utcInstant } from "./time.js";
15
15
  /**
16
16
  * Sanitize a string for inline interpolation into tool output (CN-032: hostile
17
17
  * connectors choose their own agent_name). The single implementation, imported
@@ -60,17 +60,32 @@ export function safeInline(s, cap = 200) {
60
60
  .slice(0, cap);
61
61
  }
62
62
  /**
63
- * ` [by X]` / ` [by X · external]` — agent identity only, never the human
64
- * `principal` (may be an email / PII), even though the response carries it.
63
+ * "X" / "X · external": agent identity only, never the human `principal`
64
+ * (may be an email / PII), even though the response carries it. Empty string
65
+ * when there is no renderable name.
66
+ *
67
+ * Extracted from `formatAuthorTag` (S4, structured-output plan) so
68
+ * `structuredItem` below can put the same sanitised name into
69
+ * `structuredContent.author` without re-deriving it, and so the two never
70
+ * drift: `formatAuthorTag` now builds its bracketed text FROM this value
71
+ * rather than computing its own copy.
65
72
  */
66
- export function formatAuthorTag(p) {
73
+ export function authorName(p) {
67
74
  if (!p)
68
75
  return "";
69
76
  const raw = p.agent_name || p.agent || p.client_env || "";
70
77
  const who = safeInline(raw, 64);
71
78
  if (!who)
72
79
  return "";
73
- return p.is_external ? ` [by ${who} · external]` : ` [by ${who}]`;
80
+ return p.is_external ? `${who} · external` : who;
81
+ }
82
+ /**
83
+ * ` [by X]` / ` [by X · external]` — agent identity only, never the human
84
+ * `principal` (may be an email / PII), even though the response carries it.
85
+ */
86
+ export function formatAuthorTag(p) {
87
+ const who = authorName(p);
88
+ return who ? ` [by ${who}]` : "";
74
89
  }
75
90
  /**
76
91
  * ` @"domain"` — which store the memory actually came from, printed so it can
@@ -170,6 +185,45 @@ export function formatReadItem(item, index) {
170
185
  const head = `${index + 1}. ${content}${concepts}${formatDomainTag(item?.domain)}${formatAuthorTag(item?.provenance)}${formatDateTag(item?.created_at)}`;
171
186
  return item?.atom_id ? `${head}\n id: ${item.atom_id}` : head;
172
187
  }
188
+ /**
189
+ * The `structuredContent` twin of {@link formatReadItem} (S4, structured-output
190
+ * plan): the same item, shaped for memory_read's `outputSchema` instead of for
191
+ * a line of text.
192
+ *
193
+ * PRECONDITION, enforced by the caller (src/tools.ts) before this is ever
194
+ * invoked: `item.atom_id`, `item.content` and `item.domain` are all strings.
195
+ * core's MemoryItemSchema sends all three on every item; a response that
196
+ * doesn't is caught by the handler's item guard and answered with
197
+ * `unreadableAnswerReply` before `structuredItem` is reached, so the casts
198
+ * below are a documented precondition, not a runtime assumption made here.
199
+ *
200
+ * `content` is carried EXACTLY, uncapped and unnormalised (decision OD-11,
201
+ * owner, 2026-09-23): unlike the text line, which goes through `capResult`
202
+ * for the 25K-token result-size cap, `structuredContent` is not capped
203
+ * anywhere else in this package either (memory_write's `reason` is the only
204
+ * normalised structured field, and that's control/bidi/zero-width hygiene on
205
+ * a diagnostic string, not a length cap on the memory itself); a client
206
+ * reading structured data reads `content` as the stored memory, and a
207
+ * silently shorter value there would be a different kind of lie than a
208
+ * truncated text block with a notice at the end.
209
+ */
210
+ export function structuredItem(item) {
211
+ const author = authorName(item.provenance);
212
+ const created = utcInstant(item.created_at);
213
+ return {
214
+ memory_id: item.atom_id,
215
+ content: item.content,
216
+ domain: item.domain,
217
+ // The rule the text already applies through formatDateTag: a value that
218
+ // does not parse as a date is no creation instant, whatever its type, and
219
+ // the field promises a UTC ISO-8601 instant. A value that states its
220
+ // offset is carried as sent; an offset-less one (UTC by contract) is
221
+ // re-emitted as the UTC instant the text renders, since a consumer
222
+ // would otherwise read it as local time (src/time.ts, utcInstant).
223
+ ...(created !== null ? { created_at: created } : {}),
224
+ ...(author ? { author } : {}),
225
+ };
226
+ }
173
227
  /**
174
228
  * One memory_list_recent line — a feed entry, not a search hit: no
175
229
  * relevance, date leads because the feed is ORDERED by it.
@@ -183,6 +237,14 @@ export function formatRecentItem(item, index) {
183
237
  const head = `${index + 1}. ${date ? `[${date}] ` : ""}${content}${concepts}${formatDomainTag(item?.domain)}${formatAuthorTag(item?.provenance)}`;
184
238
  return item?.atom_id ? `${head}\n id: ${item.atom_id}` : head;
185
239
  }
240
+ /**
241
+ * The shape of a continuation token this client is willing to pass on
242
+ * (CN-032): the server-supplied cursor is opaque, so this is an allowlist
243
+ * of bytes, not a format. One constant for BOTH surfaces, the text (below)
244
+ * and `structuredContent.next_cursor` (src/tools.ts), so they cannot
245
+ * disagree about which token is passable.
246
+ */
247
+ export const CURSOR_RE = /^[A-Za-z0-9_=-]{1,512}$/;
186
248
  /**
187
249
  * Full feed page: items newest-first + how to continue / that it's over.
188
250
  *
@@ -194,6 +256,9 @@ export function formatRecentItem(item, index) {
194
256
  * 2026-08-08). Once-per-answer still holds — withDomainEscapeLegend is
195
257
  * at-most-once by construction (src/names.ts).
196
258
  */
259
+ // `nextCursor` is typed loosely on purpose: it is a server-supplied wire
260
+ // value, and a number where a string was promised must fail the gate below
261
+ // rather than be coerced into it by the regex test (review, 2026-09-23).
197
262
  export function formatRecentPage(items, nextCursor) {
198
263
  const lines = items.map((it, i) => formatRecentItem(it, i));
199
264
  // Defense-in-depth (CN-032 posture): the cursor is server-supplied and
@@ -218,7 +283,7 @@ export function formatRecentPage(items, nextCursor) {
218
283
  if (nextCursor == null) {
219
284
  tail = `\n\n(end of feed — nothing older)`;
220
285
  }
221
- else if (/^[A-Za-z0-9_=-]{1,512}$/.test(nextCursor)) {
286
+ else if (typeof nextCursor === "string" && CURSOR_RE.test(nextCursor)) {
222
287
  tail = `\n\nMore older entries exist — pass cursor: ${nextCursor}`;
223
288
  }
224
289
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
@@ -69,3 +69,47 @@ export declare function recentRequestBody(a: RecentArgs): Record<string, unknown
69
69
  /** memory_write. `"general"` is the server-side default made explicit, and is
70
70
  * what 0.8.0 sent — NOT a normalisation of the caller's value. */
71
71
  export declare function writeRequestBody(a: WriteArgs): Record<string, unknown>;
72
+ /**
73
+ * Refuse a docs placeholder key from CONFIGURATION ALONE, before any tool
74
+ * call sends it anywhere.
75
+ *
76
+ * WHY THIS EXISTS. The engine sees this server calling it again and again
77
+ * with the example key from the install snippets. The agent got a 401, got
78
+ * this server's generic replace-the-key sentence, and called again. A value this client can recognise as a placeholder WITHOUT any
79
+ * request is still worth refusing before the network, exactly as
80
+ * `refuseInsecureBaseUrl` (src/index.ts) refuses an insecure
81
+ * MNEMOVERSE_API_URL before the network, for the same reason: a config-only
82
+ * guard is checked once and costs nothing, while a wasted round trip costs a
83
+ * rate-limited request every time an agent cannot help but retry.
84
+ *
85
+ * FIRES ONLY FOR VALUES THAT ARE CERTAINLY PLACEHOLDERS: two shapes, and
86
+ * nothing else, however odd:
87
+ *
88
+ * (a) mk_live_ followed by an upper-case label (mk_live_YOUR_KEY,
89
+ * mk_live_USER_KEY, mk_live_CODING_AGENT_KEY), the same shape the
90
+ * engine itself classifies as placeholder_key.
91
+ * (b) mk_live_ followed only by the letter x, at least four of them,
92
+ * either case (mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx), the
93
+ * template value shipped in src/configs/source.json.
94
+ *
95
+ * A self-hosted static-auth deployment can use an arbitrary key that does
96
+ * not start with mk_live_ at all, and a TRUNCATED real key (mk_live_deadbeef,
97
+ * say) must still reach the engine, whose answer is more precise than a
98
+ * local guess would be. So both a mixed-case label (mk_live_Your_Key) and
99
+ * anything shaped like a real key are left alone here on purpose, same as
100
+ * every other value this function has never seen.
101
+ *
102
+ * PURE AND EXPORTED, colocated with the request builders rather than with
103
+ * `refuseInsecureBaseUrl` itself, because that one needs a `URL` parse this
104
+ * module has no other reason to import (the shape of the guard is the same,
105
+ * the machinery differs). `apiFetch` in src/index.ts evaluates this once into
106
+ * a module constant next to `BASE_URL_REFUSAL`, and checks both at the same
107
+ * two call sites: inside `apiFetch`, and inside the startup probe.
108
+ *
109
+ * `KEYS_URL` is imported from src/errors.ts rather than repeated here as a
110
+ * second literal, the same console page a 401 already points at.
111
+ */
112
+ export declare function refusePlaceholderKey(key: string): {
113
+ toolCall: string;
114
+ startupLog: string;
115
+ } | undefined;
package/dist/requests.js CHANGED
@@ -30,6 +30,11 @@
30
30
  * here as a second surface needing the same treatment, was withdrawn
31
31
  * 2026-08-20 — deletion is administrative-only now.)
32
32
  */
33
+ // KEYS_URL is imported for refusePlaceholderKey at the bottom of this file:
34
+ // a config-only guard that otherwise has nothing to do with request bodies,
35
+ // but needs the same console URL a 401 already points at (src/errors.ts) and
36
+ // is documented at its own definition rather than here.
37
+ import { KEYS_URL } from "./errors.js";
33
38
  /**
34
39
  * The scope actually searched: what core receives, and therefore the only value
35
40
  * any message about the result may describe.
@@ -75,4 +80,65 @@ export function writeRequestBody(a) {
75
80
  domain: a.domain || "general",
76
81
  };
77
82
  }
83
+ /**
84
+ * Refuse a docs placeholder key from CONFIGURATION ALONE, before any tool
85
+ * call sends it anywhere.
86
+ *
87
+ * WHY THIS EXISTS. The engine sees this server calling it again and again
88
+ * with the example key from the install snippets. The agent got a 401, got
89
+ * this server's generic replace-the-key sentence, and called again. A value this client can recognise as a placeholder WITHOUT any
90
+ * request is still worth refusing before the network, exactly as
91
+ * `refuseInsecureBaseUrl` (src/index.ts) refuses an insecure
92
+ * MNEMOVERSE_API_URL before the network, for the same reason: a config-only
93
+ * guard is checked once and costs nothing, while a wasted round trip costs a
94
+ * rate-limited request every time an agent cannot help but retry.
95
+ *
96
+ * FIRES ONLY FOR VALUES THAT ARE CERTAINLY PLACEHOLDERS: two shapes, and
97
+ * nothing else, however odd:
98
+ *
99
+ * (a) mk_live_ followed by an upper-case label (mk_live_YOUR_KEY,
100
+ * mk_live_USER_KEY, mk_live_CODING_AGENT_KEY), the same shape the
101
+ * engine itself classifies as placeholder_key.
102
+ * (b) mk_live_ followed only by the letter x, at least four of them,
103
+ * either case (mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx), the
104
+ * template value shipped in src/configs/source.json.
105
+ *
106
+ * A self-hosted static-auth deployment can use an arbitrary key that does
107
+ * not start with mk_live_ at all, and a TRUNCATED real key (mk_live_deadbeef,
108
+ * say) must still reach the engine, whose answer is more precise than a
109
+ * local guess would be. So both a mixed-case label (mk_live_Your_Key) and
110
+ * anything shaped like a real key are left alone here on purpose, same as
111
+ * every other value this function has never seen.
112
+ *
113
+ * PURE AND EXPORTED, colocated with the request builders rather than with
114
+ * `refuseInsecureBaseUrl` itself, because that one needs a `URL` parse this
115
+ * module has no other reason to import (the shape of the guard is the same,
116
+ * the machinery differs). `apiFetch` in src/index.ts evaluates this once into
117
+ * a module constant next to `BASE_URL_REFUSAL`, and checks both at the same
118
+ * two call sites: inside `apiFetch`, and inside the startup probe.
119
+ *
120
+ * `KEYS_URL` is imported from src/errors.ts rather than repeated here as a
121
+ * second literal, the same console page a 401 already points at.
122
+ */
123
+ export function refusePlaceholderKey(key) {
124
+ const isLabelledPlaceholder = /^mk_live_[A-Z][A-Z_]*$/.test(key);
125
+ const isTemplatePlaceholder = /^mk_live_[xX]{4,}$/.test(key);
126
+ if (!isLabelledPlaceholder && !isTemplatePlaceholder)
127
+ return undefined;
128
+ return {
129
+ toolCall: "Mnemoverse: this tool did not run, because MNEMOVERSE_API_KEY is " +
130
+ "still the example value from the documentation, not a real key. " +
131
+ "Nothing was sent: this client refuses the call instead. This is the " +
132
+ "user's configuration, not a fault of the key, the network or the " +
133
+ `service. Tell them to create a key at ${KEYS_URL}, put it in the ` +
134
+ "MCP client config in place of the placeholder, and restart the MCP " +
135
+ "server. Do not retry until then; every memory tool will fail the " +
136
+ "same way until it is replaced.",
137
+ startupLog: "Mnemoverse: startup key check SKIPPED, and nothing was sent. " +
138
+ "MNEMOVERSE_API_KEY is still the example value from the " +
139
+ `documentation, not a real key. Create one at ${KEYS_URL}, put it in ` +
140
+ "the MCP client config in place of the placeholder, and restart. " +
141
+ "Every memory tool will fail until it is replaced.",
142
+ };
143
+ }
78
144
  //# sourceMappingURL=requests.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"requests.js","sourceRoot":"","sources":["../src/requests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AA2BH;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,MAAe;IAC3C,OAAO,MAAM,IAAI,SAAS,CAAC;AAC7B,CAAC;AAED;mEACmE;AACnE,MAAM,UAAU,eAAe,CAAC,CAAW;IACzC,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;QACnB,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;QAC/B,oBAAoB,EAAE,IAAI;QAC1B,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClE,CAAC;AACJ,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,iBAAiB,CAAC,CAAa;IAC7C,OAAO;QACL,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;QAC/B,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,SAAS;QAC3B,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,SAAS;QAC3B,cAAc,EAAE,CAAC,CAAC,cAAc,IAAI,SAAS;QAC7C,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;QACpB,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,SAAS;KAC9B,CAAC;AACJ,CAAC;AAED;kEACkE;AAClE,MAAM,UAAU,gBAAgB,CAAC,CAAY;IAC3C,OAAO;QACL,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE;QAC1B,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,SAAS;KAC9B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"requests.js","sourceRoot":"","sources":["../src/requests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,4EAA4E;AAC5E,4EAA4E;AAC5E,6EAA6E;AAC7E,wDAAwD;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AA2BvC;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,MAAe;IAC3C,OAAO,MAAM,IAAI,SAAS,CAAC;AAC7B,CAAC;AAED;mEACmE;AACnE,MAAM,UAAU,eAAe,CAAC,CAAW;IACzC,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;QACnB,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;QAC/B,oBAAoB,EAAE,IAAI;QAC1B,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClE,CAAC;AACJ,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,iBAAiB,CAAC,CAAa;IAC7C,OAAO;QACL,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;QAC/B,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,SAAS;QAC3B,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,SAAS;QAC3B,cAAc,EAAE,CAAC,CAAC,cAAc,IAAI,SAAS;QAC7C,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE;QACpB,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,SAAS;KAC9B,CAAC;AACJ,CAAC;AAED;kEACkE;AAClE,MAAM,UAAU,gBAAgB,CAAC,CAAY;IAC3C,OAAO;QACL,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE;QAC1B,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,SAAS;KAC9B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,UAAU,oBAAoB,CAClC,GAAW;IAEX,MAAM,qBAAqB,GAAG,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjE,MAAM,qBAAqB,GAAG,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7D,IAAI,CAAC,qBAAqB,IAAI,CAAC,qBAAqB;QAAE,OAAO,SAAS,CAAC;IACvE,OAAO;QACL,QAAQ,EACN,mEAAmE;YACnE,kEAAkE;YAClE,sEAAsE;YACtE,mEAAmE;YACnE,yCAAyC,QAAQ,kBAAkB;YACnE,qEAAqE;YACrE,mEAAmE;YACnE,gCAAgC;QAClC,UAAU,EACR,+DAA+D;YAC/D,yDAAyD;YACzD,gDAAgD,QAAQ,cAAc;YACtE,kEAAkE;YAClE,mDAAmD;KACtD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * MCP resource: one saved memory by id, as `memory://item/{memory_id}`.
3
+ *
4
+ * Clients that support resources (Claude.ai connectors attach them through an
5
+ * @-mention) can pull one specific memory into context. The ids are the `id:`
6
+ * lines of memory_read and memory_list_recent results, so this is the "open
7
+ * this memory" companion to the recall tools. There is no listing: the
8
+ * template is advertised, and reading needs a known id.
9
+ *
10
+ * Moved here from the hosted connector (mnemoverse-mcp-remote,
11
+ * src/resources/index.ts) in step 3c of the ADR-025 plan, with the same URI
12
+ * template, name and output, so the connector can register this instead of
13
+ * its copy. It returns only `memory_id`, `content` and `domain`: the engine's
14
+ * point read also carries importance, valence, access counts and metadata,
15
+ * which a model opening a memory does not need.
16
+ *
17
+ * Two limits, both the engine's. The point read looks only in the caller's
18
+ * own store (GET /memory/atoms/{id} takes no domain), so a memory read from a
19
+ * shared room cannot be opened here; the description says so. And a Vault
20
+ * secret is safe to open: its value lives in a column no read returns.
21
+ */
22
+ import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
23
+ import { type MemoryToolDeps } from "./tools.js";
24
+ /**
25
+ * Register the `memory://item/{memory_id}` resource on `server`. It reaches the
26
+ * API only through `deps.apiFetch`, like the tools.
27
+ */
28
+ export declare function registerMemoryResources(server: McpServer, deps: MemoryToolDeps): void;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * MCP resource: one saved memory by id, as `memory://item/{memory_id}`.
3
+ *
4
+ * Clients that support resources (Claude.ai connectors attach them through an
5
+ * @-mention) can pull one specific memory into context. The ids are the `id:`
6
+ * lines of memory_read and memory_list_recent results, so this is the "open
7
+ * this memory" companion to the recall tools. There is no listing: the
8
+ * template is advertised, and reading needs a known id.
9
+ *
10
+ * Moved here from the hosted connector (mnemoverse-mcp-remote,
11
+ * src/resources/index.ts) in step 3c of the ADR-025 plan, with the same URI
12
+ * template, name and output, so the connector can register this instead of
13
+ * its copy. It returns only `memory_id`, `content` and `domain`: the engine's
14
+ * point read also carries importance, valence, access counts and metadata,
15
+ * which a model opening a memory does not need.
16
+ *
17
+ * Two limits, both the engine's. The point read looks only in the caller's
18
+ * own store (GET /memory/atoms/{id} takes no domain), so a memory read from a
19
+ * shared room cannot be opened here; the description says so. And a Vault
20
+ * secret is safe to open: its value lives in a column no read returns.
21
+ */
22
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
23
+ import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
24
+ import { ApiError } from "./errors.js";
25
+ import { unreadableAnswerText } from "./tools.js";
26
+ /** MCP's code for a resource that does not exist (spec, "Resources: Error Handling"). */
27
+ const RESOURCE_NOT_FOUND = -32002;
28
+ /**
29
+ * The URI segment as the template hands it over is still percent-encoded.
30
+ * Decode it once, so the request path encodes the id itself rather than its
31
+ * encoding; a malformed escape is kept as sent (and then encoded, so it can
32
+ * never become a path separator).
33
+ */
34
+ function decodeOnce(segment) {
35
+ try {
36
+ return decodeURIComponent(segment);
37
+ }
38
+ catch {
39
+ return segment;
40
+ }
41
+ }
42
+ /**
43
+ * Register the `memory://item/{memory_id}` resource on `server`. It reaches the
44
+ * API only through `deps.apiFetch`, like the tools.
45
+ */
46
+ export function registerMemoryResources(server, deps) {
47
+ const { apiFetch } = deps;
48
+ server.registerResource("memory-item", new ResourceTemplate("memory://item/{memory_id}", { list: undefined }), {
49
+ title: "Saved memory",
50
+ description: "Read one saved memory by its memory ID. IDs come from memory_read results. Opens memories in your own store; a memory read from a shared room cannot be opened by ID.",
51
+ mimeType: "application/json",
52
+ }, async (uri, variables) => {
53
+ const id = decodeOnce(String(variables.memory_id));
54
+ let answer;
55
+ try {
56
+ answer = await apiFetch(`/memory/atoms/${encodeURIComponent(id)}`);
57
+ }
58
+ catch (err) {
59
+ // The message is the package's own explanation of the failure
60
+ // (src/errors.ts), so a 429 keeps the engine's quota sentence and a
61
+ // 404 says what was not found. A 404 also gets MCP's resource-not-found
62
+ // code, so a client can tell "no such memory" from a failure.
63
+ const message = err instanceof Error ? err.message : String(err);
64
+ const code = err instanceof ApiError && err.status === 404 ? RESOURCE_NOT_FOUND : ErrorCode.InternalError;
65
+ throw new McpError(code, message);
66
+ }
67
+ // A success whose body is not a memory (an empty 204, which apiFetch
68
+ // returns as {}, or any other shape) is not passed off as one: without
69
+ // this check it became {"memory_id": "<the id asked for>"}, a resource
70
+ // made up from the request (Copilot on #149). Content and domain must be
71
+ // strings, as the engine's AtomDetailSchema requires.
72
+ const atom = answer;
73
+ if (typeof atom !== "object" ||
74
+ atom === null ||
75
+ typeof atom.content !== "string" ||
76
+ typeof atom.domain !== "string") {
77
+ throw new McpError(ErrorCode.InternalError, unreadableAnswerText("The memory", "the memory", "it is empty or gone"));
78
+ }
79
+ return {
80
+ contents: [
81
+ {
82
+ uri: uri.href,
83
+ mimeType: "application/json",
84
+ text: JSON.stringify({
85
+ memory_id: typeof atom.id === "string" ? atom.id : id,
86
+ content: atom.content,
87
+ domain: atom.domain,
88
+ }),
89
+ },
90
+ ],
91
+ };
92
+ });
93
+ }
94
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,gBAAgB,EAAkB,MAAM,yCAAyC,CAAC;AAC3F,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAuB,MAAM,YAAY,CAAC;AAEvE,yFAAyF;AACzF,MAAM,kBAAkB,GAAG,CAAC,KAAK,CAAC;AAElC;;;;;GAKG;AACH,SAAS,UAAU,CAAC,OAAe;IACjC,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAiB,EAAE,IAAoB;IAC7E,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAE1B,MAAM,CAAC,gBAAgB,CACrB,aAAa,EACb,IAAI,gBAAgB,CAAC,2BAA2B,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EACtE;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,uKAAuK;QACzK,QAAQ,EAAE,kBAAkB;KAC7B,EACD,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE;QACvB,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;QACnD,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,QAAQ,CAAU,iBAAiB,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,8DAA8D;YAC9D,oEAAoE;YACpE,wEAAwE;YACxE,8DAA8D;YAC9D,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,MAAM,IAAI,GACR,GAAG,YAAY,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC;YAC/F,MAAM,IAAI,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACpC,CAAC;QACD,qEAAqE;QACrE,uEAAuE;QACvE,uEAAuE;QACvE,yEAAyE;QACzE,sDAAsD;QACtD,MAAM,IAAI,GAAG,MAAsE,CAAC;QACpF,IACE,OAAO,IAAI,KAAK,QAAQ;YACxB,IAAI,KAAK,IAAI;YACb,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ;YAChC,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,EAC/B,CAAC;YACD,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,aAAa,EACvB,oBAAoB,CAAC,YAAY,EAAE,YAAY,EAAE,qBAAqB,CAAC,CACxE,CAAC;QACJ,CAAC;QACD,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,GAAG,EAAE,GAAG,CAAC,IAAI;oBACb,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,SAAS,EAAE,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;wBACrD,OAAO,EAAE,IAAI,CAAC,OAAO;wBACrB,MAAM,EAAE,IAAI,CAAC,MAAM;qBACpB,CAAC;iBACH;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `@mnemoverse/mcp-memory-server/shared`: the MCP surface, for any server.
3
+ *
4
+ * ADR-025 (mnemoverse-core): this package defines the Mnemoverse MCP surface,
5
+ * and a server that exposes Mnemoverse memory over MCP registers these tools
6
+ * instead of keeping its own copy. Importing this entry point starts nothing:
7
+ * the stdio server lives in the package's main entry, which this file does not
8
+ * import.
9
+ *
10
+ * import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ * import {
12
+ * registerMemoryPrompts,
13
+ * registerMemoryResources,
14
+ * registerMemoryTools,
15
+ * SERVER_INSTRUCTIONS,
16
+ * } from "@mnemoverse/mcp-memory-server/shared";
17
+ *
18
+ * const server = new McpServer({ name, version }, { instructions: SERVER_INSTRUCTIONS });
19
+ * registerMemoryTools(server, { apiFetch });
20
+ * registerMemoryPrompts(server);
21
+ * registerMemoryResources(server, { apiFetch });
22
+ *
23
+ * `apiFetch` is the server's own way to reach the API (credential, base URL,
24
+ * transport). Its contract is on the `ApiFetch` type: reject with `ApiError`,
25
+ * `NetworkError` or `UnreadableBodyError`, because the tools branch on which.
26
+ */
27
+ export { registerMemoryTools, type ApiFetch, type MemoryToolDeps } from "./tools.js";
28
+ export { registerMemoryPrompts } from "./prompts.js";
29
+ export { registerMemoryResources } from "./resources.js";
30
+ export { SERVER_INSTRUCTIONS } from "./teaching.js";
31
+ export { ApiError, NetworkError, UnreadableBodyError, type ApiFailure, type ErrorEnvelope, type UnreadableBody, } from "./errors.js";
package/dist/shared.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `@mnemoverse/mcp-memory-server/shared`: the MCP surface, for any server.
3
+ *
4
+ * ADR-025 (mnemoverse-core): this package defines the Mnemoverse MCP surface,
5
+ * and a server that exposes Mnemoverse memory over MCP registers these tools
6
+ * instead of keeping its own copy. Importing this entry point starts nothing:
7
+ * the stdio server lives in the package's main entry, which this file does not
8
+ * import.
9
+ *
10
+ * import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ * import {
12
+ * registerMemoryPrompts,
13
+ * registerMemoryResources,
14
+ * registerMemoryTools,
15
+ * SERVER_INSTRUCTIONS,
16
+ * } from "@mnemoverse/mcp-memory-server/shared";
17
+ *
18
+ * const server = new McpServer({ name, version }, { instructions: SERVER_INSTRUCTIONS });
19
+ * registerMemoryTools(server, { apiFetch });
20
+ * registerMemoryPrompts(server);
21
+ * registerMemoryResources(server, { apiFetch });
22
+ *
23
+ * `apiFetch` is the server's own way to reach the API (credential, base URL,
24
+ * transport). Its contract is on the `ApiFetch` type: reject with `ApiError`,
25
+ * `NetworkError` or `UnreadableBodyError`, because the tools branch on which.
26
+ */
27
+ export { registerMemoryTools } from "./tools.js";
28
+ export { registerMemoryPrompts } from "./prompts.js";
29
+ export { registerMemoryResources } from "./resources.js";
30
+ export { SERVER_INSTRUCTIONS } from "./teaching.js";
31
+ export { ApiError, NetworkError, UnreadableBodyError, } from "./errors.js";
32
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,mBAAmB,EAAsC,MAAM,YAAY,CAAC;AACrF,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,mBAAmB,GAIpB,MAAM,aAAa,CAAC"}
package/dist/time.d.ts CHANGED
@@ -14,26 +14,15 @@
14
14
  * `safeInline` as an injected parameter precisely to avoid importing the
15
15
  * renderer), and names.ts is about printing names, not about reading clocks.
16
16
  */
17
+ export declare function parseAsUtc(value: unknown): number | null;
17
18
  /**
18
- * Parse an ISO-8601 instant the way the SERVER does: an offset-less value is
19
- * UTC, not local time. Returns epoch milliseconds, or `null` for anything that
20
- * cannot be read as an instant.
21
- *
22
- * The mismatch this fixes is not cosmetic — it moved dates. West of UTC a
23
- * perfectly sane watermark was declared to be in the future and every clause of
24
- * the future-watermark note was false; east of UTC a genuinely future watermark
25
- * was shifted into the past and the note stayed silent, missing the one case it
26
- * exists for (review, 2026-08-08). On a rendered result line the same reading
27
- * printed `2026-08-01T23:30:00` as `2026-08-02 06:30Z` in America/Los_Angeles:
28
- * a memory dated to a day it was not written, with a `Z` asserting it was UTC.
29
- *
30
- * Date-only values ("2026-08-08") are already parsed as UTC by spec, so only
31
- * date-TIME values without an offset need the Z.
32
- *
33
- * `value` is `unknown` because both call sites read it off the wire, where the
34
- * response types are aspirational: `created_at` is typed as a string and can
35
- * arrive as a number. A value that is not a string is not an ISO-8601 instant,
36
- * so it is `null` here rather than a guess — `new Date(1754082281605)` is a
37
- * perfectly good date for a field whose contract says it is text.
19
+ * The instant `parseAsUtc` reads, as a string a structured consumer can trust:
20
+ * a value that states its own offset (`Z` or `+hh:mm`) is returned exactly as
21
+ * sent, so the common core value stays byte-identical; an offset-less value,
22
+ * which this package reads as UTC by contract, is re-emitted as the UTC
23
+ * ISO-8601 instant the text renders, because a consumer parsing the naive
24
+ * string by the ISO-8601 rule would read it as LOCAL time and land on a
25
+ * different instant than the text shows (review, 2026-09-23). `null` for
26
+ * anything `parseAsUtc` cannot read.
38
27
  */
39
- export declare function parseAsUtc(value: unknown): number | null;
28
+ export declare function utcInstant(value: unknown): string | null;
package/dist/time.js CHANGED
@@ -36,13 +36,31 @@
36
36
  * so it is `null` here rather than a guess — `new Date(1754082281605)` is a
37
37
  * perfectly good date for a field whose contract says it is text.
38
38
  */
39
+ /** An explicit UTC marker or numeric offset at the end of an ISO-8601 value. */
40
+ const OFFSET_RE = /(?:Z|[+-]\d{2}:?\d{2})$/i;
39
41
  export function parseAsUtc(value) {
40
42
  if (typeof value !== "string")
41
43
  return null;
42
44
  const s = value.trim();
43
- const hasOffset = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(s);
45
+ const hasOffset = OFFSET_RE.test(s);
44
46
  const isDateTime = /\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/.test(s);
45
47
  const t = Date.parse(isDateTime && !hasOffset ? `${s.replace(" ", "T")}Z` : s);
46
48
  return Number.isNaN(t) ? null : t;
47
49
  }
50
+ /**
51
+ * The instant `parseAsUtc` reads, as a string a structured consumer can trust:
52
+ * a value that states its own offset (`Z` or `+hh:mm`) is returned exactly as
53
+ * sent, so the common core value stays byte-identical; an offset-less value,
54
+ * which this package reads as UTC by contract, is re-emitted as the UTC
55
+ * ISO-8601 instant the text renders, because a consumer parsing the naive
56
+ * string by the ISO-8601 rule would read it as LOCAL time and land on a
57
+ * different instant than the text shows (review, 2026-09-23). `null` for
58
+ * anything `parseAsUtc` cannot read.
59
+ */
60
+ export function utcInstant(value) {
61
+ const t = parseAsUtc(value);
62
+ if (t === null || typeof value !== "string")
63
+ return null;
64
+ return OFFSET_RE.test(value.trim()) ? value : new Date(t).toISOString();
65
+ }
48
66
  //# sourceMappingURL=time.js.map
package/dist/time.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"time.js","sourceRoot":"","sources":["../src/time.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,MAAM,SAAS,GAAG,0BAA0B,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG,kCAAkC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9D,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC"}
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../src/time.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,gFAAgF;AAChF,MAAM,SAAS,GAAG,0BAA0B,CAAC;AAE7C,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpC,MAAM,UAAU,GAAG,kCAAkC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9D,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The ten memory tools, as one function any MCP server can register.
3
+ *
4
+ * ADR-025 (mnemoverse-core): this package defines the MCP surface, and every
5
+ * server that exposes Mnemoverse memory over MCP registers the SAME tools from
6
+ * here instead of keeping its own copy. The stdio server in src/index.ts is one
7
+ * such server; the hosted connector (mnemoverse-mcp-remote) is the other.
8
+ *
9
+ * What differs between servers is only how a request reaches the API: which
10
+ * credential it carries, where it goes, and how a transport failure is worded.
11
+ * That is `MemoryToolDeps.apiFetch`. Nothing in this file reads configuration
12
+ * or the environment.
13
+ */
14
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15
+ /**
16
+ * How the tools reach the Mnemoverse API: send `path` (relative to the API base,
17
+ * e.g. "/memory/read") and resolve to the parsed JSON body.
18
+ *
19
+ * Contract every implementation must keep, because the tools branch on it:
20
+ * a non-2xx rejects with `ApiError` (status, core's error code and an
21
+ * agent-facing message), a request that never got an HTTP answer rejects with
22
+ * `NetworkError`, and a 2xx whose body cannot be parsed rejects with
23
+ * `UnreadableBodyError`. All three are exported from ./shared and defined in
24
+ * src/errors.ts. `options.signal` must be honoured.
25
+ */
26
+ export type ApiFetch = <T = unknown>(path: string, options?: RequestInit) => Promise<T>;
27
+ /** What a server supplies when it registers the memory tools. */
28
+ export interface MemoryToolDeps {
29
+ apiFetch: ApiFetch;
30
+ }
31
+ /**
32
+ * The sentence behind {@link unreadableAnswerReply}, exported so the memory
33
+ * resource (src/resources.ts) says the same thing about an unreadable answer
34
+ * rather than keeping its own copy.
35
+ *
36
+ * The tail attributes the unreadable 200 to "whatever answered this call", not
37
+ * to "the memory service" — this client cannot establish WHO answered: a
38
+ * gateway, a proxy, or the endpoint a mis-set MNEMOVERSE_API_URL points at
39
+ * produces the same 200 with an unrecognised body (truth re-verification,
40
+ * 2026-08-09). Pinned in test/handlers.test.ts.
41
+ */
42
+ export declare function unreadableAnswerText(subject: string, notA: string, absence: string, extra?: string): string;
43
+ /**
44
+ * A success result carrying both a text block and `structuredContent`, which
45
+ * is what a tool with a declared `outputSchema` must return, or the SDK
46
+ * itself rejects the call. `validateToolOutput` in the installed SDK
47
+ * (node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:185-207)
48
+ * turns a non-error, text-only result into an `isError` "Output validation
49
+ * error: … has an output schema but no structured content was provided", the
50
+ * moment a tool declares that schema, even though the same text alone was a
51
+ * fine answer the day before the schema was added. Pinned against that
52
+ * installed SDK in test/structured-output.test.ts.
53
+ *
54
+ * Deliberately dumb: it does not call `capResult` or `withDomainEscapeLegend`
55
+ * itself. Every tool already measures and caps its own text with a per-tool
56
+ * hint and applies the legend after the cap (see memory_read below), so a
57
+ * helper that capped again would double-truncate; callers pass `text`
58
+ * already finished.
59
+ */
60
+ export declare function structured(text: string, data: Record<string, unknown>): {
61
+ content: [{
62
+ type: "text";
63
+ text: string;
64
+ }];
65
+ structuredContent: Record<string, unknown>;
66
+ };
67
+ /**
68
+ * Register the ten memory tools on `server`. Call once per server instance.
69
+ * `deps.apiFetch` is the only way the tools reach the API.
70
+ */
71
+ export declare function registerMemoryTools(server: McpServer, deps: MemoryToolDeps): void;