@mnemoverse/mcp-memory-server 0.10.2 → 0.12.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.
@@ -0,0 +1,97 @@
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, wordedApiFetch } 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
+ // The same `wording` treatment as registerMemoryTools (STEP4-2): a
48
+ // resource read that fails is explained in this registration's own voice.
49
+ const { wording } = deps;
50
+ const apiFetch = wording === undefined ? deps.apiFetch : wordedApiFetch(deps.apiFetch, wording);
51
+ server.registerResource("memory-item", new ResourceTemplate("memory://item/{memory_id}", { list: undefined }), {
52
+ title: "Saved memory",
53
+ 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.",
54
+ mimeType: "application/json",
55
+ }, async (uri, variables) => {
56
+ const id = decodeOnce(String(variables.memory_id));
57
+ let answer;
58
+ try {
59
+ answer = await apiFetch(`/memory/atoms/${encodeURIComponent(id)}`);
60
+ }
61
+ catch (err) {
62
+ // The message is the package's own explanation of the failure
63
+ // (src/errors.ts), so a 429 keeps the engine's quota sentence and a
64
+ // 404 says what was not found. A 404 also gets MCP's resource-not-found
65
+ // code, so a client can tell "no such memory" from a failure.
66
+ const message = err instanceof Error ? err.message : String(err);
67
+ const code = err instanceof ApiError && err.status === 404 ? RESOURCE_NOT_FOUND : ErrorCode.InternalError;
68
+ throw new McpError(code, message);
69
+ }
70
+ // A success whose body is not a memory (an empty 204, which apiFetch
71
+ // returns as {}, or any other shape) is not passed off as one: without
72
+ // this check it became {"memory_id": "<the id asked for>"}, a resource
73
+ // made up from the request (Copilot on #149). Content and domain must be
74
+ // strings, as the engine's AtomDetailSchema requires.
75
+ const atom = answer;
76
+ if (typeof atom !== "object" ||
77
+ atom === null ||
78
+ typeof atom.content !== "string" ||
79
+ typeof atom.domain !== "string") {
80
+ throw new McpError(ErrorCode.InternalError, unreadableAnswerText("The memory", "the memory", "it is empty or gone"));
81
+ }
82
+ return {
83
+ contents: [
84
+ {
85
+ uri: uri.href,
86
+ mimeType: "application/json",
87
+ text: JSON.stringify({
88
+ memory_id: typeof atom.id === "string" ? atom.id : id,
89
+ content: atom.content,
90
+ domain: atom.domain,
91
+ }),
92
+ },
93
+ ],
94
+ };
95
+ });
96
+ }
97
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
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
+ * `MAX_RESULT_CHARS` and `capResult` are the tool-result size cap and the
28
+ * helper that applies it: 96,000 characters (24,000 tokens), the one bound
29
+ * both servers use for a tool result's text (`structuredContent` is not
30
+ * capped, OD-11). ADR-025 makes this the single source of truth for the
31
+ * number; the hosted connector still keeps its own 25,000-character literal
32
+ * (`src/constants.ts`) and adopts this export in its place at a later step,
33
+ * not yet done as of this release. `capResult` truncates on a code-point
34
+ * boundary (never inside a UTF-16 surrogate pair) and appends the truncation notice, so a consumer does
35
+ * not have to re-implement a `slice` that can cut a surrogate pair.
36
+ *
37
+ * `MemoryToolDeps.wording` and `.writeAuthor` (STEP4-2/3/5, owner
38
+ * 2026-09-24) are how a SECOND server registering these tools (the hosted
39
+ * connector is the first consumer) speaks in its own voice: `wording`
40
+ * swaps "this server" for "this connector" in the three descriptions that
41
+ * name it and, under `auth: "oauth"`, rewords every 401/403 explanation for
42
+ * a user who never sees an API key; `writeAuthor` lets a supplier vouch for
43
+ * the end user behind a write (core: `_get_provenance`, honoured only for a
44
+ * SERVICE/supplier caller). Both are optional, and absent, reproduce this
45
+ * package's existing behaviour exactly. Full contract: docs/shared.md.
46
+ */
47
+ export { registerMemoryTools, MAX_RESULT_CHARS, capResult, type ApiFetch, type MemoryToolDeps, } from "./tools.js";
48
+ export { registerMemoryPrompts } from "./prompts.js";
49
+ export { registerMemoryResources } from "./resources.js";
50
+ export { SERVER_INSTRUCTIONS } from "./teaching.js";
51
+ export { ApiError, NetworkError, UnreadableBodyError, type ApiFailure, type ErrorEnvelope, type UnreadableBody, type Wording, } from "./errors.js";
52
+ export { type WriteAuthor } from "./requests.js";
package/dist/shared.js ADDED
@@ -0,0 +1,52 @@
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
+ * `MAX_RESULT_CHARS` and `capResult` are the tool-result size cap and the
28
+ * helper that applies it: 96,000 characters (24,000 tokens), the one bound
29
+ * both servers use for a tool result's text (`structuredContent` is not
30
+ * capped, OD-11). ADR-025 makes this the single source of truth for the
31
+ * number; the hosted connector still keeps its own 25,000-character literal
32
+ * (`src/constants.ts`) and adopts this export in its place at a later step,
33
+ * not yet done as of this release. `capResult` truncates on a code-point
34
+ * boundary (never inside a UTF-16 surrogate pair) and appends the truncation notice, so a consumer does
35
+ * not have to re-implement a `slice` that can cut a surrogate pair.
36
+ *
37
+ * `MemoryToolDeps.wording` and `.writeAuthor` (STEP4-2/3/5, owner
38
+ * 2026-09-24) are how a SECOND server registering these tools (the hosted
39
+ * connector is the first consumer) speaks in its own voice: `wording`
40
+ * swaps "this server" for "this connector" in the three descriptions that
41
+ * name it and, under `auth: "oauth"`, rewords every 401/403 explanation for
42
+ * a user who never sees an API key; `writeAuthor` lets a supplier vouch for
43
+ * the end user behind a write (core: `_get_provenance`, honoured only for a
44
+ * SERVICE/supplier caller). Both are optional, and absent, reproduce this
45
+ * package's existing behaviour exactly. Full contract: docs/shared.md.
46
+ */
47
+ export { registerMemoryTools, MAX_RESULT_CHARS, capResult, } from "./tools.js";
48
+ export { registerMemoryPrompts } from "./prompts.js";
49
+ export { registerMemoryResources } from "./resources.js";
50
+ export { SERVER_INSTRUCTIONS } from "./teaching.js";
51
+ export { ApiError, NetworkError, UnreadableBodyError, } from "./errors.js";
52
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,SAAS,GAGV,MAAM,YAAY,CAAC;AACpB,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,GAKpB,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,134 @@
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
+ import { type WriteAuthor } from "./requests.js";
16
+ import { type Wording } from "./errors.js";
17
+ /**
18
+ * How the tools reach the Mnemoverse API: send `path` (relative to the API base,
19
+ * e.g. "/memory/read") and resolve to the parsed JSON body.
20
+ *
21
+ * Contract every implementation must keep, because the tools branch on it:
22
+ * a non-2xx rejects with `ApiError` (status, core's error code and an
23
+ * agent-facing message), a request that never got an HTTP answer rejects with
24
+ * `NetworkError`, and a 2xx whose body cannot be parsed rejects with
25
+ * `UnreadableBodyError`. All three are exported from ./shared and defined in
26
+ * src/errors.ts. `options.signal` must be honoured.
27
+ */
28
+ export type ApiFetch = <T = unknown>(path: string, options?: RequestInit) => Promise<T>;
29
+ /**
30
+ * `deps.apiFetch` with `deps.wording` applied to what it throws (STEP4-2):
31
+ * every rejection passes through {@link rewordFailure}, so an `ApiError`,
32
+ * `NetworkError` or `UnreadableBodyError` the consumer built with no
33
+ * `wording` (or with a different one) reaches the model explained under the
34
+ * wording this registration was given. Results and every other rejection
35
+ * pass through untouched. Installed only when `wording` is supplied at all:
36
+ * the stdio server supplies none, and its `apiFetch` is used exactly as
37
+ * before.
38
+ */
39
+ export declare function wordedApiFetch(inner: ApiFetch, wording: Wording): ApiFetch;
40
+ /**
41
+ * What a server supplies when it registers the memory tools.
42
+ *
43
+ * `wording` and `writeAuthor` (STEP4-2/3/5, owner decisions 2026-09-24) are
44
+ * both optional, and both default to exactly this server's own behaviour: a
45
+ * consumer that supplies neither gets byte-identical descriptions, error
46
+ * text and write bodies to every release before this one. They exist for a
47
+ * SECOND server registering these same tools (the hosted connector,
48
+ * ADR-025) that needs to say "this connector" instead of "this server",
49
+ * speak to an OAuth user who never sees an API key, and vouch for the
50
+ * end user it is writing on behalf of.
51
+ */
52
+ export interface MemoryToolDeps {
53
+ apiFetch: ApiFetch;
54
+ /** How this registration wants its own tool descriptions and error
55
+ * explanations worded. See {@link Wording} for each field. Read at
56
+ * registration time for the three descriptions that name the server,
57
+ * and applied to every `ApiError`/`NetworkError`/`UnreadableBodyError`
58
+ * the consumer's `apiFetch` rejects with (see {@link wordedApiFetch}),
59
+ * so the consumer states it once, here. Passing the same value to the
60
+ * error constructors inside `apiFetch` is allowed and changes nothing. */
61
+ wording?: Wording;
62
+ /**
63
+ * Supplier-vouched authorship for `memory_write` (STEP4-5). Called once
64
+ * per write, with no arguments; a returned value is sent as the request
65
+ * body's `author` field EXACTLY as returned (this package applies no
66
+ * normalisation beyond a `typeof` guard; core re-normalises server-side,
67
+ * see {@link WriteAuthor}). Returning `undefined`, or omitting this
68
+ * dependency entirely, sends the body this package has always sent: the
69
+ * stdio server's own requests are exactly this case, and do not change.
70
+ *
71
+ * Core honours `author` ONLY for a SERVICE/supplier caller and IGNORES it
72
+ * for an OIDC end-user (mnemoverse-core src/mnemo/api/routes.py:211-276,
73
+ * `_get_provenance`, verified 2026-09-24), so this dependency is useful
74
+ * only to a server that authenticates to core as a supplier vouching for
75
+ * someone else, not to an end-user's own key.
76
+ */
77
+ writeAuthor?: () => WriteAuthor | undefined;
78
+ }
79
+ export declare const MAX_RESULT_CHARS: number;
80
+ /**
81
+ * Truncate a result string to MAX_RESULT_CHARS, appending a notice if truncated.
82
+ * Required by Claude Connectors Directory submission policy.
83
+ *
84
+ * Defensive against splitting UTF-16 surrogate pairs: if the character right
85
+ * before the cut point is a high surrogate (U+D800–U+DBFF), drop it so the
86
+ * result stays well-formed. Otherwise an emoji or non-BMP character at the
87
+ * boundary can produce a lone surrogate and corrupt downstream JSON encoding.
88
+ *
89
+ * Exported (re-exported from ./shared) so a consumer applying MAX_RESULT_CHARS
90
+ * to its own tool results does not have to re-implement this code-point-safe
91
+ * truncation as a plain `slice`, which can cut a surrogate pair.
92
+ */
93
+ export declare function capResult(text: string, moreHint?: string): string;
94
+ /**
95
+ * The sentence behind {@link unreadableAnswerReply}, exported so the memory
96
+ * resource (src/resources.ts) says the same thing about an unreadable answer
97
+ * rather than keeping its own copy.
98
+ *
99
+ * The tail attributes the unreadable 200 to "whatever answered this call", not
100
+ * to "the memory service" — this client cannot establish WHO answered: a
101
+ * gateway, a proxy, or the endpoint a mis-set MNEMOVERSE_API_URL points at
102
+ * produces the same 200 with an unrecognised body (truth re-verification,
103
+ * 2026-08-09). Pinned in test/handlers.test.ts.
104
+ */
105
+ export declare function unreadableAnswerText(subject: string, notA: string, absence: string, extra?: string): string;
106
+ /**
107
+ * A success result carrying both a text block and `structuredContent`, which
108
+ * is what a tool with a declared `outputSchema` must return, or the SDK
109
+ * itself rejects the call. `validateToolOutput` in the installed SDK
110
+ * (node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:185-207)
111
+ * turns a non-error, text-only result into an `isError` "Output validation
112
+ * error: … has an output schema but no structured content was provided", the
113
+ * moment a tool declares that schema, even though the same text alone was a
114
+ * fine answer the day before the schema was added. Pinned against that
115
+ * installed SDK in test/structured-output.test.ts.
116
+ *
117
+ * Deliberately dumb: it does not call `capResult` or `withDomainEscapeLegend`
118
+ * itself. Every tool already measures and caps its own text with a per-tool
119
+ * hint and applies the legend after the cap (see memory_read below), so a
120
+ * helper that capped again would double-truncate; callers pass `text`
121
+ * already finished.
122
+ */
123
+ export declare function structured(text: string, data: Record<string, unknown>): {
124
+ content: [{
125
+ type: "text";
126
+ text: string;
127
+ }];
128
+ structuredContent: Record<string, unknown>;
129
+ };
130
+ /**
131
+ * Register the ten memory tools on `server`. Call once per server instance.
132
+ * `deps.apiFetch` is the only way the tools reach the API.
133
+ */
134
+ export declare function registerMemoryTools(server: McpServer, deps: MemoryToolDeps): void;