#
tokens: 10239/50000 6/6 files
lines: off (toggle) GitHub
raw markdown copy
# 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);
});

```