├── .gitignore ├── .history └── .gitkeep ├── Dockerfile ├── PLACEHOLDER.md ├── README.md ├── WORKFLOW.md ├── example.jsonl ├── img ├── read-function.png └── server-name.png ├── index.ts ├── package-lock.json ├── package.json ├── pr-instructions.md ├── smithery.yaml ├── tsconfig.base.json └── tsconfig.json /.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 | -------------------------------------------------------------------------------- /.history/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itseasy21/mcp-knowledge-graph/b4977720e02afaee222f25a86eedc013312e0e5c/.history/.gitkeep -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | FROM node:lts-alpine 3 | 4 | # Create app directory 5 | WORKDIR /app 6 | 7 | # Copy package files 8 | COPY package*.json ./ 9 | 10 | # Install dependencies 11 | RUN npm install --ignore-scripts 12 | 13 | # Copy the rest of the code 14 | COPY . . 15 | 16 | # Build the project 17 | RUN npm run build 18 | 19 | # Expose any ports if needed (not specified, so skipping) 20 | 21 | # Run the MCP server 22 | CMD [ "node", "dist/index.js" ] 23 | -------------------------------------------------------------------------------- /PLACEHOLDER.md: -------------------------------------------------------------------------------- 1 | # NPM Package Name Reservation 2 | 3 | > [!NOTE] 4 | > These instructions are for reserving the npm package name `mcp-knowledge-base` with a minimal placeholder package. 5 | 6 | ## Steps to Reserve Package Name 7 | 8 | ### 1. Create Minimal Project Structure 9 | 10 | ```bash 11 | mkdir mcp-knowledge-base 12 | cd mcp-knowledge-base 13 | ``` 14 | 15 | ### 2. Initialize Package 16 | 17 | ```bash 18 | npm init -y 19 | ``` 20 | 21 | ### 3. Update package.json 22 | 23 | ```json 24 | { 25 | "name": "mcp-knowledge-base", 26 | "version": "0.0.1", 27 | "description": "MCP server for knowledge base functionality - Coming Soon", 28 | "main": "index.js", 29 | "scripts": { 30 | "test": "echo \"Error: no test specified\" && exit 1" 31 | }, 32 | "keywords": [ 33 | "mcp", 34 | "claude", 35 | "knowledge-base", 36 | "ai" 37 | ], 38 | "author": "Your Name", 39 | "license": "MIT" 40 | } 41 | ``` 42 | 43 | ### 4. Create Minimal index.js 44 | 45 | ```javascript 46 | console.log('MCP Knowledge Base - Coming Soon'); 47 | ``` 48 | 49 | ### 5. Create README.md 50 | 51 | ```markdown 52 | # MCP Knowledge Base 53 | 54 | > [!NOTE] 55 | > This package is currently in development. Future versions will provide knowledge base functionality for Claude AI. 56 | 57 | ## Coming Soon 58 | 59 | This package will build upon mcp-knowledge-graph to provide: 60 | - Enhanced knowledge base capabilities 61 | - Improved memory management 62 | - Advanced querying features 63 | 64 | ## Status 65 | 66 | This is a placeholder release. Production version coming soon. 67 | ``` 68 | 69 | ### 6. Publish Placeholder 70 | 71 | ```bash 72 | npm login # if not already logged in 73 | npm publish 74 | ``` 75 | 76 | ## Important Notes 77 | 78 | 1. Version Strategy 79 | - Start with 0.0.1 for placeholder 80 | - Use 0.x.x for development versions 81 | - Release 1.0.0 when ready for production 82 | 83 | 2. Package Maintenance 84 | - Update placeholder occasionally to maintain npm listing 85 | - Add "under development" notices in README 86 | - Consider adding GitHub repository with roadmap 87 | 88 | 3. Name Protection 89 | - Publishing placeholder prevents name squatting 90 | - Establishes your ownership of the name 91 | - Allows time for proper development 92 | 93 | 4. Future Updates 94 | - When ready to develop, use same package name 95 | - Increment version appropriately 96 | - Update with actual functionality 97 | 98 | ## Verification 99 | 100 | After publishing, verify reservation: 101 | 102 | ```bash 103 | npm view mcp-knowledge-base 104 | ``` 105 | 106 | ## Cleanup When Ready 107 | 108 | When ready to develop the actual package: 109 | 110 | 1. Archive placeholder code 111 | 2. Start development in new repository 112 | 3. Maintain same package name 113 | 4. Update version to reflect development status 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Knowledge Graph Memory Server 2 | 3 | [![smithery badge](https://smithery.ai/badge/@itseasy21/mcp-knowledge-graph)](https://smithery.ai/server/@itseasy21/mcp-knowledge-graph) 4 | 5 | An improved implementation of persistent memory using a local knowledge graph with a customizable memory path. 6 | 7 | This lets Claude remember information about the user across chats. 8 | 9 | > [!NOTE] 10 | > This is a fork of the original [Memory Server](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) and is intended to not use the ephemeral memory npx installation method. 11 | 12 | ## Server Name 13 | 14 | ```txt 15 | mcp-knowledge-graph 16 | ``` 17 | 18 | ![screen-of-server-name](img/server-name.png) 19 | 20 | ![read-function](/img/read-function.png) 21 | 22 | ## Core Concepts 23 | 24 | ### Entities 25 | 26 | Entities are the primary nodes in the knowledge graph. Each entity has: 27 | 28 | - A unique name (identifier) 29 | - An entity type (e.g., "person", "organization", "event") 30 | - A list of observations 31 | - Creation date and version tracking 32 | 33 | The version tracking feature helps maintain a historical context of how knowledge evolves over time. 34 | 35 | Example: 36 | 37 | ```json 38 | { 39 | "name": "John_Smith", 40 | "entityType": "person", 41 | "observations": ["Speaks fluent Spanish"] 42 | } 43 | ``` 44 | 45 | ### Relations 46 | 47 | Relations define directed connections between entities. They are always stored in active voice and describe how entities interact or relate to each other. Each relation includes: 48 | 49 | - Source and target entities 50 | - Relationship type 51 | - Creation date and version information 52 | 53 | This versioning system helps track how relationships between entities evolve over time. 54 | 55 | Example: 56 | 57 | ```json 58 | { 59 | "from": "John_Smith", 60 | "to": "Anthropic", 61 | "relationType": "works_at" 62 | } 63 | ``` 64 | 65 | ### Observations 66 | 67 | Observations are discrete pieces of information about an entity. They are: 68 | 69 | - Stored as strings 70 | - Attached to specific entities 71 | - Can be added or removed independently 72 | - Should be atomic (one fact per observation) 73 | 74 | Example: 75 | 76 | ```json 77 | { 78 | "entityName": "John_Smith", 79 | "observations": [ 80 | "Speaks fluent Spanish", 81 | "Graduated in 2019", 82 | "Prefers morning meetings" 83 | ] 84 | } 85 | ``` 86 | 87 | ## API 88 | 89 | ### Tools 90 | 91 | - **create_entities** 92 | - Create multiple new entities in the knowledge graph 93 | - Input: `entities` (array of objects) 94 | - Each object contains: 95 | - `name` (string): Entity identifier 96 | - `entityType` (string): Type classification 97 | - `observations` (string[]): Associated observations 98 | - Ignores entities with existing names 99 | 100 | - **create_relations** 101 | - Create multiple new relations between entities 102 | - Input: `relations` (array of objects) 103 | - Each object contains: 104 | - `from` (string): Source entity name 105 | - `to` (string): Target entity name 106 | - `relationType` (string): Relationship type in active voice 107 | - Skips duplicate relations 108 | 109 | - **add_observations** 110 | - Add new observations to existing entities 111 | - Input: `observations` (array of objects) 112 | - Each object contains: 113 | - `entityName` (string): Target entity 114 | - `contents` (string[]): New observations to add 115 | - Returns added observations per entity 116 | - Fails if entity doesn't exist 117 | 118 | - **delete_entities** 119 | - Remove entities and their relations 120 | - Input: `entityNames` (string[]) 121 | - Cascading deletion of associated relations 122 | - Silent operation if entity doesn't exist 123 | 124 | - **delete_observations** 125 | - Remove specific observations from entities 126 | - Input: `deletions` (array of objects) 127 | - Each object contains: 128 | - `entityName` (string): Target entity 129 | - `observations` (string[]): Observations to remove 130 | - Silent operation if observation doesn't exist 131 | 132 | - **delete_relations** 133 | - Remove specific relations from the graph 134 | - Input: `relations` (array of objects) 135 | - Each object contains: 136 | - `from` (string): Source entity name 137 | - `to` (string): Target entity name 138 | - `relationType` (string): Relationship type 139 | - Silent operation if relation doesn't exist 140 | 141 | - **read_graph** 142 | - Read the entire knowledge graph 143 | - No input required 144 | - Returns complete graph structure with all entities and relations 145 | 146 | - **search_nodes** 147 | - Search for nodes based on query 148 | - Input: `query` (string) 149 | - Searches across: 150 | - Entity names 151 | - Entity types 152 | - Observation content 153 | - Returns matching entities and their relations 154 | 155 | - **open_nodes** 156 | - Retrieve specific nodes by name 157 | - Input: `names` (string[]) 158 | - Returns: 159 | - Requested entities 160 | - Relations between requested entities 161 | - Silently skips non-existent nodes 162 | 163 | ## Usage with Cursor, Cline or Claude Desktop 164 | 165 | ### Setup 166 | 167 | Add this to your mcp.json or claude_desktop_config.json: 168 | 169 | ```json 170 | { 171 | "mcpServers": { 172 | "memory": { 173 | "command": "npx", 174 | "args": [ 175 | "-y", 176 | "@itseasy21/mcp-knowledge-graph" 177 | ], 178 | "env": { 179 | "MEMORY_FILE_PATH": "/path/to/your/projects.jsonl" 180 | } 181 | } 182 | } 183 | } 184 | ``` 185 | 186 | ### Installing via Smithery 187 | 188 | To install Knowledge Graph Memory Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@itseasy21/mcp-knowledge-graph): 189 | 190 | ```bash 191 | npx -y @smithery/cli install @itseasy21/mcp-knowledge-graph --client claude 192 | ``` 193 | 194 | ### Custom Memory Path 195 | 196 | You can specify a custom path for the memory file in two ways: 197 | 198 | 1. Using command-line arguments: 199 | ```json 200 | { 201 | "mcpServers": { 202 | "memory": { 203 | "command": "npx", 204 | "args": ["-y", "@itseasy21/mcp-knowledge-graph", "--memory-path", "/path/to/your/memory.jsonl"] 205 | } 206 | } 207 | } 208 | ``` 209 | 210 | 2. Using environment variables: 211 | ```json 212 | { 213 | "mcpServers": { 214 | "memory": { 215 | "command": "npx", 216 | "args": ["-y", "@itseasy21/mcp-knowledge-graph"], 217 | "env": { 218 | "MEMORY_FILE_PATH": "/path/to/your/memory.jsonl" 219 | } 220 | } 221 | } 222 | } 223 | ``` 224 | 225 | If no path is specified, it will default to memory.jsonl in the server's installation directory. 226 | 227 | ### System Prompt 228 | 229 | The prompt for utilizing memory depends on the use case. Changing the prompt will help the model determine the frequency and types of memories created. 230 | 231 | Here is an example prompt for chat personalization. You could use this prompt in the "Custom Instructions" field of a [Claude.ai Project](https://www.anthropic.com/news/projects). 232 | 233 | ```txt 234 | Follow these steps for each interaction: 235 | 236 | 1. User Identification: 237 | - You should assume that you are interacting with default_user 238 | - If you have not identified default_user, proactively try to do so. 239 | 240 | 2. Memory Retrieval: 241 | - Always begin your chat by saying only "Remembering..." and retrieve all relevant information from your knowledge graph 242 | - Always refer to your knowledge graph as your "memory" 243 | 244 | 3. Memory 245 | - While conversing with the user, be attentive to any new information that falls into these categories: 246 | a) Basic Identity (age, gender, location, job title, education level, etc.) 247 | b) Behaviors (interests, habits, etc.) 248 | c) Preferences (communication style, preferred language, etc.) 249 | d) Goals (goals, targets, aspirations, etc.) 250 | e) Relationships (personal and professional relationships up to 3 degrees of separation) 251 | 252 | 4. Memory Update: 253 | - If any new information was gathered during the interaction, update your memory as follows: 254 | a) Create entities for recurring organizations, people, and significant events 255 | b) Connect them to the current entities using relations 256 | b) Store facts about them as observations 257 | ``` 258 | 259 | ## License 260 | 261 | This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. 262 | -------------------------------------------------------------------------------- /WORKFLOW.md: -------------------------------------------------------------------------------- 1 | # Development Workflow Setup 2 | 3 | ## Initial Environment 4 | 5 | - Windows 11 system 6 | - Node.js environment managed by Volta 7 | - PowerShell 7.4.6 as the terminal 8 | 9 | ## Project Structure 10 | 11 | This is part of a monorepo project: 12 | 13 | - Package: @modelcontextprotocol/server-memory 14 | - Version: 0.6.2 15 | - Type: ES Module (package.json "type": "module") 16 | 17 | ## Setup Steps 18 | 19 | 1. **TypeScript Configuration** 20 | - Created tsconfig.base.json with ES module support: 21 | 22 | ```json 23 | { 24 | "compilerOptions": { 25 | "target": "ES2020", 26 | "module": "NodeNext", 27 | "moduleResolution": "NodeNext", 28 | "esModuleInterop": true, 29 | "strict": true, 30 | "skipLibCheck": true, 31 | "forceConsistentCasingInFileNames": true, 32 | "declaration": true, 33 | "sourceMap": true, 34 | "allowJs": true, 35 | "checkJs": true 36 | } 37 | } 38 | ``` 39 | 40 | - Maintained monorepo compatibility in tsconfig.json: 41 | 42 | ```json 43 | { 44 | "extends": "./tsconfig.base.json", 45 | "compilerOptions": { 46 | "outDir": "./dist", 47 | "rootDir": "." 48 | }, 49 | "include": [ 50 | "./**/*.ts" 51 | ] 52 | } 53 | ``` 54 | 55 | 2. **Dependencies** 56 | - Installed TypeScript globally with Volta: 57 | 58 | ```bash 59 | volta install typescript 60 | ``` 61 | 62 | - Added type definitions for minimist: 63 | 64 | ```bash 65 | volta run npm install --save-dev @types/minimist 66 | ``` 67 | 68 | 3. **Code Fixes** 69 | - Fixed duplicate argv declarations in index.ts 70 | - Removed backup directory that was causing build conflicts 71 | - Ensured proper ES module imports 72 | 73 | 4. **Build Process** 74 | - Build script in package.json: 75 | 76 | ```json 77 | "scripts": { 78 | "build": "tsc && shx chmod +x dist/*.js", 79 | "prepare": "npm run build", 80 | "watch": "tsc --watch" 81 | } 82 | ``` 83 | 84 | - Successfully built with: 85 | 86 | ```bash 87 | volta run npm run build 88 | ``` 89 | 90 | ## Build Output 91 | 92 | The successful build generates: 93 | 94 | - dist/index.js (compiled JavaScript) 95 | - dist/index.d.ts (TypeScript declarations) 96 | - dist/index.js.map (source maps) 97 | 98 | ## Testing via Inspector 99 | 100 | - Run the inspector with a memory path argument: 101 | 102 | ```sh 103 | volta run npx @modelcontextprotocol/inspector dist/index.js --memory-path=C:/Users/shane/Desktop/memory/memory.jsonl 104 | ``` 105 | 106 | ## Development Notes 107 | 108 | - Keep monorepo compatibility in mind when making changes 109 | - Use Volta for all Node.js/npm operations 110 | - Maintain ES module format throughout the codebase 111 | - Run builds with `volta run npm run build` 112 | -------------------------------------------------------------------------------- /example.jsonl: -------------------------------------------------------------------------------- 1 | {"type":"entity","data":{"name":"Alice_Smith","entityType":"person","observations":["Works as a software engineer","Lives in San Francisco","Speaks Mandarin fluently"]}} 2 | {"type":"entity","data":{"name":"ML_Project_X","entityType":"project","observations":["Started in 2023","Focus on natural language processing","Currently in development phase"]}} 3 | {"type":"entity","data":{"name":"TechCorp","entityType":"organization","observations":["Founded in 2010","Specializes in AI development","Headquartered in San Francisco"]}} 4 | {"type":"relation","data":{"from":"Alice_Smith","to":"ML_Project_X","relationType":"leads"}} 5 | {"type":"relation","data":{"from":"Alice_Smith","to":"TechCorp","relationType":"works_at"}} 6 | {"type":"relation","data":{"from":"TechCorp","to":"ML_Project_X","relationType":"owns"} 7 | -------------------------------------------------------------------------------- /img/read-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itseasy21/mcp-knowledge-graph/b4977720e02afaee222f25a86eedc013312e0e5c/img/read-function.png -------------------------------------------------------------------------------- /img/server-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itseasy21/mcp-knowledge-graph/b4977720e02afaee222f25a86eedc013312e0e5c/img/server-name.png -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 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 path from 'path'; 11 | import { fileURLToPath } from 'url'; 12 | import minimist from 'minimist'; 13 | import { isAbsolute } from 'path'; 14 | 15 | // Parse args and handle paths safely 16 | const argv = minimist(process.argv.slice(2)); 17 | // Check for memory path in command line args or environment variable 18 | let memoryPath = argv['memory-path'] || process.env.MEMORY_FILE_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 path to the JSONL file 26 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 27 | // Use the custom path or default to the installation directory 28 | const MEMORY_FILE_PATH = memoryPath || path.join(__dirname, 'memory.jsonl'); 29 | 30 | // We are storing our memory using entities, relations, and observations in a graph structure 31 | interface Entity { 32 | name: string; 33 | entityType: string; 34 | observations: string[]; 35 | createdAt: string; 36 | version: number; 37 | } 38 | 39 | interface Relation { 40 | from: string; 41 | to: string; 42 | relationType: string; 43 | createdAt: string; 44 | version: number; 45 | } 46 | 47 | interface KnowledgeGraph { 48 | entities: Entity[]; 49 | relations: Relation[]; 50 | } 51 | 52 | // The KnowledgeGraphManager class contains all operations to interact with the knowledge graph 53 | class KnowledgeGraphManager { 54 | private async loadGraph(): Promise { 55 | try { 56 | const data = await fs.readFile(MEMORY_FILE_PATH, "utf-8"); 57 | const lines = data.split("\n").filter(line => line.trim() !== ""); 58 | return lines.reduce((graph: KnowledgeGraph, line) => { 59 | const item = JSON.parse(line); 60 | if (item.type === "entity") graph.entities.push(item as Entity); 61 | if (item.type === "relation") graph.relations.push(item as Relation); 62 | return graph; 63 | }, { entities: [], relations: [] }); 64 | } catch (error) { 65 | if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") { 66 | return { entities: [], relations: [] }; 67 | } 68 | throw error; 69 | } 70 | } 71 | 72 | private async saveGraph(graph: KnowledgeGraph): Promise { 73 | const lines = [ 74 | ...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })), 75 | ...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })), 76 | ]; 77 | await fs.writeFile(MEMORY_FILE_PATH, lines.join("\n")); 78 | } 79 | 80 | async createEntities(entities: Entity[]): Promise { 81 | const graph = await this.loadGraph(); 82 | const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name)) 83 | .map(e => ({ 84 | ...e, 85 | createdAt: new Date().toISOString(), 86 | version: e.version || 1 87 | })); 88 | graph.entities.push(...newEntities); 89 | await this.saveGraph(graph); 90 | return newEntities; 91 | } 92 | 93 | async createRelations(relations: Relation[]): Promise { 94 | const graph = await this.loadGraph(); 95 | const newRelations = relations.filter(r => !graph.relations.some(existingRelation => 96 | existingRelation.from === r.from && 97 | existingRelation.to === r.to && 98 | existingRelation.relationType === r.relationType 99 | )).map(r => ({ 100 | ...r, 101 | createdAt: new Date().toISOString(), 102 | version: r.version || 1 103 | })); 104 | graph.relations.push(...newRelations); 105 | await this.saveGraph(graph); 106 | return newRelations; 107 | } 108 | 109 | async addObservations(observations: { entityName: string; contents: string[] }[]): Promise<{ entityName: string; addedObservations: string[] }[]> { 110 | const graph = await this.loadGraph(); 111 | const results = observations.map(o => { 112 | const entity = graph.entities.find(e => e.name === o.entityName); 113 | if (!entity) { 114 | throw new Error(`Entity with name ${o.entityName} not found`); 115 | } 116 | const newObservations = o.contents.filter(content => !entity.observations.includes(content)); 117 | entity.observations.push(...newObservations); 118 | return { entityName: o.entityName, addedObservations: newObservations }; 119 | }); 120 | await this.saveGraph(graph); 121 | return results; 122 | } 123 | 124 | async deleteEntities(entityNames: string[]): Promise { 125 | const graph = await this.loadGraph(); 126 | graph.entities = graph.entities.filter(e => !entityNames.includes(e.name)); 127 | graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to)); 128 | await this.saveGraph(graph); 129 | } 130 | 131 | async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise { 132 | const graph = await this.loadGraph(); 133 | deletions.forEach(d => { 134 | const entity = graph.entities.find(e => e.name === d.entityName); 135 | if (entity) { 136 | entity.observations = entity.observations.filter(o => !d.observations.includes(o)); 137 | } 138 | }); 139 | await this.saveGraph(graph); 140 | } 141 | 142 | async deleteRelations(relations: Relation[]): Promise { 143 | const graph = await this.loadGraph(); 144 | graph.relations = graph.relations.filter(r => !relations.some(delRelation => 145 | r.from === delRelation.from && 146 | r.to === delRelation.to && 147 | r.relationType === delRelation.relationType 148 | )); 149 | await this.saveGraph(graph); 150 | } 151 | 152 | async readGraph(): Promise { 153 | return this.loadGraph(); 154 | } 155 | 156 | // Very basic search function 157 | async searchNodes(query: string): Promise { 158 | const graph = await this.loadGraph(); 159 | 160 | // Filter entities 161 | const filteredEntities = graph.entities.filter(e => 162 | e.name.toLowerCase().includes(query.toLowerCase()) || 163 | e.entityType.toLowerCase().includes(query.toLowerCase()) || 164 | e.observations.some(o => o.toLowerCase().includes(query.toLowerCase())) 165 | ); 166 | 167 | // Create a Set of filtered entity names for quick lookup 168 | const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); 169 | 170 | // Filter relations to only include those between filtered entities 171 | const filteredRelations = graph.relations.filter(r => 172 | filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to) 173 | ); 174 | 175 | const filteredGraph: KnowledgeGraph = { 176 | entities: filteredEntities, 177 | relations: filteredRelations, 178 | }; 179 | 180 | return filteredGraph; 181 | } 182 | 183 | async openNodes(names: string[]): Promise { 184 | const graph = await this.loadGraph(); 185 | 186 | // Filter entities 187 | const filteredEntities = graph.entities.filter(e => names.includes(e.name)); 188 | 189 | // Create a Set of filtered entity names for quick lookup 190 | const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); 191 | 192 | // Filter relations to only include those between filtered entities 193 | const filteredRelations = graph.relations.filter(r => 194 | filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to) 195 | ); 196 | 197 | const filteredGraph: KnowledgeGraph = { 198 | entities: filteredEntities, 199 | relations: filteredRelations, 200 | }; 201 | 202 | return filteredGraph; 203 | } 204 | 205 | async updateEntities(entities: Entity[]): Promise { 206 | const graph = await this.loadGraph(); 207 | const updatedEntities = entities.map(updateEntity => { 208 | const existingEntity = graph.entities.find(e => e.name === updateEntity.name); 209 | if (!existingEntity) { 210 | throw new Error(`Entity with name ${updateEntity.name} not found`); 211 | } 212 | return { 213 | ...existingEntity, 214 | ...updateEntity, 215 | version: existingEntity.version + 1, 216 | createdAt: new Date().toISOString() 217 | }; 218 | }); 219 | 220 | // Update entities in the graph 221 | updatedEntities.forEach(updatedEntity => { 222 | const index = graph.entities.findIndex(e => e.name === updatedEntity.name); 223 | if (index !== -1) { 224 | graph.entities[index] = updatedEntity; 225 | } 226 | }); 227 | 228 | await this.saveGraph(graph); 229 | return updatedEntities; 230 | } 231 | 232 | async updateRelations(relations: Relation[]): Promise { 233 | const graph = await this.loadGraph(); 234 | const updatedRelations = relations.map(updateRelation => { 235 | const existingRelation = graph.relations.find(r => 236 | r.from === updateRelation.from && 237 | r.to === updateRelation.to && 238 | r.relationType === updateRelation.relationType 239 | ); 240 | if (!existingRelation) { 241 | throw new Error(`Relation not found`); 242 | } 243 | return { 244 | ...existingRelation, 245 | ...updateRelation, 246 | version: existingRelation.version + 1, 247 | createdAt: new Date().toISOString() 248 | }; 249 | }); 250 | 251 | // Update relations in the graph 252 | updatedRelations.forEach(updatedRelation => { 253 | const index = graph.relations.findIndex(r => 254 | r.from === updatedRelation.from && 255 | r.to === updatedRelation.to && 256 | r.relationType === updatedRelation.relationType 257 | ); 258 | if (index !== -1) { 259 | graph.relations[index] = updatedRelation; 260 | } 261 | }); 262 | 263 | await this.saveGraph(graph); 264 | return updatedRelations; 265 | } 266 | } 267 | 268 | const knowledgeGraphManager = new KnowledgeGraphManager(); 269 | 270 | 271 | // The server instance and tools exposed to Claude 272 | const server = new Server({ 273 | name: "@itseasy21/mcp-knowledge-graph", 274 | version: "1.0.7", 275 | }, { 276 | capabilities: { 277 | tools: {}, 278 | }, 279 | },); 280 | 281 | server.setRequestHandler(ListToolsRequestSchema, async () => { 282 | return { 283 | tools: [ 284 | { 285 | name: "create_entities", 286 | description: "Create multiple new entities in the knowledge graph", 287 | inputSchema: { 288 | type: "object", 289 | properties: { 290 | entities: { 291 | type: "array", 292 | items: { 293 | type: "object", 294 | properties: { 295 | name: { type: "string", description: "The name of the entity" }, 296 | entityType: { type: "string", description: "The type of the entity" }, 297 | observations: { 298 | type: "array", 299 | items: { type: "string" }, 300 | description: "An array of observation contents associated with the entity" 301 | }, 302 | }, 303 | required: ["name", "entityType", "observations"], 304 | }, 305 | }, 306 | }, 307 | required: ["entities"], 308 | }, 309 | }, 310 | { 311 | name: "create_relations", 312 | description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice", 313 | inputSchema: { 314 | type: "object", 315 | properties: { 316 | relations: { 317 | type: "array", 318 | items: { 319 | type: "object", 320 | properties: { 321 | from: { type: "string", description: "The name of the entity where the relation starts" }, 322 | to: { type: "string", description: "The name of the entity where the relation ends" }, 323 | relationType: { type: "string", description: "The type of the relation" }, 324 | }, 325 | required: ["from", "to", "relationType"], 326 | }, 327 | }, 328 | }, 329 | required: ["relations"], 330 | }, 331 | }, 332 | { 333 | name: "add_observations", 334 | description: "Add new observations to existing entities in the knowledge graph", 335 | inputSchema: { 336 | type: "object", 337 | properties: { 338 | observations: { 339 | type: "array", 340 | items: { 341 | type: "object", 342 | properties: { 343 | entityName: { type: "string", description: "The name of the entity to add the observations to" }, 344 | contents: { 345 | type: "array", 346 | items: { type: "string" }, 347 | description: "An array of observation contents to add" 348 | }, 349 | }, 350 | required: ["entityName", "contents"], 351 | }, 352 | }, 353 | }, 354 | required: ["observations"], 355 | }, 356 | }, 357 | { 358 | name: "delete_entities", 359 | description: "Delete multiple entities and their associated relations from the knowledge graph", 360 | inputSchema: { 361 | type: "object", 362 | properties: { 363 | entityNames: { 364 | type: "array", 365 | items: { type: "string" }, 366 | description: "An array of entity names to delete" 367 | }, 368 | }, 369 | required: ["entityNames"], 370 | }, 371 | }, 372 | { 373 | name: "delete_observations", 374 | description: "Delete specific observations from entities in the knowledge graph", 375 | inputSchema: { 376 | type: "object", 377 | properties: { 378 | deletions: { 379 | type: "array", 380 | items: { 381 | type: "object", 382 | properties: { 383 | entityName: { type: "string", description: "The name of the entity containing the observations" }, 384 | observations: { 385 | type: "array", 386 | items: { type: "string" }, 387 | description: "An array of observations to delete" 388 | }, 389 | }, 390 | required: ["entityName", "observations"], 391 | }, 392 | }, 393 | }, 394 | required: ["deletions"], 395 | }, 396 | }, 397 | { 398 | name: "delete_relations", 399 | description: "Delete multiple relations from the knowledge graph", 400 | inputSchema: { 401 | type: "object", 402 | properties: { 403 | relations: { 404 | type: "array", 405 | items: { 406 | type: "object", 407 | properties: { 408 | from: { type: "string", description: "The name of the entity where the relation starts" }, 409 | to: { type: "string", description: "The name of the entity where the relation ends" }, 410 | relationType: { type: "string", description: "The type of the relation" }, 411 | }, 412 | required: ["from", "to", "relationType"], 413 | }, 414 | description: "An array of relations to delete" 415 | }, 416 | }, 417 | required: ["relations"], 418 | }, 419 | }, 420 | { 421 | name: "read_graph", 422 | description: "Read the entire knowledge graph", 423 | inputSchema: { 424 | type: "object", 425 | properties: {}, 426 | }, 427 | }, 428 | { 429 | name: "search_nodes", 430 | description: "Search for nodes in the knowledge graph based on a query", 431 | inputSchema: { 432 | type: "object", 433 | properties: { 434 | query: { type: "string", description: "The search query to match against entity names, types, and observation content" }, 435 | }, 436 | required: ["query"], 437 | }, 438 | }, 439 | { 440 | name: "open_nodes", 441 | description: "Open specific nodes in the knowledge graph by their names", 442 | inputSchema: { 443 | type: "object", 444 | properties: { 445 | names: { 446 | type: "array", 447 | items: { type: "string" }, 448 | description: "An array of entity names to retrieve", 449 | }, 450 | }, 451 | required: ["names"], 452 | }, 453 | }, 454 | { 455 | name: "update_entities", 456 | description: "Update multiple existing entities in the knowledge graph", 457 | inputSchema: { 458 | type: "object", 459 | properties: { 460 | entities: { 461 | type: "array", 462 | items: { 463 | type: "object", 464 | properties: { 465 | name: { type: "string", description: "The name of the entity to update" }, 466 | entityType: { type: "string", description: "The updated type of the entity" }, 467 | observations: { 468 | type: "array", 469 | items: { type: "string" }, 470 | description: "The updated array of observation contents" 471 | }, 472 | }, 473 | required: ["name"], 474 | }, 475 | }, 476 | }, 477 | required: ["entities"], 478 | }, 479 | }, 480 | { 481 | name: "update_relations", 482 | description: "Update multiple existing relations in the knowledge graph", 483 | inputSchema: { 484 | type: "object", 485 | properties: { 486 | relations: { 487 | type: "array", 488 | items: { 489 | type: "object", 490 | properties: { 491 | from: { type: "string", description: "The name of the entity where the relation starts" }, 492 | to: { type: "string", description: "The name of the entity where the relation ends" }, 493 | relationType: { type: "string", description: "The type of the relation" }, 494 | }, 495 | required: ["from", "to", "relationType"], 496 | }, 497 | }, 498 | }, 499 | required: ["relations"], 500 | }, 501 | }, 502 | ], 503 | }; 504 | }); 505 | 506 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 507 | const { name, arguments: args } = request.params; 508 | 509 | if (!args) { 510 | throw new Error(`No arguments provided for tool: ${name}`); 511 | } 512 | 513 | switch (name) { 514 | case "create_entities": 515 | return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createEntities(args.entities as Entity[]), null, 2) }] }; 516 | case "create_relations": 517 | return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createRelations(args.relations as Relation[]), null, 2) }] }; 518 | case "add_observations": 519 | return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.addObservations(args.observations as { entityName: string; contents: string[] }[]), null, 2) }] }; 520 | case "delete_entities": 521 | await knowledgeGraphManager.deleteEntities(args.entityNames as string[]); 522 | return { content: [{ type: "text", text: "Entities deleted successfully" }] }; 523 | case "delete_observations": 524 | await knowledgeGraphManager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[]); 525 | return { content: [{ type: "text", text: "Observations deleted successfully" }] }; 526 | case "delete_relations": 527 | await knowledgeGraphManager.deleteRelations(args.relations as Relation[]); 528 | return { content: [{ type: "text", text: "Relations deleted successfully" }] }; 529 | case "read_graph": 530 | return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.readGraph(), null, 2) }] }; 531 | case "search_nodes": 532 | return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.searchNodes(args.query as string), null, 2) }] }; 533 | case "open_nodes": 534 | return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.openNodes(args.names as string[]), null, 2) }] }; 535 | case "update_entities": 536 | return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.updateEntities(args.entities as Entity[]), null, 2) }] }; 537 | case "update_relations": 538 | return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.updateRelations(args.relations as Relation[]), null, 2) }] }; 539 | default: 540 | throw new Error(`Unknown tool: ${name}`); 541 | } 542 | }); 543 | 544 | async function main() { 545 | const transport = new StdioServerTransport(); 546 | await server.connect(transport); 547 | console.error("Knowledge Graph MCP Server running on stdio"); 548 | } 549 | 550 | main().catch((error) => { 551 | console.error("Fatal error in main():", error); 552 | process.exit(1); 553 | }); 554 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@itseasy21/mcp-knowledge-graph", 3 | "version": "1.0.7", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@itseasy21/mcp-knowledge-graph", 9 | "version": "1.0.7", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "1.0.1", 13 | "minimist": "^1.2.8" 14 | }, 15 | "bin": { 16 | "mcp-knowledge-graph": "dist/index.js" 17 | }, 18 | "devDependencies": { 19 | "@types/minimist": "^1.2.5", 20 | "@types/node": "^22.9.3", 21 | "shx": "^0.3.4", 22 | "typescript": "^5.6.2" 23 | } 24 | }, 25 | "node_modules/@modelcontextprotocol/sdk": { 26 | "version": "1.0.1", 27 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.0.1.tgz", 28 | "integrity": "sha512-slLdFaxQJ9AlRg+hw28iiTtGvShAOgOKXcD0F91nUcRYiOMuS9ZBYjcdNZRXW9G5JQ511GRTdUy1zQVZDpJ+4w==", 29 | "license": "MIT", 30 | "dependencies": { 31 | "content-type": "^1.0.5", 32 | "raw-body": "^3.0.0", 33 | "zod": "^3.23.8" 34 | } 35 | }, 36 | "node_modules/@types/minimist": { 37 | "version": "1.2.5", 38 | "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", 39 | "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", 40 | "dev": true, 41 | "license": "MIT" 42 | }, 43 | "node_modules/@types/node": { 44 | "version": "22.10.1", 45 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", 46 | "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", 47 | "dev": true, 48 | "license": "MIT", 49 | "dependencies": { 50 | "undici-types": "~6.20.0" 51 | } 52 | }, 53 | "node_modules/balanced-match": { 54 | "version": "1.0.2", 55 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 56 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 57 | "dev": true, 58 | "license": "MIT" 59 | }, 60 | "node_modules/brace-expansion": { 61 | "version": "1.1.11", 62 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 63 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 64 | "dev": true, 65 | "license": "MIT", 66 | "dependencies": { 67 | "balanced-match": "^1.0.0", 68 | "concat-map": "0.0.1" 69 | } 70 | }, 71 | "node_modules/bytes": { 72 | "version": "3.1.2", 73 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 74 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 75 | "license": "MIT", 76 | "engines": { 77 | "node": ">= 0.8" 78 | } 79 | }, 80 | "node_modules/concat-map": { 81 | "version": "0.0.1", 82 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 83 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 84 | "dev": true, 85 | "license": "MIT" 86 | }, 87 | "node_modules/content-type": { 88 | "version": "1.0.5", 89 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 90 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 91 | "license": "MIT", 92 | "engines": { 93 | "node": ">= 0.6" 94 | } 95 | }, 96 | "node_modules/depd": { 97 | "version": "2.0.0", 98 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 99 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 100 | "license": "MIT", 101 | "engines": { 102 | "node": ">= 0.8" 103 | } 104 | }, 105 | "node_modules/fs.realpath": { 106 | "version": "1.0.0", 107 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 108 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 109 | "dev": true, 110 | "license": "ISC" 111 | }, 112 | "node_modules/function-bind": { 113 | "version": "1.1.2", 114 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 115 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 116 | "dev": true, 117 | "license": "MIT", 118 | "funding": { 119 | "url": "https://github.com/sponsors/ljharb" 120 | } 121 | }, 122 | "node_modules/glob": { 123 | "version": "7.2.3", 124 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 125 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 126 | "deprecated": "Glob versions prior to v9 are no longer supported", 127 | "dev": true, 128 | "license": "ISC", 129 | "dependencies": { 130 | "fs.realpath": "^1.0.0", 131 | "inflight": "^1.0.4", 132 | "inherits": "2", 133 | "minimatch": "^3.1.1", 134 | "once": "^1.3.0", 135 | "path-is-absolute": "^1.0.0" 136 | }, 137 | "engines": { 138 | "node": "*" 139 | }, 140 | "funding": { 141 | "url": "https://github.com/sponsors/isaacs" 142 | } 143 | }, 144 | "node_modules/hasown": { 145 | "version": "2.0.2", 146 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 147 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 148 | "dev": true, 149 | "license": "MIT", 150 | "dependencies": { 151 | "function-bind": "^1.1.2" 152 | }, 153 | "engines": { 154 | "node": ">= 0.4" 155 | } 156 | }, 157 | "node_modules/http-errors": { 158 | "version": "2.0.0", 159 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 160 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 161 | "license": "MIT", 162 | "dependencies": { 163 | "depd": "2.0.0", 164 | "inherits": "2.0.4", 165 | "setprototypeof": "1.2.0", 166 | "statuses": "2.0.1", 167 | "toidentifier": "1.0.1" 168 | }, 169 | "engines": { 170 | "node": ">= 0.8" 171 | } 172 | }, 173 | "node_modules/iconv-lite": { 174 | "version": "0.6.3", 175 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 176 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 177 | "license": "MIT", 178 | "dependencies": { 179 | "safer-buffer": ">= 2.1.2 < 3.0.0" 180 | }, 181 | "engines": { 182 | "node": ">=0.10.0" 183 | } 184 | }, 185 | "node_modules/inflight": { 186 | "version": "1.0.6", 187 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 188 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 189 | "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", 190 | "dev": true, 191 | "license": "ISC", 192 | "dependencies": { 193 | "once": "^1.3.0", 194 | "wrappy": "1" 195 | } 196 | }, 197 | "node_modules/inherits": { 198 | "version": "2.0.4", 199 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 200 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 201 | "license": "ISC" 202 | }, 203 | "node_modules/interpret": { 204 | "version": "1.4.0", 205 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", 206 | "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", 207 | "dev": true, 208 | "license": "MIT", 209 | "engines": { 210 | "node": ">= 0.10" 211 | } 212 | }, 213 | "node_modules/is-core-module": { 214 | "version": "2.15.1", 215 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", 216 | "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", 217 | "dev": true, 218 | "license": "MIT", 219 | "dependencies": { 220 | "hasown": "^2.0.2" 221 | }, 222 | "engines": { 223 | "node": ">= 0.4" 224 | }, 225 | "funding": { 226 | "url": "https://github.com/sponsors/ljharb" 227 | } 228 | }, 229 | "node_modules/minimatch": { 230 | "version": "3.1.2", 231 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 232 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 233 | "dev": true, 234 | "license": "ISC", 235 | "dependencies": { 236 | "brace-expansion": "^1.1.7" 237 | }, 238 | "engines": { 239 | "node": "*" 240 | } 241 | }, 242 | "node_modules/minimist": { 243 | "version": "1.2.8", 244 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 245 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 246 | "license": "MIT", 247 | "funding": { 248 | "url": "https://github.com/sponsors/ljharb" 249 | } 250 | }, 251 | "node_modules/once": { 252 | "version": "1.4.0", 253 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 254 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 255 | "dev": true, 256 | "license": "ISC", 257 | "dependencies": { 258 | "wrappy": "1" 259 | } 260 | }, 261 | "node_modules/path-is-absolute": { 262 | "version": "1.0.1", 263 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 264 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 265 | "dev": true, 266 | "license": "MIT", 267 | "engines": { 268 | "node": ">=0.10.0" 269 | } 270 | }, 271 | "node_modules/path-parse": { 272 | "version": "1.0.7", 273 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 274 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 275 | "dev": true, 276 | "license": "MIT" 277 | }, 278 | "node_modules/raw-body": { 279 | "version": "3.0.0", 280 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 281 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 282 | "license": "MIT", 283 | "dependencies": { 284 | "bytes": "3.1.2", 285 | "http-errors": "2.0.0", 286 | "iconv-lite": "0.6.3", 287 | "unpipe": "1.0.0" 288 | }, 289 | "engines": { 290 | "node": ">= 0.8" 291 | } 292 | }, 293 | "node_modules/rechoir": { 294 | "version": "0.6.2", 295 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 296 | "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", 297 | "dev": true, 298 | "dependencies": { 299 | "resolve": "^1.1.6" 300 | }, 301 | "engines": { 302 | "node": ">= 0.10" 303 | } 304 | }, 305 | "node_modules/resolve": { 306 | "version": "1.22.8", 307 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", 308 | "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", 309 | "dev": true, 310 | "license": "MIT", 311 | "dependencies": { 312 | "is-core-module": "^2.13.0", 313 | "path-parse": "^1.0.7", 314 | "supports-preserve-symlinks-flag": "^1.0.0" 315 | }, 316 | "bin": { 317 | "resolve": "bin/resolve" 318 | }, 319 | "funding": { 320 | "url": "https://github.com/sponsors/ljharb" 321 | } 322 | }, 323 | "node_modules/safer-buffer": { 324 | "version": "2.1.2", 325 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 326 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 327 | "license": "MIT" 328 | }, 329 | "node_modules/setprototypeof": { 330 | "version": "1.2.0", 331 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 332 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 333 | "license": "ISC" 334 | }, 335 | "node_modules/shelljs": { 336 | "version": "0.8.5", 337 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", 338 | "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", 339 | "dev": true, 340 | "license": "BSD-3-Clause", 341 | "dependencies": { 342 | "glob": "^7.0.0", 343 | "interpret": "^1.0.0", 344 | "rechoir": "^0.6.2" 345 | }, 346 | "bin": { 347 | "shjs": "bin/shjs" 348 | }, 349 | "engines": { 350 | "node": ">=4" 351 | } 352 | }, 353 | "node_modules/shx": { 354 | "version": "0.3.4", 355 | "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", 356 | "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", 357 | "dev": true, 358 | "license": "MIT", 359 | "dependencies": { 360 | "minimist": "^1.2.3", 361 | "shelljs": "^0.8.5" 362 | }, 363 | "bin": { 364 | "shx": "lib/cli.js" 365 | }, 366 | "engines": { 367 | "node": ">=6" 368 | } 369 | }, 370 | "node_modules/statuses": { 371 | "version": "2.0.1", 372 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 373 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 374 | "license": "MIT", 375 | "engines": { 376 | "node": ">= 0.8" 377 | } 378 | }, 379 | "node_modules/supports-preserve-symlinks-flag": { 380 | "version": "1.0.0", 381 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 382 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 383 | "dev": true, 384 | "license": "MIT", 385 | "engines": { 386 | "node": ">= 0.4" 387 | }, 388 | "funding": { 389 | "url": "https://github.com/sponsors/ljharb" 390 | } 391 | }, 392 | "node_modules/toidentifier": { 393 | "version": "1.0.1", 394 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 395 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 396 | "license": "MIT", 397 | "engines": { 398 | "node": ">=0.6" 399 | } 400 | }, 401 | "node_modules/typescript": { 402 | "version": "5.7.2", 403 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 404 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 405 | "dev": true, 406 | "license": "Apache-2.0", 407 | "bin": { 408 | "tsc": "bin/tsc", 409 | "tsserver": "bin/tsserver" 410 | }, 411 | "engines": { 412 | "node": ">=14.17" 413 | } 414 | }, 415 | "node_modules/undici-types": { 416 | "version": "6.20.0", 417 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", 418 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", 419 | "dev": true, 420 | "license": "MIT" 421 | }, 422 | "node_modules/unpipe": { 423 | "version": "1.0.0", 424 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 425 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 426 | "license": "MIT", 427 | "engines": { 428 | "node": ">= 0.8" 429 | } 430 | }, 431 | "node_modules/wrappy": { 432 | "version": "1.0.2", 433 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 434 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 435 | "dev": true, 436 | "license": "ISC" 437 | }, 438 | "node_modules/zod": { 439 | "version": "3.23.8", 440 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", 441 | "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", 442 | "license": "MIT", 443 | "funding": { 444 | "url": "https://github.com/sponsors/colinhacks" 445 | } 446 | } 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@itseasy21/mcp-knowledge-graph", 3 | "version": "1.0.7", 4 | "description": "MCP server enabling persistent memory for Claude through a local knowledge graph", 5 | "license": "MIT", 6 | "author": "itseasy21", 7 | "homepage": "https://github.com/itseasy21/mcp-knowledge-graph", 8 | "bugs": "https://github.com/itseasy21/mcp-knowledge-graph/issues", 9 | "type": "module", 10 | "bin": "./dist/index.js", 11 | "main": "dist/index.js", 12 | "files": [ 13 | "dist" 14 | ], 15 | "scripts": { 16 | "build": "tsc && shx chmod +x dist/*.js", 17 | "prepare": "npm run build", 18 | "watch": "tsc --watch", 19 | "start": "node dist/index.js" 20 | }, 21 | "dependencies": { 22 | "@modelcontextprotocol/sdk": "1.0.1", 23 | "minimist": "^1.2.8" 24 | }, 25 | "devDependencies": { 26 | "@types/minimist": "^1.2.5", 27 | "@types/node": "^22.9.3", 28 | "shx": "^0.3.4", 29 | "typescript": "^5.6.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pr-instructions.md: -------------------------------------------------------------------------------- 1 | # MCP Memory Server Pull Request Instructions 2 | 3 | ## Current Working Setup Preservation 4 | 5 | Keep your local working version intact at `C:\Users\shane\Desktop\memory` until PR is merged: 6 | 7 | ```json 8 | { 9 | "mcpServers": { 10 | "memory": { 11 | "command": "volta", 12 | "args": [ 13 | "run", 14 | "node", 15 | "C:\\Users\\shane\\Desktop\\memory\\dist\\index.js", 16 | "--memory-path", 17 | "C:\\Users\\shane\\Desktop\\memory\\memory.jsonl" 18 | ] 19 | } 20 | } 21 | } 22 | ``` 23 | 24 | ## Getting the Original Repository 25 | 26 | ```bash 27 | git clone https://github.com/modelcontextprotocol/servers.git 28 | cd servers 29 | ``` 30 | 31 | ## Modified Files to Track 32 | 33 | Current local modifications: 34 | 35 | - `tsconfig.json` - Changed from monorepo to local paths 36 | - `tsconfig.base.json` - Created locally for standalone build 37 | - `index.ts` - Added memory path functionality 38 | 39 | ## Preparing the Pull Request 40 | 41 | ### 1. Configuration Files 42 | 43 | Revert tsconfig.json back to monorepo structure: 44 | 45 | ```json 46 | { 47 | "extends": "../../tsconfig.json", 48 | "compilerOptions": { 49 | "outDir": "./dist", 50 | "rootDir": "." 51 | }, 52 | "include": [ 53 | "./**/*.ts" 54 | ] 55 | } 56 | ``` 57 | 58 | ### 2. Remove Local-Only Files 59 | 60 | - Delete local `tsconfig.base.json` (not needed in monorepo) 61 | 62 | ### 3. Code Changes to Submit 63 | 64 | - Keep all memory path functionality changes in `index.ts` 65 | - Ensure cross-platform path handling remains intact 66 | - Verify JSONL extension usage 67 | 68 | ### 4. Dependencies 69 | 70 | Ensure these are in the monorepo's package.json: 71 | 72 | ```json 73 | { 74 | "dependencies": { 75 | "minimist": "^1.2.8" 76 | }, 77 | "devDependencies": { 78 | "@types/minimist": "^1.2.5" 79 | } 80 | } 81 | ``` 82 | 83 | ### 5. Documentation Updates 84 | 85 | - Update README.md with new --memory-path option 86 | - Document JSONL format requirement 87 | - Add cross-platform path handling notes 88 | 89 | ### 6. Pull Request Process 90 | 91 | 1. Create new branch: 92 | 93 | ```bash 94 | git checkout -b feature/custom-memory-path 95 | ``` 96 | 97 | 2. Copy modified files: 98 | 99 | ```bash 100 | cp /path/to/your/index.ts packages/server-memory/ 101 | ``` 102 | 103 | 3. Test build in monorepo: 104 | 105 | ```bash 106 | npm install 107 | npm run build 108 | ``` 109 | 110 | 4. Commit changes: 111 | 112 | ```bash 113 | git add . 114 | git commit -m "Add custom memory path support with cross-platform handling" 115 | ``` 116 | 117 | 5. Create PR: 118 | - Push to GitHub 119 | - Create pull request 120 | - Reference any related issues 121 | - Describe testing performed 122 | 123 | ## Additional Considerations 124 | 125 | - Consider adding tests for the new functionality 126 | - Follow monorepo's contribution guidelines 127 | - Document any breaking changes 128 | - Test on multiple platforms if possible 129 | 130 | ## Backup Plan 131 | 132 | Until PR is merged, maintain your working local version: 133 | 134 | 1. Keep local build working 135 | 2. Note any improvements needed for PR 136 | 3. Continue using local version for development 137 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | properties: {} 9 | commandFunction: 10 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio. 11 | |- 12 | (config) => ({ command: 'node', args: ['dist/index.js'] }) 13 | exampleConfig: {} 14 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "declaration": true, 11 | "sourceMap": true, 12 | "allowJs": true, 13 | "checkJs": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "." 6 | }, 7 | "include": [ 8 | "./**/*.ts" 9 | ] 10 | } 11 | --------------------------------------------------------------------------------