├── .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 | 
8 | 
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 | 
174 |
175 | ### Configure for Chatwise
176 |
177 | Add the memory MCP server to your Chatwise Settings - Tools.
178 |
179 | 
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
--------------------------------------------------------------------------------