├── .gitignore ├── .vscode └── launch.json ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── memory-mcp-server │ ├── main.swift │ └── tools.swift ├── install.sh └── screenshots ├── chatwise.webp └── cursor.webp /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | 10 | # ai assistant 11 | .rules 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "lldb", 5 | "request": "launch", 6 | "args": [], 7 | "cwd": "${workspaceFolder:memory-mcp-server}", 8 | "name": "Debug memory-mcp-server", 9 | "program": "${workspaceFolder:memory-mcp-server}/.build/debug/memory-mcp-server", 10 | "preLaunchTask": "swift: Build Debug memory-mcp-server" 11 | }, 12 | { 13 | "type": "lldb", 14 | "request": "launch", 15 | "args": [], 16 | "cwd": "${workspaceFolder:memory-mcp-server}", 17 | "name": "Release memory-mcp-server", 18 | "program": "${workspaceFolder:memory-mcp-server}/.build/release/memory-mcp-server", 19 | "preLaunchTask": "swift: Build Release memory-mcp-server" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "1f1ff679710d135d0235be4aa33dcb4302aa87c1bc413f7af331a3cc42e86a73", 3 | "pins" : [ 4 | { 5 | "identity" : "jsonrpc", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/gsabran/JSONRPC", 8 | "state" : { 9 | "revision" : "56c936de4e4385287dc18f260557dbdf443a55bd", 10 | "version" : "0.9.1" 11 | } 12 | }, 13 | { 14 | "identity" : "mcp-swift-sdk", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/gsabran/mcp-swift-sdk", 17 | "state" : { 18 | "revision" : "5399cccd2c5b481e641cbb3ccdba12bf5bd1d2d5", 19 | "version" : "0.2.3" 20 | } 21 | }, 22 | { 23 | "identity" : "swift-json-schema", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/ajevans99/swift-json-schema", 26 | "state" : { 27 | "revision" : "707fc4c2d53138e8fbb4ee9e6669b050ba4b2239", 28 | "version" : "0.3.2" 29 | } 30 | }, 31 | { 32 | "identity" : "swift-syntax", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/swiftlang/swift-syntax", 35 | "state" : { 36 | "revision" : "0687f71944021d616d34d922343dcef086855920", 37 | "version" : "600.0.1" 38 | } 39 | } 40 | ], 41 | "version" : 3 42 | } 43 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "memory-mcp-server", 8 | platforms: [ 9 | .macOS(.v14) 10 | ], 11 | dependencies: [ 12 | .package(url: "https://github.com/gsabran/mcp-swift-sdk", from: "0.2.0") 13 | ], 14 | targets: [ 15 | // Targets are the basic building blocks of a package, defining a module or a test suite. 16 | // Targets can depend on other targets in this package and products from dependencies. 17 | .executableTarget( 18 | name: "memory-mcp-server", 19 | dependencies: [ 20 | .product(name: "MCPServer", package: "mcp-swift-sdk") 21 | ] 22 | ) 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Memory MCP Server 2 | 3 | > A Model Context Protocol server that provides knowledge graph management capabilities. This server enables LLMs to create, read, update, and delete entities and relations in a persistent knowledge graph, helping AI assistants maintain memory across conversations. This is a Swift implementation of the official [TypeScript Memory MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/memory). 4 | 5 | **Note:** This Swift version requires **macOS 14.0 or later**. For broader platform support (including Windows, Linux, and macOS 11.0+), check out the Go language implementation: **[memory-mcp-server-go](https://github.com/okooo5km/memory-mcp-server-go)**. 6 | 7 | ![Swift Platform](https://img.shields.io/badge/platform-macOS-lightgrey) 8 | ![License](https://img.shields.io/badge/license-MIT-blue) 9 | 10 | ## ✨ Features 11 | 12 | * **Knowledge Graph Storage**: Maintain a persistent graph of entities and their relationships 13 | * **Entity Management**: Create, retrieve, update, and delete entities with custom types 14 | * **Relation Tracking**: Define and manage relationships between entities in active voice 15 | * **Observation System**: Add and remove observations about entities over time 16 | * **Powerful Search**: Find relevant nodes by name, type, or observation content 17 | * **Persistent Storage**: Data persists between sessions in a simple JSON format 18 | 19 | ## Available Tools 20 | 21 | * `create_entities` - Create multiple new entities in the knowledge graph 22 | * `entities` (array, required): Array of entity objects to create 23 | * `name` (string): The name of the entity 24 | * `entityType` (string): The type of the entity 25 | * `observations` (array of strings): Observations associated with the entity 26 | 27 | * `create_relations` - Create multiple new relations between entities 28 | * `relations` (array, required): Array of relation objects 29 | * `from` (string): The name of the entity where the relation starts 30 | * `to` (string): The name of the entity where the relation ends 31 | * `relationType` (string): The type of the relation (in active voice) 32 | 33 | * `add_observations` - Add new observations to existing entities 34 | * `observations` (array, required): Array of observation additions 35 | * `entityName` (string): The name of the entity to add observations to 36 | * `contents` (array of strings): The observations to add 37 | 38 | * `delete_entities` - Delete multiple entities and their associated relations 39 | * `entityNames` (array, required): Array of entity names to delete 40 | 41 | * `delete_observations` - Delete specific observations from entities 42 | * `deletions` (array, required): Array of observation deletions 43 | * `entityName` (string): The name of the entity containing the observations 44 | * `observations` (array of strings): The observations to delete 45 | 46 | * `delete_relations` - Delete multiple relations from the knowledge graph 47 | * `relations` (array, required): Array of relation objects to delete 48 | * `from` (string): The source entity name 49 | * `to` (string): The target entity name 50 | * `relationType` (string): The relation type 51 | 52 | * `read_graph` - Read the entire knowledge graph 53 | * No parameters required 54 | 55 | * `search_nodes` - Search for nodes in the knowledge graph based on a query 56 | * `query` (string, required): Search query to match against entity names, types, and observations 57 | 58 | * `open_nodes` - Open specific nodes in the knowledge graph by their names 59 | * `names` (array, required): Array of entity names to retrieve 60 | 61 | ## Installation 62 | 63 | ### Option 1: One-Line Installation (curl) 64 | 65 | The easiest way to install is with the one-line installer, which automatically downloads the latest version and installs it to `~/.local/bin` in your home directory: 66 | 67 | ```bash 68 | curl -fsSL https://raw.githubusercontent.com/okooo5km/memory-mcp-server/main/install.sh | bash 69 | ``` 70 | 71 | The installer will: 72 | 73 | * Create `~/.local/bin` if it doesn't exist 74 | * Add this directory to your PATH (in .zshrc or .bashrc) 75 | * Download and install the latest version 76 | * Make the binary executable 77 | 78 | ### Option 2: Build from Source 79 | 80 | 1. Clone the repository: 81 | 82 | ```bash 83 | git clone https://github.com/okooo5km/memory-mcp-server.git 84 | cd memory-mcp-server 85 | ``` 86 | 87 | 2. Build the project: 88 | 89 | ```bash 90 | swift build -c release 91 | ``` 92 | 93 | 3. Install the binary: 94 | 95 | ```bash 96 | # Install to user directory (recommended, no sudo required) 97 | mkdir -p ~/.local/bin 98 | cp $(swift build -c release --show-bin-path)/memory-mcp-server ~/.local/bin/ 99 | ``` 100 | 101 | Make sure `~/.local/bin` is in your PATH by adding to your shell configuration file: 102 | 103 | ```bash 104 | echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc # or ~/.bashrc 105 | source ~/.zshrc # or source ~/.bashrc 106 | ``` 107 | 108 | ## Command Line Arguments 109 | 110 | The server supports the following command line arguments: 111 | 112 | * `-h, --help`: Display help information about the server, its usage, and available options 113 | * `-v, --version`: Display the version number of the memory-mcp-server 114 | 115 | Example usage: 116 | 117 | ```bash 118 | # Display help information 119 | memory-mcp-server --help 120 | 121 | # Display version information 122 | memory-mcp-server --version 123 | ``` 124 | 125 | ## Configuration 126 | 127 | ### Environment Variables 128 | 129 | The server supports the following environment variables: 130 | 131 | * `MEMORY_FILE_PATH`: Custom path for storing the knowledge graph (optional) 132 | * If not specified, defaults to `memory.json` in the current working directory 133 | * Can be an absolute path or relative to the current working directory 134 | 135 | You can check the configured file path in the server logs when starting the server. 136 | 137 | ```bash 138 | export MEMORY_FILE_PATH="/path/to/your/memory.json" 139 | ``` 140 | 141 | ### Configure for Claude.app 142 | 143 | Add to your Claude settings: 144 | 145 | ```json 146 | "mcpServers": { 147 | "memory": { 148 | "command": "memory-mcp-server", 149 | "env": { 150 | "MEMORY_FILE_PATH": "/path/to/your/memory.json" 151 | } 152 | } 153 | } 154 | ``` 155 | 156 | ### Configure for Cursor 157 | 158 | Add the following configuration to your Cursor editor's Settings - mcp.json: 159 | 160 | ```json 161 | { 162 | "mcpServers": { 163 | "memory": { 164 | "command": "memory-mcp-server", 165 | "env": { 166 | "MEMORY_FILE_PATH": "/path/to/your/memory.json" 167 | } 168 | } 169 | } 170 | } 171 | ``` 172 | 173 | ![cursor-settings](screenshots/cursor.webp) 174 | 175 | ### Configure for Chatwise 176 | 177 | Add the memory MCP server to your Chatwise Settings - Tools. 178 | 179 | ![chatwise-settings-tools](screenshots/chatwise.webp) 180 | 181 | ### Example System Prompt 182 | 183 | You can use the following system prompt to help Claude utilize the memory-mcp-server effectively: 184 | 185 | ``` 186 | You have access to a Knowledge Graph memory system, which can store and retrieve information across conversations. Use it to remember important details about the user, their preferences, and any facts they've shared. 187 | 188 | When you discover important information, save it using memory tools: 189 | - `create_entities` to add new people, places, or concepts 190 | - `create_relations` to record how entities relate to each other 191 | - `add_observations` to record facts about existing entities 192 | 193 | Before answering questions that might require past context, check your memory: 194 | - `search_nodes` to find relevant information 195 | - `open_nodes` to retrieve specific entities 196 | - `read_graph` to get a complete view of your knowledge 197 | 198 | Always prioritize information from your memory when responding to the user, especially when they reference past conversations. 199 | ``` 200 | 201 | ## Development Requirements 202 | 203 | * Swift 6.0 or later 204 | * macOS 14.0 or later 205 | * MCP Swift SDK 0.2.0 or later 206 | 207 | ## Knowledge Graph Structure 208 | 209 | The Memory MCP Server uses a simple graph structure to store knowledge: 210 | 211 | * **Entities**: Nodes in the graph with a name, type, and list of observations 212 | * **Relations**: Edges between entities with a relation type in active voice 213 | * **Observations**: Facts or details associated with entities 214 | 215 | The knowledge graph is persisted to disk as a line-delimited JSON file, where each line is either an entity or relation object. 216 | 217 | ## Usage Examples 218 | 219 | ### Creating Entities 220 | 221 | ```json 222 | { 223 | "entities": [ 224 | { 225 | "name": "John Smith", 226 | "entityType": "Person", 227 | "observations": ["Software engineer", "Lives in San Francisco", "Enjoys hiking"] 228 | }, 229 | { 230 | "name": "Acme Corp", 231 | "entityType": "Company", 232 | "observations": ["Founded in 2010", "Tech startup"] 233 | } 234 | ] 235 | } 236 | ``` 237 | 238 | ### Creating Relations 239 | 240 | ```json 241 | { 242 | "relations": [ 243 | { 244 | "from": "John Smith", 245 | "to": "Acme Corp", 246 | "relationType": "works at" 247 | } 248 | ] 249 | } 250 | ``` 251 | 252 | ### Adding Observations 253 | 254 | ```json 255 | { 256 | "observations": [ 257 | { 258 | "entityName": "John Smith", 259 | "contents": ["Recently promoted to Senior Engineer", "Working on AI projects"] 260 | } 261 | ] 262 | } 263 | ``` 264 | 265 | ### Searching Nodes 266 | 267 | ```json 268 | { 269 | "query": "San Francisco" 270 | } 271 | ``` 272 | 273 | ### Opening Specific Nodes 274 | 275 | ```json 276 | { 277 | "names": ["John Smith", "Acme Corp"] 278 | } 279 | ``` 280 | 281 | ## Use Cases 282 | 283 | * **Long-term Memory for AI Assistants**: Enable AI assistants to remember user preferences, past interactions, and important facts 284 | * **Knowledge Management**: Organize information about people, places, events, and concepts 285 | * **Relationship Tracking**: Maintain networks of relationships between entities 286 | * **Context Persistence**: Preserve important context across multiple sessions 287 | * **Journal and Daily Logs**: Maintain a structured record of events, activities, and reflections over time, making it easy to retrieve and relate past experiences chronologically 288 | 289 | ## Version History 290 | 291 | See GitHub Releases for version history and changelog. 292 | 293 | ## ☕️ Support the Project 294 | 295 | If you find Memory MCP Server helpful, please consider supporting its development: 296 | 297 | * ⭐️ Star the project on GitHub 298 | * 🐛 Report bugs or suggest features 299 | * 💝 Support via: 300 | 301 |

302 | 303 | 304 | 305 |

306 | 307 | ## License 308 | 309 | memory-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. 310 | 311 | ## About 312 | 313 | A Swift implementation of a knowledge graph memory server for Model Context Protocol (MCP), enabling persistent memory capabilities for large language models. This project is based on the [official TypeScript implementation](https://github.com/modelcontextprotocol/servers/tree/main/src/memory) but rewritten in Swift using the MCP Swift SDK. 314 | -------------------------------------------------------------------------------- /Sources/memory-mcp-server/main.swift: -------------------------------------------------------------------------------- 1 | // The Swift Programming Language 2 | // https://docs.swift.org/swift-book 3 | 4 | import Foundation 5 | import MCPServer 6 | import OSLog 7 | 8 | // Define version information 9 | let APP_VERSION = "0.1.1" 10 | let APP_NAME = "memory-mcp-server" 11 | 12 | let mcpLogger = Logger( 13 | subsystem: Bundle.main.bundleIdentifier.map { "\($0).mcp" } ?? "tech.5km.memory.mcp-server", category: "mcp") 14 | 15 | // Parse command line arguments 16 | func processCommandLineArguments() -> Bool { 17 | let arguments = CommandLine.arguments 18 | 19 | if arguments.contains("--version") || arguments.contains("-v") { 20 | print("\(APP_NAME) version \(APP_VERSION)") 21 | return false 22 | } 23 | 24 | if arguments.contains("--help") || arguments.contains("-h") { 25 | printHelp() 26 | return false 27 | } 28 | 29 | return true 30 | } 31 | 32 | // Display help information 33 | func printHelp() { 34 | print( 35 | """ 36 | \(APP_NAME) - A knowledge graph memory server for MCP 37 | 38 | USAGE: 39 | \(APP_NAME) [OPTIONS] 40 | 41 | OPTIONS: 42 | -h, --help Show this help message and exit 43 | -v, --version Show version information and exit 44 | 45 | ENVIRONMENT VARIABLES: 46 | MEMORY_FILE_PATH Path to the memory storage JSON file (default: memory.json in the current directory) 47 | 48 | DESCRIPTION: 49 | This MCP server provides knowledge graph management capabilities, 50 | enabling LLMs to create, read, update, and delete entities and relations 51 | in a persistent knowledge graph, helping AI assistants maintain memory across conversations. 52 | """) 53 | } 54 | 55 | // Only run the server if no special command line arguments are provided 56 | if processCommandLineArguments() { 57 | let transport = Transport.stdio() 58 | func proxy(_ transport: Transport) -> Transport { 59 | var sendToDataSequence: AsyncStream.Continuation? 60 | let dataSequence = AsyncStream.init { continuation in 61 | sendToDataSequence = continuation 62 | } 63 | 64 | Task { 65 | for await data in transport.dataSequence { 66 | mcpLogger.info("Reading data from transport: \(String(data: data, encoding: .utf8)!, privacy: .public)") 67 | sendToDataSequence?.yield(data) 68 | } 69 | } 70 | 71 | return Transport( 72 | writeHandler: { data in 73 | mcpLogger.info("Writing data to transport: \(String(data: data, encoding: .utf8)!, privacy: .public)") 74 | try await transport.writeHandler(data) 75 | }, 76 | dataSequence: dataSequence) 77 | } 78 | 79 | // Register all tools 80 | let memoryTools: [any CallableTool] = [ 81 | createEntitiesTool, 82 | createRelationsTool, 83 | addObservationsTool, 84 | deleteEntitiesTool, 85 | deleteObservationsTool, 86 | deleteRelationsTool, 87 | readGraphTool, 88 | searchNodesTool, 89 | openNodesTool, 90 | ] 91 | 92 | do { 93 | let server = try await MCPServer( 94 | info: Implementation(name: APP_NAME, version: APP_VERSION), 95 | capabilities: ServerCapabilityHandlers(tools: memoryTools), 96 | transport: proxy(transport)) 97 | 98 | print("\(APP_NAME) v\(APP_VERSION) started successfully") 99 | print("Knowledge Graph MCP Server running on stdio") 100 | 101 | try await server.waitForDisconnection() 102 | } catch { 103 | print("Error starting server: \(error)") 104 | exit(1) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Sources/memory-mcp-server/tools.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import Foundation 3 | import JSONSchemaBuilder 4 | @preconcurrency import MCPServer 5 | 6 | /// Error type for tool operations 7 | struct ToolError: Error { 8 | let message: String 9 | } 10 | 11 | // MARK: - Memory Knowledge Graph Tools 12 | 13 | /// Defines the memory file path using environment variable or default location 14 | let memoryFilePath: String = { 15 | let currentDir = FileManager.default.currentDirectoryPath 16 | mcpLogger.info("Current working directory: \(currentDir, privacy: .public)") 17 | 18 | if let envPath = ProcessInfo.processInfo.environment["MEMORY_FILE_PATH"] { 19 | mcpLogger.info("Using memory file path from environment variable: \(envPath, privacy: .public)") 20 | return URL(fileURLWithPath: envPath).isFileURL && envPath.hasPrefix("/") 21 | ? envPath 22 | : URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent(envPath).path 23 | } else { 24 | let defaultPath = URL( 25 | fileURLWithPath: FileManager.default.currentDirectoryPath 26 | ).appendingPathComponent("memory.json").path 27 | mcpLogger.info( 28 | "Environment variable MEMORY_FILE_PATH not set, using default path: \(defaultPath, privacy: .public)") 29 | return defaultPath 30 | } 31 | }() 32 | 33 | /// Represents an entity in the knowledge graph 34 | @Schemable 35 | struct Entity: Codable, Sendable { 36 | @SchemaOptions( 37 | description: "The name of the entity" 38 | ) 39 | let name: String 40 | 41 | @SchemaOptions( 42 | description: "The type of the entity" 43 | ) 44 | let entityType: String 45 | 46 | @SchemaOptions( 47 | description: "An array of observation contents associated with the entity" 48 | ) 49 | var observations: [String] 50 | } 51 | 52 | /// Represents a relation between entities in the knowledge graph 53 | @Schemable 54 | struct Relation: Codable, Sendable { 55 | @SchemaOptions( 56 | description: "The name of the entity where the relation starts" 57 | ) 58 | let from: String 59 | 60 | @SchemaOptions( 61 | description: "The name of the entity where the relation ends" 62 | ) 63 | let to: String 64 | 65 | @SchemaOptions( 66 | description: "The type of the relation" 67 | ) 68 | let relationType: String 69 | } 70 | 71 | /// Represents the complete knowledge graph structure 72 | struct KnowledgeGraph: Codable, Sendable { 73 | var entities: [Entity] 74 | var relations: [Relation] 75 | 76 | init() { 77 | entities = [] 78 | relations = [] 79 | } 80 | 81 | init(entities: [Entity], relations: [Relation]) { 82 | self.entities = entities 83 | self.relations = relations 84 | } 85 | } 86 | 87 | /// Handles all interactions with the knowledge graph 88 | @available(macOS 10.15, *) 89 | final class KnowledgeGraphManager: @unchecked Sendable { 90 | /// Loads the graph from the file system 91 | private func loadGraph() async throws -> KnowledgeGraph { 92 | do { 93 | let data = try Data(contentsOf: URL(fileURLWithPath: memoryFilePath)) 94 | let dataString = String(data: data, encoding: .utf8) ?? "" 95 | let lines = dataString.split(separator: "\n").filter { 96 | !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty 97 | } 98 | 99 | var graph = KnowledgeGraph() 100 | for line in lines { 101 | if let lineData = line.data(using: .utf8) { 102 | let item = try JSONDecoder().decode(JSONItem.self, from: lineData) 103 | if item.type == "entity", let entity = try? JSONDecoder().decode(EntityItem.self, from: lineData) { 104 | graph.entities.append( 105 | Entity(name: entity.name, entityType: entity.entityType, observations: entity.observations)) 106 | } else if item.type == "relation", 107 | let relation = try? JSONDecoder().decode(RelationItem.self, from: lineData) 108 | { 109 | graph.relations.append( 110 | Relation(from: relation.from, to: relation.to, relationType: relation.relationType)) 111 | } 112 | } 113 | } 114 | return graph 115 | } catch { 116 | if let nsError = error as NSError?, nsError.code == NSFileReadNoSuchFileError { 117 | return KnowledgeGraph() 118 | } 119 | throw error 120 | } 121 | } 122 | 123 | /// Saves the graph to the file system 124 | private func saveGraph(_ graph: KnowledgeGraph) async throws { 125 | var lines: [String] = [] 126 | 127 | // Serialize entities 128 | for entity in graph.entities { 129 | let entityItem = EntityItem( 130 | type: "entity", name: entity.name, entityType: entity.entityType, observations: entity.observations) 131 | if let jsonData = try? JSONEncoder().encode(entityItem), 132 | let jsonString = String(data: jsonData, encoding: .utf8) 133 | { 134 | lines.append(jsonString) 135 | } 136 | } 137 | 138 | // Serialize relations 139 | for relation in graph.relations { 140 | let relationItem = RelationItem( 141 | type: "relation", from: relation.from, to: relation.to, relationType: relation.relationType) 142 | if let jsonData = try? JSONEncoder().encode(relationItem), 143 | let jsonString = String(data: jsonData, encoding: .utf8) 144 | { 145 | lines.append(jsonString) 146 | } 147 | } 148 | 149 | // Write to file 150 | let outputString = lines.joined(separator: "\n") 151 | try outputString.write(to: URL(fileURLWithPath: memoryFilePath), atomically: true, encoding: .utf8) 152 | } 153 | 154 | /// Helper types for encoding/decoding 155 | private struct JSONItem: Codable { 156 | let type: String 157 | } 158 | 159 | private struct EntityItem: Codable { 160 | let type: String 161 | let name: String 162 | let entityType: String 163 | let observations: [String] 164 | } 165 | 166 | private struct RelationItem: Codable { 167 | let type: String 168 | let from: String 169 | let to: String 170 | let relationType: String 171 | } 172 | 173 | /// Creates new entities in the knowledge graph 174 | func createEntities(_ entities: [Entity]) async throws -> [Entity] { 175 | var graph = try await loadGraph() 176 | var newEntities: [Entity] = [] 177 | 178 | for entity in entities { 179 | if !graph.entities.contains(where: { $0.name == entity.name }) { 180 | graph.entities.append(entity) 181 | newEntities.append(entity) 182 | } 183 | } 184 | 185 | try await saveGraph(graph) 186 | return newEntities 187 | } 188 | 189 | /// Creates new relations in the knowledge graph 190 | func createRelations(_ relations: [Relation]) async throws -> [Relation] { 191 | var graph = try await loadGraph() 192 | var newRelations: [Relation] = [] 193 | 194 | for relation in relations { 195 | if !graph.relations.contains(where: { 196 | $0.from == relation.from && $0.to == relation.to && $0.relationType == relation.relationType 197 | }) { 198 | graph.relations.append(relation) 199 | newRelations.append(relation) 200 | } 201 | } 202 | 203 | try await saveGraph(graph) 204 | return newRelations 205 | } 206 | 207 | /// Adds observations to existing entities 208 | func addObservations(_ observations: [ObservationAddition]) async throws -> [ObservationResult] { 209 | var graph = try await loadGraph() 210 | var results: [ObservationResult] = [] 211 | 212 | for addition in observations { 213 | if let entityIndex = graph.entities.firstIndex(where: { $0.name == addition.entityName }) { 214 | var addedObservations: [String] = [] 215 | 216 | for content in addition.contents { 217 | if !graph.entities[entityIndex].observations.contains(content) { 218 | graph.entities[entityIndex].observations.append(content) 219 | addedObservations.append(content) 220 | } 221 | } 222 | 223 | results.append(ObservationResult(entityName: addition.entityName, addedObservations: addedObservations)) 224 | } else { 225 | throw ToolError(message: "Entity with name \(addition.entityName) not found") 226 | } 227 | } 228 | 229 | try await saveGraph(graph) 230 | return results 231 | } 232 | 233 | /// Deletes entities and their associated relations 234 | func deleteEntities(_ entityNames: [String]) async throws { 235 | var graph = try await loadGraph() 236 | 237 | graph.entities.removeAll(where: { entityNames.contains($0.name) }) 238 | graph.relations.removeAll(where: { entityNames.contains($0.from) || entityNames.contains($0.to) }) 239 | 240 | try await saveGraph(graph) 241 | } 242 | 243 | /// Deletes specific observations from entities 244 | func deleteObservations(_ deletions: [ObservationDeletion]) async throws { 245 | var graph = try await loadGraph() 246 | 247 | for deletion in deletions { 248 | if let entityIndex = graph.entities.firstIndex(where: { $0.name == deletion.entityName }) { 249 | graph.entities[entityIndex].observations.removeAll(where: { deletion.observations.contains($0) }) 250 | } 251 | } 252 | 253 | try await saveGraph(graph) 254 | } 255 | 256 | /// Deletes relations from the knowledge graph 257 | func deleteRelations(_ relations: [Relation]) async throws { 258 | var graph = try await loadGraph() 259 | 260 | graph.relations.removeAll(where: { relation in 261 | relations.contains(where: { 262 | $0.from == relation.from && $0.to == relation.to && $0.relationType == relation.relationType 263 | }) 264 | }) 265 | 266 | try await saveGraph(graph) 267 | } 268 | 269 | /// Reads the entire knowledge graph 270 | func readGraph() async throws -> KnowledgeGraph { 271 | return try await loadGraph() 272 | } 273 | 274 | /// Searches for nodes in the knowledge graph 275 | func searchNodes(_ query: String) async throws -> KnowledgeGraph { 276 | let graph = try await loadGraph() 277 | let lowercaseQuery = query.lowercased() 278 | 279 | // Filter entities 280 | let filteredEntities = graph.entities.filter { entity in 281 | entity.name.lowercased().contains(lowercaseQuery) || entity.entityType.lowercased().contains(lowercaseQuery) 282 | || entity.observations.contains(where: { $0.lowercased().contains(lowercaseQuery) }) 283 | } 284 | 285 | // Get entity names for quick lookup 286 | let filteredEntityNames = Set(filteredEntities.map { $0.name }) 287 | 288 | // Filter relations to only include those between filtered entities 289 | let filteredRelations = graph.relations.filter { relation in 290 | filteredEntityNames.contains(relation.from) && filteredEntityNames.contains(relation.to) 291 | } 292 | 293 | return KnowledgeGraph(entities: filteredEntities, relations: filteredRelations) 294 | } 295 | 296 | /// Opens specific nodes by their names 297 | func openNodes(_ names: [String]) async throws -> KnowledgeGraph { 298 | let graph = try await loadGraph() 299 | 300 | // Filter entities 301 | let filteredEntities = graph.entities.filter { entity in 302 | names.contains(entity.name) 303 | } 304 | 305 | // Get entity names for quick lookup 306 | let filteredEntityNames = Set(filteredEntities.map { $0.name }) 307 | 308 | // Filter relations to only include those between filtered entities 309 | let filteredRelations = graph.relations.filter { relation in 310 | filteredEntityNames.contains(relation.from) && filteredEntityNames.contains(relation.to) 311 | } 312 | 313 | return KnowledgeGraph(entities: filteredEntities, relations: filteredRelations) 314 | } 315 | } 316 | 317 | // Create a singleton instance of the knowledge graph manager 318 | let knowledgeGraphManager = KnowledgeGraphManager() 319 | 320 | // MARK: - Memory Tool Input/Output Types 321 | 322 | /// Input for creating entities 323 | @Schemable 324 | struct CreateEntitiesInput { 325 | @SchemaOptions( 326 | description: "An array of entities to create" 327 | ) 328 | let entities: [Entity] 329 | } 330 | 331 | /// Input for creating relations 332 | @Schemable 333 | struct CreateRelationsInput { 334 | @SchemaOptions( 335 | description: "An array of relations to create" 336 | ) 337 | let relations: [Relation] 338 | } 339 | 340 | /// Input structure for adding observations to entities 341 | @Schemable 342 | struct ObservationAddition: Codable { 343 | @SchemaOptions( 344 | description: "The name of the entity to add the observations to" 345 | ) 346 | let entityName: String 347 | 348 | @SchemaOptions( 349 | description: "An array of observation contents to add" 350 | ) 351 | let contents: [String] 352 | } 353 | 354 | /// Result structure for added observations 355 | struct ObservationResult: Codable { 356 | let entityName: String 357 | let addedObservations: [String] 358 | } 359 | 360 | /// Input for adding observations 361 | @Schemable 362 | struct AddObservationsInput { 363 | @SchemaOptions( 364 | description: "An array of observations to add to entities" 365 | ) 366 | let observations: [ObservationAddition] 367 | } 368 | 369 | /// Input for deleting entities 370 | @Schemable 371 | struct DeleteEntitiesInput { 372 | @SchemaOptions( 373 | description: "An array of entity names to delete" 374 | ) 375 | let entityNames: [String] 376 | } 377 | 378 | /// Input structure for deleting observations from entities 379 | @Schemable 380 | struct ObservationDeletion: Codable { 381 | @SchemaOptions( 382 | description: "The name of the entity containing the observations" 383 | ) 384 | let entityName: String 385 | 386 | @SchemaOptions( 387 | description: "An array of observations to delete" 388 | ) 389 | let observations: [String] 390 | } 391 | 392 | /// Input for deleting observations 393 | @Schemable 394 | struct DeleteObservationsInput { 395 | @SchemaOptions( 396 | description: "An array of observations to delete from entities" 397 | ) 398 | let deletions: [ObservationDeletion] 399 | } 400 | 401 | /// Input for deleting relations 402 | @Schemable 403 | struct DeleteRelationsInput { 404 | @SchemaOptions( 405 | description: "An array of relations to delete" 406 | ) 407 | let relations: [Relation] 408 | } 409 | 410 | /// Input for reading the graph (dummy parameter for consistency) 411 | @Schemable 412 | struct ReadGraphInput { 413 | @SchemaOptions( 414 | description: "Dummy parameter for no-parameter tools" 415 | ) 416 | let random_string: String? 417 | } 418 | 419 | /// Input for searching nodes 420 | @Schemable 421 | struct SearchNodesInput { 422 | @SchemaOptions( 423 | description: "The search query to match against entity names, types, and observation content" 424 | ) 425 | let query: String 426 | } 427 | 428 | /// Input for opening nodes 429 | @Schemable 430 | struct OpenNodesInput { 431 | @SchemaOptions( 432 | description: "An array of entity names to retrieve" 433 | ) 434 | let names: [String] 435 | } 436 | 437 | // MARK: - Memory Knowledge Graph Tools 438 | 439 | /// Tool for creating new entities 440 | let createEntitiesTool = Tool( 441 | name: "create_entities", 442 | description: "Create multiple new entities in the knowledge graph" 443 | ) { (input: CreateEntitiesInput) async throws -> [TextContentOrImageContentOrEmbeddedResource] in 444 | do { 445 | let newEntities = try await knowledgeGraphManager.createEntities(input.entities) 446 | let encoder = JSONEncoder() 447 | encoder.outputFormatting = [.prettyPrinted] 448 | let jsonData = try encoder.encode(newEntities) 449 | let jsonString = String(data: jsonData, encoding: .utf8) ?? "[]" 450 | return [.text(TextContent(text: jsonString))] 451 | } catch { 452 | mcpLogger.error("Failed to create entities: \(error)") 453 | throw error 454 | } 455 | } 456 | 457 | /// Tool for creating new relations 458 | let createRelationsTool = Tool( 459 | name: "create_relations", 460 | description: 461 | "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice" 462 | ) { (input: CreateRelationsInput) async throws -> [TextContentOrImageContentOrEmbeddedResource] in 463 | do { 464 | let newRelations = try await knowledgeGraphManager.createRelations(input.relations) 465 | let encoder = JSONEncoder() 466 | encoder.outputFormatting = [.prettyPrinted] 467 | let jsonData = try encoder.encode(newRelations) 468 | let jsonString = String(data: jsonData, encoding: .utf8) ?? "[]" 469 | return [.text(TextContent(text: jsonString))] 470 | } catch { 471 | mcpLogger.error("Failed to create relations: \(error)") 472 | throw error 473 | } 474 | } 475 | 476 | /// Tool for adding observations to entities 477 | let addObservationsTool = Tool( 478 | name: "add_observations", 479 | description: "Add new observations to existing entities in the knowledge graph" 480 | ) { (input: AddObservationsInput) async throws -> [TextContentOrImageContentOrEmbeddedResource] in 481 | do { 482 | let results = try await knowledgeGraphManager.addObservations(input.observations) 483 | let encoder = JSONEncoder() 484 | encoder.outputFormatting = [.prettyPrinted] 485 | let jsonData = try encoder.encode(results) 486 | let jsonString = String(data: jsonData, encoding: .utf8) ?? "[]" 487 | return [.text(TextContent(text: jsonString))] 488 | } catch { 489 | mcpLogger.error("Failed to add observations: \(error)") 490 | throw error 491 | } 492 | } 493 | 494 | /// Tool for deleting entities 495 | let deleteEntitiesTool = Tool( 496 | name: "delete_entities", 497 | description: "Delete multiple entities and their associated relations from the knowledge graph" 498 | ) { (input: DeleteEntitiesInput) async throws -> [TextContentOrImageContentOrEmbeddedResource] in 499 | do { 500 | try await knowledgeGraphManager.deleteEntities(input.entityNames) 501 | return [.text(TextContent(text: "Entities deleted successfully"))] 502 | } catch { 503 | mcpLogger.error("Failed to delete entities: \(error)") 504 | throw error 505 | } 506 | } 507 | 508 | /// Tool for deleting observations 509 | let deleteObservationsTool = Tool( 510 | name: "delete_observations", 511 | description: "Delete specific observations from entities in the knowledge graph" 512 | ) { (input: DeleteObservationsInput) async throws -> [TextContentOrImageContentOrEmbeddedResource] in 513 | do { 514 | try await knowledgeGraphManager.deleteObservations(input.deletions) 515 | return [.text(TextContent(text: "Observations deleted successfully"))] 516 | } catch { 517 | mcpLogger.error("Failed to delete observations: \(error)") 518 | throw error 519 | } 520 | } 521 | 522 | /// Tool for deleting relations 523 | let deleteRelationsTool = Tool( 524 | name: "delete_relations", 525 | description: "Delete multiple relations from the knowledge graph" 526 | ) { (input: DeleteRelationsInput) async throws -> [TextContentOrImageContentOrEmbeddedResource] in 527 | do { 528 | try await knowledgeGraphManager.deleteRelations(input.relations) 529 | return [.text(TextContent(text: "Relations deleted successfully"))] 530 | } catch { 531 | mcpLogger.error("Failed to delete relations: \(error)") 532 | throw error 533 | } 534 | } 535 | 536 | /// Tool for reading the entire knowledge graph 537 | let readGraphTool = Tool( 538 | name: "read_graph", 539 | description: "Read the entire knowledge graph" 540 | ) { (_: ReadGraphInput) async throws -> [TextContentOrImageContentOrEmbeddedResource] in 541 | do { 542 | let graph = try await knowledgeGraphManager.readGraph() 543 | let encoder = JSONEncoder() 544 | encoder.outputFormatting = [.prettyPrinted] 545 | let jsonData = try encoder.encode(graph) 546 | let jsonString = String(data: jsonData, encoding: .utf8) ?? "{\"entities\": [], \"relations\": []}" 547 | return [.text(TextContent(text: jsonString))] 548 | } catch { 549 | mcpLogger.error("Failed to read graph: \(error)") 550 | throw error 551 | } 552 | } 553 | 554 | /// Tool for searching nodes in the knowledge graph 555 | let searchNodesTool = Tool( 556 | name: "search_nodes", 557 | description: "Search for nodes in the knowledge graph based on a query" 558 | ) { (input: SearchNodesInput) async throws -> [TextContentOrImageContentOrEmbeddedResource] in 559 | do { 560 | let result = try await knowledgeGraphManager.searchNodes(input.query) 561 | let encoder = JSONEncoder() 562 | encoder.outputFormatting = [.prettyPrinted] 563 | let jsonData = try encoder.encode(result) 564 | let jsonString = String(data: jsonData, encoding: .utf8) ?? "{\"entities\": [], \"relations\": []}" 565 | return [.text(TextContent(text: jsonString))] 566 | } catch { 567 | mcpLogger.error("Failed to search nodes: \(error)") 568 | throw error 569 | } 570 | } 571 | 572 | /// Tool for opening specific nodes in the knowledge graph 573 | let openNodesTool = Tool( 574 | name: "open_nodes", 575 | description: "Open specific nodes in the knowledge graph by their names" 576 | ) { (input: OpenNodesInput) async throws -> [TextContentOrImageContentOrEmbeddedResource] in 577 | do { 578 | let result = try await knowledgeGraphManager.openNodes(input.names) 579 | let encoder = JSONEncoder() 580 | encoder.outputFormatting = [.prettyPrinted] 581 | let jsonData = try encoder.encode(result) 582 | let jsonString = String(data: jsonData, encoding: .utf8) ?? "{\"entities\": [], \"relations\": []}" 583 | return [.text(TextContent(text: jsonString))] 584 | } catch { 585 | mcpLogger.error("Failed to open nodes: \(error)") 586 | throw error 587 | } 588 | } 589 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Memory MCP Server Installation Script 4 | # https://github.com/okooo5km/memory-mcp-server 5 | 6 | set -e 7 | 8 | # Colors for terminal output 9 | GREEN='\033[0;32m' 10 | BLUE='\033[0;34m' 11 | YELLOW='\033[1;33m' 12 | RED='\033[0;31m' 13 | NC='\033[0m' # No Color 14 | 15 | echo -e "${BLUE}Installing Memory MCP Server...${NC}" 16 | 17 | # Create ~/.local/bin directory (if it doesn't exist) 18 | if [ ! -d "$HOME/.local/bin" ]; then 19 | echo -e "${YELLOW}Creating ~/.local/bin directory...${NC}" 20 | mkdir -p "$HOME/.local/bin" 21 | fi 22 | 23 | # Add to PATH based on the current shell (if not already added) 24 | if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then 25 | echo -e "${YELLOW}Adding ~/.local/bin to your PATH...${NC}" 26 | 27 | # We need to detect the user's actual login shell, not the shell running this script 28 | USER_SHELL=$(basename "$SHELL") 29 | 30 | # Determine shell configuration file based on user's login shell, not the current shell 31 | if [ -f "$HOME/.zshrc" ] && [[ "$USER_SHELL" == "zsh" ]]; then 32 | SHELL_CONFIG="$HOME/.zshrc" 33 | echo -e "${BLUE}Detected ZSH as your login shell${NC}" 34 | elif [ -f "$HOME/.bashrc" ] && [[ "$USER_SHELL" == "bash" ]]; then 35 | SHELL_CONFIG="$HOME/.bashrc" 36 | echo -e "${BLUE}Detected Bash as your login shell${NC}" 37 | # Check for common shell configuration files as fallbacks 38 | elif [ -f "$HOME/.zshrc" ]; then 39 | SHELL_CONFIG="$HOME/.zshrc" 40 | echo -e "${BLUE}Found .zshrc configuration file${NC}" 41 | elif [ -f "$HOME/.bashrc" ]; then 42 | SHELL_CONFIG="$HOME/.bashrc" 43 | echo -e "${BLUE}Found .bashrc configuration file${NC}" 44 | else 45 | SHELL_CONFIG="$HOME/.profile" 46 | echo -e "${YELLOW}Using ~/.profile as fallback${NC}" 47 | fi 48 | 49 | if [ -f "$SHELL_CONFIG" ] || [ "$SHELL_CONFIG" = "$HOME/.profile" ]; then 50 | if [ ! -f "$SHELL_CONFIG" ]; then 51 | touch "$SHELL_CONFIG" 52 | fi 53 | echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$SHELL_CONFIG" 54 | echo -e "${GREEN}Added PATH to $SHELL_CONFIG${NC}" 55 | echo -e "${YELLOW}Note: You'll need to restart your terminal or run 'source $SHELL_CONFIG' for this change to take effect${NC}" 56 | else 57 | echo -e "${RED}Warning: Could not find shell configuration file. Please add ~/.local/bin to your PATH manually.${NC}" 58 | fi 59 | fi 60 | 61 | # Download and install the latest version 62 | echo -e "${BLUE}Downloading the latest version...${NC}" 63 | if ! curl -L "https://github.com/okooo5km/memory-mcp-server/releases/latest/download/memory-mcp-server.tar.gz" | tar xz -C "$HOME/.local/bin"; then 64 | echo -e "${RED}Error: Failed to download or extract the binary.${NC}" 65 | exit 1 66 | fi 67 | 68 | chmod +x "$HOME/.local/bin/memory-mcp-server" 69 | 70 | # Verify installation 71 | if [ -x "$HOME/.local/bin/memory-mcp-server" ]; then 72 | echo -e "${GREEN}✅ Installation completed successfully!${NC}" 73 | 74 | # Check if executable is in PATH 75 | if command -v memory-mcp-server >/dev/null 2>&1; then 76 | echo -e "${GREEN}memory-mcp-server is in your PATH and ready to use.${NC}" 77 | echo -e "${BLUE}Version information:${NC}" 78 | memory-mcp-server --version 79 | else 80 | echo -e "${YELLOW}Note: memory-mcp-server is installed but may not be in your current PATH.${NC}" 81 | echo -e "${YELLOW}Run the following command to use it immediately:${NC}" 82 | echo -e "${BLUE}$HOME/.local/bin/memory-mcp-server --version${NC}" 83 | echo -e "${YELLOW}Or restart your terminal session for PATH changes to take effect.${NC}" 84 | fi 85 | 86 | echo -e "\n${BLUE}For Configuration Instructions:${NC}" 87 | echo -e "You can set the knowledge graph path using the environment variable:" 88 | echo -e "${GREEN}export MEMORY_FILE_PATH=\"/path/to/your/memory.json\"${NC}" 89 | echo -e "By default, the memory is stored in memory.json in the current directory." 90 | else 91 | echo -e "${RED}Error: Installation failed. The binary is not executable.${NC}" 92 | exit 1 93 | fi -------------------------------------------------------------------------------- /screenshots/chatwise.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okooo5km/memory-mcp-server/e874a073288981779e14a489b7f9b10f4b576b3c/screenshots/chatwise.webp -------------------------------------------------------------------------------- /screenshots/cursor.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okooo5km/memory-mcp-server/e874a073288981779e14a489b7f9b10f4b576b3c/screenshots/cursor.webp --------------------------------------------------------------------------------