# 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: -------------------------------------------------------------------------------- ``` # Build output dist/ build/ *.tsbuildinfo # Dependencies node_modules/ .npm .pnp.* .yarn/* !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/sdks !.yarn/versions # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Testing coverage/ .nyc_output/ # IDEs and editors .idea/ .vscode/* !.vscode/extensions.json !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json *.swp *.swo .DS_Store .env .env.local .env.*.local # TypeScript cache *.tsbuildinfo # Optional eslint cache .eslintcache # Memory files (except examples) *.jsonl !example*.jsonl # Local documentation PUBLISHING.md VERSION_UPDATE.md # History files .history/ # Package files *.tgz # OS generated files .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # MCP Knowledge Graph **Persistent memory for AI models through a local knowledge graph.** Store and retrieve information across conversations using entities, relations, and observations. Works with Claude Code/Desktop and any MCP-compatible AI platform. ## Why ".aim" and "aim_" prefixes? AIM stands for **AI Memory** - the core concept of this knowledge graph system. The three AIM elements provide clear organization and safety: - **`.aim` directories**: Keep AI memory files organized and easily identifiable - **`aim_` tool prefixes**: Group related memory functions together in multi-tool setups - **`_aim` safety markers**: Each memory file starts with `{"type":"_aim","source":"mcp-knowledge-graph"}` to prevent accidental overwrites of unrelated JSONL files This consistent AIM naming makes it obvious which directories, tools, and files belong to our AI memory system. ## Storage Logic **File Location Priority:** 1. **Project with `.aim`** - Uses `.aim/memory.jsonl` (project-local) 2. **No project/no .aim** - Uses configured global directory 3. **Contexts** - Adds suffix: `memory-work.jsonl`, `memory-personal.jsonl` **Safety System:** - Every memory file starts with `{"type":"_aim","source":"mcp-knowledge-graph"}` - System refuses to write to files without this marker - Prevents accidental overwrite of unrelated JSONL files ## Master Database Concept **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`. - **Default Behavior**: All memory operations use the master database unless you specify a different one - **Always Available**: Exists in both project-local and global locations - **Primary Storage**: Your main knowledge graph that persists across all conversations - **Named Databases**: Optional additional databases (`work`, `personal`, `health`) for organizing specific topics ## Key Features - **Master Database**: Primary memory store used by default for all operations - **Multiple Databases**: Optional named databases for organizing memories by topic - **Project Detection**: Automatic project-local memory using `.aim` directories - **Location Override**: Force operations to use project or global storage - **Safe Operations**: Built-in protection against overwriting unrelated files - **Database Discovery**: List all available databases in both locations ## Quick Start ### Global Memory (Recommended) Add to your `claude_desktop_config.json` or `.claude.json`: ```json { "mcpServers": { "memory": { "command": "npx", "args": [ "-y", "mcp-knowledge-graph", "--memory-path", "/Users/yourusername/.aim/" ] } } } ``` This creates memory files in your specified directory: - `memory.jsonl` - **Master Database** (default for all operations) - `memory-work.jsonl` - Work database - `memory-personal.jsonl` - Personal database - etc. ### Project-Local Memory In any project, create a `.aim` directory: ```bash mkdir .aim ``` Now memory tools automatically use `.aim/memory.jsonl` (project-local **master database**) instead of global storage when run from this project. ## How AI Uses Databases 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: ```json // Master Database (default - no context needed) aim_create_entities({ entities: [{ name: "John_Doe", entityType: "person", observations: ["Met at conference"] }] }) // Work database aim_create_entities({ context: "work", entities: [{ name: "Q4_Project", entityType: "project", observations: ["Due December 2024"] }] }) // Personal database aim_create_entities({ context: "personal", entities: [{ name: "Mom", entityType: "person", observations: ["Birthday March 15th"] }] }) // Master database in specific location aim_create_entities({ location: "global", entities: [{ name: "Important_Info", entityType: "reference", observations: ["Stored in global master database"] }] }) ``` ## File Organization **Global Setup:** ```tree /Users/yourusername/.aim/ ├── memory.jsonl # Master Database (default) ├── memory-work.jsonl # Work database ├── memory-personal.jsonl # Personal database └── memory-health.jsonl # Health database ``` **Project Setup:** ```tree my-project/ ├── .aim/ │ ├── memory.jsonl # Project Master Database (default) │ └── memory-work.jsonl # Project Work database └── src/ ``` ## Available Tools - `aim_create_entities` - Add new people, projects, events - `aim_create_relations` - Link entities together - `aim_add_observations` - Add facts to existing entities - `aim_search_nodes` - Find information by keyword - `aim_read_graph` - View entire memory - `aim_open_nodes` - Retrieve specific entities by name - `aim_list_databases` - Show all available databases and current location - `aim_delete_entities` - Remove entities - `aim_delete_observations` - Remove specific facts - `aim_delete_relations` - Remove connections ### Parameters - `context` (optional) - Specify named database (`work`, `personal`, etc.). Defaults to **master database** - `location` (optional) - Force `project` or `global` storage location. Defaults to auto-detection ## Database Discovery Use `aim_list_databases` to see all available databases: ```json { "project_databases": [ "default", // Master Database (project-local) "project-work" // Named database ], "global_databases": [ "default", // Master Database (global) "work", "personal", "health" ], "current_location": "project (.aim directory detected)" } ``` **Key Points:** - **"default"** = Master Database in both locations - **Current location** shows whether you're using project or global storage - **Master database exists everywhere** - it's your primary memory store - **Named databases** are optional additions for specific topics ## Configuration Examples **Important:** Always specify `--memory-path` to control where your memory files are stored. **Home directory:** ```json { "mcpServers": { "memory": { "command": "npx", "args": [ "-y", "mcp-knowledge-graph", "--memory-path", "/Users/yourusername/.aim" ] } } } ``` **Custom location (e.g., Dropbox):** ```json { "mcpServers": { "memory": { "command": "npx", "args": [ "-y", "mcp-knowledge-graph", "--memory-path", "/Users/yourusername/Dropbox/.aim" ] } } } ``` **Auto-approve all operations:** ```json { "mcpServers": { "memory": { "command": "npx", "args": [ "-y", "mcp-knowledge-graph", "--memory-path", "/Users/yourusername/.aim" ], "autoapprove": [ "aim_create_entities", "aim_create_relations", "aim_add_observations", "aim_search_nodes", "aim_read_graph", "aim_open_nodes", "aim_list_databases" ] } } } ``` ## Troubleshooting **"File does not contain required _aim safety marker" error:** - The file may not belong to this system - Manual JSONL files need `{"type":"_aim","source":"mcp-knowledge-graph"}` as first line - If you created the file manually, add the `_aim` marker or delete and let the system recreate it **Memories going to unexpected locations:** - Check if you're in a project directory with `.aim` folder (uses project-local storage) - Otherwise uses the configured global `--memory-path` directory - Use `aim_list_databases` to see all available databases and current location - Use `ls .aim/` or `ls /Users/yourusername/.aim/` to see your memory files **Too many similar databases:** - AI models try to use consistent names, but may create variations - Manually delete unwanted database files if needed - Encourage AI to use simple, consistent database names - **Remember**: Master database is always available as the default - named databases are optional ## Requirements - Node.js 18+ - MCP-compatible AI platform ## License MIT ``` -------------------------------------------------------------------------------- /.claude/settings.local.json: -------------------------------------------------------------------------------- ```json { "permissions": { "allow": [ "Bash(rm:*)", "Bash(git add:*)", "Bash(git commit:*)", "Bash(git push:*)" ], "deny": [], "ask": [] } } ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "outDir": "./dist", "rootDir": ".", "module": "NodeNext", "moduleResolution": "NodeNext", "esModuleInterop": true, "strict": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "declaration": true, "sourceMap": true, "allowJs": true, "checkJs": true, "exactOptionalPropertyTypes": true, "noUncheckedIndexedAccess": true }, "include": [ "./**/*.ts" ] } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "mcp-knowledge-graph", "version": "1.2.0", "description": "MCP server enabling persistent memory for AI models through a local knowledge graph", "license": "MIT", "author": "Shane Holloman", "homepage": "https://github.com/shaneholloman/mcp-knowledge-graph", "bugs": "https://github.com/shaneholloman/mcp-knowledge-graph/issues", "type": "module", "engines": { "node": ">=18.0.0" }, "bin": { "mcp-knowledge-graph": "dist/index.js" }, "files": [ "dist" ], "scripts": { "build": "tsc && shx chmod +x dist/*.js", "prepare": "npm run build", "watch": "tsc --watch" }, "dependencies": { "@modelcontextprotocol/sdk": "1.0.1", "minimist": "^1.2.8" }, "devDependencies": { "@types/minimist": "^1.2.5", "@types/node": "^22.9.3", "shx": "^0.3.4", "typescript": "^5.6.2" } } ``` -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { promises as fs } from 'fs'; import { existsSync } from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import minimist from 'minimist'; import { isAbsolute } from 'path'; // Parse args and handle paths safely const argv = minimist(process.argv.slice(2)); let memoryPath = argv['memory-path']; // If a custom path is provided, ensure it's absolute if (memoryPath && !isAbsolute(memoryPath)) { memoryPath = path.resolve(process.cwd(), memoryPath); } // Define the base directory for memory files const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Handle memory path - could be a file or directory let baseMemoryPath: string; if (memoryPath) { // If memory-path points to a .jsonl file, use its directory as the base if (memoryPath.endsWith('.jsonl')) { baseMemoryPath = path.dirname(memoryPath); } else { // Otherwise treat it as a directory baseMemoryPath = memoryPath; } } else { baseMemoryPath = __dirname; } // Simple marker to identify our files - prevents writing to unrelated JSONL files const FILE_MARKER = { type: "_aim", source: "mcp-knowledge-graph" }; // Project detection - look for common project markers function findProjectRoot(startDir: string = process.cwd()): string | null { const projectMarkers = ['.git', 'package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod']; let currentDir = startDir; const maxDepth = 5; for (let i = 0; i < maxDepth; i++) { // Check for project markers for (const marker of projectMarkers) { if (existsSync(path.join(currentDir, marker))) { return currentDir; } } // Move up one directory const parentDir = path.dirname(currentDir); if (parentDir === currentDir) { // Reached root directory break; } currentDir = parentDir; } return null; } // Function to get memory file path based on context and optional location override function getMemoryFilePath(context?: string, location?: 'project' | 'global'): string { const filename = context ? `memory-${context}.jsonl` : 'memory.jsonl'; // If location is explicitly specified, use it if (location === 'global') { return path.join(baseMemoryPath, filename); } if (location === 'project') { const projectRoot = findProjectRoot(); if (projectRoot) { const aimDir = path.join(projectRoot, '.aim'); return path.join(aimDir, filename); // Will create .aim if it doesn't exist } else { throw new Error('No project detected - cannot use project location'); } } // Auto-detect logic (existing behavior) const projectRoot = findProjectRoot(); if (projectRoot) { const aimDir = path.join(projectRoot, '.aim'); if (existsSync(aimDir)) { return path.join(aimDir, filename); } } // Fallback to configured base directory return path.join(baseMemoryPath, filename); } // We are storing our memory using entities, relations, and observations in a graph structure interface Entity { name: string; entityType: string; observations: string[]; } interface Relation { from: string; to: string; relationType: string; } interface KnowledgeGraph { entities: Entity[]; relations: Relation[]; } // The KnowledgeGraphManager class contains all operations to interact with the knowledge graph class KnowledgeGraphManager { private async loadGraph(context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> { const filePath = getMemoryFilePath(context, location); try { const data = await fs.readFile(filePath, "utf-8"); const lines = data.split("\n").filter(line => line.trim() !== ""); if (lines.length === 0) { return { entities: [], relations: [] }; } // Check first line for our file marker const firstLine = JSON.parse(lines[0]!); if (firstLine.type !== "_aim" || firstLine.source !== "mcp-knowledge-graph") { 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"}`); } // Process remaining lines (skip metadata) return lines.slice(1).reduce((graph: KnowledgeGraph, line) => { const item = JSON.parse(line); if (item.type === "entity") graph.entities.push(item as Entity); if (item.type === "relation") graph.relations.push(item as Relation); return graph; }, { entities: [], relations: [] }); } catch (error) { if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") { // File doesn't exist - we'll create it with metadata on first save return { entities: [], relations: [] }; } throw error; } } private async saveGraph(graph: KnowledgeGraph, context?: string, location?: 'project' | 'global'): Promise<void> { const filePath = getMemoryFilePath(context, location); // Write our simple file marker const lines = [ JSON.stringify(FILE_MARKER), ...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })), ...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })), ]; // Ensure directory exists await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, lines.join("\n")); } async createEntities(entities: Entity[], context?: string, location?: 'project' | 'global'): Promise<Entity[]> { const graph = await this.loadGraph(context, location); const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name)); graph.entities.push(...newEntities); await this.saveGraph(graph, context, location); return newEntities; } async createRelations(relations: Relation[], context?: string, location?: 'project' | 'global'): Promise<Relation[]> { const graph = await this.loadGraph(context, location); const newRelations = relations.filter(r => !graph.relations.some(existingRelation => existingRelation.from === r.from && existingRelation.to === r.to && existingRelation.relationType === r.relationType )); graph.relations.push(...newRelations); await this.saveGraph(graph, context, location); return newRelations; } async addObservations(observations: { entityName: string; contents: string[] }[], context?: string, location?: 'project' | 'global'): Promise<{ entityName: string; addedObservations: string[] }[]> { const graph = await this.loadGraph(context, location); const results = observations.map(o => { const entity = graph.entities.find(e => e.name === o.entityName); if (!entity) { throw new Error(`Entity with name ${o.entityName} not found`); } const newObservations = o.contents.filter(content => !entity.observations.includes(content)); entity.observations.push(...newObservations); return { entityName: o.entityName, addedObservations: newObservations }; }); await this.saveGraph(graph, context, location); return results; } async deleteEntities(entityNames: string[], context?: string, location?: 'project' | 'global'): Promise<void> { const graph = await this.loadGraph(context, location); graph.entities = graph.entities.filter(e => !entityNames.includes(e.name)); graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to)); await this.saveGraph(graph, context, location); } async deleteObservations(deletions: { entityName: string; observations: string[] }[], context?: string, location?: 'project' | 'global'): Promise<void> { const graph = await this.loadGraph(context, location); deletions.forEach(d => { const entity = graph.entities.find(e => e.name === d.entityName); if (entity) { entity.observations = entity.observations.filter(o => !d.observations.includes(o)); } }); await this.saveGraph(graph, context, location); } async deleteRelations(relations: Relation[], context?: string, location?: 'project' | 'global'): Promise<void> { const graph = await this.loadGraph(context, location); graph.relations = graph.relations.filter(r => !relations.some(delRelation => r.from === delRelation.from && r.to === delRelation.to && r.relationType === delRelation.relationType )); await this.saveGraph(graph, context, location); } async readGraph(context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> { return this.loadGraph(context, location); } // Very basic search function async searchNodes(query: string, context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> { const graph = await this.loadGraph(context, location); // Filter entities const filteredEntities = graph.entities.filter(e => e.name.toLowerCase().includes(query.toLowerCase()) || e.entityType.toLowerCase().includes(query.toLowerCase()) || e.observations.some(o => o.toLowerCase().includes(query.toLowerCase())) ); // Create a Set of filtered entity names for quick lookup const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); // Filter relations to only include those between filtered entities const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to) ); const filteredGraph: KnowledgeGraph = { entities: filteredEntities, relations: filteredRelations, }; return filteredGraph; } async openNodes(names: string[], context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> { const graph = await this.loadGraph(context, location); // Filter entities const filteredEntities = graph.entities.filter(e => names.includes(e.name)); // Create a Set of filtered entity names for quick lookup const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); // Filter relations to only include those between filtered entities const filteredRelations = graph.relations.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to) ); const filteredGraph: KnowledgeGraph = { entities: filteredEntities, relations: filteredRelations, }; return filteredGraph; } async listDatabases(): Promise<{ project_databases: string[], global_databases: string[], current_location: string }> { const result = { project_databases: [] as string[], global_databases: [] as string[], current_location: "" }; // Check project-local .aim directory const projectRoot = findProjectRoot(); if (projectRoot) { const aimDir = path.join(projectRoot, '.aim'); if (existsSync(aimDir)) { result.current_location = "project (.aim directory detected)"; try { const files = await fs.readdir(aimDir); result.project_databases = files .filter(file => file.endsWith('.jsonl')) .map(file => file === 'memory.jsonl' ? 'default' : file.replace('memory-', '').replace('.jsonl', '')) .sort(); } catch (error) { // Directory exists but can't read - ignore } } else { result.current_location = "global (no .aim directory in project)"; } } else { result.current_location = "global (no project detected)"; } // Check global directory try { const files = await fs.readdir(baseMemoryPath); result.global_databases = files .filter(file => file.endsWith('.jsonl')) .map(file => file === 'memory.jsonl' ? 'default' : file.replace('memory-', '').replace('.jsonl', '')) .sort(); } catch (error) { // Directory doesn't exist or can't read result.global_databases = []; } return result; } } const knowledgeGraphManager = new KnowledgeGraphManager(); // The server instance and tools exposed to AI models const server = new Server({ name: "mcp-knowledge-graph", version: "1.0.1", }, { capabilities: { tools: {}, }, },); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "aim_create_entities", description: `Create multiple new entities in the knowledge graph. 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. 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). LOCATION OVERRIDE: Use the 'location' parameter to force storage in a specific location: - 'project': Always use project-local .aim directory (creates if needed) - 'global': Always use global configured directory - Leave blank: Auto-detect (project if .aim exists, otherwise global) WHEN TO USE DATABASES: - Any descriptive name: 'work', 'personal', 'health', 'research', 'basket-weaving', 'book-club', etc. - New databases are created automatically - no setup required - IMPORTANT: Use consistent, simple names - prefer 'work' over 'work-stuff' or 'job-related' - Common examples: 'work' (professional), 'personal' (private), 'health' (medical), 'research' (academic) - Leave blank: General information or when unsure (uses master database) EXAMPLES: - Master database (default): aim_create_entities({entities: [{name: "John", entityType: "person", observations: ["Met at conference"]}]}) - Work database: aim_create_entities({context: "work", entities: [{name: "Q4_Project", entityType: "project", observations: ["Due December 2024"]}]}) - Master database in global location: aim_create_entities({location: "global", entities: [{name: "John", entityType: "person", observations: ["Met at conference"]}]}) - Work database in project location: aim_create_entities({context: "work", location: "project", entities: [{name: "Q4_Project", entityType: "project", observations: ["Due December 2024"]}]})`, inputSchema: { type: "object", properties: { context: { type: "string", 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." }, location: { type: "string", enum: ["project", "global"], description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection." }, entities: { type: "array", items: { type: "object", properties: { name: { type: "string", description: "The name of the entity" }, entityType: { type: "string", description: "The type of the entity" }, observations: { type: "array", items: { type: "string" }, description: "An array of observation contents associated with the entity" }, }, required: ["name", "entityType", "observations"], }, }, }, required: ["entities"], }, }, { name: "aim_create_relations", description: `Create multiple new relations between entities in the knowledge graph. Relations should be in active voice. DATABASE SELECTION: Relations are created within the specified database's knowledge graph. Entities must exist in the same database. LOCATION OVERRIDE: Use the 'location' parameter to force storage in 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection. EXAMPLES: - Master database (default): aim_create_relations({relations: [{from: "John", to: "TechConf2024", relationType: "attended"}]}) - Work database: aim_create_relations({context: "work", relations: [{from: "Alice", to: "Q4_Project", relationType: "manages"}]}) - Master database in global location: aim_create_relations({location: "global", relations: [{from: "John", to: "TechConf2024", relationType: "attended"}]}) - Personal database in project location: aim_create_relations({context: "personal", location: "project", relations: [{from: "Mom", to: "Gardening", relationType: "enjoys"}]})`, inputSchema: { type: "object", properties: { context: { type: "string", description: "Optional memory context. Relations will be created in the specified context's knowledge graph." }, location: { type: "string", enum: ["project", "global"], description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection." }, relations: { type: "array", items: { type: "object", properties: { from: { type: "string", description: "The name of the entity where the relation starts" }, to: { type: "string", description: "The name of the entity where the relation ends" }, relationType: { type: "string", description: "The type of the relation" }, }, required: ["from", "to", "relationType"], }, }, }, required: ["relations"], }, }, { name: "aim_add_observations", description: `Add new observations to existing entities in the knowledge graph. DATABASE SELECTION: Observations are added to entities within the specified database's knowledge graph. LOCATION OVERRIDE: Use the 'location' parameter to force storage in 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection. EXAMPLES: - Master database (default): aim_add_observations({observations: [{entityName: "John", contents: ["Lives in Seattle", "Works in tech"]}]}) - Work database: aim_add_observations({context: "work", observations: [{entityName: "Q4_Project", contents: ["Behind schedule", "Need more resources"]}]}) - Master database in global location: aim_add_observations({location: "global", observations: [{entityName: "John", contents: ["Lives in Seattle", "Works in tech"]}]}) - Health database in project location: aim_add_observations({context: "health", location: "project", observations: [{entityName: "Daily_Routine", contents: ["30min morning walk", "8 glasses water"]}]})`, inputSchema: { type: "object", properties: { context: { type: "string", description: "Optional memory context. Observations will be added to entities in the specified context's knowledge graph." }, location: { type: "string", enum: ["project", "global"], description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection." }, observations: { type: "array", items: { type: "object", properties: { entityName: { type: "string", description: "The name of the entity to add the observations to" }, contents: { type: "array", items: { type: "string" }, description: "An array of observation contents to add" }, }, required: ["entityName", "contents"], }, }, }, required: ["observations"], }, }, { name: "aim_delete_entities", description: `Delete multiple entities and their associated relations from the knowledge graph. DATABASE SELECTION: Entities are deleted from the specified database's knowledge graph. LOCATION OVERRIDE: Use the 'location' parameter to force deletion from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection. EXAMPLES: - Master database (default): aim_delete_entities({entityNames: ["OldProject"]}) - Work database: aim_delete_entities({context: "work", entityNames: ["CompletedTask", "CancelledMeeting"]}) - Master database in global location: aim_delete_entities({location: "global", entityNames: ["OldProject"]}) - Personal database in project location: aim_delete_entities({context: "personal", location: "project", entityNames: ["ExpiredReminder"]})`, inputSchema: { type: "object", properties: { context: { type: "string", description: "Optional memory context. Entities will be deleted from the specified context's knowledge graph." }, location: { type: "string", enum: ["project", "global"], description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection." }, entityNames: { type: "array", items: { type: "string" }, description: "An array of entity names to delete" }, }, required: ["entityNames"], }, }, { name: "aim_delete_observations", description: `Delete specific observations from entities in the knowledge graph. DATABASE SELECTION: Observations are deleted from entities within the specified database's knowledge graph. LOCATION OVERRIDE: Use the 'location' parameter to force deletion from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection. EXAMPLES: - Master database (default): aim_delete_observations({deletions: [{entityName: "John", observations: ["Outdated info"]}]}) - Work database: aim_delete_observations({context: "work", deletions: [{entityName: "Project", observations: ["Old deadline"]}]}) - Master database in global location: aim_delete_observations({location: "global", deletions: [{entityName: "John", observations: ["Outdated info"]}]}) - Health database in project location: aim_delete_observations({context: "health", location: "project", deletions: [{entityName: "Exercise", observations: ["Injured knee"]}]})`, inputSchema: { type: "object", properties: { context: { type: "string", description: "Optional memory context. Observations will be deleted from entities in the specified context's knowledge graph." }, location: { type: "string", enum: ["project", "global"], description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection." }, deletions: { type: "array", items: { type: "object", properties: { entityName: { type: "string", description: "The name of the entity containing the observations" }, observations: { type: "array", items: { type: "string" }, description: "An array of observations to delete" }, }, required: ["entityName", "observations"], }, }, }, required: ["deletions"], }, }, { name: "aim_delete_relations", description: `Delete multiple relations from the knowledge graph. DATABASE SELECTION: Relations are deleted from the specified database's knowledge graph. LOCATION OVERRIDE: Use the 'location' parameter to force deletion from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection. EXAMPLES: - Master database (default): aim_delete_relations({relations: [{from: "John", to: "OldCompany", relationType: "worked_at"}]}) - Work database: aim_delete_relations({context: "work", relations: [{from: "Alice", to: "CancelledProject", relationType: "manages"}]}) - Master database in global location: aim_delete_relations({location: "global", relations: [{from: "John", to: "OldCompany", relationType: "worked_at"}]}) - Personal database in project location: aim_delete_relations({context: "personal", location: "project", relations: [{from: "Me", to: "OldHobby", relationType: "enjoys"}]})`, inputSchema: { type: "object", properties: { context: { type: "string", description: "Optional memory context. Relations will be deleted from the specified context's knowledge graph." }, location: { type: "string", enum: ["project", "global"], description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection." }, relations: { type: "array", items: { type: "object", properties: { from: { type: "string", description: "The name of the entity where the relation starts" }, to: { type: "string", description: "The name of the entity where the relation ends" }, relationType: { type: "string", description: "The type of the relation" }, }, required: ["from", "to", "relationType"], }, description: "An array of relations to delete" }, }, required: ["relations"], }, }, { name: "aim_read_graph", description: `Read the entire knowledge graph. DATABASE SELECTION: Reads from the specified database or master database if no database is specified. LOCATION OVERRIDE: Use the 'location' parameter to force reading from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection. EXAMPLES: - Master database (default): aim_read_graph({}) - Work database: aim_read_graph({context: "work"}) - Master database in global location: aim_read_graph({location: "global"}) - Personal database in project location: aim_read_graph({context: "personal", location: "project"})`, inputSchema: { type: "object", properties: { context: { type: "string", description: "Optional memory context. Reads from the specified context's knowledge graph or master database if not specified." }, location: { type: "string", enum: ["project", "global"], description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection." } }, }, }, { name: "aim_search_nodes", description: `Search for nodes in the knowledge graph based on a query. DATABASE SELECTION: Searches within the specified database or master database if no database is specified. LOCATION OVERRIDE: Use the 'location' parameter to force searching in 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection. EXAMPLES: - Master database (default): aim_search_nodes({query: "John"}) - Work database: aim_search_nodes({context: "work", query: "project"}) - Master database in global location: aim_search_nodes({location: "global", query: "John"}) - Personal database in project location: aim_search_nodes({context: "personal", location: "project", query: "family"})`, inputSchema: { type: "object", properties: { context: { type: "string", description: "Optional memory context. Searches within the specified context's knowledge graph or master database if not specified." }, location: { type: "string", enum: ["project", "global"], description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection." }, query: { type: "string", description: "The search query to match against entity names, types, and observation content" }, }, required: ["query"], }, }, { name: "aim_open_nodes", description: `Open specific nodes in the knowledge graph by their names. DATABASE SELECTION: Retrieves entities from the specified database or master database if no database is specified. LOCATION OVERRIDE: Use the 'location' parameter to force retrieval from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection. EXAMPLES: - Master database (default): aim_open_nodes({names: ["John", "TechConf2024"]}) - Work database: aim_open_nodes({context: "work", names: ["Q4_Project", "Alice"]}) - Master database in global location: aim_open_nodes({location: "global", names: ["John", "TechConf2024"]}) - Personal database in project location: aim_open_nodes({context: "personal", location: "project", names: ["Mom", "Birthday_Plans"]})`, inputSchema: { type: "object", properties: { context: { type: "string", description: "Optional memory context. Retrieves entities from the specified context's knowledge graph or master database if not specified." }, location: { type: "string", enum: ["project", "global"], description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection." }, names: { type: "array", items: { type: "string" }, description: "An array of entity names to retrieve", }, }, required: ["names"], }, }, { name: "aim_list_databases", description: `List all available memory databases in both project and global locations. DISCOVERY: Shows which databases exist, where they're stored, and which location is currently active. EXAMPLES: - aim_list_databases() - Shows all available databases and current storage location`, inputSchema: { type: "object", properties: {}, }, }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (!args) { throw new Error(`No arguments provided for tool: ${name}`); } switch (name) { case "aim_create_entities": 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) }] }; case "aim_create_relations": 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) }] }; case "aim_add_observations": 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) }] }; case "aim_delete_entities": await knowledgeGraphManager.deleteEntities(args.entityNames as string[], args.context as string, args.location as 'project' | 'global'); return { content: [{ type: "text", text: "Entities deleted successfully" }] }; case "aim_delete_observations": await knowledgeGraphManager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[], args.context as string, args.location as 'project' | 'global'); return { content: [{ type: "text", text: "Observations deleted successfully" }] }; case "aim_delete_relations": await knowledgeGraphManager.deleteRelations(args.relations as Relation[], args.context as string, args.location as 'project' | 'global'); return { content: [{ type: "text", text: "Relations deleted successfully" }] }; case "aim_read_graph": return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.readGraph(args.context as string, args.location as 'project' | 'global'), null, 2) }] }; case "aim_search_nodes": 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) }] }; case "aim_open_nodes": 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) }] }; case "aim_list_databases": return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.listDatabases(), null, 2) }] }; default: throw new Error(`Unknown tool: ${name}`); } }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Knowledge Graph MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); }); ```