A persistent knowledge graph with binary storage, PageRank-based ranking, and Maximum Entropy Random Walk (MERW) exploration. Designed as an MCP server for use with LLM agents.
The knowledge graph is stored in two binary files using a custom mmap-backed arena allocator:
<base>.graph— Entity records (72 bytes each), adjacency blocks, and node log<base>.strings— Interned, refcounted string table
This replaces the original JSONL format. The binary format supports O(1) entity lookup, POSIX file locking for concurrent access, and in-place mutation without rewriting the entire file.
Note
Migrating from JSONL: If you have an existing .json knowledge graph, use the migration script:
npx tsx scripts/migrate-jsonl.ts [path/to/memory.json]The original .json file is preserved. See also scripts/verify-migration.ts to validate the result. The MEMORY_FILE_PATH does not need to change.
Note
Automatic v1→v2 migration: Graph files using the v1 format (64-byte entity records) are automatically migrated to v2 (72-byte records with MERW ψ field) on first open. The old file is preserved as <name>.graph.v1.
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 (max 2, each max 140 characters)
- Modification timestamps (
mtimefor any change,obsMtimefor observation changes)
Example:
{
"name": "John_Smith",
"entityType": "person",
"observations": ["Speaks fluent Spanish"],
"mtime": 1733423456789,
"obsMtime": 1733423456789
}Relations define directed connections between entities. They are always stored in active voice and describe how entities interact or relate to each other. Each relation has a modification timestamp (mtime).
Example:
{
"from": "John_Smith",
"to": "Anthropic",
"relationType": "works_at",
"mtime": 1733423456789
}Observations are discrete pieces of information about an entity. They are:
- Stored as strings (max 140 characters each)
- Attached to specific entities (max 2 per entity)
- Can be added or removed independently
- Should be atomic (one fact per observation)
Example:
{
"entityName": "John_Smith",
"observations": [
"Speaks fluent Spanish",
"Graduated in 2019"
]
}Two ranking systems are maintained and updated after every graph mutation:
- PageRank (
pagerank) — Structural importance via Monte Carlo random walks on graph topology (Avrachenkov et al. Algorithm 4). Each mutation triggers a full sampling pass. - LLM Rank (
llmrank) — Walker visit counts that track which nodes the LLM actually opens/searches. Primary sort forllmrankis walker visits, with PageRank as tiebreaker.
The random_walk tool uses MERW rather than a standard uniform random walk. MERW maximizes the global entropy rate by sampling uniformly among all paths in the graph, rather than locally maximizing entropy at each vertex.
Transition probabilities are computed from the dominant eigenvector ψ of the (damped) adjacency matrix:
S_ij = (A_ij / λ) · (ψ_j / ψ_i)
The eigenvector is computed via sparse power iteration with teleportation damping (α=0.85), warm-started from the previously stored ψ values. After a small graph mutation, convergence typically requires only 2–5 iterations rather than a full cold start.
Practical effect: Walks gravitate toward structurally rich regions of the graph rather than wandering down linear chains, making serendipitous exploration more productive.
-
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 (max 2, each max 140 chars)
- 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 max 140 chars)
- Each object contains:
- Returns added observations per entity
- Fails if entity doesn't exist or would exceed 2 observations
-
delete_entities
- Remove entities and their relations
- Input:
entityNames(string[]) - Cascading deletion of associated relations
- Silent operation if entity doesn't exist
-
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:
- Silent operation if observation doesn't exist
-
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:
- Silent operation if relation doesn't exist
-
search_nodes
- Search for nodes using a regex pattern
- Input:
query(string): Regex pattern to searchsortBy(string, optional): Sort field (mtime,obsMtime,name,pagerank,llmrank). Default:llmranksortDir(string, optional): Sort direction (ascordesc)direction(string, optional): Edge direction filter (forward,backward,any). Default:forwardentityCursor,relationCursor(number, optional): Pagination cursors
- Searches across entity names, types, and observation content
- Returns matching entities and their relations (paginated)
-
open_nodes
- Retrieve specific nodes by name
- Input:
names(string[]): Entity names to retrievedirection(string, optional): Edge direction filter (forward,backward,any). Default:forwardentityCursor,relationCursor(number, optional): Pagination cursors
- Returns requested entities and relations originating from them (paginated)
- Silently skips non-existent nodes
-
get_neighbors
- Get names of neighboring entities connected to a specific entity within a given depth
- Input:
entityName(string): The entity to find neighbors fordepth(number, default: 1): Maximum traversal depthsortBy(string, optional): Sort field (mtime,obsMtime,name,pagerank,llmrank). Default:llmranksortDir(string, optional): Sort direction (ascordesc)direction(string, optional): Edge direction filter (forward,backward,any). Default:forwardcursor(number, optional): Pagination cursor
- Returns neighbor names with timestamps (paginated)
- Use
open_nodesto get full entity data for neighbors
-
find_path
- Find a path between two entities in the knowledge graph
- Input:
fromEntity(string): Starting entitytoEntity(string): Target entitymaxDepth(number, default: 5): Maximum search depthdirection(string, optional): Edge direction filter (forward,backward,any). Default:forwardcursor(number, optional): Pagination cursor
- Returns path between entities if one exists (paginated)
-
get_entities_by_type
- Get all entities of a specific type
- Input:
entityType(string): Type to filter bysortBy(string, optional): Sort field (mtime,obsMtime,name,pagerank,llmrank). Default:llmranksortDir(string, optional): Sort direction (ascordesc)cursor(number, optional): Pagination cursor
- Returns all entities matching the specified type (paginated)
-
get_entity_types
- Get all unique entity types in the knowledge graph
- No input required
- Returns list of all entity types
-
get_relation_types
- Get all unique relation types in the knowledge graph
- No input required
- Returns list of all relation types
-
get_stats
- Get statistics about the knowledge graph
- No input required
- Returns entity count, relation count, entity types count, relation types count
-
get_orphaned_entities
- Get entities that have no relations (orphaned entities)
- Input:
strict(boolean, default: false): If true, returns entities not connected to 'Self' entitysortBy(string, optional): Sort field (mtime,obsMtime,name,pagerank,llmrank). Default:llmranksortDir(string, optional): Sort direction (ascordesc)cursor(number, optional): Pagination cursor
- Returns entities with no connections (paginated)
-
validate_graph
- Validate the knowledge graph
- No input required
- Returns missing entities referenced in relations and observation limit violations
-
decode_timestamp
- Decode a millisecond timestamp to human-readable UTC format
- Input:
timestamp(number, optional): Millisecond timestamp to decode. If omitted, returns current timerelative(boolean, optional): If true, include relative time (e.g., "3 days ago")
- Returns timestamp, ISO 8601 string, formatted UTC string, and optional relative time
- Useful for interpreting
mtime/obsMtimevalues from entities
-
random_walk
- Perform a MERW-weighted random walk from a starting entity
- Input:
start(string): Name of the entity to start the walk fromdepth(number, default: 3): Number of hops to takeseed(string, optional): Seed for reproducible walksdirection(string, optional): Edge direction filter (forward,backward,any). Default:forward
- Neighbors are selected proportional to their MERW eigenvector component ψ
- Falls back to uniform sampling if ψ has not been computed
- Returns the terminal entity name and the path taken
-
sequentialthinking
- Record a thought in the knowledge graph
- Input:
observations(string[], max 2, each max 140 chars),previousCtxId(string, optional) - Creates a Thought entity and links it to the previous thought if provided
- Returns the new thought's context ID for chaining
-
kb_load
- Load a plaintext document into the knowledge graph
- Input:
filePath(string): Absolute path to a plaintext file (.txt,.md,.tex, source code, etc.)title(string, optional): Document title. Defaults to filename without extensiontopK(number, optional): Number of top TextRank sentences to highlight in the index. Default: 15
- Creates a doubly-linked chain of TextChunk entities, a Document entity, and a DocumentIndex with TextRank-selected entry points
- For PDFs, convert to text first (e.g.,
pdftotext)
Add this to your claude_desktop_config.json:
{
"mcpServers": {
"memory": {
"command": "docker",
"args": ["run", "-i", "-v", "claude-memory:/app/dist", "--rm", "mcp/memory"]
}
}
}{
"mcpServers": {
"memory": {
"command": "npx",
"args": [
"-y",
"@levalicious/server-memory"
]
}
}
}The server can be configured using the following environment variables:
{
"mcpServers": {
"memory": {
"command": "npx",
"args": [
"-y",
"@levalicious/server-memory"
],
"env": {
"MEMORY_FILE_PATH": "/path/to/custom/memory.json"
}
}
}
}MEMORY_FILE_PATH: Path to the memory storage JSON file (default:memory.jsonin the server directory)
For quick installation, use one of the one-click installation buttons below:
For manual installation, add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing Ctrl + Shift + P and typing Preferences: Open Settings (JSON).
Optionally, you can add it to a file called .vscode/mcp.json in your workspace. This will allow you to share the configuration with others.
Note that the
mcpkey is not needed in the.vscode/mcp.jsonfile.
{
"mcp": {
"servers": {
"memory": {
"command": "npx",
"args": [
"-y",
"@levalicious/server-memory"
]
}
}
}
}{
"mcp": {
"servers": {
"memory": {
"command": "docker",
"args": [
"run",
"-i",
"-v",
"claude-memory:/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 could use this prompt in the "Custom Instructions" field of a Claude.ai Project.
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
Docker:
docker build -t mcp/memory -f src/memory/Dockerfile . 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.