Normalized for Mintlify from
knowledge-base/neurigraph-memory-architecture/neurigraph-tool-references/01-MCP-Knowledge-Graph-Memory-Server.mdx.Clean-Room Specification 01: MCP Knowledge Graph Memory Server
Document Purpose
This specification describes a persistent knowledge graph memory server that exposes graph operations (entities, relations, observations) through the Model Context Protocol (MCP). An AI coding model should be able to read this document and produce a functionally identical, working implementation without any additional references.1. System Overview
1.1 What This System Does
This is a single-file TypeScript application that provides an AI assistant with persistent memory by storing a knowledge graph on disk. The server exposes 9 tools via MCP that allow an AI to create, query, and delete entities, relations, and observations. All data is stored in a single JSONL (JSON Lines) file — one JSON object per line.1.2 Core Architecture
1.3 Key Design Decisions
-
Full file read/write on every operation:
loadGraph()reads the entire file;saveGraph()writes the entire file. There is no incremental append or partial update. This is intentional simplicity — the file is small enough for typical use. - No database: Pure file-based storage. No SQLite, no external dependencies for persistence.
- Single-file implementation: The entire server is one TypeScript file (~300 lines). No separate modules.
-
JSONL format: One JSON object per line. Entities first, then relations. Each object has a
typediscriminator field ("entity"or"relation"). -
Deduplication by name: Entity names are unique identifiers. No numeric IDs. Entity deduplication uses exact string match on
name. Relation deduplication uses exact match on the tuple(from, to, relationType). -
Case-insensitive search: The
searchNodesmethod lowercases the query and compares against lowercased entity fields. -
Cascading deletes: Deleting an entity also removes all relations where that entity appears as either
fromorto.
2. Data Model
2.1 Core Types
2.2 JSONL File Format
The persistence file stores one JSON object per line. Each object includes atype discriminator field that is only present in the file format, not in the in-memory data structures.
Entity line format:
2.3 Loading and Saving
Loading (loadGraph):
- Read the entire file as a UTF-8 string
- Split by newline character (
\n) - Filter out empty lines
- Parse each line as JSON
- Use a
reduceoperation to accumulate into aKnowledgeGraph:- If
item.type === "entity": strip thetypefield, push toentitiesarray - If
item.type === "relation": strip thetypefield, push torelationsarray
- If
type field is stripped from each object. The in-memory Entity and Relation objects do NOT contain a type property. The type field only exists in the serialized JSONL format.
Saving (saveGraph):
- Concatenate all entity lines, then all relation lines, joined by
\n - Write the entire string to the file (overwriting completely)
3. KnowledgeGraphManager Class — Complete Method Specifications
3.1 Constructor and Initialization
The class takes a single constructor parameter: the file path (string) for the JSONL storage file.3.2 loadGraph(): Promise<KnowledgeGraph>
Reads and parses the entire JSONL file.
Algorithm:
3.3 saveGraph(graph: KnowledgeGraph): Promise<void>
Writes the entire graph to disk.
Algorithm:
3.4 createEntities(entities: Entity[]): Promise<Entity[]>
Adds new entities, skipping any whose name already exists.
Algorithm:
3.5 createRelations(relations: Relation[]): Promise<Relation[]>
Adds new relations, skipping exact duplicates.
Algorithm:
from and to but different relationType values are NOT duplicates. They are distinct relations.
3.7 deleteEntities(entityNames: string[]): Promise<void>
Removes entities AND all relations connected to those entities (cascading delete).
Algorithm:
addObservations: deleteObservations does NOT throw an error when an entity is not found. It silently ignores the deletion request for non-existent entities.
3.9 deleteRelations(relations: Relation[]): Promise<void>
Removes specific relations by exact match on all three fields.
Algorithm:
3.10 readGraph(): Promise<KnowledgeGraph>
Returns the complete graph.
Algorithm:
loadGraph().
3.11 searchNodes(query: string): Promise<KnowledgeGraph>
Performs case-insensitive substring search across entity names, types, and observations.
Algorithm:
- The search is SUBSTRING matching, not exact match. If query is “john”, it matches “Johnny”, “john_smith”, etc.
- The search is case-INSENSITIVE — both query and target are lowercased before comparison.
- Relations are included if EITHER endpoint matches — not just both.
3.12 openNodes(names: string[]): Promise<KnowledgeGraph>
Retrieves specific entities by exact name match, plus their connected relations.
Algorithm:
- Entity lookup is EXACT string match (case-sensitive), unlike
searchNodeswhich is case-insensitive substring. - Relations are returned if AT LEAST ONE endpoint matches a requested name. This means you may get relations pointing to/from entities that are NOT in the returned entities list.
4. MCP Tool Definitions
The server registers exactly 9 tools. Each tool has a name, description, input schema (defined with Zod), and a handler function. Below is the complete specification for each.4.1 create_entities
Description: “Create multiple new entities in the knowledge graph”
Input Schema:
- Extract
entitiesfrom parsed input arguments - Call
manager.createEntities(entities) - Return the result (array of created entities) as a JSON-stringified text content response
4.2 create_relations
Description: “Create multiple new relations between entities in the knowledge graph. Relations are directed edges.”
Input Schema:
- Extract
relationsfrom parsed input arguments - Call
manager.createRelations(relations) - Return result as JSON-stringified text content
4.3 add_observations
Description: “Add new observations to existing entities in the knowledge graph”
Input Schema:
- Extract
observationsfrom parsed input arguments - Call
manager.addObservations(observations) - Return result as JSON-stringified text content
4.4 delete_entities
Description: “Delete multiple entities and their associated relations from the knowledge graph”
Input Schema:
- Extract
entityNamesfrom parsed input arguments - Call
manager.deleteEntities(entityNames) - Return confirmation message:
"Entities deleted successfully"
4.5 delete_observations
Description: “Delete specific observations from entities in the knowledge graph”
Input Schema:
- Extract
deletionsfrom parsed input arguments - Call
manager.deleteObservations(deletions) - Return confirmation message:
"Observations deleted successfully"
4.6 delete_relations
Description: “Delete multiple relations from the knowledge graph”
Input Schema:
- Extract
relationsfrom parsed input arguments - Call
manager.deleteRelations(relations) - Return confirmation message:
"Relations deleted successfully"
4.7 read_graph
Description: “Read the entire knowledge graph”
- Call
manager.readGraph() - Return the full KnowledgeGraph object as JSON-stringified text content
4.8 search_nodes
Description: “Search for nodes in the knowledge graph based on a query”
Input Schema:
- Extract
queryfrom parsed input arguments - Call
manager.searchNodes(query) - Return the matching KnowledgeGraph as JSON-stringified text content
4.9 open_nodes
Description: “Open specific nodes in the knowledge graph by their names”
Input Schema:
- Extract
namesfrom parsed input arguments - Call
manager.openNodes(names) - Return the matching KnowledgeGraph as JSON-stringified text content
5. MCP Server Setup and Transport
5.1 Server Initialization
The server uses the MCP SDK (@modelcontextprotocol/sdk) with the following setup:
5.2 Tool Registration Pattern
Each tool is registered usingserver.tool() with this signature:
server.tool() is a flat object of Zod schemas, NOT a nested Zod object. For example:
5.3 Server Startup
5.4 Process Entry Point
The startup flow:- Determine the memory file path (see Section 6)
- Instantiate
KnowledgeGraphManagerwith the file path - Register all 9 tools
- Create
StdioServerTransportand connect
6. File Path Resolution and Migration
6.1 Memory File Path Resolution
The storage file path is determined by a functionensureMemoryFilePath():
Algorithm:
6.2 Legacy Migration (.json → .jsonl)
The system migrates from an older.json format to the current .jsonl format:
Algorithm:
.json extension).
7. Dependencies and Build Configuration
7.1 Runtime Dependencies
| Dependency | Version | Purpose |
|---|---|---|
@modelcontextprotocol/sdk | ^1.26.0 | MCP server framework, Zod included transitively |
zod).
7.2 Package Configuration
- ESM module (
"type": "module") - Single entry point compiled to
dist/index.js - The
buildscript compiles TypeScript and makes the output executable (chmod 755) - The
binfield allowsnpxexecution
7.3 TypeScript Configuration
- Target: ES2022 or later (uses top-level await)
- Module: ESM (Node16 or NodeNext module resolution)
- Strict mode enabled
- Output to
dist/directory
7.4 Shebang Line
The source file begins with:8. Complete Behavioral Test Specifications
These test cases define the exact expected behavior. An implementation must pass all of these.8.1 Entity Operations
Test: Create basic entities- Expected: Returns array with one entity. Graph now contains one entity.
- Setup: Create entity with name “Alice”
- Input: Create entity with name “Alice” again (same or different type/observations)
- Expected: Returns empty array (no new entity created). Only one “Alice” in graph.
- Setup: Create entity “Alice”
- Input: Create entities [“Alice”, “Bob”]
- Expected: Returns array with only “Bob”. Both exist in graph.
- Setup: Create entity “alice”
- Input: Create entity “Alice” (capital A)
- Expected: Returns [“Alice”]. Both “alice” and “Alice” exist as separate entities.
8.2 Relation Operations
Test: Create basic relation- Expected: Returns array with one relation.
- Input: Create same relation again
- Expected: Returns empty array. Only one relation in graph.
- Expected: Returns the new relation. Both relations exist (they are distinct).
- Expected: Returns the new relation. Both exist (A→B and B→A are different).
8.3 Observation Operations
Test: Add observations to existing entity- Setup: Create entity “Alice” with observations [“Is a student”]
- Input: Add observations to “Alice”: [“Likes pizza”]
- Entity “Alice” now has observations: [“Is a student”, “Likes pizza”]
- Setup: Entity “Alice” with observations [“Is a student”]
- Input: Add observations to “Alice”: [“Is a student”, “Likes pizza”]
- Only the new, non-duplicate observation is added.
- Input: Add observations to “Nonexistent”: [“anything”]
- Expected: THROWS Error “Entity with name Nonexistent not found”
- Setup: Entity “Alice” with observations [“Is a student”, “Likes pizza”]
- Input: Delete observations from “Alice”: [“Likes pizza”]
- Expected: Entity “Alice” now has observations: [“Is a student”]
- Input: Delete observations from “Nonexistent”: [“anything”]
- Expected: NO error. Silent no-op. (Contrast with
addObservationswhich DOES throw.)
8.4 Delete Operations
Test: Delete entity cascades to relations- Setup: Entities “Alice” and “Bob”. Relation: Alice → Bob “knows”
- Input: Delete entities [“Alice”]
- Expected: “Alice” entity removed. The “knows” relation is ALSO removed (cascade).
- Setup: Entities “Alice” and “Bob”. Relation: Bob → Alice “reports_to”
- Input: Delete entities [“Alice”]
- Expected: “Alice” removed. “reports_to” relation ALSO removed (Alice was the ‘to’ endpoint).
- Setup: Entities A, B, C. Relations: A→B, B→C
- Input: Delete entities [“A”]
- Expected: A removed. A→B removed. B→C preserved (neither endpoint is A).
- Setup: Relations: A→B “knows”, A→B “likes”
- Expected: “knows” removed. “likes” preserved.
8.5 Search Operations
Test: Search by entity name (case-insensitive)- Setup: Entity “John_Smith”
- Input: Search query “john”
- Expected: Returns entity “John_Smith”
- Setup: Entity with entityType “Person”
- Input: Search query “person”
- Expected: Returns the entity
- Setup: Entity with observation “Works at Google”
- Input: Search query “google”
- Expected: Returns the entity
- Setup: Entities A and B. Relation A→B. Only A matches search.
- Input: Search query matching only A
- Expected: Returns entity A and relation A→B (because A is an endpoint)
- Setup: Entities A, B, C. Relations: A→B, B→C. Search matches only B.
- Input: Search query matching only B
- Expected: Returns entity B, relation A→B, and relation B→C
- Input: Search query “xyznonexistent”
8.6 Open Nodes Operations
Test: Open specific nodes- Setup: Entities A and B
- Input: Open nodes [“A”]
- Expected: Returns entity A (not B)
- Setup: Entities A, B. Relation A→B.
- Input: Open nodes [“A”]
- Expected: Returns entity A and relation A→B
- Setup: Entity “Alice”
- Input: Open nodes [“alice”]
- Expected: Returns empty (no match — exact case required)
- Setup: Entities A, B, C. Relations: A→B, B→C, A→C.
- Input: Open nodes [“A”, “C”]
- Expected: Returns entities A and C. Returns relations A→B (A matches), B→C (C matches), A→C (both match).
8.7 Persistence Tests
Test: Data survives save/load cycle- Create entities and relations
- Load graph from disk
- Verify all data matches
- Create entities and a relation
- Read the raw file
- Verify each line is valid JSON
- Verify entity lines have
"type":"entity" - Verify relation lines have
"type":"relation" - Verify entities come before relations in the file
- Write JSONL manually with type fields
- Load graph
- Verify loaded entities/relations do NOT contain
typeproperty
8.8 File Path and Migration Tests
Test: Default path- No MEMORY_FILE_PATH env var
- Set MEMORY_FILE_PATH to “/tmp/custom.jsonl”
- Expected path:
/tmp/custom.jsonl
- Set MEMORY_FILE_PATH to “data/graph.jsonl”
- Create a file at “memory.json” with valid content
- Run ensureMemoryFilePath()
- Expected: “memory.jsonl” now exists with same content. “memory.json” is deleted.
- Both “memory.json” and “memory.jsonl” exist
- Run ensureMemoryFilePath()
- Expected: Neither file modified (JSONL takes priority)
9. Recommended System Prompt for AI Client Integration
When this server is used with an AI assistant (e.g., Claude Desktop), the following system prompt pattern is recommended to guide the AI in using the memory tools effectively:10. Edge Cases and Implementation Notes
10.1 Thread Safety
There is NO concurrency protection. If two operations happen simultaneously, they will bothloadGraph() and then saveGraph(), with the second write overwriting the first. This is acceptable for single-client MCP usage.
10.2 Empty Graph
10.3 File Encoding
All file reads and writes use UTF-8 encoding.10.4 No Validation of Referential Integrity
createRelations does NOT verify that the from and to entity names actually exist in the graph. You can create relations referencing non-existent entities. Only addObservations validates entity existence.
10.5 No Pagination
All operations return full result sets.readGraph() returns every entity and relation. There is no pagination, limits, or cursor mechanism.
10.6 MCP Error Handling
When a tool handler throws an error (e.g.,addObservations for non-existent entity), the MCP SDK framework catches it and returns it as an error response to the calling AI. The server itself does not crash — the error is per-tool-call.
10.7 Process Lifecycle
The server runs as a long-lived process, communicating over stdin/stdout. It stays alive until the parent process (e.g., Claude Desktop) terminates the connection.11. Implementation Checklist
To build a functionally identical system, implement these in order:- Data types: Define
Entity,Relation,KnowledgeGraphinterfaces - KnowledgeGraphManager class:
loadGraph()— JSONL parsing with type field strippingsaveGraph()— JSONL serialization with type field addition- All 9 operation methods exactly as specified in Section 3
- File path resolution:
ensureMemoryFilePath()with env var support and .json→.jsonl migration - MCP server setup: Initialize
McpServerwith name “memory”, version “1.0.0” - Tool registration: Register all 9 tools with Zod schemas as specified in Section 4
- Transport: Create
StdioServerTransportand connect - Package configuration: ESM module with shebang line and bin entry
- Test suite: All test cases from Section 8