@cybermem/mcp 0.6.8 → 0.8.1
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/dist/auth.js +238 -0
- package/dist/index.js +188 -182
- package/package.json +5 -4
- package/src/auth.ts +244 -0
- package/src/index.ts +232 -195
- package/src/openmemory-js.d.ts +23 -0
package/dist/auth.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CyberMem MCP Auth Module
|
|
4
|
+
*
|
|
5
|
+
* Token storage and browser-based OAuth login flow.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.getToken = getToken;
|
|
42
|
+
exports.isLoggedIn = isLoggedIn;
|
|
43
|
+
exports.getUserInfo = getUserInfo;
|
|
44
|
+
exports.logout = logout;
|
|
45
|
+
exports.showStatus = showStatus;
|
|
46
|
+
exports.login = login;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const http = __importStar(require("http"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const AUTH_DIR = path.join(os.homedir(), ".cybermem");
|
|
52
|
+
const TOKEN_FILE = path.join(AUTH_DIR, "token.json");
|
|
53
|
+
const AUTH_URL = process.env.CYBERMEM_AUTH_URL || "https://cybermem.dev";
|
|
54
|
+
/**
|
|
55
|
+
* Ensure the .cybermem directory exists
|
|
56
|
+
*/
|
|
57
|
+
function ensureAuthDir() {
|
|
58
|
+
if (!fs.existsSync(AUTH_DIR)) {
|
|
59
|
+
fs.mkdirSync(AUTH_DIR, { recursive: true, mode: 0o700 });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get stored token if valid
|
|
64
|
+
*/
|
|
65
|
+
function getToken() {
|
|
66
|
+
try {
|
|
67
|
+
if (!fs.existsSync(TOKEN_FILE)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const data = JSON.parse(fs.readFileSync(TOKEN_FILE, "utf-8"));
|
|
71
|
+
// Check expiration
|
|
72
|
+
if (new Date(data.expires_at) < new Date()) {
|
|
73
|
+
console.error("Token expired, please run --login");
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return data.access_token;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if user is logged in with valid token
|
|
84
|
+
*/
|
|
85
|
+
function isLoggedIn() {
|
|
86
|
+
return getToken() !== null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get user info from stored token
|
|
90
|
+
*/
|
|
91
|
+
function getUserInfo() {
|
|
92
|
+
try {
|
|
93
|
+
if (!fs.existsSync(TOKEN_FILE)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const data = JSON.parse(fs.readFileSync(TOKEN_FILE, "utf-8"));
|
|
97
|
+
return { email: data.email, name: data.name };
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Save token to disk
|
|
105
|
+
*/
|
|
106
|
+
function saveToken(token, expiresIn, email, name) {
|
|
107
|
+
ensureAuthDir();
|
|
108
|
+
const expiresAt = new Date(Date.now() + expiresIn * 1000);
|
|
109
|
+
const data = {
|
|
110
|
+
access_token: token,
|
|
111
|
+
expires_at: expiresAt.toISOString(),
|
|
112
|
+
email,
|
|
113
|
+
name,
|
|
114
|
+
};
|
|
115
|
+
fs.writeFileSync(TOKEN_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Remove stored token
|
|
119
|
+
*/
|
|
120
|
+
function logout() {
|
|
121
|
+
if (fs.existsSync(TOKEN_FILE)) {
|
|
122
|
+
fs.unlinkSync(TOKEN_FILE);
|
|
123
|
+
console.log("✅ Logged out successfully");
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
console.log("Already logged out");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Show current auth status
|
|
131
|
+
*/
|
|
132
|
+
function showStatus() {
|
|
133
|
+
const token = getToken();
|
|
134
|
+
const userInfo = getUserInfo();
|
|
135
|
+
if (token && userInfo) {
|
|
136
|
+
console.log("✅ Logged in as:", userInfo.email || userInfo.name || "Unknown");
|
|
137
|
+
if (userInfo.name)
|
|
138
|
+
console.log(" Name:", userInfo.name);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
console.log("❌ Not logged in");
|
|
142
|
+
console.log(" Run: npx @cybermem/mcp --login");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Start OAuth login flow
|
|
147
|
+
* Opens browser and waits for callback with token
|
|
148
|
+
*/
|
|
149
|
+
async function login() {
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
// Find available port
|
|
152
|
+
const server = http.createServer();
|
|
153
|
+
server.listen(0, "127.0.0.1", () => {
|
|
154
|
+
const address = server.address();
|
|
155
|
+
if (!address || typeof address === "string") {
|
|
156
|
+
reject(new Error("Failed to start callback server"));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const port = address.port;
|
|
160
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
161
|
+
const authUrl = `${AUTH_URL}/api/auth/signin?callbackUrl=${encodeURIComponent(`${AUTH_URL}/api/auth/cli/callback?redirect=${encodeURIComponent(callbackUrl)}`)}`;
|
|
162
|
+
console.log("🔐 Opening browser for GitHub login...");
|
|
163
|
+
console.log(` If browser doesn't open, visit: ${authUrl}`);
|
|
164
|
+
// Open browser
|
|
165
|
+
const open = async (url) => {
|
|
166
|
+
const { exec } = await import("child_process");
|
|
167
|
+
const cmd = process.platform === "darwin"
|
|
168
|
+
? `open "${url}"`
|
|
169
|
+
: process.platform === "win32"
|
|
170
|
+
? `start "${url}"`
|
|
171
|
+
: `xdg-open "${url}"`;
|
|
172
|
+
exec(cmd);
|
|
173
|
+
};
|
|
174
|
+
open(authUrl);
|
|
175
|
+
// Handle callback
|
|
176
|
+
server.on("request", async (req, res) => {
|
|
177
|
+
if (!req.url?.startsWith("/callback")) {
|
|
178
|
+
res.writeHead(404);
|
|
179
|
+
res.end("Not found");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
183
|
+
const token = url.searchParams.get("token");
|
|
184
|
+
if (!token) {
|
|
185
|
+
res.writeHead(400);
|
|
186
|
+
res.end("Missing token");
|
|
187
|
+
server.close();
|
|
188
|
+
reject(new Error("No token received"));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Decode token to get user info (JWT payload)
|
|
192
|
+
let email;
|
|
193
|
+
let name;
|
|
194
|
+
try {
|
|
195
|
+
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
|
|
196
|
+
email = payload.email;
|
|
197
|
+
name = payload.name;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Ignore decode errors
|
|
201
|
+
}
|
|
202
|
+
// Save token (30 days expiry)
|
|
203
|
+
saveToken(token, 30 * 24 * 60 * 60, email, name);
|
|
204
|
+
// Send success page
|
|
205
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
206
|
+
res.end(`
|
|
207
|
+
<!DOCTYPE html>
|
|
208
|
+
<html>
|
|
209
|
+
<head>
|
|
210
|
+
<title>CyberMem - Logged In</title>
|
|
211
|
+
<style>
|
|
212
|
+
body { font-family: system-ui; text-align: center; padding: 50px; background: #0a0a0a; color: #fff; }
|
|
213
|
+
h1 { color: #22c55e; }
|
|
214
|
+
.logo { font-size: 48px; margin-bottom: 20px; }
|
|
215
|
+
</style>
|
|
216
|
+
</head>
|
|
217
|
+
<body>
|
|
218
|
+
<div class="logo">🧠</div>
|
|
219
|
+
<h1>Successfully Logged In!</h1>
|
|
220
|
+
<p>You can close this window and return to your terminal.</p>
|
|
221
|
+
<p style="color: #888;">Logged in as: ${email || name || "Unknown"}</p>
|
|
222
|
+
</body>
|
|
223
|
+
</html>
|
|
224
|
+
`);
|
|
225
|
+
console.log("");
|
|
226
|
+
console.log("✅ Successfully logged in as:", email || name || "Unknown");
|
|
227
|
+
console.log(" Token saved to:", TOKEN_FILE);
|
|
228
|
+
server.close();
|
|
229
|
+
resolve();
|
|
230
|
+
});
|
|
231
|
+
// Timeout after 5 minutes
|
|
232
|
+
setTimeout(() => {
|
|
233
|
+
server.close();
|
|
234
|
+
reject(new Error("Login timeout - no callback received"));
|
|
235
|
+
}, 5 * 60 * 1000);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,35 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* CyberMem MCP Server
|
|
5
|
+
*
|
|
6
|
+
* MCP server for AI agents to interact with CyberMem memory system.
|
|
7
|
+
* Uses openmemory-js SDK directly (no HTTP, embedded SQLite).
|
|
8
|
+
*/
|
|
3
9
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
10
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
11
|
};
|
|
6
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const
|
|
8
|
-
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
13
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
9
14
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
10
|
-
const
|
|
11
|
-
const axios_1 = __importDefault(require("axios"));
|
|
15
|
+
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
12
16
|
const cors_1 = __importDefault(require("cors"));
|
|
13
17
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
14
18
|
const express_1 = __importDefault(require("express"));
|
|
19
|
+
const openmemory_js_1 = require("openmemory-js");
|
|
20
|
+
const zod_1 = require("zod");
|
|
21
|
+
const auth_js_1 = require("./auth.js");
|
|
15
22
|
dotenv_1.default.config();
|
|
16
|
-
//
|
|
23
|
+
// Handle CLI auth commands first
|
|
17
24
|
const args = process.argv.slice(2);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
if (args.includes("--login")) {
|
|
26
|
+
(0, auth_js_1.login)()
|
|
27
|
+
.then(() => process.exit(0))
|
|
28
|
+
.catch((err) => {
|
|
29
|
+
console.error("Login failed:", err.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
else if (args.includes("--logout")) {
|
|
34
|
+
(0, auth_js_1.logout)();
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
else if (args.includes("--status")) {
|
|
38
|
+
(0, auth_js_1.showStatus)();
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Continue with MCP server startup
|
|
43
|
+
startServer();
|
|
44
|
+
}
|
|
45
|
+
async function startServer() {
|
|
46
|
+
// Parse CLI args
|
|
47
|
+
const getArg = (name) => {
|
|
48
|
+
const idx = args.indexOf(name);
|
|
49
|
+
return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
|
|
50
|
+
};
|
|
51
|
+
const cliClientName = getArg("--client-name");
|
|
52
|
+
// Track client name per session (used in tags)
|
|
53
|
+
const currentClientName = cliClientName || "cybermem-mcp";
|
|
54
|
+
// Configure openmemory-js SDK data path
|
|
55
|
+
// Use ~/.cybermem/data/ so db-exporter can mount it
|
|
56
|
+
const homedir = process.env.HOME || process.env.USERPROFILE || "";
|
|
57
|
+
const dataDir = `${homedir}/.cybermem/data`;
|
|
58
|
+
process.env.OM_DB_PATH = `${dataDir}/openmemory.sqlite`;
|
|
59
|
+
// Ensure data directory exists
|
|
60
|
+
const fs = require("fs");
|
|
61
|
+
try {
|
|
62
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
// Initialize openmemory-js SDK (embedded SQLite)
|
|
66
|
+
const memory = new openmemory_js_1.Memory();
|
|
67
|
+
// CyberMem Agent Protocol - instructions sent to clients on handshake
|
|
68
|
+
const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
|
|
33
69
|
|
|
34
70
|
PROTOCOL:
|
|
35
71
|
1. On session start: call query_memory("user context profile") to load persona
|
|
@@ -48,182 +84,152 @@ INTEGRITY RULES:
|
|
|
48
84
|
- Sync before critical decisions
|
|
49
85
|
- Last-write-wins for conflicts
|
|
50
86
|
|
|
51
|
-
For full protocol: https://cybermem.dev/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
{
|
|
63
|
-
name: "add_memory",
|
|
64
|
-
description: "Store a new memory in CyberMem",
|
|
65
|
-
inputSchema: {
|
|
66
|
-
type: "object",
|
|
67
|
-
properties: {
|
|
68
|
-
content: { type: "string" },
|
|
69
|
-
user_id: { type: "string" },
|
|
70
|
-
tags: { type: "array", items: { type: "string" } },
|
|
71
|
-
},
|
|
72
|
-
required: ["content"],
|
|
87
|
+
For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
88
|
+
// Short protocol reminder for tool descriptions
|
|
89
|
+
const PROTOCOL_REMINDER = "CyberMem Protocol: Store FULL content (no summaries), always include tags [topic, year, source:client-name]. Query 'user context profile' on session start.";
|
|
90
|
+
// Create McpServer instance
|
|
91
|
+
const server = new mcp_js_1.McpServer({
|
|
92
|
+
name: "cybermem",
|
|
93
|
+
version: "0.8.0",
|
|
94
|
+
}, {
|
|
95
|
+
capabilities: {
|
|
96
|
+
tools: {},
|
|
97
|
+
resources: {},
|
|
73
98
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
99
|
+
instructions: CYBERMEM_INSTRUCTIONS,
|
|
100
|
+
});
|
|
101
|
+
// Register resources
|
|
102
|
+
server.registerResource("CyberMem Agent Protocol", "cybermem://protocol", {
|
|
103
|
+
description: "Instructions for AI agents using CyberMem memory system",
|
|
104
|
+
mimeType: "text/plain",
|
|
105
|
+
}, async () => ({
|
|
106
|
+
contents: [
|
|
107
|
+
{
|
|
108
|
+
uri: "cybermem://protocol",
|
|
109
|
+
mimeType: "text/plain",
|
|
110
|
+
text: CYBERMEM_INSTRUCTIONS,
|
|
83
111
|
},
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
{
|
|
88
|
-
|
|
112
|
+
],
|
|
113
|
+
}));
|
|
114
|
+
// Register tools using openmemory-js SDK
|
|
115
|
+
server.registerTool("add_memory", {
|
|
116
|
+
description: `Store a new memory in CyberMem. ${PROTOCOL_REMINDER}`,
|
|
117
|
+
inputSchema: zod_1.z.object({
|
|
118
|
+
content: zod_1.z
|
|
119
|
+
.string()
|
|
120
|
+
.describe("Full content with all details - NO truncation or summarization"),
|
|
121
|
+
user_id: zod_1.z.string().optional(),
|
|
122
|
+
tags: zod_1.z
|
|
123
|
+
.array(zod_1.z.string())
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Always include [topic, year, source:your-client-name]"),
|
|
126
|
+
}),
|
|
127
|
+
}, async (args) => {
|
|
128
|
+
// Add source tag automatically
|
|
129
|
+
const tags = args.tags || [];
|
|
130
|
+
if (!tags.some((t) => t.startsWith("source:"))) {
|
|
131
|
+
tags.push(`source:${currentClientName}`);
|
|
132
|
+
}
|
|
133
|
+
const result = await memory.add(args.content, {
|
|
134
|
+
user_id: args.user_id,
|
|
135
|
+
tags,
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
server.registerTool("query_memory", {
|
|
142
|
+
description: `Search for relevant memories. On session start, call query_memory("user context profile") first.`,
|
|
143
|
+
inputSchema: zod_1.z.object({
|
|
144
|
+
query: zod_1.z.string(),
|
|
145
|
+
k: zod_1.z.number().default(5),
|
|
146
|
+
}),
|
|
147
|
+
}, async (args) => {
|
|
148
|
+
const results = await memory.search(args.query, { limit: args.k });
|
|
149
|
+
return {
|
|
150
|
+
content: [{ type: "text", text: JSON.stringify(results) }],
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
server.registerTool("list_memories", {
|
|
89
154
|
description: "List recent memories",
|
|
90
|
-
inputSchema: {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
155
|
+
inputSchema: zod_1.z.object({
|
|
156
|
+
limit: zod_1.z.number().default(10),
|
|
157
|
+
}),
|
|
158
|
+
}, async (args) => {
|
|
159
|
+
// Use search with empty query to list recent
|
|
160
|
+
const results = await memory.search("", { limit: args.limit || 10 });
|
|
161
|
+
return {
|
|
162
|
+
content: [{ type: "text", text: JSON.stringify(results) }],
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
server.registerTool("delete_memory", {
|
|
99
166
|
description: "Delete a memory by ID",
|
|
100
|
-
inputSchema: {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
167
|
+
inputSchema: zod_1.z.object({
|
|
168
|
+
id: zod_1.z.string(),
|
|
169
|
+
}),
|
|
170
|
+
}, async (args) => {
|
|
171
|
+
// openmemory-js doesn't have delete by ID, use wipe for now
|
|
172
|
+
// TODO: Implement delete_by_id in SDK or via direct DB query
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
{
|
|
176
|
+
type: "text",
|
|
177
|
+
text: `Delete not yet implemented in SDK. Memory ID: ${args.id}`,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
server.registerTool("update_memory", {
|
|
110
183
|
description: "Update a memory by ID",
|
|
111
|
-
inputSchema: {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
},
|
|
119
|
-
required: ["id"],
|
|
120
|
-
},
|
|
121
|
-
}
|
|
122
|
-
];
|
|
123
|
-
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
124
|
-
tools,
|
|
125
|
-
}));
|
|
126
|
-
// Create axios instance
|
|
127
|
-
const apiClient = axios_1.default.create({
|
|
128
|
-
baseURL: API_URL,
|
|
129
|
-
headers: {
|
|
130
|
-
"Authorization": `Bearer ${API_KEY}`,
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
// Helper to get client with context
|
|
134
|
-
function getClient(customHeaders = {}) {
|
|
135
|
-
// Get client name from MCP protocol (sent during initialize) or fallback to CLI arg
|
|
136
|
-
const clientVersion = server.getClientVersion();
|
|
137
|
-
const clientName = customHeaders["X-Client-Name"] || clientVersion?.name || currentClientName;
|
|
138
|
-
return {
|
|
139
|
-
...apiClient,
|
|
140
|
-
get: (url, config) => apiClient.get(url, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
|
|
141
|
-
post: (url, data, config) => apiClient.post(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
|
|
142
|
-
put: (url, data, config) => apiClient.put(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
|
|
143
|
-
patch: (url, data, config) => apiClient.patch(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
|
|
144
|
-
delete: (url, config) => apiClient.delete(url, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
148
|
-
const { name, arguments: args } = request.params;
|
|
149
|
-
try {
|
|
150
|
-
switch (name) {
|
|
151
|
-
case "add_memory": {
|
|
152
|
-
const response = await getClient().post("/add", args);
|
|
153
|
-
return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
|
|
154
|
-
}
|
|
155
|
-
case "query_memory": {
|
|
156
|
-
const response = await getClient().post("/query", args);
|
|
157
|
-
return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
|
|
158
|
-
}
|
|
159
|
-
case "list_memories": {
|
|
160
|
-
const limit = args?.limit || 10;
|
|
161
|
-
const response = await getClient().get(`/all?l=${limit}`);
|
|
162
|
-
return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
|
|
163
|
-
}
|
|
164
|
-
case "delete_memory": {
|
|
165
|
-
const { id } = args;
|
|
166
|
-
await getClient().delete(`/${id}`);
|
|
167
|
-
return { content: [{ type: "text", text: `Memory ${id} deleted` }] };
|
|
168
|
-
}
|
|
169
|
-
case "update_memory": {
|
|
170
|
-
const { id, ...updates } = args;
|
|
171
|
-
const response = await getClient().patch(`/${id}`, updates);
|
|
172
|
-
return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
|
|
173
|
-
}
|
|
174
|
-
default:
|
|
175
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
184
|
+
inputSchema: zod_1.z.object({
|
|
185
|
+
id: zod_1.z.string(),
|
|
186
|
+
content: zod_1.z.string().optional(),
|
|
187
|
+
tags: zod_1.z.array(zod_1.z.string()).optional(),
|
|
188
|
+
}),
|
|
189
|
+
}, async (args) => {
|
|
190
|
+
// TODO: Implement update in SDK
|
|
179
191
|
return {
|
|
180
|
-
content: [
|
|
181
|
-
|
|
192
|
+
content: [
|
|
193
|
+
{
|
|
194
|
+
type: "text",
|
|
195
|
+
text: `Update not yet implemented in SDK. Memory ID: ${args.id}`,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
182
198
|
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
199
|
+
});
|
|
200
|
+
// Determine transport mode
|
|
201
|
+
const transportArg = args.find((arg) => arg === "--stdio" || arg === "--http");
|
|
202
|
+
const useHttp = transportArg === "--http" || args.includes("--port");
|
|
203
|
+
if (useHttp) {
|
|
204
|
+
// HTTP mode for testing/development
|
|
205
|
+
const port = parseInt(getArg("--port") || "3100", 10);
|
|
188
206
|
const app = (0, express_1.default)();
|
|
189
207
|
app.use((0, cors_1.default)());
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
currentClientName = clientName;
|
|
197
|
-
}
|
|
198
|
-
transport = new sse_js_1.SSEServerTransport("/messages", res);
|
|
199
|
-
await server.connect(transport);
|
|
208
|
+
app.use(express_1.default.json());
|
|
209
|
+
app.get("/health", (_req, res) => {
|
|
210
|
+
res.json({ ok: true, version: "0.8.0", mode: "sdk" });
|
|
211
|
+
});
|
|
212
|
+
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
213
|
+
sessionIdGenerator: () => crypto.randomUUID(),
|
|
200
214
|
});
|
|
201
|
-
app.
|
|
202
|
-
|
|
203
|
-
const clientName = req.headers["x-client-name"];
|
|
204
|
-
if (clientName) {
|
|
205
|
-
currentClientName = clientName;
|
|
206
|
-
}
|
|
207
|
-
if (transport) {
|
|
208
|
-
await transport.handlePostMessage(req, res);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
res.status(400).send("Session not established");
|
|
212
|
-
}
|
|
215
|
+
app.all("/mcp", async (req, res) => {
|
|
216
|
+
await transport.handleRequest(req, res, req.body);
|
|
213
217
|
});
|
|
214
|
-
app.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
+
app.all("/sse", async (req, res) => {
|
|
219
|
+
await transport.handleRequest(req, res, req.body);
|
|
220
|
+
});
|
|
221
|
+
server.connect(transport).then(() => {
|
|
222
|
+
app.listen(port, () => {
|
|
223
|
+
console.log(`CyberMem MCP (SDK mode) running on http://localhost:${port}`);
|
|
224
|
+
console.log("Health: /health | MCP: /mcp");
|
|
225
|
+
});
|
|
218
226
|
});
|
|
219
227
|
}
|
|
220
228
|
else {
|
|
229
|
+
// STDIO mode (default for MCP clients)
|
|
221
230
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
222
|
-
|
|
223
|
-
|
|
231
|
+
server.connect(transport).then(() => {
|
|
232
|
+
console.error("CyberMem MCP (SDK mode) connected via STDIO");
|
|
233
|
+
});
|
|
224
234
|
}
|
|
225
235
|
}
|
|
226
|
-
run().catch((error) => {
|
|
227
|
-
console.error("Fatal error running server:", error);
|
|
228
|
-
process.exit(1);
|
|
229
|
-
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cybermem/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CyberMem MCP Server
|
|
3
|
+
"version": "0.8.1",
|
|
4
|
+
"description": "CyberMem MCP Server - AI Memory with openmemory-js SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cybermem-mcp": "./dist/index.js"
|
|
@@ -36,10 +36,11 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
39
|
-
"axios": "^1.13.2",
|
|
40
39
|
"cors": "^2.8.5",
|
|
41
40
|
"dotenv": "^16.0.0",
|
|
42
|
-
"express": "^5.2.1"
|
|
41
|
+
"express": "^5.2.1",
|
|
42
|
+
"openmemory-js": "^1.3.2",
|
|
43
|
+
"zod": "^3.25.76"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@types/cors": "^2.8.19",
|