@cybermem/mcp 0.5.3 โ†’ 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +203 -28
  3. package/package.json +29 -28
  4. package/requirements.txt +2 -0
  5. package/server.py +347 -0
  6. package/src/index.ts +227 -0
  7. package/test_mcp.py +111 -0
  8. package/tsconfig.json +14 -0
  9. package/dist/commands/__tests__/backup.test.js +0 -75
  10. package/dist/commands/__tests__/restore.test.js +0 -70
  11. package/dist/commands/backup.js +0 -52
  12. package/dist/commands/deploy.js +0 -242
  13. package/dist/commands/init.js +0 -65
  14. package/dist/commands/restore.js +0 -62
  15. package/dist/templates/ansible/inventory/hosts.ini +0 -3
  16. package/dist/templates/ansible/playbooks/deploy-cybermem.yml +0 -71
  17. package/dist/templates/ansible/playbooks/stop-cybermem.yml +0 -17
  18. package/dist/templates/charts/cybermem/Chart.yaml +0 -6
  19. package/dist/templates/charts/cybermem/templates/dashboard-deployment.yaml +0 -29
  20. package/dist/templates/charts/cybermem/templates/dashboard-service.yaml +0 -20
  21. package/dist/templates/charts/cybermem/templates/openmemory-deployment.yaml +0 -40
  22. package/dist/templates/charts/cybermem/templates/openmemory-pvc.yaml +0 -10
  23. package/dist/templates/charts/cybermem/templates/openmemory-service.yaml +0 -13
  24. package/dist/templates/charts/cybermem/values-vps.yaml +0 -18
  25. package/dist/templates/charts/cybermem/values.yaml +0 -42
  26. package/dist/templates/docker-compose.yml +0 -236
  27. package/dist/templates/envs/local.example +0 -27
  28. package/dist/templates/envs/rpi.example +0 -27
  29. package/dist/templates/envs/vps.example +0 -25
  30. package/dist/templates/mcp-responder/Dockerfile +0 -6
  31. package/dist/templates/mcp-responder/server.js +0 -22
  32. package/dist/templates/monitoring/db_exporter/Dockerfile +0 -19
  33. package/dist/templates/monitoring/db_exporter/exporter.py +0 -313
  34. package/dist/templates/monitoring/db_exporter/requirements.txt +0 -2
  35. package/dist/templates/monitoring/grafana/dashboards/cybermem.json +0 -1088
  36. package/dist/templates/monitoring/grafana/provisioning/dashboards/default.yml +0 -12
  37. package/dist/templates/monitoring/grafana/provisioning/datasources/prometheus.yml +0 -9
  38. package/dist/templates/monitoring/log_exporter/Dockerfile +0 -13
  39. package/dist/templates/monitoring/log_exporter/exporter.py +0 -274
  40. package/dist/templates/monitoring/log_exporter/requirements.txt +0 -1
  41. package/dist/templates/monitoring/postgres_exporter/queries.yml +0 -22
  42. package/dist/templates/monitoring/prometheus/prometheus.yml +0 -22
  43. package/dist/templates/monitoring/traefik/dynamic/.gitkeep +0 -0
  44. package/dist/templates/monitoring/traefik/traefik.yml +0 -32
  45. package/dist/templates/monitoring/vector/vector.toml/vector.yaml +0 -77
  46. package/dist/templates/monitoring/vector/vector.yaml +0 -106
  47. package/dist/templates/openmemory/Dockerfile +0 -19
  48. package/templates/ansible/inventory/hosts.ini +0 -3
  49. package/templates/ansible/playbooks/deploy-cybermem.yml +0 -71
  50. package/templates/ansible/playbooks/stop-cybermem.yml +0 -17
  51. package/templates/charts/cybermem/Chart.yaml +0 -6
  52. package/templates/charts/cybermem/templates/dashboard-deployment.yaml +0 -29
  53. package/templates/charts/cybermem/templates/dashboard-service.yaml +0 -20
  54. package/templates/charts/cybermem/templates/openmemory-deployment.yaml +0 -40
  55. package/templates/charts/cybermem/templates/openmemory-pvc.yaml +0 -10
  56. package/templates/charts/cybermem/templates/openmemory-service.yaml +0 -13
  57. package/templates/charts/cybermem/values-vps.yaml +0 -18
  58. package/templates/charts/cybermem/values.yaml +0 -42
  59. package/templates/docker-compose.yml +0 -236
  60. package/templates/envs/local.example +0 -27
  61. package/templates/envs/rpi.example +0 -27
  62. package/templates/envs/vps.example +0 -25
  63. package/templates/mcp-responder/Dockerfile +0 -6
  64. package/templates/mcp-responder/server.js +0 -22
  65. package/templates/monitoring/db_exporter/Dockerfile +0 -19
  66. package/templates/monitoring/db_exporter/exporter.py +0 -313
  67. package/templates/monitoring/db_exporter/requirements.txt +0 -2
  68. package/templates/monitoring/grafana/dashboards/cybermem.json +0 -1088
  69. package/templates/monitoring/grafana/provisioning/dashboards/default.yml +0 -12
  70. package/templates/monitoring/grafana/provisioning/datasources/prometheus.yml +0 -9
  71. package/templates/monitoring/log_exporter/Dockerfile +0 -13
  72. package/templates/monitoring/log_exporter/exporter.py +0 -274
  73. package/templates/monitoring/log_exporter/requirements.txt +0 -1
  74. package/templates/monitoring/postgres_exporter/queries.yml +0 -22
  75. package/templates/monitoring/prometheus/prometheus.yml +0 -22
  76. package/templates/monitoring/traefik/dynamic/.gitkeep +0 -0
  77. package/templates/monitoring/traefik/traefik.yml +0 -32
  78. package/templates/monitoring/vector/vector.toml/vector.yaml +0 -77
  79. package/templates/monitoring/vector/vector.yaml +0 -106
  80. package/templates/openmemory/Dockerfile +0 -19
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # @cybermem/mcp
2
2
 
3
- Universal Long-Term Memory for AI Agents.
3
+ CyberMem MCP Server โ€” AI Memory via Model Context Protocol.
4
4
 
5
5
  ๐ŸŒ [cybermem.dev](https://cybermem.dev) ยท ๐Ÿ“– [Docs](https://docs.cybermem.dev) ยท ๐Ÿ“ฆ [GitHub](https://github.com/mikhailkogan17/cybermem)
package/dist/index.js CHANGED
@@ -1,30 +1,205 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const commander_1 = require("commander");
4
- const backup_1 = require("./commands/backup");
5
- const deploy_1 = require("./commands/deploy");
6
- const restore_1 = require("./commands/restore");
7
- const program = new commander_1.Command();
8
- program
9
- .name('mcp')
10
- .description('CyberMem - Deploy your AI memory server in one command')
11
- .version('1.0.0');
12
- // Default Command: Deploy
13
- program
14
- .command('deploy', { isDefault: true })
15
- .description('Deploy CyberMem (Default)')
16
- .option('--rpi', 'Deploy to Raspberry Pi (default: local)')
17
- .option('--vps', 'Deploy to VPS/Cloud server')
18
- .option('-h, --host <host>', 'SSH Host (user@ip) for remote deployment')
19
- .option('--remote-access', 'Enable Tailscale Funnel for HTTPS remote access')
20
- .action(deploy_1.deploy);
21
- program
22
- .command('backup')
23
- .description('Backup CyberMem data to a tarball')
24
- .action(backup_1.backup);
25
- program
26
- .command('restore')
27
- .description('Restore CyberMem data from a backup file')
28
- .argument('<file>', 'Backup file to restore')
29
- .action(restore_1.restore);
30
- program.parse(process.argv);
6
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
7
+ const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
8
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
10
+ const axios_1 = __importDefault(require("axios"));
11
+ const cors_1 = __importDefault(require("cors"));
12
+ const dotenv_1 = __importDefault(require("dotenv"));
13
+ const express_1 = __importDefault(require("express"));
14
+ dotenv_1.default.config();
15
+ // Parse CLI args for remote mode
16
+ const args = process.argv.slice(2);
17
+ const getArg = (name) => {
18
+ const idx = args.indexOf(name);
19
+ return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
20
+ };
21
+ const cliUrl = getArg('--url');
22
+ const cliApiKey = getArg('--api-key');
23
+ const cliClientName = getArg('--client-name');
24
+ // Use CLI args first, then env, then defaults
25
+ // Default to local CyberMem backend (via Traefik on port 8626)
26
+ const API_URL = cliUrl || process.env.CYBERMEM_URL || "http://localhost:8626/memory";
27
+ const API_KEY = cliApiKey || process.env.OM_API_KEY || "";
28
+ // Track client name per session
29
+ let currentClientName = cliClientName || "cybermem-mcp";
30
+ const server = new index_js_1.Server({
31
+ name: "cybermem-mcp",
32
+ version: "0.2.0",
33
+ }, {
34
+ capabilities: {
35
+ tools: {},
36
+ },
37
+ });
38
+ const tools = [
39
+ {
40
+ name: "add_memory",
41
+ description: "Store a new memory in CyberMem",
42
+ inputSchema: {
43
+ type: "object",
44
+ properties: {
45
+ content: { type: "string" },
46
+ user_id: { type: "string" },
47
+ tags: { type: "array", items: { type: "string" } },
48
+ },
49
+ required: ["content"],
50
+ },
51
+ },
52
+ {
53
+ name: "query_memory",
54
+ description: "Search for relevant memories",
55
+ inputSchema: {
56
+ type: "object",
57
+ properties: {
58
+ query: { type: "string" },
59
+ k: { type: "number", default: 5 },
60
+ },
61
+ required: ["query"],
62
+ },
63
+ },
64
+ {
65
+ name: "list_memories",
66
+ description: "List recent memories",
67
+ inputSchema: {
68
+ type: "object",
69
+ properties: {
70
+ limit: { type: "number", default: 10 },
71
+ },
72
+ },
73
+ },
74
+ {
75
+ name: "delete_memory",
76
+ description: "Delete a memory by ID",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: {
80
+ id: { type: "string" },
81
+ },
82
+ required: ["id"],
83
+ },
84
+ },
85
+ {
86
+ name: "update_memory",
87
+ description: "Update a memory by ID",
88
+ inputSchema: {
89
+ type: "object",
90
+ properties: {
91
+ id: { type: "string" },
92
+ content: { type: "string" },
93
+ tags: { type: "array", items: { type: "string" } },
94
+ metadata: { type: "object" },
95
+ },
96
+ required: ["id"],
97
+ },
98
+ }
99
+ ];
100
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
101
+ tools,
102
+ }));
103
+ // Create axios instance
104
+ const apiClient = axios_1.default.create({
105
+ baseURL: API_URL,
106
+ headers: {
107
+ "Authorization": `Bearer ${API_KEY}`,
108
+ },
109
+ });
110
+ // Helper to get client with context
111
+ function getClient(customHeaders = {}) {
112
+ // Identity is taken from currentClientName which is updated per-request in SSE mode
113
+ const clientName = customHeaders["X-Client-Name"] || currentClientName;
114
+ return {
115
+ ...apiClient,
116
+ get: (url, config) => apiClient.get(url, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
117
+ post: (url, data, config) => apiClient.post(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
118
+ put: (url, data, config) => apiClient.put(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
119
+ patch: (url, data, config) => apiClient.patch(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
120
+ delete: (url, config) => apiClient.delete(url, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
121
+ };
122
+ }
123
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
124
+ const { name, arguments: args } = request.params;
125
+ try {
126
+ switch (name) {
127
+ case "add_memory": {
128
+ const response = await getClient().post("/add", args);
129
+ return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
130
+ }
131
+ case "query_memory": {
132
+ const response = await getClient().post("/query", args);
133
+ return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
134
+ }
135
+ case "list_memories": {
136
+ const limit = args?.limit || 10;
137
+ const response = await getClient().get(`/all?l=${limit}`);
138
+ return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
139
+ }
140
+ case "delete_memory": {
141
+ const { id } = args;
142
+ await getClient().delete(`/${id}`);
143
+ return { content: [{ type: "text", text: `Memory ${id} deleted` }] };
144
+ }
145
+ case "update_memory": {
146
+ const { id, ...updates } = args;
147
+ const response = await getClient().patch(`/${id}`, updates);
148
+ return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
149
+ }
150
+ default:
151
+ throw new Error(`Unknown tool: ${name}`);
152
+ }
153
+ }
154
+ catch (error) {
155
+ return {
156
+ content: [{ type: "text", text: `Error: ${error.message}` }],
157
+ isError: true,
158
+ };
159
+ }
160
+ });
161
+ async function run() {
162
+ const isSse = process.argv.includes("--sse") || !!process.env.PORT;
163
+ if (isSse) {
164
+ const app = (0, express_1.default)();
165
+ app.use((0, cors_1.default)());
166
+ const port = process.env.PORT || 8627;
167
+ let transport = null;
168
+ app.get("/sse", async (req, res) => {
169
+ // Extract client name from header
170
+ const clientName = req.headers["x-client-name"];
171
+ if (clientName) {
172
+ currentClientName = clientName;
173
+ }
174
+ transport = new sse_js_1.SSEServerTransport("/messages", res);
175
+ await server.connect(transport);
176
+ });
177
+ app.post("/messages", async (req, res) => {
178
+ // Also check headers on messages
179
+ const clientName = req.headers["x-client-name"];
180
+ if (clientName) {
181
+ currentClientName = clientName;
182
+ }
183
+ if (transport) {
184
+ await transport.handlePostMessage(req, res);
185
+ }
186
+ else {
187
+ res.status(400).send("Session not established");
188
+ }
189
+ });
190
+ app.listen(port, () => {
191
+ console.error(`CyberMem MCP Server running on SSE at http://localhost:${port}`);
192
+ console.error(` - SSE endpoint: http://localhost:${port}/sse`);
193
+ console.error(` - Message endpoint: http://localhost:${port}/messages`);
194
+ });
195
+ }
196
+ else {
197
+ const transport = new stdio_js_1.StdioServerTransport();
198
+ await server.connect(transport);
199
+ console.error("CyberMem MCP Server running on stdio");
200
+ }
201
+ }
202
+ run().catch((error) => {
203
+ console.error("Fatal error running server:", error);
204
+ process.exit(1);
205
+ });
package/package.json CHANGED
@@ -1,48 +1,49 @@
1
1
  {
2
2
  "name": "@cybermem/mcp",
3
- "version": "0.5.3",
4
- "description": "CyberMem โ€” Universal Long-Term Memory for AI Agents",
5
- "homepage": "https://cybermem.dev",
3
+ "version": "0.6.0",
4
+ "description": "CyberMem MCP Server (TypeScript)",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "cybermem-mcp": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js",
12
+ "dev": "ts-node src/index.ts"
13
+ },
6
14
  "repository": {
7
15
  "type": "git",
8
16
  "url": "https://github.com/mikhailkogan17/cybermem.git",
9
- "directory": "packages/cli"
17
+ "directory": "packages/mcp"
10
18
  },
11
- "bin": {
12
- "mcp": "./dist/index.js"
13
- },
14
- "files": [
15
- "dist",
16
- "templates"
17
- ],
18
- "scripts": {
19
- "build": "tsc && cp -r templates dist/",
20
- "start": "ts-node src/index.ts",
21
- "test:e2e": "ts-node e2e/test-mcp.ts",
22
- "prepublishOnly": "npm run build"
19
+ "homepage": "https://cybermem.dev",
20
+ "bugs": {
21
+ "url": "https://github.com/mikhailkogan17/cybermem/issues"
23
22
  },
24
23
  "keywords": [
25
- "cybermem",
26
- "cli",
27
- "ai",
28
- "mcp"
24
+ "mcp",
25
+ "ai-memory",
26
+ "claude",
27
+ "cursor",
28
+ "antigravity",
29
+ "openmemory",
30
+ "llm"
29
31
  ],
30
- "author": "Mikhail Kogan",
32
+ "author": "Mikhail Kogan <mikhailkogan17@gmail.com>",
31
33
  "license": "MIT",
32
34
  "publishConfig": {
33
35
  "access": "public"
34
36
  },
35
- "type": "commonjs",
36
37
  "dependencies": {
37
- "chalk": "^4.1.2",
38
- "commander": "^9.0.0",
38
+ "@modelcontextprotocol/sdk": "^1.0.0",
39
+ "axios": "^1.13.2",
40
+ "cors": "^2.8.5",
39
41
  "dotenv": "^16.0.0",
40
- "execa": "^5.1.1",
41
- "inquirer": "^8.2.0",
42
- "ora": "^5.4.1"
42
+ "express": "^5.2.1"
43
43
  },
44
44
  "devDependencies": {
45
- "@types/inquirer": "^9.0.3",
45
+ "@types/cors": "^2.8.19",
46
+ "@types/express": "^5.0.6",
46
47
  "@types/node": "^18.0.0",
47
48
  "ts-node": "^10.9.1",
48
49
  "typescript": "^5.0.0"
@@ -0,0 +1,2 @@
1
+ mcp>=0.9.0
2
+ httpx>=0.27.0
package/server.py ADDED
@@ -0,0 +1,347 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CyberMem MCP Server
4
+
5
+ MCP server that exposes shared memory functionality to several LLM.
6
+ """
7
+
8
+ import asyncio
9
+ import os
10
+ from typing import Any
11
+
12
+ import httpx
13
+ from mcp import types
14
+ from mcp.server import NotificationOptions, Server
15
+ from mcp.server.models import InitializationOptions
16
+ from mcp.server.stdio import stdio_server
17
+
18
+ # CyberMem configuration (OpenMemory backend)
19
+ OPENMEMORY_URL = os.getenv("OPENMEMORY_URL", "http://localhost:8080")
20
+ CYBERMEM_API_KEY = os.getenv("CYBERMEM_API_KEY", "dev-secret-key")
21
+
22
+ # Create MCP server instance
23
+ server = Server("cybermem")
24
+
25
+
26
+ @server.list_tools()
27
+ async def handle_list_tools() -> list[types.Tool]:
28
+ """List available memory management tools."""
29
+ return [
30
+ types.Tool(
31
+ name="add_memory",
32
+ description="Store information in long-term memory. Use this to remember important facts, decisions, or context for future conversations.",
33
+ inputSchema={
34
+ "type": "object",
35
+ "properties": {
36
+ "content": {
37
+ "type": "string",
38
+ "description": "The information to remember (text content)",
39
+ },
40
+ "metadata": {
41
+ "type": "object",
42
+ "description": "Optional metadata (tags, category, importance, etc.)",
43
+ "additionalProperties": True,
44
+ },
45
+ },
46
+ "required": ["content"],
47
+ },
48
+ ),
49
+ types.Tool(
50
+ name="search_memory",
51
+ description="Search through stored memories to find relevant information. Use this to recall past conversations, decisions, or facts.",
52
+ inputSchema={
53
+ "type": "object",
54
+ "properties": {
55
+ "query": {
56
+ "type": "string",
57
+ "description": "What to search for (semantic search query)",
58
+ },
59
+ "limit": {
60
+ "type": "number",
61
+ "description": "Maximum number of results (default: 5)",
62
+ "default": 5,
63
+ },
64
+ },
65
+ "required": ["query"],
66
+ },
67
+ ),
68
+ types.Tool(
69
+ name="list_memories",
70
+ description="List recent memories stored in the system. Useful for browsing what has been remembered.",
71
+ inputSchema={
72
+ "type": "object",
73
+ "properties": {
74
+ "limit": {
75
+ "type": "number",
76
+ "description": "Maximum number of memories to retrieve (default: 10)",
77
+ "default": 10,
78
+ }
79
+ },
80
+ },
81
+ ),
82
+ types.Tool(
83
+ name="delete_memory",
84
+ description="Delete a specific memory by its ID. Use this to remove outdated or incorrect information.",
85
+ inputSchema={
86
+ "type": "object",
87
+ "properties": {
88
+ "memory_id": {
89
+ "type": "string",
90
+ "description": "The UUID of the memory to delete",
91
+ }
92
+ },
93
+ "required": ["memory_id"],
94
+ },
95
+ ),
96
+ types.Tool(
97
+ name="update_memory",
98
+ description="Update an existing memory's content, tags, or metadata.",
99
+ inputSchema={
100
+ "type": "object",
101
+ "properties": {
102
+ "memory_id": {
103
+ "type": "string",
104
+ "description": "The UUID of the memory to update",
105
+ },
106
+ "content": {
107
+ "type": "string",
108
+ "description": "New content for the memory (optional)",
109
+ },
110
+ "tags": {
111
+ "type": "array",
112
+ "items": {"type": "string"},
113
+ "description": "New tags for the memory (optional)",
114
+ },
115
+ "metadata": {
116
+ "type": "object",
117
+ "description": "New metadata for the memory (optional)",
118
+ "additionalProperties": True,
119
+ },
120
+ },
121
+ "required": ["memory_id"],
122
+ },
123
+ ),
124
+ ]
125
+
126
+
127
+ @server.call_tool()
128
+ async def handle_call_tool(
129
+ name: str, arguments: dict[str, Any]
130
+ ) -> list[types.TextContent]:
131
+ """Handle tool execution requests."""
132
+
133
+ # Get client info from MCP context
134
+ client_name, client_version = "unknown", "unknown"
135
+ try:
136
+ ctx = server.request_context
137
+ if hasattr(ctx, "session"):
138
+ session = ctx.session
139
+ if hasattr(session, "_client_params"):
140
+ client_params = session._client_params
141
+ if client_params and hasattr(client_params, "clientInfo"):
142
+ client_info = client_params.clientInfo
143
+ client_name = client_info.name or "unknown"
144
+ client_version = client_info.version or "unknown"
145
+ except Exception:
146
+ pass
147
+
148
+ headers = {
149
+ "Authorization": f"Bearer {CYBERMEM_API_KEY}",
150
+ "Content-Type": "application/json",
151
+ "X-Client-Name": client_name,
152
+ "X-Client-Version": client_version,
153
+ }
154
+
155
+ async with httpx.AsyncClient(timeout=30.0) as client:
156
+ try:
157
+ if name == "add_memory":
158
+ # Store memory in OpenMemory
159
+ content = arguments.get("content")
160
+ metadata = arguments.get("metadata", {})
161
+
162
+ response = await client.post(
163
+ f"{OPENMEMORY_URL}/memory/add",
164
+ json={"content": content, "metadata": metadata},
165
+ headers=headers,
166
+ )
167
+ response.raise_for_status()
168
+ result = response.json()
169
+
170
+ return [
171
+ types.TextContent(
172
+ type="text",
173
+ text=f"โœ… Memory stored successfully!\n\nID: {result.get('id')}\nChunks: {result.get('chunks')}\nSectors: {', '.join(result.get('sectors', []))}",
174
+ )
175
+ ]
176
+
177
+ elif name == "search_memory":
178
+ # Search memories in OpenMemory
179
+ query = arguments.get("query")
180
+ limit = arguments.get("limit", 5)
181
+
182
+ response = await client.post(
183
+ f"{OPENMEMORY_URL}/memory/query",
184
+ json={"query": query, "k": limit},
185
+ headers=headers,
186
+ )
187
+ response.raise_for_status()
188
+ data = response.json()
189
+ matches = data.get("matches", [])
190
+
191
+ if not matches:
192
+ return [
193
+ types.TextContent(
194
+ type="text",
195
+ text="๐Ÿ” No memories found matching your query.",
196
+ )
197
+ ]
198
+
199
+ # Format search results
200
+ formatted_results = []
201
+ for i, memory in enumerate(matches, 1):
202
+ content = memory.get("content", "")
203
+ score = memory.get("score", 0)
204
+ sector = memory.get("primary_sector", "")
205
+
206
+ formatted_results.append(
207
+ f"**Result {i}** (score: {score:.2f}, sector: {sector})\n{content}\n"
208
+ )
209
+
210
+ return [
211
+ types.TextContent(
212
+ type="text",
213
+ text=f"๐Ÿ” Found {len(matches)} memories:\n\n"
214
+ + "\n".join(formatted_results),
215
+ )
216
+ ]
217
+
218
+ elif name == "list_memories":
219
+ # List recent memories
220
+ limit = arguments.get("limit", 10)
221
+
222
+ # Note: OpenMemory doesn't have a /list endpoint yet,
223
+ # so we'll do a broad query with generic term
224
+ response = await client.post(
225
+ f"{OPENMEMORY_URL}/memory/query",
226
+ json={"query": "memory context", "k": limit},
227
+ headers=headers,
228
+ )
229
+ response.raise_for_status()
230
+ data = response.json()
231
+ matches = data.get("matches", [])
232
+
233
+ if not matches:
234
+ return [
235
+ types.TextContent(
236
+ type="text", text="๐Ÿ“‹ No memories stored yet."
237
+ )
238
+ ]
239
+
240
+ # Format list
241
+ formatted_list = []
242
+ for i, memory in enumerate(matches, 1):
243
+ content = memory.get("content", "")
244
+ sector = memory.get("primary_sector", "")
245
+ # Truncate long content
246
+ if len(content) > 100:
247
+ content = content[:97] + "..."
248
+ formatted_list.append(f"{i}. [{sector}] {content}")
249
+
250
+ return [
251
+ types.TextContent(
252
+ type="text",
253
+ text=f"๐Ÿ“‹ Recent memories ({len(matches)}):\n\n"
254
+ + "\n".join(formatted_list),
255
+ )
256
+ ]
257
+
258
+ elif name == "delete_memory":
259
+ # Delete memory by ID
260
+ memory_id = arguments.get("memory_id")
261
+
262
+ try:
263
+ response = await client.delete(
264
+ f"{OPENMEMORY_URL}/memory/{memory_id}", headers=headers
265
+ )
266
+ response.raise_for_status()
267
+ result = response.json()
268
+ status = result.get("ok", "deleted")
269
+ except httpx.HTTPStatusError as e:
270
+ # Workaround: OpenMemory returns 500 on successful delete (bug in OpenMemory)
271
+ # We'll treat it as success since the memory is actually deleted
272
+ if e.response.status_code == 500:
273
+ status = "deleted (500 ignored - OpenMemory bug)"
274
+ else:
275
+ raise
276
+
277
+ return [
278
+ types.TextContent(
279
+ type="text",
280
+ text=f"๐Ÿ—‘๏ธ Memory deleted successfully!\n\nID: {memory_id}\nStatus: {status}",
281
+ )
282
+ ]
283
+
284
+ elif name == "update_memory":
285
+ # Update memory by ID
286
+ memory_id = arguments.get("memory_id")
287
+ content = arguments.get("content")
288
+ tags = arguments.get("tags")
289
+ metadata = arguments.get("metadata")
290
+
291
+ payload = {}
292
+ if content:
293
+ payload["content"] = content
294
+ if tags:
295
+ payload["tags"] = tags
296
+ if metadata:
297
+ payload["metadata"] = metadata
298
+
299
+ response = await client.patch(
300
+ f"{OPENMEMORY_URL}/memory/{memory_id}",
301
+ json=payload,
302
+ headers=headers,
303
+ )
304
+ response.raise_for_status()
305
+ result = response.json()
306
+
307
+ return [
308
+ types.TextContent(
309
+ type="text",
310
+ text=f"โœ๏ธ Memory updated successfully!\n\nID: {result.get('id', memory_id)}",
311
+ )
312
+ ]
313
+
314
+ else:
315
+ raise ValueError(f"Unknown tool: {name}")
316
+
317
+ except httpx.HTTPStatusError as e:
318
+ return [
319
+ types.TextContent(
320
+ type="text",
321
+ text=f"โŒ API Error: {e.response.status_code}\n{e.response.text}",
322
+ )
323
+ ]
324
+ except Exception as e:
325
+ return [types.TextContent(type="text", text=f"โŒ Error: {str(e)}")]
326
+
327
+
328
+ async def main():
329
+ """Run the MCP server."""
330
+
331
+ async with stdio_server() as (read_stream, write_stream):
332
+ await server.run(
333
+ read_stream,
334
+ write_stream,
335
+ InitializationOptions(
336
+ server_name="cybermem",
337
+ server_version="0.1.0",
338
+ capabilities=server.get_capabilities(
339
+ notification_options=NotificationOptions(),
340
+ experimental_capabilities={},
341
+ ),
342
+ ),
343
+ )
344
+
345
+
346
+ if __name__ == "__main__":
347
+ asyncio.run(main())