A basic implementation of persistent memory using a local knowledge graph. This lets AI assistants remember information about the user across chats.
Entities are the primary nodes in the knowledge graph. Each entity has:
- A unique name (identifier)
- An entity type (e.g., "person", "organization", "event")
- A list of observations
Example:
{
"name": "Jaime_Villanueve",
"entityType": "person",
"observations": ["Speaks fluent Spanish"]
}Relations define directed connections between entities. They are always stored in active voice and describe how entities interact or relate to each other.
Example:
{
"from": "Jaime_Chicharra",
"to": "Anthropic",
"relationType": "works_at"
}Observations are discrete pieces of information about an entity. They are:
- Stored as strings
- Attached to specific entities
- Can be added or removed independently
- Should be atomic (one fact per observation)
Example:
{
"entityName": "Jaime_Chicharra",
"observations": ["Speaks fluent Spanish", "Graduated in 2019", "Prefers morning meetings"]
}The knowledge graph is stored in a local SQLite database using Node's built-in
node:sqlite module — no native dependencies, requires Node.js >= 24. The
database contains three tables:
entities—name(primary key) andentityTypeobservations— one row per observation, linked to its entity byentityName; removed automatically when the entity is deletedrelations—from,to, andrelationType(primary key across all three)
All mutation operations run inside transactions, so the graph is never left in a half-written state.
Versions before 0.7.0 stored the graph as a JSONL document (memory.jsonl). On
first start with an empty database, the server automatically imports entities
and relations from a legacy JSONL file next to the database (e.g. memory.jsonl
or memory.json next to memory.db) and logs what it imported. The legacy file
is left in place as a backup.
If MEMORY_FILE_PATH still points at a .jsonl/.json file, the server
redirects the database to the sibling .db path and imports the legacy contents
on first start.
this MCP server is designed as a drop-in replacement for mcp-server-memory. we do slightly bend some tools semantric so we can use a stricter schema than the jsonl file, but our intention is to match the upstream tool call signatures.
A caller written against upstream keeps working: upstream's nine tools keep
their names, inputs, and the response fields they read. On top of that we add
fields and one verb where upstream's shape makes the graph hard to work with —
relations come back with their far endpoints (see neighbors), deletes report
what actually matched instead of a blanket success, and update_observations
exists because upstream's edit is a delete plus an add: two calls, non-atomic,
and unverifiable.
-
create_entities
- Create multiple new entities in the knowledge graph
- Input:
entities(array of objects)- Each object contains:
name(string): Entity identifierentityType(string): Type classificationobservations(string[]): Associated observations
- Each object contains:
- Ignores entities with existing names
-
create_relations
- Create multiple new relations between entities
- Input:
relations(array of objects)- Each object contains:
from(string): Source entity nameto(string): Target entity namerelationType(string): Relationship type in active voice
- Each object contains:
- Skips duplicate relations
-
add_observations
- Add new observations to existing entities
- Input:
observations(array of objects)- Each object contains:
entityName(string): Target entitycontents(string[]): New observations to add
- Each object contains:
- Returns added observations per entity
- Fails if entity doesn't exist
-
delete_entities
- Remove entities and their relations
- Input:
entityNames(string[]) - Cascading deletion of associated relations
- Returns
deletedEntities: the canonical names that were actually removed - Idempotent: names that do not exist are skipped, not an error
-
delete_observations
- Remove specific observations from entities
- Input:
deletions(array of objects)- Each object contains:
entityName(string): Target entityobservations(string[]): Observations to remove
- Each object contains:
- Returns
results: per entity, thedeletedObservationsthat actually matched (messagecarriesdeleted N of M requested) - Observation content is the primary key, so the match must be exact
-
delete_relations
- Remove specific relations from the graph
- Input:
relations(array of objects)- Each object contains:
from(string): Source entity nameto(string): Target entity namerelationType(string): Relationship type
- Each object contains:
- Returns
deletedRelations: the edges that actually matched - A materialized inverse is removed with its primary edge but not reported as a separate deletion
-
update_observations
- Replace the text of existing observations in place
- Input:
updates(array of objects)- Each object contains:
entityName(string): Entity holding the observationmatch(string): The exact current content to replacereplacement(string): The new content
- Each object contains:
- Returns
updatedObservationsper entity - The row is updated, not rewritten:
created_atkeeps the first-written time andupdated_atrecords the edit; the search index follows - Fails the whole batch (no partial application) if a
matchis absent, if the entity is unknown, or ifreplacementalready exists on that entity — including whenreplacementequalsmatch, since replacing text with itself is not an edit
-
read_graph
- Read the entire knowledge graph
- No input required
- Returns complete graph structure with all entities and relations. Every
entity is loaded, so there is no frontier:
neighborsis always empty
-
search_nodes
- Search for nodes based on query
- Input:
query(string) - Searches across:
- Entity names
- Entity types
- Observation content
- Returns matching entities, their relations, and the relations' far
endpoints as
neighbors
-
open_nodes
- Retrieve specific nodes by name
- Input:
names(string[]) - Returns:
- Requested entities
- Relations touching those entities
neighbors: the far endpoints of those relations that were not requested themselves, so one call returns a node and everything it points at
- Silently skips non-existent nodes
search_nodes and open_nodes return neighbors (deduped, in rowid order);
the list is empty when a result set is closed, and read_graph — which loads
every entity — always returns [].
- knowledge-graph (
memory://knowledge-graph)- The full knowledge graph as a readable MCP Resource
- MIME type:
application/json - Returns the same shape as
read_graph(entities, relations, and an emptyneighborslist) - Mutation tools (
create_entities,create_relations,add_observations,update_observations,delete_entities,delete_observations,delete_relations) emitnotifications/resources/updatedfor this URI, so subscribed clients see live changes
Add this to your mcp.json:
{
"mcpServers": {
"memory": {
"command": "docker",
"args": ["run", "-i", "-v", "memory-data:/app/dist", "--rm", "mcp/memory"]
}
}
}{
"mcpServers": {
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"]
}
}
}On Windows, use cmd /c to launch npx:
{
"mcpServers": {
"memory": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@modelcontextprotocol/server-memory"]
}
}
}The server can be configured using the following environment variables:
{
"mcpServers": {
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"],
"env": {
"MEMORY_FILE_PATH": "/path/to/custom/memory.db"
}
}
}
}On Windows, use:
{
"mcpServers": {
"memory": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@modelcontextprotocol/server-memory"],
"env": {
"MEMORY_FILE_PATH": "/path/to/custom/memory.db"
}
}
}
}MEMORY_FILE_PATH: Path to the memory SQLite database file (default:memory.dbin the server directory). If the path ends in.jsonlor.json, it is treated as a legacy JSONL memory file: the database is created at the sibling.dbpath and the JSONL contents are imported on first start.
search_nodes, read_graph, and open_nodes return the full graph of
matches — entities with all their observations, plus every relation touching
them and the far endpoints of those relations (neighbors). On a realistic
graph the serialized tool result is large: 50–100 KiB per search on a live
graph, and much more for the whole graph. Some MCP clients bound the tool
results they expose to the model: the pi coding agent's adapter
(pi-mcp-adapter) replaces any tool result whose JSON exceeds its
detailsMaxBytes (default 16 KiB) with an { omitted: true } summary that
has no entities/relations keys — so on the mcpScript/worker call path a
large search appeared to return "no results".
The server does nothing about this, deliberately: it returns the full
result and leaves the bound to the client. The adapter's guard spills an
oversized result to a temp file (mode 0600) and returns a summary carrying
fullResultPath, so the caller can still read everything — whereas a
server-side trim is lossy with no way back to the dropped data. That bound also
fails in a way the guard does not: a board is a single entity, so a
trim keeps the head and the rest of it cannot be reached with a narrower query
at all. An unbounded reply is noisy; a bounded reply that silently drops half a
board is worse.
If a client's cap is too tight, raise it (pi: settings.outputGuard. detailsMaxBytes in mcp.json — note that setting is global for all servers),
or read less per call: open_nodes on fewer names, a narrower search_nodes
query, or read_graph piped somewhere the model does not have to hold all of
it.
The behavior is pinned by __tests__/output-guard.test.ts (hermetic),
__tests__/pi-adapter-output-guard.integration.test.ts (against the real
adapter, skipped when it is not installed), and
__tests__/pi-client-output-schema.integration.test.ts (against the real pi
client, likewise skipped).
You can configure the MCP server using one of these methods:
Method 1: User Configuration (Recommended) Add the configuration to your
user-level MCP configuration file. Open the Command Palette (Ctrl + Shift + P)
and run MCP: Open User Configuration. This will open your user mcp.json file
where you can add the server configuration.
Method 2: Workspace Configuration Alternatively, you can add the
configuration to a file called .vscode/mcp.json in your workspace. This will
allow you to share the configuration with others.
For more details about MCP configuration in VS Code, see the official VS Code MCP documentation.
{
"servers": {
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"]
}
}
}On Windows, use:
{
"servers": {
"memory": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@modelcontextprotocol/server-memory"]
}
}
}{
"servers": {
"memory": {
"command": "docker",
"args": ["run", "-i", "-v", "memory-data:/app/dist", "--rm", "mcp/memory"]
}
}
}The prompt for utilizing memory depends on the use case. Changing the prompt will help the model determine the frequency and types of memories created.
Here is an example prompt for chat personalization. You can use this prompt as a system prompt for your AI assistant.
Follow these steps for each interaction:
1. User Identification:
- You should assume that you are interacting with default_user
- If you have not identified default_user, proactively try to do so.
2. Memory Retrieval:
- Always begin your chat by saying only "Remembering..." and retrieve all relevant information from your knowledge graph
- Always refer to your knowledge graph as your "memory"
3. Memory
- While conversing with the user, be attentive to any new information that falls into these categories:
a) Basic Identity (age, gender, location, job title, education level, etc.)
b) Behaviors (interests, habits, etc.)
c) Preferences (communication style, preferred language, etc.)
d) Goals (goals, targets, aspirations, etc.)
e) Relationships (personal and professional relationships up to 3 degrees of separation)
4. Memory Update:
- If any new information was gathered during the interaction, update your memory as follows:
a) Create entities for recurring organizations, people, and significant events
b) Connect them to the current entities using relations
c) Store facts about them as observations
The server uses Node's built-in node:sqlite module, which requires Node.js
= 24.
Docker:
docker build -t mcp/memory .For Awareness: the Docker command above persists the server in a named volume
mounted over /app/dist. A volume created by a prior image shadows the new
container's /app/dist, including the compiled schema migrations at
/app/dist/migrations; the server then fails to start with a "Failed to load
migrations" error. If you use a docker volume for storage, delete the old volume
(or at least its contents) before starting the new container so the fresh code
and migrations are used.
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.