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