@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.
- package/README.md +81 -15
- package/dist/errors.d.ts +90 -7
- package/dist/errors.js +179 -45
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +0 -21
- package/dist/index.js +13 -1474
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +68 -0
- package/dist/limits.js +69 -0
- package/dist/limits.js.map +1 -0
- package/dist/names.d.ts +76 -4
- package/dist/names.js +118 -8
- package/dist/names.js.map +1 -1
- package/dist/prompts.d.ts +25 -0
- package/dist/prompts.js +110 -0
- package/dist/prompts.js.map +1 -0
- package/dist/render.d.ts +114 -3
- package/dist/render.js +157 -9
- package/dist/render.js.map +1 -1
- package/dist/requests.d.ts +34 -3
- package/dist/requests.js +12 -3
- package/dist/requests.js.map +1 -1
- package/dist/resources.d.ts +28 -0
- package/dist/resources.js +97 -0
- package/dist/resources.js.map +1 -0
- package/dist/shared.d.ts +52 -0
- package/dist/shared.js +52 -0
- package/dist/shared.js.map +1 -0
- package/dist/time.d.ts +10 -21
- package/dist/time.js +19 -1
- package/dist/time.js.map +1 -1
- package/dist/tools.d.ts +134 -0
- package/dist/tools.js +2437 -0
- package/dist/tools.js.map +1 -0
- package/package.json +24 -5
|
@@ -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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"}
|
package/dist/shared.d.ts
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, 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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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
|
|
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 =
|
|
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,
|
|
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"}
|
package/dist/tools.d.ts
ADDED
|
@@ -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;
|