@cybermem/mcp 0.6.10 → 0.8.3
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/.dockerignore +8 -0
- package/Dockerfile +43 -0
- package/dist/auth.js +238 -0
- package/dist/index.js +226 -254
- package/package.json +5 -3
- package/src/auth.ts +244 -0
- package/src/index.ts +249 -268
- package/src/openmemory-js.d.ts +23 -0
package/.dockerignore
ADDED
package/Dockerfile
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
FROM node:18-alpine AS builder
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
# Install build dependencies for native modules (better-sqlite3)
|
|
5
|
+
RUN apk add --no-cache python3 make g++
|
|
6
|
+
|
|
7
|
+
# Copy package files
|
|
8
|
+
COPY package.json ./
|
|
9
|
+
# Install ALL dependencies (including devDeps for build if TS)
|
|
10
|
+
RUN npm install
|
|
11
|
+
|
|
12
|
+
# Copy source
|
|
13
|
+
COPY . .
|
|
14
|
+
|
|
15
|
+
# Build TypeScript
|
|
16
|
+
RUN npm run build
|
|
17
|
+
|
|
18
|
+
# Prune dev dependencies
|
|
19
|
+
RUN npm prune --production
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
FROM node:18-alpine AS runner
|
|
23
|
+
|
|
24
|
+
WORKDIR /app
|
|
25
|
+
# Install runtime dependencies for native modules if needed (usually just glibc/musl compatibility, but better-sqlite3 bundles binaries or needs rebuild.
|
|
26
|
+
# We copy node_modules from builder so native addons should work if arc matches.
|
|
27
|
+
# For cross-platform (building amd64 on arm64 or vice versa), we might need QEMU or specific handling.
|
|
28
|
+
# GitHub Actions runner is likely amd64, RPi is arm64.
|
|
29
|
+
# We rely on docker buildx (multi-arch) in CI.
|
|
30
|
+
|
|
31
|
+
# Copy built artifacts and deps
|
|
32
|
+
COPY --from=builder /app/dist ./dist
|
|
33
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
34
|
+
COPY --from=builder /app/package.json ./package.json
|
|
35
|
+
|
|
36
|
+
# Environment defaults
|
|
37
|
+
ENV NODE_ENV=production
|
|
38
|
+
ENV PORT=8080
|
|
39
|
+
|
|
40
|
+
EXPOSE 8080
|
|
41
|
+
|
|
42
|
+
# Start server in HTTP mode
|
|
43
|
+
CMD ["node", "dist/index.js", "--http", "--port", "8080"]
|
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
|
+
}
|