@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.
- package/README.md +1 -1
- package/dist/index.js +203 -28
- package/package.json +29 -28
- package/requirements.txt +2 -0
- package/server.py +347 -0
- package/src/index.ts +227 -0
- package/test_mcp.py +111 -0
- package/tsconfig.json +14 -0
- package/dist/commands/__tests__/backup.test.js +0 -75
- package/dist/commands/__tests__/restore.test.js +0 -70
- package/dist/commands/backup.js +0 -52
- package/dist/commands/deploy.js +0 -242
- package/dist/commands/init.js +0 -65
- package/dist/commands/restore.js +0 -62
- package/dist/templates/ansible/inventory/hosts.ini +0 -3
- package/dist/templates/ansible/playbooks/deploy-cybermem.yml +0 -71
- package/dist/templates/ansible/playbooks/stop-cybermem.yml +0 -17
- package/dist/templates/charts/cybermem/Chart.yaml +0 -6
- package/dist/templates/charts/cybermem/templates/dashboard-deployment.yaml +0 -29
- package/dist/templates/charts/cybermem/templates/dashboard-service.yaml +0 -20
- package/dist/templates/charts/cybermem/templates/openmemory-deployment.yaml +0 -40
- package/dist/templates/charts/cybermem/templates/openmemory-pvc.yaml +0 -10
- package/dist/templates/charts/cybermem/templates/openmemory-service.yaml +0 -13
- package/dist/templates/charts/cybermem/values-vps.yaml +0 -18
- package/dist/templates/charts/cybermem/values.yaml +0 -42
- package/dist/templates/docker-compose.yml +0 -236
- package/dist/templates/envs/local.example +0 -27
- package/dist/templates/envs/rpi.example +0 -27
- package/dist/templates/envs/vps.example +0 -25
- package/dist/templates/mcp-responder/Dockerfile +0 -6
- package/dist/templates/mcp-responder/server.js +0 -22
- package/dist/templates/monitoring/db_exporter/Dockerfile +0 -19
- package/dist/templates/monitoring/db_exporter/exporter.py +0 -313
- package/dist/templates/monitoring/db_exporter/requirements.txt +0 -2
- package/dist/templates/monitoring/grafana/dashboards/cybermem.json +0 -1088
- package/dist/templates/monitoring/grafana/provisioning/dashboards/default.yml +0 -12
- package/dist/templates/monitoring/grafana/provisioning/datasources/prometheus.yml +0 -9
- package/dist/templates/monitoring/log_exporter/Dockerfile +0 -13
- package/dist/templates/monitoring/log_exporter/exporter.py +0 -274
- package/dist/templates/monitoring/log_exporter/requirements.txt +0 -1
- package/dist/templates/monitoring/postgres_exporter/queries.yml +0 -22
- package/dist/templates/monitoring/prometheus/prometheus.yml +0 -22
- package/dist/templates/monitoring/traefik/dynamic/.gitkeep +0 -0
- package/dist/templates/monitoring/traefik/traefik.yml +0 -32
- package/dist/templates/monitoring/vector/vector.toml/vector.yaml +0 -77
- package/dist/templates/monitoring/vector/vector.yaml +0 -106
- package/dist/templates/openmemory/Dockerfile +0 -19
- package/templates/ansible/inventory/hosts.ini +0 -3
- package/templates/ansible/playbooks/deploy-cybermem.yml +0 -71
- package/templates/ansible/playbooks/stop-cybermem.yml +0 -17
- package/templates/charts/cybermem/Chart.yaml +0 -6
- package/templates/charts/cybermem/templates/dashboard-deployment.yaml +0 -29
- package/templates/charts/cybermem/templates/dashboard-service.yaml +0 -20
- package/templates/charts/cybermem/templates/openmemory-deployment.yaml +0 -40
- package/templates/charts/cybermem/templates/openmemory-pvc.yaml +0 -10
- package/templates/charts/cybermem/templates/openmemory-service.yaml +0 -13
- package/templates/charts/cybermem/values-vps.yaml +0 -18
- package/templates/charts/cybermem/values.yaml +0 -42
- package/templates/docker-compose.yml +0 -236
- package/templates/envs/local.example +0 -27
- package/templates/envs/rpi.example +0 -27
- package/templates/envs/vps.example +0 -25
- package/templates/mcp-responder/Dockerfile +0 -6
- package/templates/mcp-responder/server.js +0 -22
- package/templates/monitoring/db_exporter/Dockerfile +0 -19
- package/templates/monitoring/db_exporter/exporter.py +0 -313
- package/templates/monitoring/db_exporter/requirements.txt +0 -2
- package/templates/monitoring/grafana/dashboards/cybermem.json +0 -1088
- package/templates/monitoring/grafana/provisioning/dashboards/default.yml +0 -12
- package/templates/monitoring/grafana/provisioning/datasources/prometheus.yml +0 -9
- package/templates/monitoring/log_exporter/Dockerfile +0 -13
- package/templates/monitoring/log_exporter/exporter.py +0 -274
- package/templates/monitoring/log_exporter/requirements.txt +0 -1
- package/templates/monitoring/postgres_exporter/queries.yml +0 -22
- package/templates/monitoring/prometheus/prometheus.yml +0 -22
- package/templates/monitoring/traefik/dynamic/.gitkeep +0 -0
- package/templates/monitoring/traefik/traefik.yml +0 -32
- package/templates/monitoring/vector/vector.toml/vector.yaml +0 -77
- package/templates/monitoring/vector/vector.yaml +0 -106
- package/templates/openmemory/Dockerfile +0 -19
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# @cybermem/mcp
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
.
|
|
30
|
-
|
|
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.
|
|
4
|
-
"description": "CyberMem
|
|
5
|
-
"
|
|
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/
|
|
17
|
+
"directory": "packages/mcp"
|
|
10
18
|
},
|
|
11
|
-
"
|
|
12
|
-
|
|
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
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
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
|
-
"
|
|
38
|
-
"
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
39
|
+
"axios": "^1.13.2",
|
|
40
|
+
"cors": "^2.8.5",
|
|
39
41
|
"dotenv": "^16.0.0",
|
|
40
|
-
"
|
|
41
|
-
"inquirer": "^8.2.0",
|
|
42
|
-
"ora": "^5.4.1"
|
|
42
|
+
"express": "^5.2.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@types/
|
|
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"
|
package/requirements.txt
ADDED
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())
|