#
tokens: 13074/50000 6/6 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .aim
│   ├── memory-project-work.jsonl
│   └── memory.jsonl
├── .claude
│   └── settings.local.json
├── .gitignore
├── example.jsonl
├── img
│   ├── read-function.png
│   └── server-name.png
├── index.ts
├── LICENSE
├── package.json
├── README.md
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Build output
 2 | dist/
 3 | build/
 4 | *.tsbuildinfo
 5 | 
 6 | # Dependencies
 7 | node_modules/
 8 | .npm
 9 | .pnp.*
10 | .yarn/*
11 | !.yarn/patches
12 | !.yarn/plugins
13 | !.yarn/releases
14 | !.yarn/sdks
15 | !.yarn/versions
16 | 
17 | # Logs
18 | logs
19 | *.log
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | 
24 | # Runtime data
25 | pids
26 | *.pid
27 | *.seed
28 | *.pid.lock
29 | 
30 | # Testing
31 | coverage/
32 | .nyc_output/
33 | 
34 | # IDEs and editors
35 | .idea/
36 | .vscode/*
37 | !.vscode/extensions.json
38 | !.vscode/settings.json
39 | !.vscode/tasks.json
40 | !.vscode/launch.json
41 | *.swp
42 | *.swo
43 | .DS_Store
44 | .env
45 | .env.local
46 | .env.*.local
47 | 
48 | # TypeScript cache
49 | *.tsbuildinfo
50 | 
51 | # Optional eslint cache
52 | .eslintcache
53 | 
54 | # Memory files (except examples)
55 | *.jsonl
56 | !example*.jsonl
57 | 
58 | # Local documentation
59 | PUBLISHING.md
60 | VERSION_UPDATE.md
61 | 
62 | # History files
63 | .history/
64 | 
65 | # Package files
66 | *.tgz
67 | 
68 | # OS generated files
69 | .DS_Store
70 | .DS_Store?
71 | ._*
72 | .Spotlight-V100
73 | .Trashes
74 | ehthumbs.db
75 | Thumbs.db
76 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Knowledge Graph
  2 | 
  3 | **Persistent memory for AI models through a local knowledge graph.**
  4 | 
  5 | Store and retrieve information across conversations using entities, relations, and observations. Works with Claude Code/Desktop and any MCP-compatible AI platform.
  6 | 
  7 | ## Why ".aim" and "aim_" prefixes?
  8 | 
  9 | AIM stands for **AI Memory** - the core concept of this knowledge graph system. The three AIM elements provide clear organization and safety:
 10 | 
 11 | - **`.aim` directories**: Keep AI memory files organized and easily identifiable
 12 | - **`aim_` tool prefixes**: Group related memory functions together in multi-tool setups
 13 | - **`_aim` safety markers**: Each memory file starts with `{"type":"_aim","source":"mcp-knowledge-graph"}` to prevent accidental overwrites of unrelated JSONL files
 14 | 
 15 | This consistent AIM naming makes it obvious which directories, tools, and files belong to our AI memory system.
 16 | 
 17 | ## Storage Logic
 18 | 
 19 | **File Location Priority:**
 20 | 
 21 | 1. **Project with `.aim`** - Uses `.aim/memory.jsonl` (project-local)
 22 | 2. **No project/no .aim** - Uses configured global directory
 23 | 3. **Contexts** - Adds suffix: `memory-work.jsonl`, `memory-personal.jsonl`
 24 | 
 25 | **Safety System:**
 26 | 
 27 | - Every memory file starts with `{"type":"_aim","source":"mcp-knowledge-graph"}`
 28 | - System refuses to write to files without this marker
 29 | - Prevents accidental overwrite of unrelated JSONL files
 30 | 
 31 | ## Master Database Concept
 32 | 
 33 | **The master database is your primary memory store** - used by default when no specific database is requested. It's always named `default` in listings and stored as `memory.jsonl`.
 34 | 
 35 | - **Default Behavior**: All memory operations use the master database unless you specify a different one
 36 | - **Always Available**: Exists in both project-local and global locations
 37 | - **Primary Storage**: Your main knowledge graph that persists across all conversations
 38 | - **Named Databases**: Optional additional databases (`work`, `personal`, `health`) for organizing specific topics
 39 | 
 40 | ## Key Features
 41 | 
 42 | - **Master Database**: Primary memory store used by default for all operations
 43 | - **Multiple Databases**: Optional named databases for organizing memories by topic
 44 | - **Project Detection**: Automatic project-local memory using `.aim` directories
 45 | - **Location Override**: Force operations to use project or global storage
 46 | - **Safe Operations**: Built-in protection against overwriting unrelated files
 47 | - **Database Discovery**: List all available databases in both locations
 48 | 
 49 | ## Quick Start
 50 | 
 51 | ### Global Memory (Recommended)
 52 | 
 53 | Add to your `claude_desktop_config.json` or `.claude.json`:
 54 | 
 55 | ```json
 56 | {
 57 |   "mcpServers": {
 58 |     "memory": {
 59 |       "command": "npx",
 60 |       "args": [
 61 |         "-y",
 62 |         "mcp-knowledge-graph",
 63 |         "--memory-path",
 64 |         "/Users/yourusername/.aim/"
 65 |       ]
 66 |     }
 67 |   }
 68 | }
 69 | ```
 70 | 
 71 | This creates memory files in your specified directory:
 72 | 
 73 | - `memory.jsonl` - **Master Database** (default for all operations)
 74 | - `memory-work.jsonl` - Work database
 75 | - `memory-personal.jsonl` - Personal database
 76 | - etc.
 77 | 
 78 | ### Project-Local Memory
 79 | 
 80 | In any project, create a `.aim` directory:
 81 | 
 82 | ```bash
 83 | mkdir .aim
 84 | ```
 85 | 
 86 | Now memory tools automatically use `.aim/memory.jsonl` (project-local **master database**) instead of global storage when run from this project.
 87 | 
 88 | ## How AI Uses Databases
 89 | 
 90 | Once configured, AI models use the **master database by default** or can specify named databases with a `context` parameter. New databases are created automatically - no setup required:
 91 | 
 92 | ```json
 93 | // Master Database (default - no context needed)
 94 | aim_create_entities({
 95 |   entities: [{
 96 |     name: "John_Doe",
 97 |     entityType: "person",
 98 |     observations: ["Met at conference"]
 99 |   }]
100 | })
101 | 
102 | // Work database
103 | aim_create_entities({
104 |   context: "work",
105 |   entities: [{
106 |     name: "Q4_Project",
107 |     entityType: "project",
108 |     observations: ["Due December 2024"]
109 |   }]
110 | })
111 | 
112 | // Personal database
113 | aim_create_entities({
114 |   context: "personal",
115 |   entities: [{
116 |     name: "Mom",
117 |     entityType: "person",
118 |     observations: ["Birthday March 15th"]
119 |   }]
120 | })
121 | 
122 | // Master database in specific location
123 | aim_create_entities({
124 |   location: "global",
125 |   entities: [{
126 |     name: "Important_Info",
127 |     entityType: "reference",
128 |     observations: ["Stored in global master database"]
129 |   }]
130 | })
131 | ```
132 | 
133 | ## File Organization
134 | 
135 | **Global Setup:**
136 | 
137 | ```tree
138 | /Users/yourusername/.aim/
139 | ├── memory.jsonl           # Master Database (default)
140 | ├── memory-work.jsonl      # Work database
141 | ├── memory-personal.jsonl  # Personal database
142 | └── memory-health.jsonl    # Health database
143 | ```
144 | 
145 | **Project Setup:**
146 | 
147 | ```tree
148 | my-project/
149 | ├── .aim/
150 | │   ├── memory.jsonl       # Project Master Database (default)
151 | │   └── memory-work.jsonl  # Project Work database
152 | └── src/
153 | ```
154 | 
155 | ## Available Tools
156 | 
157 | - `aim_create_entities` - Add new people, projects, events
158 | - `aim_create_relations` - Link entities together
159 | - `aim_add_observations` - Add facts to existing entities
160 | - `aim_search_nodes` - Find information by keyword
161 | - `aim_read_graph` - View entire memory
162 | - `aim_open_nodes` - Retrieve specific entities by name
163 | - `aim_list_databases` - Show all available databases and current location
164 | - `aim_delete_entities` - Remove entities
165 | - `aim_delete_observations` - Remove specific facts
166 | - `aim_delete_relations` - Remove connections
167 | 
168 | ### Parameters
169 | 
170 | - `context` (optional) - Specify named database (`work`, `personal`, etc.). Defaults to **master database**
171 | - `location` (optional) - Force `project` or `global` storage location. Defaults to auto-detection
172 | 
173 | ## Database Discovery
174 | 
175 | Use `aim_list_databases` to see all available databases:
176 | 
177 | ```json
178 | {
179 |   "project_databases": [
180 |     "default",      // Master Database (project-local)
181 |     "project-work"  // Named database
182 |   ],
183 |   "global_databases": [
184 |     "default",      // Master Database (global)
185 |     "work",
186 |     "personal",
187 |     "health"
188 |   ],
189 |   "current_location": "project (.aim directory detected)"
190 | }
191 | ```
192 | 
193 | **Key Points:**
194 | 
195 | - **"default"** = Master Database in both locations
196 | - **Current location** shows whether you're using project or global storage
197 | - **Master database exists everywhere** - it's your primary memory store
198 | - **Named databases** are optional additions for specific topics
199 | 
200 | ## Configuration Examples
201 | 
202 | **Important:** Always specify `--memory-path` to control where your memory files are stored.
203 | 
204 | **Home directory:**
205 | 
206 | ```json
207 | {
208 |   "mcpServers": {
209 |     "memory": {
210 |       "command": "npx",
211 |       "args": [
212 |         "-y",
213 |         "mcp-knowledge-graph",
214 |         "--memory-path",
215 |         "/Users/yourusername/.aim"
216 |       ]
217 |     }
218 |   }
219 | }
220 | ```
221 | 
222 | **Custom location (e.g., Dropbox):**
223 | 
224 | ```json
225 | {
226 |   "mcpServers": {
227 |     "memory": {
228 |       "command": "npx",
229 |       "args": [
230 |         "-y",
231 |         "mcp-knowledge-graph",
232 |         "--memory-path",
233 |         "/Users/yourusername/Dropbox/.aim"
234 |       ]
235 |     }
236 |   }
237 | }
238 | ```
239 | 
240 | **Auto-approve all operations:**
241 | 
242 | ```json
243 | {
244 |   "mcpServers": {
245 |     "memory": {
246 |       "command": "npx",
247 |       "args": [
248 |         "-y",
249 |         "mcp-knowledge-graph",
250 |         "--memory-path",
251 |         "/Users/yourusername/.aim"
252 |       ],
253 |       "autoapprove": [
254 |         "aim_create_entities",
255 |         "aim_create_relations",
256 |         "aim_add_observations",
257 |         "aim_search_nodes",
258 |         "aim_read_graph",
259 |         "aim_open_nodes",
260 |         "aim_list_databases"
261 |       ]
262 |     }
263 |   }
264 | }
265 | ```
266 | 
267 | ## Troubleshooting
268 | 
269 | **"File does not contain required _aim safety marker" error:**
270 | 
271 | - The file may not belong to this system
272 | - Manual JSONL files need `{"type":"_aim","source":"mcp-knowledge-graph"}` as first line
273 | - If you created the file manually, add the `_aim` marker or delete and let the system recreate it
274 | 
275 | **Memories going to unexpected locations:**
276 | 
277 | - Check if you're in a project directory with `.aim` folder (uses project-local storage)
278 | - Otherwise uses the configured global `--memory-path` directory
279 | - Use `aim_list_databases` to see all available databases and current location
280 | - Use `ls .aim/` or `ls /Users/yourusername/.aim/` to see your memory files
281 | 
282 | **Too many similar databases:**
283 | 
284 | - AI models try to use consistent names, but may create variations
285 | - Manually delete unwanted database files if needed
286 | - Encourage AI to use simple, consistent database names
287 | - **Remember**: Master database is always available as the default - named databases are optional
288 | 
289 | ## Requirements
290 | 
291 | - Node.js 18+
292 | - MCP-compatible AI platform
293 | 
294 | ## License
295 | 
296 | MIT
297 | 
```

--------------------------------------------------------------------------------
/.claude/settings.local.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "permissions": {
 3 |     "allow": [
 4 |       "Bash(rm:*)",
 5 |       "Bash(git add:*)",
 6 |       "Bash(git commit:*)",
 7 |       "Bash(git push:*)"
 8 |     ],
 9 |     "deny": [],
10 |     "ask": []
11 |   }
12 | }
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "outDir": "./dist",
 4 |     "rootDir": ".",
 5 |     "module": "NodeNext",
 6 |     "moduleResolution": "NodeNext",
 7 |     "esModuleInterop": true,
 8 |     "strict": true,
 9 |     "skipLibCheck": true,
10 |     "forceConsistentCasingInFileNames": true,
11 |     "declaration": true,
12 |     "sourceMap": true,
13 |     "allowJs": true,
14 |     "checkJs": true,
15 |     "exactOptionalPropertyTypes": true,
16 |     "noUncheckedIndexedAccess": true
17 |   },
18 |   "include": [
19 |     "./**/*.ts"
20 |   ]
21 | }
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "mcp-knowledge-graph",
 3 |   "version": "1.2.0",
 4 |   "description": "MCP server enabling persistent memory for AI models through a local knowledge graph",
 5 |   "license": "MIT",
 6 |   "author": "Shane Holloman",
 7 |   "homepage": "https://github.com/shaneholloman/mcp-knowledge-graph",
 8 |   "bugs": "https://github.com/shaneholloman/mcp-knowledge-graph/issues",
 9 |   "type": "module",
10 |   "engines": {
11 |     "node": ">=18.0.0"
12 |   },
13 |   "bin": {
14 |     "mcp-knowledge-graph": "dist/index.js"
15 |   },
16 |   "files": [
17 |     "dist"
18 |   ],
19 |   "scripts": {
20 |     "build": "tsc && shx chmod +x dist/*.js",
21 |     "prepare": "npm run build",
22 |     "watch": "tsc --watch"
23 |   },
24 |   "dependencies": {
25 |     "@modelcontextprotocol/sdk": "1.0.1",
26 |     "minimist": "^1.2.8"
27 |   },
28 |   "devDependencies": {
29 |     "@types/minimist": "^1.2.5",
30 |     "@types/node": "^22.9.3",
31 |     "shx": "^0.3.4",
32 |     "typescript": "^5.6.2"
33 |   }
34 | }
```

--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  5 | import {
  6 |   CallToolRequestSchema,
  7 |   ListToolsRequestSchema,
  8 | } from "@modelcontextprotocol/sdk/types.js";
  9 | import { promises as fs } from 'fs';
 10 | import { existsSync } from 'fs';
 11 | import path from 'path';
 12 | import { fileURLToPath } from 'url';
 13 | import minimist from 'minimist';
 14 | import { isAbsolute } from 'path';
 15 | 
 16 | // Parse args and handle paths safely
 17 | const argv = minimist(process.argv.slice(2));
 18 | let memoryPath = argv['memory-path'];
 19 | 
 20 | // If a custom path is provided, ensure it's absolute
 21 | if (memoryPath && !isAbsolute(memoryPath)) {
 22 |     memoryPath = path.resolve(process.cwd(), memoryPath);
 23 | }
 24 | 
 25 | // Define the base directory for memory files
 26 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
 27 | 
 28 | // Handle memory path - could be a file or directory
 29 | let baseMemoryPath: string;
 30 | if (memoryPath) {
 31 |   // If memory-path points to a .jsonl file, use its directory as the base
 32 |   if (memoryPath.endsWith('.jsonl')) {
 33 |     baseMemoryPath = path.dirname(memoryPath);
 34 |   } else {
 35 |     // Otherwise treat it as a directory
 36 |     baseMemoryPath = memoryPath;
 37 |   }
 38 | } else {
 39 |   baseMemoryPath = __dirname;
 40 | }
 41 | 
 42 | // Simple marker to identify our files - prevents writing to unrelated JSONL files
 43 | const FILE_MARKER = {
 44 |   type: "_aim",
 45 |   source: "mcp-knowledge-graph"
 46 | };
 47 | 
 48 | // Project detection - look for common project markers
 49 | function findProjectRoot(startDir: string = process.cwd()): string | null {
 50 |   const projectMarkers = ['.git', 'package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod'];
 51 |   let currentDir = startDir;
 52 |   const maxDepth = 5;
 53 | 
 54 |   for (let i = 0; i < maxDepth; i++) {
 55 |     // Check for project markers
 56 |     for (const marker of projectMarkers) {
 57 |       if (existsSync(path.join(currentDir, marker))) {
 58 |         return currentDir;
 59 |       }
 60 |     }
 61 | 
 62 |     // Move up one directory
 63 |     const parentDir = path.dirname(currentDir);
 64 |     if (parentDir === currentDir) {
 65 |       // Reached root directory
 66 |       break;
 67 |     }
 68 |     currentDir = parentDir;
 69 |   }
 70 | 
 71 |   return null;
 72 | }
 73 | 
 74 | // Function to get memory file path based on context and optional location override
 75 | function getMemoryFilePath(context?: string, location?: 'project' | 'global'): string {
 76 |   const filename = context ? `memory-${context}.jsonl` : 'memory.jsonl';
 77 |   
 78 |   // If location is explicitly specified, use it
 79 |   if (location === 'global') {
 80 |     return path.join(baseMemoryPath, filename);
 81 |   }
 82 |   
 83 |   if (location === 'project') {
 84 |     const projectRoot = findProjectRoot();
 85 |     if (projectRoot) {
 86 |       const aimDir = path.join(projectRoot, '.aim');
 87 |       return path.join(aimDir, filename); // Will create .aim if it doesn't exist
 88 |     } else {
 89 |       throw new Error('No project detected - cannot use project location');
 90 |     }
 91 |   }
 92 |   
 93 |   // Auto-detect logic (existing behavior)
 94 |   const projectRoot = findProjectRoot();
 95 |   if (projectRoot) {
 96 |     const aimDir = path.join(projectRoot, '.aim');
 97 |     if (existsSync(aimDir)) {
 98 |       return path.join(aimDir, filename);
 99 |     }
100 |   }
101 |   
102 |   // Fallback to configured base directory
103 |   return path.join(baseMemoryPath, filename);
104 | }
105 | 
106 | // We are storing our memory using entities, relations, and observations in a graph structure
107 | interface Entity {
108 |   name: string;
109 |   entityType: string;
110 |   observations: string[];
111 | }
112 | 
113 | interface Relation {
114 |   from: string;
115 |   to: string;
116 |   relationType: string;
117 | }
118 | 
119 | interface KnowledgeGraph {
120 |   entities: Entity[];
121 |   relations: Relation[];
122 | }
123 | 
124 | // The KnowledgeGraphManager class contains all operations to interact with the knowledge graph
125 | class KnowledgeGraphManager {
126 |   private async loadGraph(context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> {
127 |     const filePath = getMemoryFilePath(context, location);
128 |     
129 |     try {
130 |       const data = await fs.readFile(filePath, "utf-8");
131 |       const lines = data.split("\n").filter(line => line.trim() !== "");
132 |       
133 |       if (lines.length === 0) {
134 |         return { entities: [], relations: [] };
135 |       }
136 |       
137 |       // Check first line for our file marker
138 |       const firstLine = JSON.parse(lines[0]!);
139 |       if (firstLine.type !== "_aim" || firstLine.source !== "mcp-knowledge-graph") {
140 |         throw new Error(`File ${filePath} does not contain required _aim safety marker. This file may not belong to the knowledge graph system. Expected first line: {"type":"_aim","source":"mcp-knowledge-graph"}`);
141 |       }
142 |       
143 |       // Process remaining lines (skip metadata)
144 |       return lines.slice(1).reduce((graph: KnowledgeGraph, line) => {
145 |         const item = JSON.parse(line);
146 |         if (item.type === "entity") graph.entities.push(item as Entity);
147 |         if (item.type === "relation") graph.relations.push(item as Relation);
148 |         return graph;
149 |       }, { entities: [], relations: [] });
150 |     } catch (error) {
151 |       if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") {
152 |         // File doesn't exist - we'll create it with metadata on first save
153 |         return { entities: [], relations: [] };
154 |       }
155 |       throw error;
156 |     }
157 |   }
158 | 
159 |   private async saveGraph(graph: KnowledgeGraph, context?: string, location?: 'project' | 'global'): Promise<void> {
160 |     const filePath = getMemoryFilePath(context, location);
161 |     
162 |     // Write our simple file marker
163 |     
164 |     const lines = [
165 |       JSON.stringify(FILE_MARKER),
166 |       ...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })),
167 |       ...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })),
168 |     ];
169 |     
170 |     // Ensure directory exists
171 |     await fs.mkdir(path.dirname(filePath), { recursive: true });
172 |     
173 |     await fs.writeFile(filePath, lines.join("\n"));
174 |   }
175 | 
176 |   async createEntities(entities: Entity[], context?: string, location?: 'project' | 'global'): Promise<Entity[]> {
177 |     const graph = await this.loadGraph(context, location);
178 |     const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name));
179 |     graph.entities.push(...newEntities);
180 |     await this.saveGraph(graph, context, location);
181 |     return newEntities;
182 |   }
183 | 
184 |   async createRelations(relations: Relation[], context?: string, location?: 'project' | 'global'): Promise<Relation[]> {
185 |     const graph = await this.loadGraph(context, location);
186 |     const newRelations = relations.filter(r => !graph.relations.some(existingRelation =>
187 |       existingRelation.from === r.from &&
188 |       existingRelation.to === r.to &&
189 |       existingRelation.relationType === r.relationType
190 |     ));
191 |     graph.relations.push(...newRelations);
192 |     await this.saveGraph(graph, context, location);
193 |     return newRelations;
194 |   }
195 | 
196 |   async addObservations(observations: { entityName: string; contents: string[] }[], context?: string, location?: 'project' | 'global'): Promise<{ entityName: string; addedObservations: string[] }[]> {
197 |     const graph = await this.loadGraph(context, location);
198 |     const results = observations.map(o => {
199 |       const entity = graph.entities.find(e => e.name === o.entityName);
200 |       if (!entity) {
201 |         throw new Error(`Entity with name ${o.entityName} not found`);
202 |       }
203 |       const newObservations = o.contents.filter(content => !entity.observations.includes(content));
204 |       entity.observations.push(...newObservations);
205 |       return { entityName: o.entityName, addedObservations: newObservations };
206 |     });
207 |     await this.saveGraph(graph, context, location);
208 |     return results;
209 |   }
210 | 
211 |   async deleteEntities(entityNames: string[], context?: string, location?: 'project' | 'global'): Promise<void> {
212 |     const graph = await this.loadGraph(context, location);
213 |     graph.entities = graph.entities.filter(e => !entityNames.includes(e.name));
214 |     graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to));
215 |     await this.saveGraph(graph, context, location);
216 |   }
217 | 
218 |   async deleteObservations(deletions: { entityName: string; observations: string[] }[], context?: string, location?: 'project' | 'global'): Promise<void> {
219 |     const graph = await this.loadGraph(context, location);
220 |     deletions.forEach(d => {
221 |       const entity = graph.entities.find(e => e.name === d.entityName);
222 |       if (entity) {
223 |         entity.observations = entity.observations.filter(o => !d.observations.includes(o));
224 |       }
225 |     });
226 |     await this.saveGraph(graph, context, location);
227 |   }
228 | 
229 |   async deleteRelations(relations: Relation[], context?: string, location?: 'project' | 'global'): Promise<void> {
230 |     const graph = await this.loadGraph(context, location);
231 |     graph.relations = graph.relations.filter(r => !relations.some(delRelation =>
232 |       r.from === delRelation.from &&
233 |       r.to === delRelation.to &&
234 |       r.relationType === delRelation.relationType
235 |     ));
236 |     await this.saveGraph(graph, context, location);
237 |   }
238 | 
239 |   async readGraph(context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> {
240 |     return this.loadGraph(context, location);
241 |   }
242 | 
243 |   // Very basic search function
244 |   async searchNodes(query: string, context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> {
245 |     const graph = await this.loadGraph(context, location);
246 | 
247 |     // Filter entities
248 |     const filteredEntities = graph.entities.filter(e =>
249 |       e.name.toLowerCase().includes(query.toLowerCase()) ||
250 |       e.entityType.toLowerCase().includes(query.toLowerCase()) ||
251 |       e.observations.some(o => o.toLowerCase().includes(query.toLowerCase()))
252 |     );
253 | 
254 |     // Create a Set of filtered entity names for quick lookup
255 |     const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
256 | 
257 |     // Filter relations to only include those between filtered entities
258 |     const filteredRelations = graph.relations.filter(r =>
259 |       filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
260 |     );
261 | 
262 |     const filteredGraph: KnowledgeGraph = {
263 |       entities: filteredEntities,
264 |       relations: filteredRelations,
265 |     };
266 | 
267 |     return filteredGraph;
268 |   }
269 | 
270 |   async openNodes(names: string[], context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> {
271 |     const graph = await this.loadGraph(context, location);
272 | 
273 |     // Filter entities
274 |     const filteredEntities = graph.entities.filter(e => names.includes(e.name));
275 | 
276 |     // Create a Set of filtered entity names for quick lookup
277 |     const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
278 | 
279 |     // Filter relations to only include those between filtered entities
280 |     const filteredRelations = graph.relations.filter(r =>
281 |       filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
282 |     );
283 | 
284 |     const filteredGraph: KnowledgeGraph = {
285 |       entities: filteredEntities,
286 |       relations: filteredRelations,
287 |     };
288 | 
289 |     return filteredGraph;
290 |   }
291 | 
292 |   async listDatabases(): Promise<{ project_databases: string[], global_databases: string[], current_location: string }> {
293 |     const result = {
294 |       project_databases: [] as string[],
295 |       global_databases: [] as string[],
296 |       current_location: ""
297 |     };
298 | 
299 |     // Check project-local .aim directory
300 |     const projectRoot = findProjectRoot();
301 |     if (projectRoot) {
302 |       const aimDir = path.join(projectRoot, '.aim');
303 |       if (existsSync(aimDir)) {
304 |         result.current_location = "project (.aim directory detected)";
305 |         try {
306 |           const files = await fs.readdir(aimDir);
307 |           result.project_databases = files
308 |             .filter(file => file.endsWith('.jsonl'))
309 |             .map(file => file === 'memory.jsonl' ? 'default' : file.replace('memory-', '').replace('.jsonl', ''))
310 |             .sort();
311 |         } catch (error) {
312 |           // Directory exists but can't read - ignore
313 |         }
314 |       } else {
315 |         result.current_location = "global (no .aim directory in project)";
316 |       }
317 |     } else {
318 |       result.current_location = "global (no project detected)";
319 |     }
320 | 
321 |     // Check global directory
322 |     try {
323 |       const files = await fs.readdir(baseMemoryPath);
324 |       result.global_databases = files
325 |         .filter(file => file.endsWith('.jsonl'))
326 |         .map(file => file === 'memory.jsonl' ? 'default' : file.replace('memory-', '').replace('.jsonl', ''))
327 |         .sort();
328 |     } catch (error) {
329 |       // Directory doesn't exist or can't read
330 |       result.global_databases = [];
331 |     }
332 | 
333 |     return result;
334 |   }
335 | }
336 | 
337 | const knowledgeGraphManager = new KnowledgeGraphManager();
338 | 
339 | 
340 | // The server instance and tools exposed to AI models
341 | const server = new Server({
342 |   name: "mcp-knowledge-graph",
343 |   version: "1.0.1",
344 | },    {
345 |     capabilities: {
346 |       tools: {},
347 |     },
348 |   },);
349 | 
350 | server.setRequestHandler(ListToolsRequestSchema, async () => {
351 |   return {
352 |     tools: [
353 |       {
354 |         name: "aim_create_entities",
355 |         description: `Create multiple new entities in the knowledge graph.
356 | 
357 | DATABASE SELECTION: By default, all memories are stored in the master database. Use the 'context' parameter to organize information into separate knowledge graphs for different areas of life or work.
358 | 
359 | STORAGE LOCATION: Files are stored in the user's configured directory, or project-local .aim directory if one exists. Each database creates its own file (e.g., memory-work.jsonl, memory-personal.jsonl).
360 | 
361 | LOCATION OVERRIDE: Use the 'location' parameter to force storage in a specific location:
362 | - 'project': Always use project-local .aim directory (creates if needed)
363 | - 'global': Always use global configured directory
364 | - Leave blank: Auto-detect (project if .aim exists, otherwise global)
365 | 
366 | WHEN TO USE DATABASES:
367 | - Any descriptive name: 'work', 'personal', 'health', 'research', 'basket-weaving', 'book-club', etc.
368 | - New databases are created automatically - no setup required
369 | - IMPORTANT: Use consistent, simple names - prefer 'work' over 'work-stuff' or 'job-related'
370 | - Common examples: 'work' (professional), 'personal' (private), 'health' (medical), 'research' (academic)  
371 | - Leave blank: General information or when unsure (uses master database)
372 | 
373 | EXAMPLES:
374 | - Master database (default): aim_create_entities({entities: [{name: "John", entityType: "person", observations: ["Met at conference"]}]})
375 | - Work database: aim_create_entities({context: "work", entities: [{name: "Q4_Project", entityType: "project", observations: ["Due December 2024"]}]})
376 | - Master database in global location: aim_create_entities({location: "global", entities: [{name: "John", entityType: "person", observations: ["Met at conference"]}]})
377 | - Work database in project location: aim_create_entities({context: "work", location: "project", entities: [{name: "Q4_Project", entityType: "project", observations: ["Due December 2024"]}]})`,
378 |         inputSchema: {
379 |           type: "object",
380 |           properties: {
381 |             context: {
382 |               type: "string",
383 |               description: "Optional memory context. Defaults to master database if not specified. Use any descriptive name ('work', 'personal', 'health', 'basket-weaving', etc.) - new contexts created automatically."
384 |             },
385 |             location: {
386 |               type: "string",
387 |               enum: ["project", "global"],
388 |               description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
389 |             },
390 |             entities: {
391 |               type: "array",
392 |               items: {
393 |                 type: "object",
394 |                 properties: {
395 |                   name: { type: "string", description: "The name of the entity" },
396 |                   entityType: { type: "string", description: "The type of the entity" },
397 |                   observations: {
398 |                     type: "array",
399 |                     items: { type: "string" },
400 |                     description: "An array of observation contents associated with the entity"
401 |                   },
402 |                 },
403 |                 required: ["name", "entityType", "observations"],
404 |               },
405 |             },
406 |           },
407 |           required: ["entities"],
408 |         },
409 |       },
410 |       {
411 |         name: "aim_create_relations",
412 |         description: `Create multiple new relations between entities in the knowledge graph. Relations should be in active voice.
413 | 
414 | DATABASE SELECTION: Relations are created within the specified database's knowledge graph. Entities must exist in the same database.
415 | 
416 | LOCATION OVERRIDE: Use the 'location' parameter to force storage in 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
417 | 
418 | EXAMPLES:
419 | - Master database (default): aim_create_relations({relations: [{from: "John", to: "TechConf2024", relationType: "attended"}]})
420 | - Work database: aim_create_relations({context: "work", relations: [{from: "Alice", to: "Q4_Project", relationType: "manages"}]})
421 | - Master database in global location: aim_create_relations({location: "global", relations: [{from: "John", to: "TechConf2024", relationType: "attended"}]})
422 | - Personal database in project location: aim_create_relations({context: "personal", location: "project", relations: [{from: "Mom", to: "Gardening", relationType: "enjoys"}]})`,
423 |         inputSchema: {
424 |           type: "object",
425 |           properties: {
426 |             context: {
427 |               type: "string",
428 |               description: "Optional memory context. Relations will be created in the specified context's knowledge graph."
429 |             },
430 |             location: {
431 |               type: "string",
432 |               enum: ["project", "global"],
433 |               description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
434 |             },
435 |             relations: {
436 |               type: "array",
437 |               items: {
438 |                 type: "object",
439 |                 properties: {
440 |                   from: { type: "string", description: "The name of the entity where the relation starts" },
441 |                   to: { type: "string", description: "The name of the entity where the relation ends" },
442 |                   relationType: { type: "string", description: "The type of the relation" },
443 |                 },
444 |                 required: ["from", "to", "relationType"],
445 |               },
446 |             },
447 |           },
448 |           required: ["relations"],
449 |         },
450 |       },
451 |       {
452 |         name: "aim_add_observations",
453 |         description: `Add new observations to existing entities in the knowledge graph.
454 | 
455 | DATABASE SELECTION: Observations are added to entities within the specified database's knowledge graph.
456 | 
457 | LOCATION OVERRIDE: Use the 'location' parameter to force storage in 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
458 | 
459 | EXAMPLES:
460 | - Master database (default): aim_add_observations({observations: [{entityName: "John", contents: ["Lives in Seattle", "Works in tech"]}]})
461 | - Work database: aim_add_observations({context: "work", observations: [{entityName: "Q4_Project", contents: ["Behind schedule", "Need more resources"]}]})
462 | - Master database in global location: aim_add_observations({location: "global", observations: [{entityName: "John", contents: ["Lives in Seattle", "Works in tech"]}]})
463 | - Health database in project location: aim_add_observations({context: "health", location: "project", observations: [{entityName: "Daily_Routine", contents: ["30min morning walk", "8 glasses water"]}]})`,
464 |         inputSchema: {
465 |           type: "object",
466 |           properties: {
467 |             context: {
468 |               type: "string",
469 |               description: "Optional memory context. Observations will be added to entities in the specified context's knowledge graph."
470 |             },
471 |             location: {
472 |               type: "string",
473 |               enum: ["project", "global"],
474 |               description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
475 |             },
476 |             observations: {
477 |               type: "array",
478 |               items: {
479 |                 type: "object",
480 |                 properties: {
481 |                   entityName: { type: "string", description: "The name of the entity to add the observations to" },
482 |                   contents: {
483 |                     type: "array",
484 |                     items: { type: "string" },
485 |                     description: "An array of observation contents to add"
486 |                   },
487 |                 },
488 |                 required: ["entityName", "contents"],
489 |               },
490 |             },
491 |           },
492 |           required: ["observations"],
493 |         },
494 |       },
495 |       {
496 |         name: "aim_delete_entities",
497 |         description: `Delete multiple entities and their associated relations from the knowledge graph.
498 | 
499 | DATABASE SELECTION: Entities are deleted from the specified database's knowledge graph.
500 | 
501 | LOCATION OVERRIDE: Use the 'location' parameter to force deletion from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
502 | 
503 | EXAMPLES:
504 | - Master database (default): aim_delete_entities({entityNames: ["OldProject"]})
505 | - Work database: aim_delete_entities({context: "work", entityNames: ["CompletedTask", "CancelledMeeting"]})
506 | - Master database in global location: aim_delete_entities({location: "global", entityNames: ["OldProject"]})
507 | - Personal database in project location: aim_delete_entities({context: "personal", location: "project", entityNames: ["ExpiredReminder"]})`,
508 |         inputSchema: {
509 |           type: "object",
510 |           properties: {
511 |             context: {
512 |               type: "string",
513 |               description: "Optional memory context. Entities will be deleted from the specified context's knowledge graph."
514 |             },
515 |             location: {
516 |               type: "string",
517 |               enum: ["project", "global"],
518 |               description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
519 |             },
520 |             entityNames: {
521 |               type: "array",
522 |               items: { type: "string" },
523 |               description: "An array of entity names to delete"
524 |             },
525 |           },
526 |           required: ["entityNames"],
527 |         },
528 |       },
529 |       {
530 |         name: "aim_delete_observations",
531 |         description: `Delete specific observations from entities in the knowledge graph.
532 | 
533 | DATABASE SELECTION: Observations are deleted from entities within the specified database's knowledge graph.
534 | 
535 | LOCATION OVERRIDE: Use the 'location' parameter to force deletion from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
536 | 
537 | EXAMPLES:
538 | - Master database (default): aim_delete_observations({deletions: [{entityName: "John", observations: ["Outdated info"]}]})
539 | - Work database: aim_delete_observations({context: "work", deletions: [{entityName: "Project", observations: ["Old deadline"]}]})
540 | - Master database in global location: aim_delete_observations({location: "global", deletions: [{entityName: "John", observations: ["Outdated info"]}]})
541 | - Health database in project location: aim_delete_observations({context: "health", location: "project", deletions: [{entityName: "Exercise", observations: ["Injured knee"]}]})`,
542 |         inputSchema: {
543 |           type: "object",
544 |           properties: {
545 |             context: {
546 |               type: "string",
547 |               description: "Optional memory context. Observations will be deleted from entities in the specified context's knowledge graph."
548 |             },
549 |             location: {
550 |               type: "string",
551 |               enum: ["project", "global"],
552 |               description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
553 |             },
554 |             deletions: {
555 |               type: "array",
556 |               items: {
557 |                 type: "object",
558 |                 properties: {
559 |                   entityName: { type: "string", description: "The name of the entity containing the observations" },
560 |                   observations: {
561 |                     type: "array",
562 |                     items: { type: "string" },
563 |                     description: "An array of observations to delete"
564 |                   },
565 |                 },
566 |                 required: ["entityName", "observations"],
567 |               },
568 |             },
569 |           },
570 |           required: ["deletions"],
571 |         },
572 |       },
573 |       {
574 |         name: "aim_delete_relations",
575 |         description: `Delete multiple relations from the knowledge graph.
576 | 
577 | DATABASE SELECTION: Relations are deleted from the specified database's knowledge graph.
578 | 
579 | LOCATION OVERRIDE: Use the 'location' parameter to force deletion from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
580 | 
581 | EXAMPLES:
582 | - Master database (default): aim_delete_relations({relations: [{from: "John", to: "OldCompany", relationType: "worked_at"}]})
583 | - Work database: aim_delete_relations({context: "work", relations: [{from: "Alice", to: "CancelledProject", relationType: "manages"}]})
584 | - Master database in global location: aim_delete_relations({location: "global", relations: [{from: "John", to: "OldCompany", relationType: "worked_at"}]})
585 | - Personal database in project location: aim_delete_relations({context: "personal", location: "project", relations: [{from: "Me", to: "OldHobby", relationType: "enjoys"}]})`,
586 |         inputSchema: {
587 |           type: "object",
588 |           properties: {
589 |             context: {
590 |               type: "string",
591 |               description: "Optional memory context. Relations will be deleted from the specified context's knowledge graph."
592 |             },
593 |             location: {
594 |               type: "string",
595 |               enum: ["project", "global"],
596 |               description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
597 |             },
598 |             relations: {
599 |               type: "array",
600 |               items: {
601 |                 type: "object",
602 |                 properties: {
603 |                   from: { type: "string", description: "The name of the entity where the relation starts" },
604 |                   to: { type: "string", description: "The name of the entity where the relation ends" },
605 |                   relationType: { type: "string", description: "The type of the relation" },
606 |                 },
607 |                 required: ["from", "to", "relationType"],
608 |               },
609 |               description: "An array of relations to delete"
610 |             },
611 |           },
612 |           required: ["relations"],
613 |         },
614 |       },
615 |       {
616 |         name: "aim_read_graph",
617 |         description: `Read the entire knowledge graph.
618 | 
619 | DATABASE SELECTION: Reads from the specified database or master database if no database is specified.
620 | 
621 | LOCATION OVERRIDE: Use the 'location' parameter to force reading from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
622 | 
623 | EXAMPLES:
624 | - Master database (default): aim_read_graph({})
625 | - Work database: aim_read_graph({context: "work"})
626 | - Master database in global location: aim_read_graph({location: "global"})
627 | - Personal database in project location: aim_read_graph({context: "personal", location: "project"})`,
628 |         inputSchema: {
629 |           type: "object",
630 |           properties: {
631 |             context: {
632 |               type: "string",
633 |               description: "Optional memory context. Reads from the specified context's knowledge graph or master database if not specified."
634 |             },
635 |             location: {
636 |               type: "string",
637 |               enum: ["project", "global"],
638 |               description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
639 |             }
640 |           },
641 |         },
642 |       },
643 |       {
644 |         name: "aim_search_nodes",
645 |         description: `Search for nodes in the knowledge graph based on a query.
646 | 
647 | DATABASE SELECTION: Searches within the specified database or master database if no database is specified.
648 | 
649 | LOCATION OVERRIDE: Use the 'location' parameter to force searching in 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
650 | 
651 | EXAMPLES:
652 | - Master database (default): aim_search_nodes({query: "John"})
653 | - Work database: aim_search_nodes({context: "work", query: "project"})
654 | - Master database in global location: aim_search_nodes({location: "global", query: "John"})
655 | - Personal database in project location: aim_search_nodes({context: "personal", location: "project", query: "family"})`,
656 |         inputSchema: {
657 |           type: "object",
658 |           properties: {
659 |             context: {
660 |               type: "string",
661 |               description: "Optional memory context. Searches within the specified context's knowledge graph or master database if not specified."
662 |             },
663 |             location: {
664 |               type: "string",
665 |               enum: ["project", "global"],
666 |               description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
667 |             },
668 |             query: { type: "string", description: "The search query to match against entity names, types, and observation content" },
669 |           },
670 |           required: ["query"],
671 |         },
672 |       },
673 |       {
674 |         name: "aim_open_nodes",
675 |         description: `Open specific nodes in the knowledge graph by their names.
676 | 
677 | DATABASE SELECTION: Retrieves entities from the specified database or master database if no database is specified.
678 | 
679 | LOCATION OVERRIDE: Use the 'location' parameter to force retrieval from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
680 | 
681 | EXAMPLES:
682 | - Master database (default): aim_open_nodes({names: ["John", "TechConf2024"]})
683 | - Work database: aim_open_nodes({context: "work", names: ["Q4_Project", "Alice"]})
684 | - Master database in global location: aim_open_nodes({location: "global", names: ["John", "TechConf2024"]})
685 | - Personal database in project location: aim_open_nodes({context: "personal", location: "project", names: ["Mom", "Birthday_Plans"]})`,
686 |         inputSchema: {
687 |           type: "object",
688 |           properties: {
689 |             context: {
690 |               type: "string",
691 |               description: "Optional memory context. Retrieves entities from the specified context's knowledge graph or master database if not specified."
692 |             },
693 |             location: {
694 |               type: "string",
695 |               enum: ["project", "global"],
696 |               description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
697 |             },
698 |             names: {
699 |               type: "array",
700 |               items: { type: "string" },
701 |               description: "An array of entity names to retrieve",
702 |             },
703 |           },
704 |           required: ["names"],
705 |         },
706 |       },
707 |       {
708 |         name: "aim_list_databases",
709 |         description: `List all available memory databases in both project and global locations.
710 | 
711 | DISCOVERY: Shows which databases exist, where they're stored, and which location is currently active.
712 | 
713 | EXAMPLES:
714 | - aim_list_databases() - Shows all available databases and current storage location`,
715 |         inputSchema: {
716 |           type: "object",
717 |           properties: {},
718 |         },
719 |       },
720 |     ],
721 |   };
722 | });
723 | 
724 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
725 |   const { name, arguments: args } = request.params;
726 | 
727 |   if (!args) {
728 |     throw new Error(`No arguments provided for tool: ${name}`);
729 |   }
730 | 
731 |   switch (name) {
732 |     case "aim_create_entities":
733 |       return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createEntities(args.entities as Entity[], args.context as string, args.location as 'project' | 'global'), null, 2) }] };
734 |     case "aim_create_relations":
735 |       return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createRelations(args.relations as Relation[], args.context as string, args.location as 'project' | 'global'), null, 2) }] };
736 |     case "aim_add_observations":
737 |       return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.addObservations(args.observations as { entityName: string; contents: string[] }[], args.context as string, args.location as 'project' | 'global'), null, 2) }] };
738 |     case "aim_delete_entities":
739 |       await knowledgeGraphManager.deleteEntities(args.entityNames as string[], args.context as string, args.location as 'project' | 'global');
740 |       return { content: [{ type: "text", text: "Entities deleted successfully" }] };
741 |     case "aim_delete_observations":
742 |       await knowledgeGraphManager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[], args.context as string, args.location as 'project' | 'global');
743 |       return { content: [{ type: "text", text: "Observations deleted successfully" }] };
744 |     case "aim_delete_relations":
745 |       await knowledgeGraphManager.deleteRelations(args.relations as Relation[], args.context as string, args.location as 'project' | 'global');
746 |       return { content: [{ type: "text", text: "Relations deleted successfully" }] };
747 |     case "aim_read_graph":
748 |       return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.readGraph(args.context as string, args.location as 'project' | 'global'), null, 2) }] };
749 |     case "aim_search_nodes":
750 |       return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.searchNodes(args.query as string, args.context as string, args.location as 'project' | 'global'), null, 2) }] };
751 |     case "aim_open_nodes":
752 |       return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.openNodes(args.names as string[], args.context as string, args.location as 'project' | 'global'), null, 2) }] };
753 |     case "aim_list_databases":
754 |       return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.listDatabases(), null, 2) }] };
755 |     default:
756 |       throw new Error(`Unknown tool: ${name}`);
757 |   }
758 | });
759 | 
760 | async function main() {
761 |   const transport = new StdioServerTransport();
762 |   await server.connect(transport);
763 |   console.error("Knowledge Graph MCP Server running on stdio");
764 | }
765 | 
766 | main().catch((error) => {
767 |   console.error("Fatal error in main():", error);
768 |   process.exit(1);
769 | });
770 | 
```