Generated banner for Mnemoverse Memory
Generated logo for Mnemoverse Memory

Mnemoverse Memory

Memory serverOfficial Mnemoverse project

Created by Mnemoverse

Published in mnemoverse/mcp-memory-server. Publisher page

28 GitHub stars

What it is

Hosted long-term memory for AI agents over MCP. Write and read memories from Claude Code, Cursor, VS Code and ChatGPT; rating a recalled memory as helpful or misleading re-ranks what comes back next. OAuth sign-in or API key, free plan.
— the description by Mnemoverse

AutoGineer overview, from the brain’s own files

Mnemoverse Memory is a hosted memory server that enables AI agents to persist information across different sessions and tools. By utilizing the Model Context Protocol (MCP), it allows agents in environments like Claude, Cursor, and VS Code to read and write to a centralized memory store. The system features a feedback mechanism where agents can rate recalled memories, which the server uses to re-rank future retrieval results. It also supports collaborative memory through shared rooms, allowing multiple users or agents to contribute to a common knowledge pool.

How it works

Memory model: Hybrid

AutoGineer overview, from the brain’s own files

The system acts as an MCP server that provides tools for agents to interact with a hosted memory database. Agents use memory_write to store information and memory_read to retrieve it using natural language. The server maintains a graph of associations between concepts. Users can create shared rooms for collaborative memory, and agents can provide feedback on the utility of retrieved information, which influences the ranking of future search results.

Folders
FolderRoleFiles
.githubagent-config8
docsdocs24
scriptsscripts11
srcsource-code15
testtests49

The agent reads

The agent writes

Requirements

Runtimes: Node.js >=18

Databases: PostgreSQL

API keys (by name): MNEMOVERSE_API_KEY

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Google Antigravity, Kiro, OpenCode, Amp, Roo Code, Cline, JetBrains Junie, Devin (ex-Windsurf)

Set it up

  1. Add mcp-memory-server to .mcp.json:

    {
      "mcpServers": {
        "mcp-memory-server": {
          "type": "http",
          "url": "https://mcp.mnemoverse.com/mcp"
        }
      }
    }
    
  2. Or add it with the Claude Code CLI:

    claude mcp add --transport http mcp-memory-server https://mcp.mnemoverse.com/mcp
  3. Your data is sent to mcp.mnemoverse.com.

  4. Add mnemoverse to .mcp.json:

    {
      "mcpServers": {
        "mnemoverse": {
          "type": "http",
          "url": "https://mcp.mnemoverse.com/mcp"
        }
      }
    }
    
  5. Or add it with the Claude Code CLI:

    claude mcp add --transport http mnemoverse https://mcp.mnemoverse.com/mcp
  6. Your data is sent to mcp.mnemoverse.com.

  7. Not scanned by AutoGineerIts dependencies are installed by npm when you run it and were not scanned by AutoGineer (2 declared). https://www.npmjs.com/package/@mnemoverse/mcp-memory-server
  8. Created by Mnemoverse. Published in mnemoverse/mcp-memory-server. Licence: MIT License.

Usage

AutoGineer overview, from the brain’s own files

  • Store project-specific preferences or coding patterns for future sessions.
  • Retrieve historical context when starting work on a previously touched project.
  • Collaborate with teammates by sharing a memory room.
  • Provide feedback on memory relevance to improve future retrieval accuracy.
“Save that we prefer using Vitest for testing in this project.”
The agent writes this preference to the memory server, making it available for future tasks.
“How do we usually handle authentication in this codebase?”
The agent queries the memory server and retrieves previously saved notes on authentication patterns.

Documentation by Mnemoverse

Read the documentation at the source

docs/shared.md

@mnemoverse/mcp-memory-server/shared: building a second MCP server

This is the consumer guide for /shared, the entry point ADR-025 (mnemoverse-core) designates as the single source of the Mnemoverse MCP surface. A server that wants to expose the same eleven tools does not keep its own copy: it imports this entry point, supplies its own credential and wording, and registers.

The first consumer is the hosted connector, mnemoverse-mcp-remote, adopting this package in step 4 of the ADR-025 plan. This file is also that step's S0 delta: what the connector's adapter needs from the package, stated once here instead of scattered across commit messages.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import {
  registerMemoryPrompts,
  registerMemoryResources,
  registerMemoryTools,
  SERVER_INSTRUCTIONS,
  MAX_RESULT_CHARS,
  capResult,
  ApiError,
  NetworkError,
  UnreadableBodyError,
} from "@mnemoverse/mcp-memory-server/shared";

const server = new McpServer({ name, version }, { instructions: SERVER_INSTRUCTIONS });
registerMemoryTools(server, { apiFetch, wording, writeAuthor });
registerMemoryPrompts(server);
registerMemoryResources(server, { apiFetch, wording });

Importing /shared starts nothing. It never pulls in the package's own stdio entry point (src/index.ts), which opens a stdio transport as a side effect of being imported. A second server can import /shared inside its own process without inheriting that behaviour.

What /shared exports

  • registerMemoryTools(server, deps), registerMemoryPrompts(server), registerMemoryResources(server, deps). Three separate calls, because a consumer that only wants the tools (no prompts, no resources) should not have to register the other two.

  • SERVER_INSTRUCTIONS: the string this package's own stdio server passes as instructions when constructing McpServer. Reusing it keeps the model-facing instructions identical across every server that registers this surface; a consumer that wants its own instructions can ignore it and pass something else.

  • MAX_RESULT_CHARS and capResult(text, moreHint?): the tool-result text cap (96,000 characters, 24,000 tokens) and the helper that applies it. See below.

  • ApiError, NetworkError, UnreadableBodyError and their supporting types ApiFailure, ErrorEnvelope, UnreadableBody: the three error shapes apiFetch must throw. See below.

  • Wording and WriteAuthor: the two dependency types described in this file.

  • The types ApiFetch and MemoryToolDeps.

  • serveToolSchemasIn2020_12(server): makes the server's tools/list describe every tool schema in JSON Schema 2020-12. registerMemoryTools calls it; see "Tool schemas: JSON Schema 2020-12" below.

Tool schemas: JSON Schema 2020-12

@modelcontextprotocol/sdk 1.x writes every tool schema it lists as draft-07 ("$schema": "http://json-schema.org/draft-07/schema#"): it converts zod schemas without a target, and its converter falls back to draft-07 (modelcontextprotocol/typescript-sdk#2084). MCP 2025-11-25 makes 2020-12 the default dialect, and a client that validates only 2020-12 refuses such a tool. #189 reports that refusal: "JSON Schema declares an unsupported dialect (draft-07). The default validator supports JSON Schema 2020-12 only", the message of the validator in @modelcontextprotocol/client 2.0.0-alpha.4 to 2.0.0-beta.5.

registerMemoryTools therefore starts by calling serveToolSchemasIn2020_12(server), which wraps the SDK's tools/list handler: every listed tool's inputSchema and outputSchema is converted again with the SDK's own converter and target: "draft-2020-12", and the $schema key is left out (under the spec, no key means 2020-12; a client that validates with a draft-07 Ajv instance still compiles such a schema, which it cannot do when the key names 2020-12). This applies to every tool on the server, so a consumer's own tools get 2020-12 too. A zod 3 schema, which the SDK cannot convert to 2020-12, is left exactly as the SDK built it.

What this asks of a consumer:

  • Call registerMemoryTools before registering any tool of your own. The SDK installs its tools/list handler at the first registration and never again, so the wrapper has to be in place before that. registerMemoryTools throws when a tool is already registered, rather than serve draft-07 silently.

  • A setRequestHandler shadow of your own keeps working. The wrapper intercepts only the tools/list installation, passes every other handler to whatever setRequestHandler was before it (the hosted connector's tools/call wrap in src/host-surface.ts, for example), and puts that back once tools/list is installed. A shadow you install after calling it, still before your first registration, stays in place too: the wrapper then remains underneath it and passes every handler through.

  • A server that does not call registerMemoryTools (the hosted connector's own fallback copy of the tools, for one) can call serveToolSchemasIn2020_12(server) itself, before its first registration, to serve the same dialect. Calling it again on the same server does nothing.

  • An SDK without the parts the wrapper reads still runs the server. The wrapper uses two SDK modules that are not documented API (server/zod-json-schema-compat.js and server/zod-compat.js, loaded with a dynamic import()) and the McpServer._registeredTools map. When a later SDK 1.x does not have one of them, or installs tools/list before any tool is registered, the wrapper leaves the SDK's own tools/list answer as it is (draft-07 schemas, as in 0.15.0) and writes one line to stderr, once per process, that starts with Mnemoverse: tool schemas are listed as the MCP SDK builds them. Nothing is written to stdout. The tools themselves work either way.

The apiFetch contract

Every tool reaches the API through exactly one function, supplied as deps.apiFetch:

type ApiFetch = <T = unknown>(path: string, options?: RequestInit) => Promise<T>;
  • path is relative to the API base, for example /memory/read. This package never constructs a full URL; the base, the credential, and the transport are entirely the consumer's own.

  • options.signal must be honoured. Several tools set a deadline on a probe request (AbortSignal.timeout(...)); an apiFetch that ignores the signal turns a bounded probe into an unbounded one.

  • A non-2xx response must reject with ApiError, constructed from an ApiFailure: { status, body, method, path, retryAfter? }. body is the response body verbatim and unparsed; the raw wire body is preserved because the agent-facing message quotes it back (see rawDetail below).

  • A request that never got an HTTP answer at all (DNS, connectivity, a timeout, a refused redirect) must reject with NetworkError, constructed from (method, path, cause).

  • A 2xx response whose body cannot be parsed as this API's JSON must reject with UnreadableBodyError, constructed from an UnreadableBody: { status, method, path, bodyPreview?, cause }. This is a DIFFERENT failure from NetworkError: a reply arrived, so telling the caller "nothing answered" would be false. bodyPreview is absent only when the body read itself failed partway through.

The tools branch on these three types (instanceof), not on message text, so apiFetch must throw exactly these classes and not, say, a plain Error carrying a similar message. One example: memory_list_recent checks e instanceof ApiError && e.isBare404 to degrade gracefully when the feed endpoint is not yet deployed, and that check only fires for the real class.

wording: how this registration speaks

MemoryToolDeps.wording is an optional, typed object. Every field is optional, and every field's absence reproduces this package's own existing behaviour exactly, so a consumer supplying no wording at all gets byte-identical descriptions, error text, and request bodies to every release before this dependency existed.

interface Wording {
  serverNoun?: "this server" | "this connector";
  auth?: "api-key" | "oauth";
  keysUrl?: string;
  rawDetail?: boolean;
}
serverNoun

Three tool descriptions name the server they run on: memory_read's top_k parameter, memory_feedback's coactivation_edges output field, and vault_list's own description. The default is "this server", what this package's stdio server has always said. Passing "this connector" substitutes the noun in exactly those three descriptions and nowhere else on tools/list; every other description is untouched.

auth

Which credential the CALLER holds, not which one core issued. This is the switch that stops the error text from telling an OAuth user to edit an environment variable they do not have.

  • "api-key" (the default) keeps every existing MNEMOVERSE_API_KEY sentence: the docs-placeholder wording, the five details.reason branches (placeholder_key, revoked_key, invalid_key, malformed_key, missing_key), and the "The API key is NOT the problem" opener on a 403.

  • "oauth" is for a server whose user never sees an API key at all, for example a hosted connector that mints one on their behalf. No 401 or 403 explanation under this mode names MNEMOVERSE_API_KEY, an environment variable, an MCP client config file, or the keys console. Each says what an OAuth user can actually do instead.

The exact sentences auth switches, api-key default on the left and "oauth" on the right:

401, no named tenant problem. Every 401 that is not "caller org not identified" (every details.reason value, an unrecognised reason, the raw API-key substring guess, a bare envelope, or silence) collapses under "oauth" to one sentence, in place of five separate reason-flavoured sentences and the founder-endorsed generic one:

api-key (one of several, e.g. the generic form): "Mnemoverse: your API key was rejected (401). Tell the user their MNEMOVERSE_API_KEY is not valid ... it must be replaced with a real key from https://console.mnemoverse.com/dashboard/keys. Do not retry until they replace it."

oauth: "Mnemoverse: the user's sign-in was rejected (401). There is no credential for them to edit — tell them to disconnect and reconnect the app, or sign in again, to refresh their session. Do not retry until they do."

401, "caller org not identified". This branch is about tenant identification, not about a key, so it fires under either mode, worded for whichever credential the caller actually holds:

api-key: "...The API key itself was not rejected — do NOT tell the user to replace it..."

oauth: "...The caller's sign-in itself was not rejected — do NOT tell the user to reconnect over this..."

403, a named cause. Every noun for the credential changes and nothing else does: the clause naming it as innocent, and the holder the room-permission cause speaks about ("This key" becomes "This account", since an OAuth user holds no key and room membership is the account's). The archived-room and invalid-address causes name no holder and are identical under both modes.

api-key: "Mnemoverse: this request was refused (403). The API key is NOT the problem — it identified the account fine, and this was a permission decision. This key is not an active member of the room you addressed. Ask the room's owner for an invite; memory_list_rooms shows the rooms it can already reach. Do not retry the same call: it will be refused again."

oauth: "Mnemoverse: this request was refused (403). Your sign-in is NOT the problem — it identified the account fine, and this was a permission decision. This account is not an active member of the room you addressed. Ask the room's owner for an invite; memory_list_rooms shows the rooms it can already reach. Do not retry the same call: it will be refused again."

403, the engine said nothing parseable (a proxy, a WAF, a wrong base URL). One word changes: the branch declines to blame "the key" under api-key and "the sign-in" under oauth; the rest is identical, because the module cannot tell who refused the request either way.

429, per-minute limit. One noun changes: the closing sentence "stop and tell the user this key is hitting its rate limit" says "this account" under oauth. 429, daily quota and unknown. Identical under both modes: neither sentence ever named MNEMOVERSE_API_KEY, an environment variable, an MCP config file, the keys console or a key; "wait for the retry window" and "check the account's plan" (the quota branch already points at the usage console) already read correctly for an OAuth user.

auth has no effect on 404, 409, 422/400, 5xx, or the network- and unreadable-body-failure explanations. Several of those sentences mention a key only to rule it out as the cause ("This is NOT a rejected API key", "do not tell them their key is the problem"); none sends the user to a key, an environment variable, a config file or the console, and none changes under auth: "oauth". Rewording those exculpatory clauses for an OAuth reader is a possible later slice, not part of this one.

keysUrl

Replaces the console URL wherever the api-key vocabulary prints one (https://console.mnemoverse.com/dashboard/keys by default), for a deployment whose key-management page lives somewhere else. The engine's own validated details.keys_url on a live 401, when present, still wins over this fallback: it is the more specific, more current source for that one failure. keysUrl has no effect under auth: "oauth", which prints no console URL at all.

rawDetail

Whether the wire body's raw tail (the Raw detail — Mnemoverse API error <status> on <method> <path>: ... line, or its network- and unreadable-body-failure equivalents) is appended after the guidance. Defaults to true, unchanged from every release before this field existed.

This is held in reserve, per the owner's STEP4-3 decision: ship the flag, default it on, and set it to false only once a specific deployment's core-side error envelopes are checked and found to echo request content back in details. Until that check happens, there is no reason to drop diagnostic detail a human debugging at 3am relies on.

How wording reaches the error text

registerMemoryTools and registerMemoryResources apply deps.wording to every ApiError, NetworkError or UnreadableBodyError that deps.apiFetch rejects with, before the SDK turns it into the tool result. Each is re-rendered with withWording(deps.wording): a new instance of the same class whose message is what its constructor would have produced under that wording, every field and isBare404 unchanged. The consumer therefore states its wording once, on deps. Its apiFetch may construct the three classes with no wording at all, or pass the same value to their optional second constructor argument; a message is a pure function of the failure and the wording, so the text is the same either way. If a constructor was given a different wording, deps.wording wins. A rejection that is none of the three classes passes through untouched. When deps carries no wording, apiFetch is used exactly as supplied, with no wrapper at all.

writeAuthor: vouching for the end user behind a write

writeAuthor?: () => WriteAuthor | undefined;

interface WriteAuthor {
  principal?: string;
  agent?: string;
  agent_name?: string;
  client_env?: string;
  is_external?: boolean;
}

Called once per memory_write, with no arguments. A returned value is sent as the request body's author field exactly as returned, with no field-level normalisation beyond a typeof guard confirming it is an object (core re-normalises every field server-side). Returning undefined, or omitting the dependency entirely, produces the exact write body this package has always sent; the stdio server supplies no writeAuthor at all, and its requests do not change.

WriteAuthor mirrors core's ProvenanceWriteSchema (mnemoverse-core src/mnemo/api/schemas.py:123-150) field for field, all five fields optional.

Core's supplier rule, verified against _get_provenance (mnemoverse-core src/mnemo/api/routes.py:211-276) on 2026-09-24: the body author is honoured ONLY for a SERVICE/supplier caller vouching for a participant, and is IGNORED entirely for an OIDC end-user, whose provenance is stamped solely from their own verified token. An end-user can never set authorship this way; that is the anti-spoof boundary the schema exists to hold. Practically, this dependency is useful only to a server that authenticates to core as a supplier (a hosted connector minting its own service token) and wants to vouch for the human behind the request, such as attributing a shared-room memory to its actual author instead of to the connector's own service identity.

MAX_RESULT_CHARS and capResult

MAX_RESULT_CHARS is 96_000 (24,000 tokens), the hard cap this package applies to a tool result's TEXT, required by the Claude Connectors Directory submission policy. capResult(text, moreHint?) truncates a string to that cap on a code-point boundary (never inside a UTF-16 surrogate pair) and appends a truncation notice; the default moreHint points at narrowing a query, and a caller with a no-argument tool (a discovery list) can pass its own hint instead.

structuredContent is NOT capped (OD-11): the cap is text-only, because a client reading structuredContent already receives a typed, bounded shape and does not need the same defensive truncation a raw text blob does.

Prompts and resources

registerMemoryPrompts(server) registers four prompts that call nothing themselves: setup_memory, which renders memory rules for the user's own agent instructions, and three named shortcuts (recall, save_insight, what_do_you_know); each shortcut renders one message asking the model to use memory_read or memory_write. It takes no dependencies.

registerMemoryResources(server, deps) registers one resource template, memory://item/{memory_id}, that opens a single saved memory by id through deps.apiFetch. It reads the caller's own store only; a memory from a shared room cannot be opened by id here. It takes the same MemoryToolDeps shape as registerMemoryTools and reads apiFetch and wording (a failed read is re-rendered under wording exactly as a tool failure is, see above); writeAuthor does not apply, since a resource read never writes.

Versioning

This package is pre-1.0, and its own CHANGELOG states the PATCH/MINOR rules for its tool surface (see the README's "Tool surface stability" section). A consumer depending on /shared:

  • Keeps its OWN semver line. A consumer's release and this package's release are different events; bumping this dependency is not, by itself, a reason to bump the consumer's own version, and a consumer fixing its own bug is not a reason to touch this package's pin.

  • Pins an exact version (or a narrow range plus a committed lockfile), the same way mnemoverse-mcp-remote pins every other production dependency, rather than floating on a caret range against a package whose MINOR releases can add tools and output-schema fields.

  • Reports the PACKAGE's version as the surface version, alongside its own. A client asking "what memory surface am I actually talking to" needs the package version (what tools/list shape to expect), not just the consumer's own release number, which tracks the consumer's unrelated changes (auth, deployment, observability) as much as the surface.

The currency guard pattern

A consumer that depends on this package should run a scheduled and per-PR check comparing the version it has LOCKED against the version CURRENTLY PUBLISHED on npm, and fail loudly when it falls behind. The connector's own guard (mnemoverse-mcp-remote scripts/check-package-currency.mjs) is the reference implementation: it reads the declared range from package.json and the installed version from package-lock.json, fetches https://registry.npmjs.org/@mnemoverse/mcp-memory-server/latest, and compares the two as [major, minor, patch] tuples, reporting current, ahead (a prerelease, or npm has not caught up), behind (fails the check), or unreadable (fails the check). Paired with Dependabot opening the version-bump PR, this is the alarm for a bump that was proposed but never merged: the drift this whole /shared entry point exists to end is exactly a consumer's copy of the surface falling behind the package's, and an unmerged dependency bump is that same drift in a different shape.

134 files · 124 not served here

Advantages

Limitations

Security

Risk level: Medium risk

This brain runs

Agent instructions that run or fetch something (14)

  • AGENTS.md: ChatGPT: install the published Mnemoverse Memory plugin and complete browser sign-in.
  • AGENTS.md: Per-client setup: https://mnemoverse.com/docs/api/install-by-host
  • AGENTS.md: Local stdio server: follow `llms-install.md`, which is written for an agent installing on a user's behalf.
  • AGENTS.md: Install with `npm ci`.
  • AGENTS.md: Build: `npm run build` (the prebuild step runs the config generator, the postbuild step regenerates `tools.json`).
  • AGENTS.md: Tests: `npm test` (vitest).
  • AGENTS.md: Test types: `npm run typecheck:test`.
  • AGENTS.md: Drift checks: `npm run verify:configs` (generated files match `src/configs/source.json`) and `npm run verify:tools` (committed `tools.json` matches the built server).
  • AGENTS.md: `src/configs/source.json` is the only file edited by hand for distribution metadata: descriptions, install snippets, env variables, registry fields.
  • AGENTS.md: `npm run generate:configs`
  • AGENTS.md: `npm run verify:configs`
  • AGENTS.md: A hand edit is overwritten on the next run and fails `verify-configs.yml` in the meantime.
  • AGENTS.md: For tool-description changes, run `npm run build` and commit the source plus regenerated `tools.json` and `manifest.json`.
  • AGENTS.md: Keep one concern per PR, run `npm test` and `npm run verify:configs` before pushing, and say in the description which of the checks above you ran.

MCP servers (11)

  • README.md (mnemoverse): npx -y mcp-remote@latest https://mcp.mnemoverse.com/mcp
  • README.md (mnemoverse): npx -y mcp-remote@latest https://mcp.mnemoverse.com/mcp
  • README.md (mnemoverse): https://mcp.mnemoverse.com/mcp · uses the network
  • README.md (mnemoverse): npx -y mcp-remote@latest https://mcp.mnemoverse.com/mcp
  • README.md (mnemoverse): npx -y mcp-remote@latest https://mcp.mnemoverse.com/mcp
  • README.md (mnemoverse): npx -y @mnemoverse/mcp-memory-server@latest
  • README.md (mnemoverse): npx -y @mnemoverse/mcp-memory-server@latest
  • README.md (mnemoverse): npx -y @mnemoverse/mcp-memory-server@latest
  • README.md (mnemoverse): npx -y @mnemoverse/mcp-memory-server@latest
  • README.md (mnemoverse): npx -y @mnemoverse/mcp-memory-server@latest
  • gemini-extension.json (mnemoverse): https://mcp.mnemoverse.com/mcp · uses the network
  • Medium risk: runs-mcp-server-unpinned-runner (README.md:60)
  • Medium risk: runs-mcp-server-unpinned-runner (README.md:100)
  • Medium risk: runs-mcp-server-unpinned-runner (README.md:132)
  • Medium risk: runs-mcp-server-unpinned-runner (README.md:171)
  • Medium risk: runs-mcp-server-unpinned-runner (README.md:275)
  • Medium risk: runs-mcp-server-unpinned-runner (README.md:324)
  • Medium risk: runs-mcp-server-unpinned-runner (README.md:344)
  • Medium risk: runs-mcp-server-unpinned-runner (README.md:364)
  • Medium risk: runs-mcp-server-unpinned-runner (README.md:405)
  • Low risk: network-instructions (package.json:49)
  • Low risk: scripts-present (scripts/generate-configs.mjs)

Attribution and licence

Created by
Mnemoverse
Declared authors
  • Mnemoverse (declared in package.json)
Publisher
Mnemoverse (Official Mnemoverse project)
Source
mnemoverse/mcp-memory-server · commit 9fd95f6
Licence
MIT License (files served here)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Mnemoverse.

Install from source

124 of its files are not served here, so there is no ZIP. mnemoverse/mcp-memory-server at 9fd95f6