UNPKG

@mnemoverse/mcp-memory-server

Version:

Hosted AI agent memory over MCP that learns from outcomes — feedback re-ranks recall — with shared rooms for multi-agent work; one key or OAuth across Claude Code, Cursor, VS Code & ChatGPT, no infra to run

139 lines (138 loc) • 7.56 kB
/** * The eleven tools (ten memory tools and vault_list), as one function any MCP server can register. * * ADR-025 (mnemoverse-core): this package defines the MCP surface, and every * server that exposes Mnemoverse memory over MCP registers the SAME tools from * here instead of keeping its own copy. The stdio server in src/index.ts is one * such server; the hosted connector (mnemoverse-mcp-remote) is the other. * * What differs between servers is only how a request reaches the API: which * credential it carries, where it goes, and how a transport failure is worded. * That is `MemoryToolDeps.apiFetch`. Nothing in this file reads configuration * or the environment. */ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { type WriteAuthor } from "./requests.js"; import { type Wording } from "./errors.js"; /** * How the tools reach the Mnemoverse API: send `path` (relative to the API base, * e.g. "/memory/read") and resolve to the parsed JSON body. * * Contract every implementation must keep, because the tools branch on it: * a non-2xx rejects with `ApiError` (status, core's error code and an * agent-facing message), a request that never got an HTTP answer rejects with * `NetworkError`, and a 2xx whose body cannot be parsed rejects with * `UnreadableBodyError`. All three are exported from ./shared and defined in * src/errors.ts. `options.signal` must be honoured. */ export type ApiFetch = <T = unknown>(path: string, options?: RequestInit) => Promise<T>; /** * `deps.apiFetch` with `deps.wording` applied to what it throws (STEP4-2): * every rejection passes through {@link rewordFailure}, so an `ApiError`, * `NetworkError` or `UnreadableBodyError` the consumer built with no * `wording` (or with a different one) reaches the model explained under the * wording this registration was given. Results and every other rejection * pass through untouched. Installed only when `wording` is supplied at all: * the stdio server supplies none, and its `apiFetch` is used exactly as * before. */ export declare function wordedApiFetch(inner: ApiFetch, wording: Wording): ApiFetch; /** * What a server supplies when it registers the memory tools. * * `wording` and `writeAuthor` (STEP4-2/3/5, owner decisions 2026-09-24) are * both optional, and both default to exactly this server's own behaviour: a * consumer that supplies neither gets byte-identical descriptions, error * text and write bodies to every release before this one. They exist for a * SECOND server registering these same tools (the hosted connector, * ADR-025) that needs to say "this connector" instead of "this server", * speak to an OAuth user who never sees an API key, and vouch for the * end user it is writing on behalf of. */ export interface MemoryToolDeps { apiFetch: ApiFetch; /** How this registration wants its own tool descriptions and error * explanations worded. See {@link Wording} for each field. Read at * registration time for the three descriptions that name the server, * and applied to every `ApiError`/`NetworkError`/`UnreadableBodyError` * the consumer's `apiFetch` rejects with (see {@link wordedApiFetch}), * so the consumer states it once, here. Passing the same value to the * error constructors inside `apiFetch` is allowed and changes nothing. */ wording?: Wording; /** * Supplier-vouched authorship for `memory_write` (STEP4-5). Called once * per write, with no arguments; a returned value is sent as the request * body's `author` field EXACTLY as returned (this package applies no * normalisation beyond a `typeof` guard; core re-normalises server-side, * see {@link WriteAuthor}). Returning `undefined`, or omitting this * dependency entirely, sends the body this package has always sent: the * stdio server's own requests are exactly this case, and do not change. * * Core honours `author` ONLY for a SERVICE/supplier caller and IGNORES it * for an OIDC end-user (mnemoverse-core src/mnemo/api/routes.py:211-276, * `_get_provenance`, verified 2026-09-24), so this dependency is useful * only to a server that authenticates to core as a supplier vouching for * someone else, not to an end-user's own key. */ writeAuthor?: () => WriteAuthor | undefined; } export declare const MAX_RESULT_CHARS: number; /** * Truncate a result string to MAX_RESULT_CHARS, appending a notice if truncated. * Required by Claude Connectors Directory submission policy. * * Defensive against splitting UTF-16 surrogate pairs: if the character right * before the cut point is a high surrogate (U+D800–U+DBFF), drop it so the * result stays well-formed. Otherwise an emoji or non-BMP character at the * boundary can produce a lone surrogate and corrupt downstream JSON encoding. * * Exported (re-exported from ./shared) so a consumer applying MAX_RESULT_CHARS * to its own tool results does not have to re-implement this code-point-safe * truncation as a plain `slice`, which can cut a surrogate pair. */ export declare function capResult(text: string, moreHint?: string): string; /** * The sentence behind {@link unreadableAnswerReply}, exported so the memory * resource (src/resources.ts) says the same thing about an unreadable answer * rather than keeping its own copy. * * The tail attributes the unreadable 200 to "whatever answered this call", not * to "the memory service" — this client cannot establish WHO answered: a * gateway, a proxy, or the endpoint a mis-set MNEMOVERSE_API_URL points at * produces the same 200 with an unrecognised body (truth re-verification, * 2026-08-09). Pinned in test/handlers.test.ts. */ export declare function unreadableAnswerText(subject: string, notA: string, absence: string, extra?: string): string; /** * A success result carrying both a text block and `structuredContent`, which * is what a tool with a declared `outputSchema` must return, or the SDK * itself rejects the call. `validateToolOutput` in the installed SDK * (node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:185-207) * turns a non-error, text-only result into an `isError` "Output validation * error: … has an output schema but no structured content was provided", the * moment a tool declares that schema, even though the same text alone was a * fine answer the day before the schema was added. Pinned against that * installed SDK in test/structured-output.test.ts. * * Deliberately dumb: it does not call `capResult` or `withDomainEscapeLegend` * itself. Every tool already measures and caps its own text with a per-tool * hint and applies the legend after the cap (see memory_read below), so a * helper that capped again would double-truncate; callers pass `text` * already finished. */ export declare function structured(text: string, data: Record<string, unknown>): { content: [{ type: "text"; text: string; }]; structuredContent: Record<string, unknown>; }; /** * Register the eleven tools on `server`. Call once per server instance, before * any other tool is registered on it: the first thing this does is make the * server's `tools/list` describe every tool schema in JSON Schema 2020-12 * (src/schema-dialect.ts, #189), which has to happen before the SDK installs * that handler at the first registration. * `deps.apiFetch` is the only way the tools reach the API. */ export declare function registerMemoryTools(server: McpServer, deps: MemoryToolDeps): void;