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