@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
166 lines • 8.54 kB
JavaScript
/**
* MCP prompts: three named entry points to the memory tools, and
* setup_memory, which hands the user rules for their own agent (2026-09-29).
*
* Clients that support the prompts capability show them as commands (Claude
* Code as `/mcp__mnemoverse__<name>`, Claude.ai connectors in the slash menu).
* Each returns one user message that points the model at the right tool. They
* add no capability beyond the tools; a client without prompt support ignores
* them.
*
* Moved here from the hosted connector (mnemoverse-mcp-remote,
* src/prompts/index.ts) in step 3c of the ADR-025 plan, with the same names,
* arguments and wording, so the connector can register these instead of its
* copy. One difference is deliberate: `domain` in save_insight is printed as
* an exact JSON literal (src/names.ts), like every domain this package names,
* because the model has to send it back to memory_write byte for byte. For a
* plain domain the message is identical to the connector's. The other
* arguments are trimmed, as the connector trims them, because a blank topic is
* not a request.
*/
import { z } from "zod";
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
import { exactLiteral, withDomainEscapeLegend } from "./names.js";
/**
* The rules setup_memory hands out. They carry what the server text no longer
* says (src/teaching.ts, product frame): when to read, when to write, when to
* rate. They are written for the user to place in their own agent's
* instructions, where a host loads them as the user's own rules at the start
* of every session. Exported so the docs and tests read one copy.
*/
export const MEMORY_RULES = [
"## Memory (Mnemoverse)",
"- Before answering something that may depend on an earlier session (my preferences, past decisions, project setup, people), search memory with memory_read.",
"- When I state a lasting preference or make a decision, or you learn a durable fact about this project, save it with memory_write as one self-contained statement. You do not need to ask me first.",
"- When a message also carries a durable fact (who now owns or does what, a lesson learned, a new convention), save that fact with memory_write before doing the rest of the task.",
"- After acting on recalled memories, rate them with memory_feedback: helpful or not. For a shared room's memories, pass the room's address as domain; a read-only member cannot rate them.",
"- Never store passwords, API keys, payment data, MFA codes, government IDs, or health records.",
].join("\n");
/** Where the rules go, per host. `host` is matched after trimming and lowercasing. */
export const RULES_PLACES = {
"claude-code": "Claude Code: add them to CLAUDE.md in the project root, or to ~/.claude/CLAUDE.md to apply them in every project.",
"claude-ai": "Claude on the web, desktop or mobile: paste them into Instructions for Claude in Settings, or into a project's instructions.",
cursor: "Cursor: save them as a rule file in .cursor/rules/, for example mnemoverse-memory.mdc with alwaysApply: true.",
codex: "Codex and other agents that read AGENTS.md: add them to AGENTS.md in the project root.",
};
/**
* Register the memory prompts on `server`. They call nothing: recall,
* save_insight and what_do_you_know render a message that asks the model to
* use memory_read or memory_write; setup_memory renders the rules above with
* where they go.
*/
export function registerMemoryPrompts(server) {
server.registerPrompt("recall", {
title: "Recall from memory",
description: "Search your Mnemoverse memory for saved information about a specific topic.",
argsSchema: {
topic: z
.string()
.trim()
.min(1)
.describe("What to recall — a topic, project, person, or open question."),
},
}, ({ topic }) => ({
messages: [
{
role: "user",
content: {
type: "text",
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.`,
},
},
],
}));
server.registerPrompt("save_insight", {
title: "Save an insight to memory",
description: "Store a new insight, fact, or decision in your Mnemoverse long-term memory.",
argsSchema: {
insight: z
.string()
.trim()
.min(1)
.describe("The insight, fact, or decision to remember."),
domain: z
.string()
.optional()
.describe("Optional domain/category to file it under (e.g. 'project-x')."),
},
}, ({ insight, domain }) => {
// The domain is an identifier the model must reproduce exactly, so it is
// printed as a JSON literal: a quote, backslash, newline or invisible
// character inside it is escaped instead of closing the quotes early
// (Copilot on #148). A plain domain prints exactly as the connector's
// `"${domain}"` did. A domain too long to print exactly is refused
// rather than named inexactly or dropped, since dropping it would file
// the insight somewhere the user did not ask for.
let under = "";
if (domain) {
const exact = exactLiteral(domain);
if (exact === null) {
throw new McpError(ErrorCode.InvalidParams, "save_insight: this domain is too long to be quoted exactly in the message. Pass a shorter domain.");
}
under = ` under the domain ${exact.literal}`;
}
const text = `Use the memory_write tool to store this insight in my long-term memory${under}: "${insight}". Then confirm exactly what was stored.`;
return {
messages: [
{
role: "user",
content: {
type: "text",
text: withDomainEscapeLegend(text, domain),
},
},
],
};
});
server.registerPrompt("what_do_you_know", {
title: "What do you know about…",
description: "Get a briefing of what your memory holds about a subject.",
argsSchema: {
subject: z.string().trim().min(1).describe("The subject to brief on."),
},
}, ({ subject }) => ({
messages: [
{
role: "user",
content: {
type: "text",
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.`,
},
},
],
}));
server.registerPrompt("setup_memory", {
title: "Set up memory rules",
description: "Get memory rules to add to CLAUDE.md, AGENTS.md, Cursor rules or your chat preferences, so your assistant checks and saves memory without being reminded.",
argsSchema: {
host: z
.string()
.optional()
.describe("Where the rules will live: claude-code, claude-ai, cursor or codex. Leave empty to see every option."),
},
}, ({ host }) => {
const key = (host ?? "").trim().toLowerCase();
// Own keys only: a host of "constructor" or "__proto__" must not reach
// Object.prototype and print a function where a sentence belongs.
const place = Object.hasOwn(RULES_PLACES, key) ? RULES_PLACES[key] : undefined;
const where = place
? place
: `Where they go:\n${Object.values(RULES_PLACES)
.map((line) => `- ${line}`)
.join("\n")}`;
const text = "Help me set up memory rules so you use my Mnemoverse memory on your own. " +
`These are the rules:\n\n${MEMORY_RULES}\n\n${where}\n\n` +
"Show me where they go, explain in two sentences why rules like these work better than asking in every chat, and offer to add them for me.";
return {
messages: [
{
role: "user",
content: { type: "text", text },
},
],
};
});
}
//# sourceMappingURL=prompts.js.map