@lanonasis/oauth-client 1.2.2 → 1.2.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/dist/{browser-CUJNgghM.d.cts → api-key-storage-web-DannE11B.d.cts} +1 -39
- package/dist/{browser-CUJNgghM.d.ts → api-key-storage-web-DannE11B.d.ts} +1 -39
- package/dist/browser.cjs +80 -599
- package/dist/browser.d.cts +45 -1
- package/dist/browser.d.ts +45 -1
- package/dist/browser.mjs +80 -599
- package/dist/index.d.cts +41 -3
- package/dist/index.d.ts +41 -3
- package/package.json +2 -1
package/dist/browser.mjs
CHANGED
|
@@ -214,300 +214,8 @@ var DesktopOAuthFlow = class extends BaseOAuthFlow {
|
|
|
214
214
|
}
|
|
215
215
|
};
|
|
216
216
|
|
|
217
|
-
// src/client/mcp-client.ts
|
|
218
|
-
import
|
|
219
|
-
|
|
220
|
-
// src/storage/token-storage.ts
|
|
221
|
-
var TokenStorage = class {
|
|
222
|
-
constructor() {
|
|
223
|
-
this.storageKey = "lanonasis_mcp_tokens";
|
|
224
|
-
this.webEncryptionKeyStorage = "lanonasis_web_token_enc_key";
|
|
225
|
-
if (this.isNode()) {
|
|
226
|
-
try {
|
|
227
|
-
this.keytar = __require("keytar");
|
|
228
|
-
} catch (e) {
|
|
229
|
-
console.warn("Keytar not available - falling back to file storage");
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
async store(tokens) {
|
|
234
|
-
const tokensWithTimestamp = {
|
|
235
|
-
...tokens,
|
|
236
|
-
issued_at: Date.now()
|
|
237
|
-
};
|
|
238
|
-
const tokenString = JSON.stringify(tokensWithTimestamp);
|
|
239
|
-
if (this.isNode()) {
|
|
240
|
-
if (this.keytar) {
|
|
241
|
-
await this.keytar.setPassword("lanonasis-mcp", "tokens", tokenString);
|
|
242
|
-
} else {
|
|
243
|
-
await this.storeToFile(tokenString);
|
|
244
|
-
}
|
|
245
|
-
} else if (this.isElectron()) {
|
|
246
|
-
await window.electronAPI.secureStore.set(this.storageKey, tokensWithTimestamp);
|
|
247
|
-
} else if (this.isMobile()) {
|
|
248
|
-
await window.SecureStorage.set(this.storageKey, tokenString);
|
|
249
|
-
} else {
|
|
250
|
-
const encrypted = await this.encrypt(tokenString);
|
|
251
|
-
localStorage.setItem(this.storageKey, encrypted);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
async retrieve() {
|
|
255
|
-
let tokenString = null;
|
|
256
|
-
try {
|
|
257
|
-
if (this.isNode()) {
|
|
258
|
-
if (this.keytar) {
|
|
259
|
-
tokenString = await this.keytar.getPassword("lanonasis-mcp", "tokens");
|
|
260
|
-
}
|
|
261
|
-
if (!tokenString) {
|
|
262
|
-
tokenString = await this.retrieveFromFile();
|
|
263
|
-
}
|
|
264
|
-
} else if (this.isElectron()) {
|
|
265
|
-
const tokens = await window.electronAPI.secureStore.get(this.storageKey);
|
|
266
|
-
return tokens || null;
|
|
267
|
-
} else if (this.isMobile()) {
|
|
268
|
-
tokenString = await window.SecureStorage.get(this.storageKey);
|
|
269
|
-
} else {
|
|
270
|
-
const encrypted = localStorage.getItem(this.storageKey);
|
|
271
|
-
if (encrypted) {
|
|
272
|
-
tokenString = await this.decrypt(encrypted);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return tokenString ? JSON.parse(tokenString) : null;
|
|
276
|
-
} catch (error) {
|
|
277
|
-
console.error("Error retrieving tokens:", error);
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
async clear() {
|
|
282
|
-
if (this.isNode()) {
|
|
283
|
-
if (this.keytar) {
|
|
284
|
-
await this.keytar.deletePassword("lanonasis-mcp", "tokens");
|
|
285
|
-
}
|
|
286
|
-
await this.deleteFile();
|
|
287
|
-
} else if (this.isElectron()) {
|
|
288
|
-
await window.electronAPI.secureStore.delete(this.storageKey);
|
|
289
|
-
} else if (this.isMobile()) {
|
|
290
|
-
await window.SecureStorage.remove(this.storageKey);
|
|
291
|
-
} else {
|
|
292
|
-
localStorage.removeItem(this.storageKey);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
isTokenExpired(tokens) {
|
|
296
|
-
if (tokens.token_type === "api-key" || tokens.expires_in === 0) {
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
|
-
if (!tokens.expires_in) return false;
|
|
300
|
-
if (!tokens.issued_at) {
|
|
301
|
-
console.warn("Token missing issued_at timestamp, treating as expired");
|
|
302
|
-
return true;
|
|
303
|
-
}
|
|
304
|
-
const expiresAt = tokens.issued_at + tokens.expires_in * 1e3;
|
|
305
|
-
const now = Date.now();
|
|
306
|
-
return expiresAt - now < 3e5;
|
|
307
|
-
}
|
|
308
|
-
async storeToFile(tokenString) {
|
|
309
|
-
if (!this.isNode()) return;
|
|
310
|
-
const fs = __require("fs").promises;
|
|
311
|
-
const path = __require("path");
|
|
312
|
-
const os = __require("os");
|
|
313
|
-
const crypto2 = __require("crypto");
|
|
314
|
-
const configDir = path.join(os.homedir(), ".lanonasis");
|
|
315
|
-
const tokenFile = path.join(configDir, "mcp-tokens.enc");
|
|
316
|
-
await fs.mkdir(configDir, { recursive: true });
|
|
317
|
-
const key = this.getFileEncryptionKey();
|
|
318
|
-
const iv = crypto2.randomBytes(16);
|
|
319
|
-
const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
|
|
320
|
-
let encrypted = cipher.update(tokenString, "utf8", "hex");
|
|
321
|
-
encrypted += cipher.final("hex");
|
|
322
|
-
const authTag = cipher.getAuthTag().toString("hex");
|
|
323
|
-
const data = [iv.toString("hex"), authTag, encrypted].join(":");
|
|
324
|
-
await fs.writeFile(tokenFile, data, { mode: 384 });
|
|
325
|
-
}
|
|
326
|
-
async retrieveFromFile() {
|
|
327
|
-
if (!this.isNode()) return null;
|
|
328
|
-
const fs = __require("fs").promises;
|
|
329
|
-
const path = __require("path");
|
|
330
|
-
const os = __require("os");
|
|
331
|
-
const crypto2 = __require("crypto");
|
|
332
|
-
const tokenFile = path.join(os.homedir(), ".lanonasis", "mcp-tokens.enc");
|
|
333
|
-
try {
|
|
334
|
-
const data = await fs.readFile(tokenFile, "utf8");
|
|
335
|
-
const parts = data.split(":");
|
|
336
|
-
const key = this.getFileEncryptionKey();
|
|
337
|
-
if (parts.length === 3) {
|
|
338
|
-
const [ivHex, authTagHex, encrypted] = parts;
|
|
339
|
-
const iv = Buffer.from(ivHex, "hex");
|
|
340
|
-
const authTag = Buffer.from(authTagHex, "hex");
|
|
341
|
-
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
|
|
342
|
-
decipher.setAuthTag(authTag);
|
|
343
|
-
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
344
|
-
decrypted += decipher.final("utf8");
|
|
345
|
-
return decrypted;
|
|
346
|
-
}
|
|
347
|
-
if (parts.length === 2) {
|
|
348
|
-
const [ivHex, encrypted] = parts;
|
|
349
|
-
const iv = Buffer.from(ivHex, "hex");
|
|
350
|
-
const decipher = crypto2.createDecipheriv("aes-256-cbc", key, iv);
|
|
351
|
-
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
352
|
-
decrypted += decipher.final("utf8");
|
|
353
|
-
return decrypted;
|
|
354
|
-
}
|
|
355
|
-
throw new Error("Invalid encrypted token format");
|
|
356
|
-
} catch (error) {
|
|
357
|
-
return null;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
async deleteFile() {
|
|
361
|
-
if (!this.isNode()) return;
|
|
362
|
-
const fs = __require("fs").promises;
|
|
363
|
-
const path = __require("path");
|
|
364
|
-
const os = __require("os");
|
|
365
|
-
const tokenFile = path.join(os.homedir(), ".lanonasis", "mcp-tokens.enc");
|
|
366
|
-
try {
|
|
367
|
-
await fs.unlink(tokenFile);
|
|
368
|
-
} catch (error) {
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
getFileEncryptionKey() {
|
|
372
|
-
const crypto2 = __require("crypto");
|
|
373
|
-
const os = __require("os");
|
|
374
|
-
const machineId = os.hostname() + os.userInfo().username;
|
|
375
|
-
const salt = "lanonasis-mcp-oauth-2024";
|
|
376
|
-
return crypto2.pbkdf2Sync(machineId, salt, 1e5, 32, "sha256");
|
|
377
|
-
}
|
|
378
|
-
async encrypt(text) {
|
|
379
|
-
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
380
|
-
const encoder2 = new TextEncoder();
|
|
381
|
-
const data2 = encoder2.encode(text);
|
|
382
|
-
return this.base64Encode(data2);
|
|
383
|
-
}
|
|
384
|
-
const encoder = new TextEncoder();
|
|
385
|
-
const data = encoder.encode(text);
|
|
386
|
-
const passphrase = await this.getWebEncryptionKey();
|
|
387
|
-
const keyMaterial = await window.crypto.subtle.importKey(
|
|
388
|
-
"raw",
|
|
389
|
-
encoder.encode(passphrase),
|
|
390
|
-
"PBKDF2",
|
|
391
|
-
false,
|
|
392
|
-
["deriveBits", "deriveKey"]
|
|
393
|
-
);
|
|
394
|
-
const key = await window.crypto.subtle.deriveKey(
|
|
395
|
-
{
|
|
396
|
-
name: "PBKDF2",
|
|
397
|
-
salt: encoder.encode("lanonasis-token-salt"),
|
|
398
|
-
iterations: 1e5,
|
|
399
|
-
hash: "SHA-256"
|
|
400
|
-
},
|
|
401
|
-
keyMaterial,
|
|
402
|
-
{ name: "AES-GCM", length: 256 },
|
|
403
|
-
true,
|
|
404
|
-
["encrypt", "decrypt"]
|
|
405
|
-
);
|
|
406
|
-
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
|
407
|
-
const encrypted = await window.crypto.subtle.encrypt(
|
|
408
|
-
{ name: "AES-GCM", iv },
|
|
409
|
-
key,
|
|
410
|
-
data
|
|
411
|
-
);
|
|
412
|
-
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
413
|
-
combined.set(iv, 0);
|
|
414
|
-
combined.set(new Uint8Array(encrypted), iv.length);
|
|
415
|
-
return this.base64Encode(combined);
|
|
416
|
-
}
|
|
417
|
-
async decrypt(encrypted) {
|
|
418
|
-
if (typeof window === "undefined" || !window.crypto?.subtle) {
|
|
419
|
-
const bytes2 = this.base64Decode(encrypted);
|
|
420
|
-
const decoder2 = new TextDecoder();
|
|
421
|
-
return decoder2.decode(bytes2);
|
|
422
|
-
}
|
|
423
|
-
const bytes = this.base64Decode(encrypted);
|
|
424
|
-
const iv = bytes.slice(0, 12);
|
|
425
|
-
const data = bytes.slice(12);
|
|
426
|
-
const encoder = new TextEncoder();
|
|
427
|
-
const decoder = new TextDecoder();
|
|
428
|
-
const passphrase = await this.getWebEncryptionKey();
|
|
429
|
-
const keyMaterial = await window.crypto.subtle.importKey(
|
|
430
|
-
"raw",
|
|
431
|
-
encoder.encode(passphrase),
|
|
432
|
-
"PBKDF2",
|
|
433
|
-
false,
|
|
434
|
-
["deriveBits", "deriveKey"]
|
|
435
|
-
);
|
|
436
|
-
const key = await window.crypto.subtle.deriveKey(
|
|
437
|
-
{
|
|
438
|
-
name: "PBKDF2",
|
|
439
|
-
salt: encoder.encode("lanonasis-token-salt"),
|
|
440
|
-
iterations: 1e5,
|
|
441
|
-
hash: "SHA-256"
|
|
442
|
-
},
|
|
443
|
-
keyMaterial,
|
|
444
|
-
{ name: "AES-GCM", length: 256 },
|
|
445
|
-
true,
|
|
446
|
-
["encrypt", "decrypt"]
|
|
447
|
-
);
|
|
448
|
-
const decrypted = await window.crypto.subtle.decrypt(
|
|
449
|
-
{ name: "AES-GCM", iv },
|
|
450
|
-
key,
|
|
451
|
-
data
|
|
452
|
-
);
|
|
453
|
-
return decoder.decode(decrypted);
|
|
454
|
-
}
|
|
455
|
-
isNode() {
|
|
456
|
-
return !!(typeof process !== "undefined" && process.versions && process.versions.node && !this.isElectron());
|
|
457
|
-
}
|
|
458
|
-
isElectron() {
|
|
459
|
-
return typeof window !== "undefined" && window.electronAPI !== void 0;
|
|
460
|
-
}
|
|
461
|
-
isMobile() {
|
|
462
|
-
return typeof window !== "undefined" && window.SecureStorage !== void 0;
|
|
463
|
-
}
|
|
464
|
-
base64Encode(bytes) {
|
|
465
|
-
if (typeof btoa !== "undefined") {
|
|
466
|
-
let binary = "";
|
|
467
|
-
bytes.forEach((b) => {
|
|
468
|
-
binary += String.fromCharCode(b);
|
|
469
|
-
});
|
|
470
|
-
return btoa(binary);
|
|
471
|
-
}
|
|
472
|
-
if (typeof Buffer !== "undefined") {
|
|
473
|
-
return Buffer.from(bytes).toString("base64");
|
|
474
|
-
}
|
|
475
|
-
throw new Error("No base64 encoder available");
|
|
476
|
-
}
|
|
477
|
-
base64Decode(value) {
|
|
478
|
-
if (typeof atob !== "undefined") {
|
|
479
|
-
const binary = atob(value);
|
|
480
|
-
const bytes = new Uint8Array(binary.length);
|
|
481
|
-
for (let i = 0; i < binary.length; i++) {
|
|
482
|
-
bytes[i] = binary.charCodeAt(i);
|
|
483
|
-
}
|
|
484
|
-
return bytes;
|
|
485
|
-
}
|
|
486
|
-
if (typeof Buffer !== "undefined") {
|
|
487
|
-
return new Uint8Array(Buffer.from(value, "base64"));
|
|
488
|
-
}
|
|
489
|
-
throw new Error("No base64 decoder available");
|
|
490
|
-
}
|
|
491
|
-
async getWebEncryptionKey() {
|
|
492
|
-
const existing = typeof localStorage !== "undefined" ? localStorage.getItem(this.webEncryptionKeyStorage) : null;
|
|
493
|
-
if (existing) {
|
|
494
|
-
return existing;
|
|
495
|
-
}
|
|
496
|
-
let raw = "";
|
|
497
|
-
if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
|
|
498
|
-
const buf = new Uint8Array(32);
|
|
499
|
-
window.crypto.getRandomValues(buf);
|
|
500
|
-
raw = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
501
|
-
} else {
|
|
502
|
-
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "node";
|
|
503
|
-
raw = `${ua}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
|
|
504
|
-
}
|
|
505
|
-
if (typeof localStorage !== "undefined") {
|
|
506
|
-
localStorage.setItem(this.webEncryptionKeyStorage, raw);
|
|
507
|
-
}
|
|
508
|
-
return raw;
|
|
509
|
-
}
|
|
510
|
-
};
|
|
217
|
+
// src/client/mcp-client-browser.ts
|
|
218
|
+
import fetch3 from "cross-fetch";
|
|
511
219
|
|
|
512
220
|
// src/storage/token-storage-web.ts
|
|
513
221
|
var TokenStorageWeb = class {
|
|
@@ -647,123 +355,8 @@ var TokenStorageWeb = class {
|
|
|
647
355
|
}
|
|
648
356
|
};
|
|
649
357
|
|
|
650
|
-
// src/flows/terminal-flow.ts
|
|
651
|
-
import fetch2 from "cross-fetch";
|
|
652
|
-
var TerminalOAuthFlow = class extends BaseOAuthFlow {
|
|
653
|
-
constructor(config) {
|
|
654
|
-
super({
|
|
655
|
-
...config,
|
|
656
|
-
clientId: config.clientId || "lanonasis-mcp-cli"
|
|
657
|
-
});
|
|
658
|
-
this.pollInterval = 5;
|
|
659
|
-
}
|
|
660
|
-
async authenticate() {
|
|
661
|
-
try {
|
|
662
|
-
const deviceResponse = await this.requestDeviceCode();
|
|
663
|
-
this.displayInstructions(deviceResponse);
|
|
664
|
-
if (deviceResponse.verification_uri_complete) {
|
|
665
|
-
await this.openBrowser(deviceResponse.verification_uri_complete);
|
|
666
|
-
}
|
|
667
|
-
return await this.pollForToken(deviceResponse);
|
|
668
|
-
} catch (error) {
|
|
669
|
-
console.error("Authentication failed:", error);
|
|
670
|
-
throw error;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
async requestDeviceCode() {
|
|
674
|
-
const response = await fetch2(`${this.authBaseUrl}/oauth/device`, {
|
|
675
|
-
method: "POST",
|
|
676
|
-
headers: { "Content-Type": "application/json" },
|
|
677
|
-
body: JSON.stringify({
|
|
678
|
-
client_id: this.clientId,
|
|
679
|
-
scope: this.scope
|
|
680
|
-
})
|
|
681
|
-
});
|
|
682
|
-
if (!response.ok) {
|
|
683
|
-
const error = await response.json();
|
|
684
|
-
throw new Error(error.error_description || "Failed to request device code");
|
|
685
|
-
}
|
|
686
|
-
const data = await response.json();
|
|
687
|
-
this.pollInterval = data.interval || 5;
|
|
688
|
-
return data;
|
|
689
|
-
}
|
|
690
|
-
displayInstructions(response) {
|
|
691
|
-
console.log("\n\u{1F510} Lan Onasis Authentication Required\n");
|
|
692
|
-
console.log(`Please visit: ${response.verification_uri}`);
|
|
693
|
-
console.log(`Enter code: ${response.user_code}
|
|
694
|
-
`);
|
|
695
|
-
console.log("Or press Enter to open your browser automatically...");
|
|
696
|
-
}
|
|
697
|
-
async openBrowser(url) {
|
|
698
|
-
try {
|
|
699
|
-
const { default: open } = await import("open");
|
|
700
|
-
await Promise.race([
|
|
701
|
-
this.waitForEnter(),
|
|
702
|
-
new Promise((resolve) => setTimeout(resolve, 2e3))
|
|
703
|
-
]);
|
|
704
|
-
console.log("Opening browser...");
|
|
705
|
-
await open(url);
|
|
706
|
-
} catch (error) {
|
|
707
|
-
console.log("Please open the URL manually in your browser.");
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
waitForEnter() {
|
|
711
|
-
return new Promise((resolve) => {
|
|
712
|
-
if (process.stdin.isTTY) {
|
|
713
|
-
process.stdin.setRawMode(true);
|
|
714
|
-
process.stdin.resume();
|
|
715
|
-
process.stdin.once("data", () => {
|
|
716
|
-
process.stdin.setRawMode(false);
|
|
717
|
-
process.stdin.pause();
|
|
718
|
-
resolve();
|
|
719
|
-
});
|
|
720
|
-
} else {
|
|
721
|
-
resolve();
|
|
722
|
-
}
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
async pollForToken(deviceResponse) {
|
|
726
|
-
const startTime = Date.now();
|
|
727
|
-
const expiresAt = startTime + deviceResponse.expires_in * 1e3;
|
|
728
|
-
console.log("Waiting for authorization...");
|
|
729
|
-
while (Date.now() < expiresAt) {
|
|
730
|
-
await new Promise((resolve) => setTimeout(resolve, this.pollInterval * 1e3));
|
|
731
|
-
try {
|
|
732
|
-
const token = await this.checkDeviceCode(deviceResponse.device_code);
|
|
733
|
-
console.log("\u2705 Authorization successful!\n");
|
|
734
|
-
return token;
|
|
735
|
-
} catch (error) {
|
|
736
|
-
if (error.message === "authorization_pending") {
|
|
737
|
-
process.stdout.write(".");
|
|
738
|
-
} else if (error.message === "slow_down") {
|
|
739
|
-
this.pollInterval += 5;
|
|
740
|
-
} else {
|
|
741
|
-
throw error;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
throw new Error("Authorization timeout - please try again");
|
|
746
|
-
}
|
|
747
|
-
async checkDeviceCode(deviceCode) {
|
|
748
|
-
const response = await fetch2(`${this.authBaseUrl}/oauth/token`, {
|
|
749
|
-
method: "POST",
|
|
750
|
-
headers: { "Content-Type": "application/json" },
|
|
751
|
-
body: JSON.stringify({
|
|
752
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
753
|
-
device_code: deviceCode,
|
|
754
|
-
client_id: this.clientId
|
|
755
|
-
})
|
|
756
|
-
});
|
|
757
|
-
const data = await response.json();
|
|
758
|
-
if (!response.ok) {
|
|
759
|
-
throw new Error(data.error || "Token request failed");
|
|
760
|
-
}
|
|
761
|
-
return data;
|
|
762
|
-
}
|
|
763
|
-
};
|
|
764
|
-
|
|
765
358
|
// src/flows/apikey-flow.ts
|
|
766
|
-
import
|
|
359
|
+
import fetch2 from "cross-fetch";
|
|
767
360
|
var APIKeyFlow = class extends BaseOAuthFlow {
|
|
768
361
|
constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
|
|
769
362
|
super({
|
|
@@ -806,7 +399,7 @@ var APIKeyFlow = class extends BaseOAuthFlow {
|
|
|
806
399
|
*/
|
|
807
400
|
async validateAPIKey() {
|
|
808
401
|
try {
|
|
809
|
-
const response = await
|
|
402
|
+
const response = await fetch2(`${this.config.authBaseUrl}/api/v1/health`, {
|
|
810
403
|
headers: {
|
|
811
404
|
"x-api-key": this.apiKey
|
|
812
405
|
}
|
|
@@ -819,10 +412,9 @@ var APIKeyFlow = class extends BaseOAuthFlow {
|
|
|
819
412
|
}
|
|
820
413
|
};
|
|
821
414
|
|
|
822
|
-
// src/client/mcp-client.ts
|
|
415
|
+
// src/client/mcp-client-browser.ts
|
|
823
416
|
var MCPClient = class {
|
|
824
417
|
constructor(config = {}) {
|
|
825
|
-
// ← NEW: Track auth mode
|
|
826
418
|
this.ws = null;
|
|
827
419
|
this.eventSource = null;
|
|
828
420
|
this.accessToken = null;
|
|
@@ -832,8 +424,7 @@ var MCPClient = class {
|
|
|
832
424
|
autoRefresh: true,
|
|
833
425
|
...config
|
|
834
426
|
};
|
|
835
|
-
|
|
836
|
-
this.tokenStorage = config.tokenStorage || defaultStorage;
|
|
427
|
+
this.tokenStorage = config.tokenStorage || new TokenStorageWeb();
|
|
837
428
|
this.authMode = config.apiKey ? "apikey" : "oauth";
|
|
838
429
|
if (this.authMode === "apikey") {
|
|
839
430
|
this.authFlow = new APIKeyFlow(
|
|
@@ -841,11 +432,7 @@ var MCPClient = class {
|
|
|
841
432
|
config.authBaseUrl || "https://mcp.lanonasis.com"
|
|
842
433
|
);
|
|
843
434
|
} else {
|
|
844
|
-
|
|
845
|
-
this.authFlow = new TerminalOAuthFlow(config);
|
|
846
|
-
} else {
|
|
847
|
-
this.authFlow = new DesktopOAuthFlow(config);
|
|
848
|
-
}
|
|
435
|
+
this.authFlow = new DesktopOAuthFlow(config);
|
|
849
436
|
}
|
|
850
437
|
}
|
|
851
438
|
async connect() {
|
|
@@ -871,186 +458,124 @@ var MCPClient = class {
|
|
|
871
458
|
}
|
|
872
459
|
this.accessToken = tokens.access_token;
|
|
873
460
|
if (this.config.autoRefresh && tokens.expires_in) {
|
|
874
|
-
this.scheduleTokenRefresh(tokens);
|
|
461
|
+
this.scheduleTokenRefresh(tokens.expires_in);
|
|
875
462
|
}
|
|
876
463
|
}
|
|
877
464
|
await this.establishConnection();
|
|
878
465
|
} catch (error) {
|
|
879
|
-
console.error("
|
|
466
|
+
console.error("MCP connection failed:", error);
|
|
880
467
|
throw error;
|
|
881
468
|
}
|
|
882
469
|
}
|
|
883
470
|
async authenticate() {
|
|
884
|
-
console.log("Authenticating with Lan Onasis...");
|
|
885
471
|
const tokens = await this.authFlow.authenticate();
|
|
886
472
|
await this.tokenStorage.store(tokens);
|
|
887
473
|
return tokens;
|
|
888
474
|
}
|
|
889
475
|
async ensureAccessToken() {
|
|
890
|
-
if (this.accessToken)
|
|
891
|
-
|
|
892
|
-
if (!tokens) {
|
|
893
|
-
throw new Error("Not authenticated");
|
|
894
|
-
}
|
|
895
|
-
if (this.authMode === "apikey") {
|
|
896
|
-
this.accessToken = tokens.access_token;
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
if (this.tokenStorage.isTokenExpired(tokens)) {
|
|
900
|
-
if (tokens.refresh_token) {
|
|
901
|
-
try {
|
|
902
|
-
const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
|
|
903
|
-
await this.tokenStorage.store(newTokens);
|
|
904
|
-
this.accessToken = newTokens.access_token;
|
|
905
|
-
return;
|
|
906
|
-
} catch (error) {
|
|
907
|
-
console.error("Token refresh failed:", error);
|
|
908
|
-
throw new Error("Token expired and refresh failed");
|
|
909
|
-
}
|
|
910
|
-
} else {
|
|
911
|
-
throw new Error("Token expired and no refresh token available");
|
|
912
|
-
}
|
|
476
|
+
if (!this.accessToken) {
|
|
477
|
+
await this.connect();
|
|
913
478
|
}
|
|
914
|
-
this.accessToken
|
|
479
|
+
return this.accessToken;
|
|
915
480
|
}
|
|
916
|
-
scheduleTokenRefresh(
|
|
481
|
+
scheduleTokenRefresh(expiresIn) {
|
|
482
|
+
const refreshTime = (expiresIn - 300) * 1e3;
|
|
917
483
|
if (this.refreshTimer) {
|
|
918
484
|
clearTimeout(this.refreshTimer);
|
|
919
485
|
}
|
|
920
|
-
const refreshIn = (tokens.expires_in - 300) * 1e3;
|
|
921
486
|
this.refreshTimer = setTimeout(async () => {
|
|
922
487
|
try {
|
|
923
|
-
|
|
488
|
+
const tokens = await this.tokenStorage.retrieve();
|
|
489
|
+
if (tokens?.refresh_token) {
|
|
924
490
|
const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
|
|
925
491
|
await this.tokenStorage.store(newTokens);
|
|
926
492
|
this.accessToken = newTokens.access_token;
|
|
927
|
-
|
|
928
|
-
|
|
493
|
+
if (newTokens.expires_in) {
|
|
494
|
+
this.scheduleTokenRefresh(newTokens.expires_in);
|
|
495
|
+
}
|
|
929
496
|
}
|
|
930
497
|
} catch (error) {
|
|
931
498
|
console.error("Token refresh failed:", error);
|
|
499
|
+
await this.connect();
|
|
932
500
|
}
|
|
933
|
-
},
|
|
501
|
+
}, refreshTime);
|
|
934
502
|
}
|
|
935
503
|
async establishConnection() {
|
|
936
504
|
const endpoint = this.config.mcpEndpoint;
|
|
937
|
-
if (endpoint.startsWith("wss://")) {
|
|
505
|
+
if (endpoint.startsWith("ws://") || endpoint.startsWith("wss://")) {
|
|
938
506
|
await this.connectWebSocket(endpoint);
|
|
939
|
-
} else if (endpoint.startsWith("https://")) {
|
|
940
|
-
await this.connectSSE(endpoint);
|
|
941
507
|
} else {
|
|
942
|
-
|
|
508
|
+
await this.connectSSE(endpoint);
|
|
943
509
|
}
|
|
944
510
|
}
|
|
945
511
|
async connectWebSocket(endpoint) {
|
|
946
|
-
const wsUrl = new URL(endpoint);
|
|
947
|
-
wsUrl.pathname = "/ws";
|
|
948
|
-
if (this.accessToken) {
|
|
949
|
-
wsUrl.searchParams.set("access_token", this.accessToken);
|
|
950
|
-
}
|
|
951
512
|
if (typeof WebSocket !== "undefined") {
|
|
952
|
-
this.ws = new WebSocket(
|
|
953
|
-
} else {
|
|
954
|
-
const { default: WS } = await import("ws");
|
|
955
|
-
if (this.authMode === "apikey") {
|
|
956
|
-
this.ws = new WS(wsUrl.toString(), {
|
|
957
|
-
headers: {
|
|
958
|
-
"x-api-key": this.accessToken
|
|
959
|
-
}
|
|
960
|
-
});
|
|
961
|
-
} else {
|
|
962
|
-
this.ws = new WS(wsUrl.toString(), {
|
|
963
|
-
headers: {
|
|
964
|
-
"Authorization": `Bearer ${this.accessToken}`
|
|
965
|
-
}
|
|
966
|
-
});
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
return new Promise((resolve, reject) => {
|
|
970
|
-
if (!this.ws) {
|
|
971
|
-
reject(new Error("WebSocket not initialized"));
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
513
|
+
this.ws = new WebSocket(endpoint);
|
|
974
514
|
this.ws.onopen = () => {
|
|
975
515
|
console.log("MCP WebSocket connected");
|
|
976
|
-
|
|
516
|
+
this.ws?.send(JSON.stringify({
|
|
517
|
+
type: "auth",
|
|
518
|
+
token: this.accessToken
|
|
519
|
+
}));
|
|
977
520
|
};
|
|
978
|
-
this.ws.
|
|
979
|
-
|
|
980
|
-
reject(error);
|
|
521
|
+
this.ws.onmessage = (event) => {
|
|
522
|
+
this.handleMessage(JSON.parse(event.data));
|
|
981
523
|
};
|
|
982
|
-
this.ws.
|
|
983
|
-
console.
|
|
984
|
-
if (event.code !== 1008 && event.code !== 4001) {
|
|
985
|
-
setTimeout(() => this.reconnect(), 5e3);
|
|
986
|
-
}
|
|
524
|
+
this.ws.onerror = (error) => {
|
|
525
|
+
console.error("MCP WebSocket error:", error);
|
|
987
526
|
};
|
|
988
|
-
this.ws.
|
|
989
|
-
|
|
527
|
+
this.ws.onclose = () => {
|
|
528
|
+
console.log("MCP WebSocket disconnected");
|
|
529
|
+
setTimeout(() => this.reconnect(), 5e3);
|
|
990
530
|
};
|
|
991
|
-
}
|
|
531
|
+
} else {
|
|
532
|
+
throw new Error("WebSocket is not available in this environment");
|
|
533
|
+
}
|
|
992
534
|
}
|
|
993
535
|
async connectSSE(endpoint) {
|
|
994
536
|
const sseUrl = new URL(endpoint);
|
|
995
|
-
sseUrl.pathname = "/sse";
|
|
996
537
|
if (typeof EventSource !== "undefined") {
|
|
997
|
-
this.eventSource = new EventSource(sseUrl.toString());
|
|
998
|
-
} else {
|
|
999
|
-
const EventSourceModule = await import("eventsource");
|
|
1000
|
-
const ES = EventSourceModule.default || EventSourceModule;
|
|
1001
538
|
if (this.authMode === "apikey") {
|
|
1002
|
-
|
|
1003
|
-
headers: {
|
|
1004
|
-
"x-api-key": this.accessToken
|
|
1005
|
-
}
|
|
1006
|
-
});
|
|
539
|
+
sseUrl.searchParams.set("api_key", this.accessToken);
|
|
1007
540
|
} else {
|
|
1008
|
-
|
|
1009
|
-
headers: {
|
|
1010
|
-
"Authorization": `Bearer ${this.accessToken}`
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
541
|
+
sseUrl.searchParams.set("token", this.accessToken);
|
|
1013
542
|
}
|
|
543
|
+
this.eventSource = new EventSource(sseUrl.toString());
|
|
544
|
+
this.eventSource.onopen = () => {
|
|
545
|
+
console.log("MCP SSE connected");
|
|
546
|
+
};
|
|
547
|
+
this.eventSource.onmessage = (event) => {
|
|
548
|
+
this.handleMessage(JSON.parse(event.data));
|
|
549
|
+
};
|
|
550
|
+
this.eventSource.onerror = () => {
|
|
551
|
+
console.error("MCP SSE error");
|
|
552
|
+
this.eventSource?.close();
|
|
553
|
+
setTimeout(() => this.reconnect(), 5e3);
|
|
554
|
+
};
|
|
555
|
+
} else {
|
|
556
|
+
throw new Error("EventSource is not available in this environment");
|
|
1014
557
|
}
|
|
1015
|
-
this.eventSource.onopen = () => {
|
|
1016
|
-
console.log("MCP SSE connected");
|
|
1017
|
-
};
|
|
1018
|
-
this.eventSource.onerror = (error) => {
|
|
1019
|
-
console.error("SSE error:", error);
|
|
1020
|
-
setTimeout(() => this.reconnect(), 5e3);
|
|
1021
|
-
};
|
|
1022
|
-
this.eventSource.onmessage = (event) => {
|
|
1023
|
-
this.handleMessage(event.data);
|
|
1024
|
-
};
|
|
1025
558
|
}
|
|
1026
|
-
handleMessage(
|
|
559
|
+
handleMessage(message) {
|
|
560
|
+
console.log("MCP message:", message);
|
|
561
|
+
}
|
|
562
|
+
async reconnect() {
|
|
1027
563
|
try {
|
|
1028
|
-
|
|
1029
|
-
console.log("MCP message:", message);
|
|
564
|
+
await this.connect();
|
|
1030
565
|
} catch (error) {
|
|
1031
|
-
console.error("
|
|
566
|
+
console.error("Reconnection failed:", error);
|
|
567
|
+
setTimeout(() => this.reconnect(), 1e4);
|
|
1032
568
|
}
|
|
1033
569
|
}
|
|
1034
|
-
async reconnect() {
|
|
1035
|
-
this.disconnect();
|
|
1036
|
-
await this.establishConnection();
|
|
1037
|
-
}
|
|
1038
570
|
async request(method, params) {
|
|
1039
571
|
await this.ensureAccessToken();
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
}
|
|
1043
|
-
const headers = {
|
|
1044
|
-
"Content-Type": "application/json"
|
|
1045
|
-
};
|
|
1046
|
-
if (this.authMode === "apikey") {
|
|
1047
|
-
headers["x-api-key"] = this.accessToken;
|
|
1048
|
-
} else {
|
|
1049
|
-
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
1050
|
-
}
|
|
1051
|
-
const response = await fetch4(`${this.config.mcpEndpoint}/api`, {
|
|
572
|
+
const endpoint = this.config.mcpEndpoint.replace(/^ws/, "http");
|
|
573
|
+
const response = await fetch3(`${endpoint}/rpc`, {
|
|
1052
574
|
method: "POST",
|
|
1053
|
-
headers
|
|
575
|
+
headers: {
|
|
576
|
+
"Content-Type": "application/json",
|
|
577
|
+
"Authorization": `Bearer ${this.accessToken}`
|
|
578
|
+
},
|
|
1054
579
|
body: JSON.stringify({
|
|
1055
580
|
jsonrpc: "2.0",
|
|
1056
581
|
id: this.generateId(),
|
|
@@ -1058,26 +583,11 @@ var MCPClient = class {
|
|
|
1058
583
|
params
|
|
1059
584
|
})
|
|
1060
585
|
});
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
}
|
|
1065
|
-
const tokens = await this.tokenStorage.retrieve();
|
|
1066
|
-
if (tokens?.refresh_token) {
|
|
1067
|
-
const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
|
|
1068
|
-
await this.tokenStorage.store(newTokens);
|
|
1069
|
-
this.accessToken = newTokens.access_token;
|
|
1070
|
-
return this.request(method, params);
|
|
1071
|
-
} else {
|
|
1072
|
-
await this.connect();
|
|
1073
|
-
return this.request(method, params);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
const result = await response.json();
|
|
1077
|
-
if (result.error) {
|
|
1078
|
-
throw new Error(result.error.message || "Request failed");
|
|
586
|
+
const data = await response.json();
|
|
587
|
+
if (data.error) {
|
|
588
|
+
throw new Error(data.error.message || "MCP request failed");
|
|
1079
589
|
}
|
|
1080
|
-
return
|
|
590
|
+
return data.result;
|
|
1081
591
|
}
|
|
1082
592
|
disconnect() {
|
|
1083
593
|
if (this.ws) {
|
|
@@ -1092,59 +602,30 @@ var MCPClient = class {
|
|
|
1092
602
|
clearTimeout(this.refreshTimer);
|
|
1093
603
|
this.refreshTimer = null;
|
|
1094
604
|
}
|
|
605
|
+
this.accessToken = null;
|
|
1095
606
|
}
|
|
1096
607
|
async logout() {
|
|
1097
|
-
const tokens = await this.tokenStorage.retrieve();
|
|
1098
|
-
if (tokens) {
|
|
1099
|
-
try {
|
|
1100
|
-
if (tokens.access_token) {
|
|
1101
|
-
await this.authFlow.revokeToken(tokens.access_token, "access_token");
|
|
1102
|
-
}
|
|
1103
|
-
if (tokens.refresh_token) {
|
|
1104
|
-
await this.authFlow.revokeToken(tokens.refresh_token, "refresh_token");
|
|
1105
|
-
}
|
|
1106
|
-
} catch (error) {
|
|
1107
|
-
console.error("Failed to revoke tokens:", error);
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
await this.tokenStorage.clear();
|
|
1111
608
|
this.disconnect();
|
|
1112
|
-
this.
|
|
1113
|
-
}
|
|
1114
|
-
isTerminal() {
|
|
1115
|
-
return !!(typeof process !== "undefined" && process.versions && process.versions.node && !this.isElectron());
|
|
1116
|
-
}
|
|
1117
|
-
isElectron() {
|
|
1118
|
-
return typeof window !== "undefined" && window.electronAPI !== void 0;
|
|
609
|
+
await this.tokenStorage.clear();
|
|
1119
610
|
}
|
|
1120
611
|
generateId() {
|
|
1121
|
-
return
|
|
612
|
+
return Math.random().toString(36).substring(2, 15);
|
|
1122
613
|
}
|
|
1123
|
-
//
|
|
614
|
+
// Convenience methods for memory operations
|
|
1124
615
|
async createMemory(title, content, options) {
|
|
1125
|
-
return this.request("
|
|
1126
|
-
title,
|
|
1127
|
-
content,
|
|
1128
|
-
...options
|
|
1129
|
-
});
|
|
616
|
+
return this.request("memories.create", { title, content, ...options || {} });
|
|
1130
617
|
}
|
|
1131
618
|
async searchMemories(query, options) {
|
|
1132
|
-
return this.request("
|
|
1133
|
-
query,
|
|
1134
|
-
...options
|
|
1135
|
-
});
|
|
619
|
+
return this.request("memories.search", { query, ...options || {} });
|
|
1136
620
|
}
|
|
1137
621
|
async getMemory(id) {
|
|
1138
|
-
return this.request("
|
|
622
|
+
return this.request("memories.get", { id });
|
|
1139
623
|
}
|
|
1140
624
|
async updateMemory(id, updates) {
|
|
1141
|
-
return this.request("
|
|
1142
|
-
id,
|
|
1143
|
-
...updates
|
|
1144
|
-
});
|
|
625
|
+
return this.request("memories.update", { id, updates });
|
|
1145
626
|
}
|
|
1146
627
|
async deleteMemory(id) {
|
|
1147
|
-
return this.request("
|
|
628
|
+
return this.request("memories.delete", { id });
|
|
1148
629
|
}
|
|
1149
630
|
};
|
|
1150
631
|
|