@danielsimonjr/memory-mcp 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,283 @@
1
+ # Knowledge Graph Memory Server
2
+
3
+ A basic implementation of persistent memory using a local knowledge graph. This lets Claude remember information about the user across chats.
4
+
5
+ ## Core Concepts
6
+
7
+ ### Entities
8
+ Entities are the primary nodes in the knowledge graph. Each entity has:
9
+ - A unique name (identifier)
10
+ - An entity type (e.g., "person", "organization", "event")
11
+ - A list of observations
12
+
13
+ Example:
14
+ ```json
15
+ {
16
+ "name": "John_Smith",
17
+ "entityType": "person",
18
+ "observations": ["Speaks fluent Spanish"]
19
+ }
20
+ ```
21
+
22
+ ### Relations
23
+ Relations define directed connections between entities. They are always stored in active voice and describe how entities interact or relate to each other.
24
+
25
+ Example:
26
+ ```json
27
+ {
28
+ "from": "John_Smith",
29
+ "to": "Anthropic",
30
+ "relationType": "works_at"
31
+ }
32
+ ```
33
+ ### Observations
34
+ Observations are discrete pieces of information about an entity. They are:
35
+
36
+ - Stored as strings
37
+ - Attached to specific entities
38
+ - Can be added or removed independently
39
+ - Should be atomic (one fact per observation)
40
+
41
+ Example:
42
+ ```json
43
+ {
44
+ "entityName": "John_Smith",
45
+ "observations": [
46
+ "Speaks fluent Spanish",
47
+ "Graduated in 2019",
48
+ "Prefers morning meetings"
49
+ ]
50
+ }
51
+ ```
52
+
53
+ ## API
54
+
55
+ ### Tools
56
+ - **create_entities**
57
+ - Create multiple new entities in the knowledge graph
58
+ - Input: `entities` (array of objects)
59
+ - Each object contains:
60
+ - `name` (string): Entity identifier
61
+ - `entityType` (string): Type classification
62
+ - `observations` (string[]): Associated observations
63
+ - Ignores entities with existing names
64
+
65
+ - **create_relations**
66
+ - Create multiple new relations between entities
67
+ - Input: `relations` (array of objects)
68
+ - Each object contains:
69
+ - `from` (string): Source entity name
70
+ - `to` (string): Target entity name
71
+ - `relationType` (string): Relationship type in active voice
72
+ - Skips duplicate relations
73
+
74
+ - **add_observations**
75
+ - Add new observations to existing entities
76
+ - Input: `observations` (array of objects)
77
+ - Each object contains:
78
+ - `entityName` (string): Target entity
79
+ - `contents` (string[]): New observations to add
80
+ - Returns added observations per entity
81
+ - Fails if entity doesn't exist
82
+
83
+ - **delete_entities**
84
+ - Remove entities and their relations
85
+ - Input: `entityNames` (string[])
86
+ - Cascading deletion of associated relations
87
+ - Silent operation if entity doesn't exist
88
+
89
+ - **delete_observations**
90
+ - Remove specific observations from entities
91
+ - Input: `deletions` (array of objects)
92
+ - Each object contains:
93
+ - `entityName` (string): Target entity
94
+ - `observations` (string[]): Observations to remove
95
+ - Silent operation if observation doesn't exist
96
+
97
+ - **delete_relations**
98
+ - Remove specific relations from the graph
99
+ - Input: `relations` (array of objects)
100
+ - Each object contains:
101
+ - `from` (string): Source entity name
102
+ - `to` (string): Target entity name
103
+ - `relationType` (string): Relationship type
104
+ - Silent operation if relation doesn't exist
105
+
106
+ - **read_graph**
107
+ - Read the entire knowledge graph
108
+ - No input required
109
+ - Returns complete graph structure with all entities and relations
110
+
111
+ - **search_nodes**
112
+ - Search for nodes based on query
113
+ - Input: `query` (string)
114
+ - Searches across:
115
+ - Entity names
116
+ - Entity types
117
+ - Observation content
118
+ - Returns matching entities and their relations
119
+
120
+ - **open_nodes**
121
+ - Retrieve specific nodes by name
122
+ - Input: `names` (string[])
123
+ - Returns:
124
+ - Requested entities
125
+ - Relations between requested entities
126
+ - Silently skips non-existent nodes
127
+
128
+ # Usage with Claude Desktop
129
+
130
+ ### Setup
131
+
132
+ Add this to your claude_desktop_config.json:
133
+
134
+ #### Docker
135
+
136
+ ```json
137
+ {
138
+ "mcpServers": {
139
+ "memory": {
140
+ "command": "docker",
141
+ "args": ["run", "-i", "-v", "claude-memory:/app/dist", "--rm", "mcp/memory"]
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
147
+ #### NPX
148
+ ```json
149
+ {
150
+ "mcpServers": {
151
+ "memory": {
152
+ "command": "npx",
153
+ "args": [
154
+ "-y",
155
+ "@modelcontextprotocol/server-memory"
156
+ ]
157
+ }
158
+ }
159
+ }
160
+ ```
161
+
162
+ #### NPX with custom setting
163
+
164
+ The server can be configured using the following environment variables:
165
+
166
+ ```json
167
+ {
168
+ "mcpServers": {
169
+ "memory": {
170
+ "command": "npx",
171
+ "args": [
172
+ "-y",
173
+ "@modelcontextprotocol/server-memory"
174
+ ],
175
+ "env": {
176
+ "MEMORY_FILE_PATH": "/path/to/custom/memory.jsonl"
177
+ }
178
+ }
179
+ }
180
+ }
181
+ ```
182
+
183
+ - `MEMORY_FILE_PATH`: Path to the memory storage JSONL file (default: `memory.jsonl` in the server directory)
184
+
185
+ # VS Code Installation Instructions
186
+
187
+ For quick installation, use one of the one-click installation buttons below:
188
+
189
+ [![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=memory&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-memory%22%5D%7D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=memory&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40modelcontextprotocol%2Fserver-memory%22%5D%7D&quality=insiders)
190
+
191
+ [![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Docker-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=memory&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22-v%22%2C%22claude-memory%3A%2Fapp%2Fdist%22%2C%22--rm%22%2C%22mcp%2Fmemory%22%5D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Docker-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=memory&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22-v%22%2C%22claude-memory%3A%2Fapp%2Fdist%22%2C%22--rm%22%2C%22mcp%2Fmemory%22%5D%7D&quality=insiders)
192
+
193
+ For manual installation, you can configure the MCP server using one of these methods:
194
+
195
+ **Method 1: User Configuration (Recommended)**
196
+ 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.
197
+
198
+ **Method 2: Workspace Configuration**
199
+ 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.
200
+
201
+ > For more details about MCP configuration in VS Code, see the [official VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/mcp).
202
+
203
+ #### NPX
204
+
205
+ ```json
206
+ {
207
+ "servers": {
208
+ "memory": {
209
+ "command": "npx",
210
+ "args": [
211
+ "-y",
212
+ "@modelcontextprotocol/server-memory"
213
+ ]
214
+ }
215
+ }
216
+ }
217
+ ```
218
+
219
+ #### Docker
220
+
221
+ ```json
222
+ {
223
+ "servers": {
224
+ "memory": {
225
+ "command": "docker",
226
+ "args": [
227
+ "run",
228
+ "-i",
229
+ "-v",
230
+ "claude-memory:/app/dist",
231
+ "--rm",
232
+ "mcp/memory"
233
+ ]
234
+ }
235
+ }
236
+ }
237
+ ```
238
+
239
+ ### System Prompt
240
+
241
+ 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.
242
+
243
+ Here is an example prompt for chat personalization. You could use this prompt in the "Custom Instructions" field of a [Claude.ai Project](https://www.anthropic.com/news/projects).
244
+
245
+ ```
246
+ Follow these steps for each interaction:
247
+
248
+ 1. User Identification:
249
+ - You should assume that you are interacting with default_user
250
+ - If you have not identified default_user, proactively try to do so.
251
+
252
+ 2. Memory Retrieval:
253
+ - Always begin your chat by saying only "Remembering..." and retrieve all relevant information from your knowledge graph
254
+ - Always refer to your knowledge graph as your "memory"
255
+
256
+ 3. Memory
257
+ - While conversing with the user, be attentive to any new information that falls into these categories:
258
+ a) Basic Identity (age, gender, location, job title, education level, etc.)
259
+ b) Behaviors (interests, habits, etc.)
260
+ c) Preferences (communication style, preferred language, etc.)
261
+ d) Goals (goals, targets, aspirations, etc.)
262
+ e) Relationships (personal and professional relationships up to 3 degrees of separation)
263
+
264
+ 4. Memory Update:
265
+ - If any new information was gathered during the interaction, update your memory as follows:
266
+ a) Create entities for recurring organizations, people, and significant events
267
+ b) Connect them to the current entities using relations
268
+ c) Store facts about them as observations
269
+ ```
270
+
271
+ ## Building
272
+
273
+ Docker:
274
+
275
+ ```sh
276
+ docker build -t mcp/memory -f src/memory/Dockerfile .
277
+ ```
278
+
279
+ For Awareness: a prior mcp/memory volume contains an index.js file that could be overwritten by the new container. If you are using a docker volume for storage, delete the old docker volume's `index.js` file before starting the new container.
280
+
281
+ ## License
282
+
283
+ 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.
@@ -0,0 +1,119 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { ensureMemoryFilePath, defaultMemoryPath } from '../index.js';
6
+ describe('ensureMemoryFilePath', () => {
7
+ const testDir = path.dirname(fileURLToPath(import.meta.url));
8
+ const oldMemoryPath = path.join(testDir, '..', 'memory.json');
9
+ const newMemoryPath = path.join(testDir, '..', 'memory.jsonl');
10
+ let originalEnv;
11
+ beforeEach(() => {
12
+ // Save original environment variable
13
+ originalEnv = process.env.MEMORY_FILE_PATH;
14
+ // Delete environment variable
15
+ delete process.env.MEMORY_FILE_PATH;
16
+ });
17
+ afterEach(async () => {
18
+ // Restore original environment variable
19
+ if (originalEnv !== undefined) {
20
+ process.env.MEMORY_FILE_PATH = originalEnv;
21
+ }
22
+ else {
23
+ delete process.env.MEMORY_FILE_PATH;
24
+ }
25
+ // Clean up test files
26
+ try {
27
+ await fs.unlink(oldMemoryPath);
28
+ }
29
+ catch {
30
+ // Ignore if file doesn't exist
31
+ }
32
+ try {
33
+ await fs.unlink(newMemoryPath);
34
+ }
35
+ catch {
36
+ // Ignore if file doesn't exist
37
+ }
38
+ });
39
+ describe('with MEMORY_FILE_PATH environment variable', () => {
40
+ it('should return absolute path when MEMORY_FILE_PATH is absolute', async () => {
41
+ const absolutePath = '/tmp/custom-memory.jsonl';
42
+ process.env.MEMORY_FILE_PATH = absolutePath;
43
+ const result = await ensureMemoryFilePath();
44
+ expect(result).toBe(absolutePath);
45
+ });
46
+ it('should convert relative path to absolute when MEMORY_FILE_PATH is relative', async () => {
47
+ const relativePath = 'custom-memory.jsonl';
48
+ process.env.MEMORY_FILE_PATH = relativePath;
49
+ const result = await ensureMemoryFilePath();
50
+ expect(path.isAbsolute(result)).toBe(true);
51
+ expect(result).toContain('custom-memory.jsonl');
52
+ });
53
+ it('should handle Windows absolute paths', async () => {
54
+ const windowsPath = 'C:\\temp\\memory.jsonl';
55
+ process.env.MEMORY_FILE_PATH = windowsPath;
56
+ const result = await ensureMemoryFilePath();
57
+ // On Windows, should return as-is; on Unix, will be treated as relative
58
+ if (process.platform === 'win32') {
59
+ expect(result).toBe(windowsPath);
60
+ }
61
+ else {
62
+ expect(path.isAbsolute(result)).toBe(true);
63
+ }
64
+ });
65
+ });
66
+ describe('without MEMORY_FILE_PATH environment variable', () => {
67
+ it('should return default path when no files exist', async () => {
68
+ const result = await ensureMemoryFilePath();
69
+ expect(result).toBe(defaultMemoryPath);
70
+ });
71
+ it('should migrate from memory.json to memory.jsonl when only old file exists', async () => {
72
+ // Create old memory.json file
73
+ await fs.writeFile(oldMemoryPath, '{"test":"data"}');
74
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
75
+ const result = await ensureMemoryFilePath();
76
+ expect(result).toBe(defaultMemoryPath);
77
+ // Verify migration happened
78
+ const newFileExists = await fs.access(newMemoryPath).then(() => true).catch(() => false);
79
+ const oldFileExists = await fs.access(oldMemoryPath).then(() => true).catch(() => false);
80
+ expect(newFileExists).toBe(true);
81
+ expect(oldFileExists).toBe(false);
82
+ // Verify console messages
83
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('DETECTED: Found legacy memory.json file'));
84
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('COMPLETED: Successfully migrated'));
85
+ consoleErrorSpy.mockRestore();
86
+ });
87
+ it('should use new file when both old and new files exist', async () => {
88
+ // Create both files
89
+ await fs.writeFile(oldMemoryPath, '{"old":"data"}');
90
+ await fs.writeFile(newMemoryPath, '{"new":"data"}');
91
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
92
+ const result = await ensureMemoryFilePath();
93
+ expect(result).toBe(defaultMemoryPath);
94
+ // Verify no migration happened (both files should still exist)
95
+ const newFileExists = await fs.access(newMemoryPath).then(() => true).catch(() => false);
96
+ const oldFileExists = await fs.access(oldMemoryPath).then(() => true).catch(() => false);
97
+ expect(newFileExists).toBe(true);
98
+ expect(oldFileExists).toBe(true);
99
+ // Verify no console messages about migration
100
+ expect(consoleErrorSpy).not.toHaveBeenCalled();
101
+ consoleErrorSpy.mockRestore();
102
+ });
103
+ it('should preserve file content during migration', async () => {
104
+ const testContent = '{"entities": [{"name": "test", "type": "person"}]}';
105
+ await fs.writeFile(oldMemoryPath, testContent);
106
+ await ensureMemoryFilePath();
107
+ const migratedContent = await fs.readFile(newMemoryPath, 'utf-8');
108
+ expect(migratedContent).toBe(testContent);
109
+ });
110
+ });
111
+ describe('defaultMemoryPath', () => {
112
+ it('should end with memory.jsonl', () => {
113
+ expect(defaultMemoryPath).toMatch(/memory\.jsonl$/);
114
+ });
115
+ it('should be an absolute path', () => {
116
+ expect(path.isAbsolute(defaultMemoryPath)).toBe(true);
117
+ });
118
+ });
119
+ });