@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
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP prompts: three named entry points to the memory tools.
|
|
3
|
+
*
|
|
4
|
+
* Clients that support the prompts capability show them as commands (Claude
|
|
5
|
+
* Code as `/mcp__mnemoverse__<name>`, Claude.ai connectors in the slash menu).
|
|
6
|
+
* Each returns one user message that points the model at the right tool. They
|
|
7
|
+
* add no capability beyond the tools; a client without prompt support ignores
|
|
8
|
+
* them.
|
|
9
|
+
*
|
|
10
|
+
* Moved here from the hosted connector (mnemoverse-mcp-remote,
|
|
11
|
+
* src/prompts/index.ts) in step 3c of the ADR-025 plan, with the same names,
|
|
12
|
+
* arguments and wording, so the connector can register these instead of its
|
|
13
|
+
* copy. One difference is deliberate: `domain` in save_insight is printed as
|
|
14
|
+
* an exact JSON literal (src/names.ts), like every domain this package names,
|
|
15
|
+
* because the model has to send it back to memory_write byte for byte. For a
|
|
16
|
+
* plain domain the message is identical to the connector's. The other
|
|
17
|
+
* arguments are trimmed, as the connector trims them, because a blank topic is
|
|
18
|
+
* not a request.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
22
|
+
import { exactLiteral, withDomainEscapeLegend } from "./names.js";
|
|
23
|
+
/**
|
|
24
|
+
* Register the three memory prompts on `server`. They call nothing: each
|
|
25
|
+
* renders a message that asks the model to use memory_read or memory_write.
|
|
26
|
+
*/
|
|
27
|
+
export function registerMemoryPrompts(server) {
|
|
28
|
+
server.registerPrompt("recall", {
|
|
29
|
+
title: "Recall from memory",
|
|
30
|
+
description: "Search your Mnemoverse memory for saved information about a specific topic.",
|
|
31
|
+
argsSchema: {
|
|
32
|
+
topic: z
|
|
33
|
+
.string()
|
|
34
|
+
.trim()
|
|
35
|
+
.min(1)
|
|
36
|
+
.describe("What to recall — a topic, project, person, or open question."),
|
|
37
|
+
},
|
|
38
|
+
}, ({ topic }) => ({
|
|
39
|
+
messages: [
|
|
40
|
+
{
|
|
41
|
+
role: "user",
|
|
42
|
+
content: {
|
|
43
|
+
type: "text",
|
|
44
|
+
text: `Use memory_read to search my saved Mnemoverse memories for this specific topic: "${topic}". Summarize only the returned memory text. If nothing relevant is stored, say so plainly.`,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
}));
|
|
49
|
+
server.registerPrompt("save_insight", {
|
|
50
|
+
title: "Save an insight to memory",
|
|
51
|
+
description: "Store a new insight, fact, or decision in your Mnemoverse long-term memory.",
|
|
52
|
+
argsSchema: {
|
|
53
|
+
insight: z
|
|
54
|
+
.string()
|
|
55
|
+
.trim()
|
|
56
|
+
.min(1)
|
|
57
|
+
.describe("The insight, fact, or decision to remember."),
|
|
58
|
+
domain: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Optional domain/category to file it under (e.g. 'project-x')."),
|
|
62
|
+
},
|
|
63
|
+
}, ({ insight, domain }) => {
|
|
64
|
+
// The domain is an identifier the model must reproduce exactly, so it is
|
|
65
|
+
// printed as a JSON literal: a quote, backslash, newline or invisible
|
|
66
|
+
// character inside it is escaped instead of closing the quotes early
|
|
67
|
+
// (Copilot on #148). A plain domain prints exactly as the connector's
|
|
68
|
+
// `"${domain}"` did. A domain too long to print exactly is refused
|
|
69
|
+
// rather than named inexactly or dropped, since dropping it would file
|
|
70
|
+
// the insight somewhere the user did not ask for.
|
|
71
|
+
let under = "";
|
|
72
|
+
if (domain) {
|
|
73
|
+
const exact = exactLiteral(domain);
|
|
74
|
+
if (exact === null) {
|
|
75
|
+
throw new McpError(ErrorCode.InvalidParams, "save_insight: this domain is too long to be quoted exactly in the message. Pass a shorter domain.");
|
|
76
|
+
}
|
|
77
|
+
under = ` under the domain ${exact.literal}`;
|
|
78
|
+
}
|
|
79
|
+
const text = `Use the memory_write tool to store this insight in my long-term memory${under}: "${insight}". Then confirm exactly what was stored.`;
|
|
80
|
+
return {
|
|
81
|
+
messages: [
|
|
82
|
+
{
|
|
83
|
+
role: "user",
|
|
84
|
+
content: {
|
|
85
|
+
type: "text",
|
|
86
|
+
text: withDomainEscapeLegend(text, domain),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
server.registerPrompt("what_do_you_know", {
|
|
93
|
+
title: "What do you know about…",
|
|
94
|
+
description: "Get a briefing of what your memory holds about a subject.",
|
|
95
|
+
argsSchema: {
|
|
96
|
+
subject: z.string().trim().min(1).describe("The subject to brief on."),
|
|
97
|
+
},
|
|
98
|
+
}, ({ subject }) => ({
|
|
99
|
+
messages: [
|
|
100
|
+
{
|
|
101
|
+
role: "user",
|
|
102
|
+
content: {
|
|
103
|
+
type: "text",
|
|
104
|
+
text: `Use memory_read to look up what I have explicitly stored about "${subject}". Give me a concise briefing based only on the returned memory text and flag gaps where nothing is stored.`,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAElE;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAiB;IACrD,MAAM,CAAC,cAAc,CACnB,QAAQ,EACR;QACE,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EAAE,6EAA6E;QAC1F,UAAU,EAAE;YACV,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,IAAI,EAAE;iBACN,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CAAC,8DAA8D,CAAC;SAC5E;KACF,EACD,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QACd,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAe;gBACrB,OAAO,EAAE;oBACP,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,oFAAoF,KAAK,4FAA4F;iBAC5L;aACF;SACF;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,cAAc,CACnB,cAAc,EACd;QACE,KAAK,EAAE,2BAA2B;QAClC,WAAW,EAAE,6EAA6E;QAC1F,UAAU,EAAE;YACV,OAAO,EAAE,CAAC;iBACP,MAAM,EAAE;iBACR,IAAI,EAAE;iBACN,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CAAC,6CAA6C,CAAC;YAC1D,MAAM,EAAE,CAAC;iBACN,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE;QACtB,yEAAyE;QACzE,sEAAsE;QACtE,qEAAqE;QACrE,sEAAsE;QACtE,mEAAmE;QACnE,uEAAuE;QACvE,kDAAkD;QAClD,IAAI,KAAK,GAAG,EAAE,CAAC;QACf,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YACnC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,aAAa,EACvB,mGAAmG,CACpG,CAAC;YACJ,CAAC;YACD,KAAK,GAAG,qBAAqB,KAAK,CAAC,OAAO,EAAE,CAAC;QAC/C,CAAC;QACD,MAAM,IAAI,GAAG,yEAAyE,KAAK,MAAM,OAAO,0CAA0C,CAAC;QACnJ,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAe;oBACrB,OAAO,EAAE;wBACP,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,sBAAsB,CAAC,IAAI,EAAE,MAAM,CAAC;qBAC3C;iBACF;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,cAAc,CACnB,kBAAkB,EAClB;QACE,KAAK,EAAE,yBAAyB;QAChC,WAAW,EAAE,2DAA2D;QACxE,UAAU,EAAE;YACV,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,0BAA0B,CAAC;SACvE;KACF,EACD,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;QAChB,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAe;gBACrB,OAAO,EAAE;oBACP,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,mEAAmE,OAAO,6GAA6G;iBAC9L;aACF;SACF;KACF,CAAC,CACH,CAAC;AACJ,CAAC"}
|
package/dist/render.d.ts
CHANGED
|
@@ -75,8 +75,82 @@ export type RecentItem = {
|
|
|
75
75
|
*/
|
|
76
76
|
export declare function safeInline(s: unknown, cap?: number): string;
|
|
77
77
|
/**
|
|
78
|
-
* `
|
|
79
|
-
*
|
|
78
|
+
* `agent_name || agent || client_env`, exactly as the wire sent it: no
|
|
79
|
+
* sanitising, no suffix. The single raw value `authorName` and
|
|
80
|
+
* `formatAuthorTag` below BOTH derive from, so the bare structured field and
|
|
81
|
+
* the quoted text tag cannot end up naming two different agents. The same
|
|
82
|
+
* raw value is what src/tools.ts passes to `withDomainEscapeLegend` as an
|
|
83
|
+
* author candidate, so its independent recomputation of `exactLiteral` finds
|
|
84
|
+
* the same literal `formatAuthorTag` printed (I66-3, issue #66).
|
|
85
|
+
*
|
|
86
|
+
* Returns `""`, not `null`/`undefined`, when there is no renderable name at
|
|
87
|
+
* all: an absent provenance, every field empty, or the field that won the
|
|
88
|
+
* `||` chain arriving with the wrong wire type. A numeric `agent_name` is a
|
|
89
|
+
* real shape a hostile or buggy connector sends, not a hypothetical one; see
|
|
90
|
+
* "a non-string where the type promised a string" below, and CN-032's own
|
|
91
|
+
* history with this exact field.
|
|
92
|
+
*/
|
|
93
|
+
export declare function rawAuthorName(p?: Provenance | null): string;
|
|
94
|
+
/**
|
|
95
|
+
* "X" / "X · external": agent identity only, never the human `principal`
|
|
96
|
+
* (may be an email / PII), even though the response carries it. Empty string
|
|
97
|
+
* when there is no renderable name.
|
|
98
|
+
*
|
|
99
|
+
* Feeds `structuredContent.author` (via `structuredItem`, below), not the
|
|
100
|
+
* text tag; `formatAuthorTag` now quotes `rawAuthorName`'s value itself
|
|
101
|
+
* (I66-1) rather than building its bracketed text from this one, so the two
|
|
102
|
+
* no longer share a derivation the way the original S4 extraction intended,
|
|
103
|
+
* and instead share `rawAuthorName` as their common raw input.
|
|
104
|
+
*
|
|
105
|
+
* Until owner decision I66-2 (issue #66, 2026-09-23), this ran the raw name
|
|
106
|
+
* through `safeInline`, the same ASCII-only, bracket-stripping sanitiser the
|
|
107
|
+
* text tag used, which erased Cyrillic/CJK/Arabic names from the DATA just
|
|
108
|
+
* as completely as it erased them from the text: a second, previously
|
|
109
|
+
* unreported instance of the bug the issue reports for the tag, found with
|
|
110
|
+
* zero non-Latin fixtures anywhere in this file's history. It now runs
|
|
111
|
+
* through `structuredText` (src/names.ts) instead, the same
|
|
112
|
+
* control/bidi/zero-width-only normalisation `reason` already gets on this
|
|
113
|
+
* surface, so a name survives here whenever it survives on the page.
|
|
114
|
+
*
|
|
115
|
+
* This deliberately drops bracket-stripping on this field: a bare JSON
|
|
116
|
+
* value cannot be "closed early" by a literal `]` or `"` the way a
|
|
117
|
+
* hand-built sentence can (the MCP SDK serialises the field; this code does
|
|
118
|
+
* not concatenate it into one). The property CN-032 actually needs here, a
|
|
119
|
+
* reader must not be shown something that is not there or have text
|
|
120
|
+
* hidden/reordered inside it, is exactly what `structuredText` still strips
|
|
121
|
+
* (control, bidi, zero-width). The `[by "…"]` TEXT tag carries the
|
|
122
|
+
* anti-injection burden for the rendered surface instead, via
|
|
123
|
+
* `exactLiteral`'s quoting.
|
|
124
|
+
*/
|
|
125
|
+
export declare function authorName(p?: Provenance | null): string;
|
|
126
|
+
/**
|
|
127
|
+
* ` [by "X"]` / ` [by "X" · external]`: agent identity only, never the
|
|
128
|
+
* human `principal` (may be an email / PII), even though the response
|
|
129
|
+
* carries it.
|
|
130
|
+
*
|
|
131
|
+
* Quoted as an exact JSON literal (`exactLiteral`, src/names.ts), the same
|
|
132
|
+
* treatment `formatDomainTag` below already gives `@domain`, for EVERY
|
|
133
|
+
* name, including a plain ASCII one like "sigma", not only names that
|
|
134
|
+
* happen to need escaping (owner decision I66-1, issue #66, 2026-09-24:
|
|
135
|
+
* full symmetry with domains, no second unquoted branch). Before this, a
|
|
136
|
+
* Cyrillic, CJK or Arabic `agent_name` sanitised through `safeInline`'s
|
|
137
|
+
* ASCII-only charset to an empty string, and the tag disappeared with no
|
|
138
|
+
* trace, the exact defect `formatDomainTag` was already fixed for in 0.8.1,
|
|
139
|
+
* reached here in a later release (issue #66).
|
|
140
|
+
*
|
|
141
|
+
* Operates on `rawAuthorName`, not `authorName`'s sanitised value: quoting
|
|
142
|
+
* the raw wire name is what lets `exactLiteral` print and escape characters
|
|
143
|
+
* a sanitiser would have dropped, and what lets `withDomainEscapeLegend`'s
|
|
144
|
+
* independent recomputation (src/tools.ts) find the same literal this
|
|
145
|
+
* function printed.
|
|
146
|
+
*
|
|
147
|
+
* `" · external"` sits OUTSIDE the quotes (I66-3): it is a server-added
|
|
148
|
+
* qualifier, not part of the name, so it is not part of what gets escaped
|
|
149
|
+
* and not part of what the escape-legend candidate has to match.
|
|
150
|
+
*
|
|
151
|
+
* The cap is `MAX_DOMAIN_TAG_LITERAL` itself, not a second constant with the
|
|
152
|
+
* same value (I66-4): a name too long to print exactly gets the same
|
|
153
|
+
* disclosed, name-free fallback `formatDomainTag` uses, reworded for "name".
|
|
80
154
|
*/
|
|
81
155
|
export declare function formatAuthorTag(p?: Provenance | null): string;
|
|
82
156
|
/**
|
|
@@ -159,11 +233,48 @@ export declare function formatDateTag(createdAt?: string): string;
|
|
|
159
233
|
* it; we simply do not put it in front of a reader yet.
|
|
160
234
|
*/
|
|
161
235
|
export declare function formatReadItem(item: ReadItem, index: number): string;
|
|
236
|
+
/**
|
|
237
|
+
* The `structuredContent` twin of {@link formatReadItem} (S4, structured-output
|
|
238
|
+
* plan): the same item, shaped for memory_read's `outputSchema` instead of for
|
|
239
|
+
* a line of text.
|
|
240
|
+
*
|
|
241
|
+
* PRECONDITION, enforced by the caller (src/tools.ts) before this is ever
|
|
242
|
+
* invoked: `item.atom_id`, `item.content` and `item.domain` are all strings.
|
|
243
|
+
* core's MemoryItemSchema sends all three on every item; a response that
|
|
244
|
+
* doesn't is caught by the handler's item guard and answered with
|
|
245
|
+
* `unreadableAnswerReply` before `structuredItem` is reached, so the casts
|
|
246
|
+
* below are a documented precondition, not a runtime assumption made here.
|
|
247
|
+
*
|
|
248
|
+
* `content` is carried EXACTLY, uncapped and unnormalised (decision OD-11,
|
|
249
|
+
* owner, 2026-09-23): unlike the text line, which goes through `capResult`
|
|
250
|
+
* for the 25K-token result-size cap, `structuredContent` is not capped
|
|
251
|
+
* anywhere else in this package either (memory_write's `reason` is the only
|
|
252
|
+
* normalised structured field, and that's control/bidi/zero-width hygiene on
|
|
253
|
+
* a diagnostic string, not a length cap on the memory itself); a client
|
|
254
|
+
* reading structured data reads `content` as the stored memory, and a
|
|
255
|
+
* silently shorter value there would be a different kind of lie than a
|
|
256
|
+
* truncated text block with a notice at the end.
|
|
257
|
+
*/
|
|
258
|
+
export declare function structuredItem(item: ReadItem): {
|
|
259
|
+
memory_id: string;
|
|
260
|
+
content: string;
|
|
261
|
+
domain: string;
|
|
262
|
+
created_at?: string;
|
|
263
|
+
author?: string;
|
|
264
|
+
};
|
|
162
265
|
/**
|
|
163
266
|
* One memory_list_recent line — a feed entry, not a search hit: no
|
|
164
267
|
* relevance, date leads because the feed is ORDERED by it.
|
|
165
268
|
*/
|
|
166
269
|
export declare function formatRecentItem(item: RecentItem, index: number): string;
|
|
270
|
+
/**
|
|
271
|
+
* The shape of a continuation token this client is willing to pass on
|
|
272
|
+
* (CN-032): the server-supplied cursor is opaque, so this is an allowlist
|
|
273
|
+
* of bytes, not a format. One constant for BOTH surfaces, the text (below)
|
|
274
|
+
* and `structuredContent.next_cursor` (src/tools.ts), so they cannot
|
|
275
|
+
* disagree about which token is passable.
|
|
276
|
+
*/
|
|
277
|
+
export declare const CURSOR_RE: RegExp;
|
|
167
278
|
/**
|
|
168
279
|
* Full feed page: items newest-first + how to continue / that it's over.
|
|
169
280
|
*
|
|
@@ -175,4 +286,4 @@ export declare function formatRecentItem(item: RecentItem, index: number): strin
|
|
|
175
286
|
* 2026-08-08). Once-per-answer still holds — withDomainEscapeLegend is
|
|
176
287
|
* at-most-once by construction (src/names.ts).
|
|
177
288
|
*/
|
|
178
|
-
export declare function formatRecentPage(items: RecentItem[], nextCursor?:
|
|
289
|
+
export declare function formatRecentPage(items: RecentItem[], nextCursor?: unknown): string;
|
package/dist/render.js
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* shapes without atom_id/created_at degrade gracefully: those parts of
|
|
11
11
|
* the line are simply omitted.
|
|
12
12
|
*/
|
|
13
|
-
import { MAX_DOMAIN_TAG_LITERAL, exactLiteral } from "./names.js";
|
|
14
|
-
import { parseAsUtc } from "./time.js";
|
|
13
|
+
import { MAX_DOMAIN_TAG_LITERAL, exactLiteral, structuredText } from "./names.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,115 @@ export function safeInline(s, cap = 200) {
|
|
|
60
60
|
.slice(0, cap);
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
* `
|
|
64
|
-
*
|
|
63
|
+
* `agent_name || agent || client_env`, exactly as the wire sent it: no
|
|
64
|
+
* sanitising, no suffix. The single raw value `authorName` and
|
|
65
|
+
* `formatAuthorTag` below BOTH derive from, so the bare structured field and
|
|
66
|
+
* the quoted text tag cannot end up naming two different agents. The same
|
|
67
|
+
* raw value is what src/tools.ts passes to `withDomainEscapeLegend` as an
|
|
68
|
+
* author candidate, so its independent recomputation of `exactLiteral` finds
|
|
69
|
+
* the same literal `formatAuthorTag` printed (I66-3, issue #66).
|
|
70
|
+
*
|
|
71
|
+
* Returns `""`, not `null`/`undefined`, when there is no renderable name at
|
|
72
|
+
* all: an absent provenance, every field empty, or the field that won the
|
|
73
|
+
* `||` chain arriving with the wrong wire type. A numeric `agent_name` is a
|
|
74
|
+
* real shape a hostile or buggy connector sends, not a hypothetical one; see
|
|
75
|
+
* "a non-string where the type promised a string" below, and CN-032's own
|
|
76
|
+
* history with this exact field.
|
|
65
77
|
*/
|
|
66
|
-
export function
|
|
78
|
+
export function rawAuthorName(p) {
|
|
67
79
|
if (!p)
|
|
68
80
|
return "";
|
|
69
|
-
const raw = p.agent_name || p.agent || p.client_env
|
|
70
|
-
|
|
81
|
+
const raw = p.agent_name || p.agent || p.client_env;
|
|
82
|
+
return typeof raw === "string" ? raw : "";
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* "X" / "X · external": agent identity only, never the human `principal`
|
|
86
|
+
* (may be an email / PII), even though the response carries it. Empty string
|
|
87
|
+
* when there is no renderable name.
|
|
88
|
+
*
|
|
89
|
+
* Feeds `structuredContent.author` (via `structuredItem`, below), not the
|
|
90
|
+
* text tag; `formatAuthorTag` now quotes `rawAuthorName`'s value itself
|
|
91
|
+
* (I66-1) rather than building its bracketed text from this one, so the two
|
|
92
|
+
* no longer share a derivation the way the original S4 extraction intended,
|
|
93
|
+
* and instead share `rawAuthorName` as their common raw input.
|
|
94
|
+
*
|
|
95
|
+
* Until owner decision I66-2 (issue #66, 2026-09-23), this ran the raw name
|
|
96
|
+
* through `safeInline`, the same ASCII-only, bracket-stripping sanitiser the
|
|
97
|
+
* text tag used, which erased Cyrillic/CJK/Arabic names from the DATA just
|
|
98
|
+
* as completely as it erased them from the text: a second, previously
|
|
99
|
+
* unreported instance of the bug the issue reports for the tag, found with
|
|
100
|
+
* zero non-Latin fixtures anywhere in this file's history. It now runs
|
|
101
|
+
* through `structuredText` (src/names.ts) instead, the same
|
|
102
|
+
* control/bidi/zero-width-only normalisation `reason` already gets on this
|
|
103
|
+
* surface, so a name survives here whenever it survives on the page.
|
|
104
|
+
*
|
|
105
|
+
* This deliberately drops bracket-stripping on this field: a bare JSON
|
|
106
|
+
* value cannot be "closed early" by a literal `]` or `"` the way a
|
|
107
|
+
* hand-built sentence can (the MCP SDK serialises the field; this code does
|
|
108
|
+
* not concatenate it into one). The property CN-032 actually needs here, a
|
|
109
|
+
* reader must not be shown something that is not there or have text
|
|
110
|
+
* hidden/reordered inside it, is exactly what `structuredText` still strips
|
|
111
|
+
* (control, bidi, zero-width). The `[by "…"]` TEXT tag carries the
|
|
112
|
+
* anti-injection burden for the rendered surface instead, via
|
|
113
|
+
* `exactLiteral`'s quoting.
|
|
114
|
+
*/
|
|
115
|
+
export function authorName(p) {
|
|
116
|
+
// Present exactly when the text tag prints the name (Sigma, review round
|
|
117
|
+
// 3 on #66): the same exactLiteral check under MAX_DOMAIN_TAG_LITERAL
|
|
118
|
+
// decides, so a name the tag refuses ("(name cannot be printed exactly)")
|
|
119
|
+
// is withheld here too, and a name the tag prints is carried whole (the
|
|
120
|
+
// structuredText normalisation, never a truncated prefix). The old cap of
|
|
121
|
+
// 64 code points, inherited from safeInline, let the data hold a shorter
|
|
122
|
+
// name than the page showed, with nothing marking the cut.
|
|
123
|
+
const raw = rawAuthorName(p);
|
|
124
|
+
if (!raw || !exactLiteral(raw, MAX_DOMAIN_TAG_LITERAL))
|
|
125
|
+
return "";
|
|
126
|
+
// One stated exception: a name made only of the characters structuredText
|
|
127
|
+
// removes (whitespace, control, bidi, zero-width) is printed exactly in
|
|
128
|
+
// the tag, as an escaped literal with the legend, but leaves nothing to
|
|
129
|
+
// carry as a plain data value; the data omits the field rather than carry
|
|
130
|
+
// "" or the raw characters.
|
|
131
|
+
const who = structuredText(raw, MAX_DOMAIN_TAG_LITERAL);
|
|
71
132
|
if (!who)
|
|
72
133
|
return "";
|
|
73
|
-
return p
|
|
134
|
+
return p?.is_external === true ? `${who} · external` : who;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* ` [by "X"]` / ` [by "X" · external]`: agent identity only, never the
|
|
138
|
+
* human `principal` (may be an email / PII), even though the response
|
|
139
|
+
* carries it.
|
|
140
|
+
*
|
|
141
|
+
* Quoted as an exact JSON literal (`exactLiteral`, src/names.ts), the same
|
|
142
|
+
* treatment `formatDomainTag` below already gives `@domain`, for EVERY
|
|
143
|
+
* name, including a plain ASCII one like "sigma", not only names that
|
|
144
|
+
* happen to need escaping (owner decision I66-1, issue #66, 2026-09-24:
|
|
145
|
+
* full symmetry with domains, no second unquoted branch). Before this, a
|
|
146
|
+
* Cyrillic, CJK or Arabic `agent_name` sanitised through `safeInline`'s
|
|
147
|
+
* ASCII-only charset to an empty string, and the tag disappeared with no
|
|
148
|
+
* trace, the exact defect `formatDomainTag` was already fixed for in 0.8.1,
|
|
149
|
+
* reached here in a later release (issue #66).
|
|
150
|
+
*
|
|
151
|
+
* Operates on `rawAuthorName`, not `authorName`'s sanitised value: quoting
|
|
152
|
+
* the raw wire name is what lets `exactLiteral` print and escape characters
|
|
153
|
+
* a sanitiser would have dropped, and what lets `withDomainEscapeLegend`'s
|
|
154
|
+
* independent recomputation (src/tools.ts) find the same literal this
|
|
155
|
+
* function printed.
|
|
156
|
+
*
|
|
157
|
+
* `" · external"` sits OUTSIDE the quotes (I66-3): it is a server-added
|
|
158
|
+
* qualifier, not part of the name, so it is not part of what gets escaped
|
|
159
|
+
* and not part of what the escape-legend candidate has to match.
|
|
160
|
+
*
|
|
161
|
+
* The cap is `MAX_DOMAIN_TAG_LITERAL` itself, not a second constant with the
|
|
162
|
+
* same value (I66-4): a name too long to print exactly gets the same
|
|
163
|
+
* disclosed, name-free fallback `formatDomainTag` uses, reworded for "name".
|
|
164
|
+
*/
|
|
165
|
+
export function formatAuthorTag(p) {
|
|
166
|
+
const raw = rawAuthorName(p);
|
|
167
|
+
if (!raw)
|
|
168
|
+
return "";
|
|
169
|
+
const exact = exactLiteral(raw, MAX_DOMAIN_TAG_LITERAL);
|
|
170
|
+
const printed = exact ? exact.literal : "(name cannot be printed exactly)";
|
|
171
|
+
return ` [by ${printed}${p?.is_external === true ? " · external" : ""}]`;
|
|
74
172
|
}
|
|
75
173
|
/**
|
|
76
174
|
* ` @"domain"` — which store the memory actually came from, printed so it can
|
|
@@ -170,6 +268,45 @@ export function formatReadItem(item, index) {
|
|
|
170
268
|
const head = `${index + 1}. ${content}${concepts}${formatDomainTag(item?.domain)}${formatAuthorTag(item?.provenance)}${formatDateTag(item?.created_at)}`;
|
|
171
269
|
return item?.atom_id ? `${head}\n id: ${item.atom_id}` : head;
|
|
172
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* The `structuredContent` twin of {@link formatReadItem} (S4, structured-output
|
|
273
|
+
* plan): the same item, shaped for memory_read's `outputSchema` instead of for
|
|
274
|
+
* a line of text.
|
|
275
|
+
*
|
|
276
|
+
* PRECONDITION, enforced by the caller (src/tools.ts) before this is ever
|
|
277
|
+
* invoked: `item.atom_id`, `item.content` and `item.domain` are all strings.
|
|
278
|
+
* core's MemoryItemSchema sends all three on every item; a response that
|
|
279
|
+
* doesn't is caught by the handler's item guard and answered with
|
|
280
|
+
* `unreadableAnswerReply` before `structuredItem` is reached, so the casts
|
|
281
|
+
* below are a documented precondition, not a runtime assumption made here.
|
|
282
|
+
*
|
|
283
|
+
* `content` is carried EXACTLY, uncapped and unnormalised (decision OD-11,
|
|
284
|
+
* owner, 2026-09-23): unlike the text line, which goes through `capResult`
|
|
285
|
+
* for the 25K-token result-size cap, `structuredContent` is not capped
|
|
286
|
+
* anywhere else in this package either (memory_write's `reason` is the only
|
|
287
|
+
* normalised structured field, and that's control/bidi/zero-width hygiene on
|
|
288
|
+
* a diagnostic string, not a length cap on the memory itself); a client
|
|
289
|
+
* reading structured data reads `content` as the stored memory, and a
|
|
290
|
+
* silently shorter value there would be a different kind of lie than a
|
|
291
|
+
* truncated text block with a notice at the end.
|
|
292
|
+
*/
|
|
293
|
+
export function structuredItem(item) {
|
|
294
|
+
const author = authorName(item.provenance);
|
|
295
|
+
const created = utcInstant(item.created_at);
|
|
296
|
+
return {
|
|
297
|
+
memory_id: item.atom_id,
|
|
298
|
+
content: item.content,
|
|
299
|
+
domain: item.domain,
|
|
300
|
+
// The rule the text already applies through formatDateTag: a value that
|
|
301
|
+
// does not parse as a date is no creation instant, whatever its type, and
|
|
302
|
+
// the field promises a UTC ISO-8601 instant. A value that states its
|
|
303
|
+
// offset is carried as sent; an offset-less one (UTC by contract) is
|
|
304
|
+
// re-emitted as the UTC instant the text renders, since a consumer
|
|
305
|
+
// would otherwise read it as local time (src/time.ts, utcInstant).
|
|
306
|
+
...(created !== null ? { created_at: created } : {}),
|
|
307
|
+
...(author ? { author } : {}),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
173
310
|
/**
|
|
174
311
|
* One memory_list_recent line — a feed entry, not a search hit: no
|
|
175
312
|
* relevance, date leads because the feed is ORDERED by it.
|
|
@@ -183,6 +320,14 @@ export function formatRecentItem(item, index) {
|
|
|
183
320
|
const head = `${index + 1}. ${date ? `[${date}] ` : ""}${content}${concepts}${formatDomainTag(item?.domain)}${formatAuthorTag(item?.provenance)}`;
|
|
184
321
|
return item?.atom_id ? `${head}\n id: ${item.atom_id}` : head;
|
|
185
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* The shape of a continuation token this client is willing to pass on
|
|
325
|
+
* (CN-032): the server-supplied cursor is opaque, so this is an allowlist
|
|
326
|
+
* of bytes, not a format. One constant for BOTH surfaces, the text (below)
|
|
327
|
+
* and `structuredContent.next_cursor` (src/tools.ts), so they cannot
|
|
328
|
+
* disagree about which token is passable.
|
|
329
|
+
*/
|
|
330
|
+
export const CURSOR_RE = /^[A-Za-z0-9_=-]{1,512}$/;
|
|
186
331
|
/**
|
|
187
332
|
* Full feed page: items newest-first + how to continue / that it's over.
|
|
188
333
|
*
|
|
@@ -194,6 +339,9 @@ export function formatRecentItem(item, index) {
|
|
|
194
339
|
* 2026-08-08). Once-per-answer still holds — withDomainEscapeLegend is
|
|
195
340
|
* at-most-once by construction (src/names.ts).
|
|
196
341
|
*/
|
|
342
|
+
// `nextCursor` is typed loosely on purpose: it is a server-supplied wire
|
|
343
|
+
// value, and a number where a string was promised must fail the gate below
|
|
344
|
+
// rather than be coerced into it by the regex test (review, 2026-09-23).
|
|
197
345
|
export function formatRecentPage(items, nextCursor) {
|
|
198
346
|
const lines = items.map((it, i) => formatRecentItem(it, i));
|
|
199
347
|
// Defense-in-depth (CN-032 posture): the cursor is server-supplied and
|
|
@@ -218,7 +366,7 @@ export function formatRecentPage(items, nextCursor) {
|
|
|
218
366
|
if (nextCursor == null) {
|
|
219
367
|
tail = `\n\n(end of feed — nothing older)`;
|
|
220
368
|
}
|
|
221
|
-
else if (
|
|
369
|
+
else if (typeof nextCursor === "string" && CURSOR_RE.test(nextCursor)) {
|
|
222
370
|
tail = `\n\nMore older entries exist — pass cursor: ${nextCursor}`;
|
|
223
371
|
}
|
|
224
372
|
else {
|
package/dist/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
package/dist/requests.d.ts
CHANGED
|
@@ -52,6 +52,29 @@ export interface WriteArgs {
|
|
|
52
52
|
concepts?: string[];
|
|
53
53
|
domain?: string;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Supplier-vouched authorship for a write (STEP4-5, owner 2026-09-24),
|
|
57
|
+
* mirroring core's `ProvenanceWriteSchema` field for field
|
|
58
|
+
* (mnemoverse-core src/mnemo/api/schemas.py:123-150): `principal`, `agent`,
|
|
59
|
+
* `agent_name`, `client_env` and `is_external`, all optional.
|
|
60
|
+
*
|
|
61
|
+
* Core honours this ONLY for a SERVICE/supplier caller (`author` in the
|
|
62
|
+
* write body) and IGNORES it entirely for an OIDC end-user, who is stamped
|
|
63
|
+
* solely from their own verified token (`routes._get_provenance`,
|
|
64
|
+
* mnemoverse-core src/mnemo/api/routes.py:211-276), so a caller supplying
|
|
65
|
+
* this for a plain end-user key gets no error, just a silently dropped
|
|
66
|
+
* value. Core also RE-NORMALISES every field server-side (an allow-list on
|
|
67
|
+
* `client_env`, length caps, `is_external` coerced to a real boolean), so
|
|
68
|
+
* this package does not re-validate the shape beyond the `typeof` guard at
|
|
69
|
+
* the one call site that reads it: that is core's job, not this one's.
|
|
70
|
+
*/
|
|
71
|
+
export interface WriteAuthor {
|
|
72
|
+
principal?: string;
|
|
73
|
+
agent?: string;
|
|
74
|
+
agent_name?: string;
|
|
75
|
+
client_env?: string;
|
|
76
|
+
is_external?: boolean;
|
|
77
|
+
}
|
|
55
78
|
/**
|
|
56
79
|
* The scope actually searched: what core receives, and therefore the only value
|
|
57
80
|
* any message about the result may describe.
|
|
@@ -66,9 +89,17 @@ export declare function searchedScope(domain?: string): string | undefined;
|
|
|
66
89
|
export declare function readRequestBody(a: ReadArgs): Record<string, unknown>;
|
|
67
90
|
/** memory_list_recent. */
|
|
68
91
|
export declare function recentRequestBody(a: RecentArgs): Record<string, unknown>;
|
|
69
|
-
/**
|
|
70
|
-
*
|
|
71
|
-
|
|
92
|
+
/**
|
|
93
|
+
* memory_write. `"general"` is the server-side default made explicit, and is
|
|
94
|
+
* what 0.8.0 sent — NOT a normalisation of the caller's value.
|
|
95
|
+
*
|
|
96
|
+
* `author` (STEP4-5): sent verbatim, exactly as the caller's `writeAuthor()`
|
|
97
|
+
* dependency returned it, when present: this function does no field-level
|
|
98
|
+
* normalisation of it (core re-normalises server-side, see {@link WriteAuthor}).
|
|
99
|
+
* Omitted entirely, not sent as `undefined`, when there is none, so every
|
|
100
|
+
* existing write body this function ever produced stays byte-identical.
|
|
101
|
+
*/
|
|
102
|
+
export declare function writeRequestBody(a: WriteArgs, author?: WriteAuthor): Record<string, unknown>;
|
|
72
103
|
/**
|
|
73
104
|
* Refuse a docs placeholder key from CONFIGURATION ALONE, before any tool
|
|
74
105
|
* call sends it anywhere.
|
package/dist/requests.js
CHANGED
|
@@ -71,13 +71,22 @@ export function recentRequestBody(a) {
|
|
|
71
71
|
cursor: a.cursor || undefined,
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
/**
|
|
75
|
-
*
|
|
76
|
-
|
|
74
|
+
/**
|
|
75
|
+
* memory_write. `"general"` is the server-side default made explicit, and is
|
|
76
|
+
* what 0.8.0 sent — NOT a normalisation of the caller's value.
|
|
77
|
+
*
|
|
78
|
+
* `author` (STEP4-5): sent verbatim, exactly as the caller's `writeAuthor()`
|
|
79
|
+
* dependency returned it, when present: this function does no field-level
|
|
80
|
+
* normalisation of it (core re-normalises server-side, see {@link WriteAuthor}).
|
|
81
|
+
* Omitted entirely, not sent as `undefined`, when there is none, so every
|
|
82
|
+
* existing write body this function ever produced stays byte-identical.
|
|
83
|
+
*/
|
|
84
|
+
export function writeRequestBody(a, author) {
|
|
77
85
|
return {
|
|
78
86
|
content: a.content,
|
|
79
87
|
concepts: a.concepts || [],
|
|
80
88
|
domain: a.domain || "general",
|
|
89
|
+
...(author === undefined ? {} : { author }),
|
|
81
90
|
};
|
|
82
91
|
}
|
|
83
92
|
/**
|
package/dist/requests.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAmDvC;;;;;;;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;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAY,EAAE,MAAoB;IACjE,OAAO;QACL,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,EAAE;QAC1B,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,SAAS;QAC7B,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;KAC5C,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;
|