toolcraft 0.0.314 → 0.0.316

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.
Files changed (22) hide show
  1. package/composition.json +3 -3
  2. package/dist/composition.json +3 -3
  3. package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
  4. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/README.md +40 -2
  5. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +1 -6
  6. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +6 -18
  7. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.d.ts +6 -0
  8. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/client-registration.js +148 -0
  9. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +112 -49
  10. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-auth-method.d.ts +2 -0
  11. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-auth-method.js +7 -0
  12. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +3 -1
  13. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +19 -9
  14. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/types.d.ts +36 -5
  15. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -1
  16. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +1 -0
  17. package/node_modules/tiny-http-mcp-server/package.json +1 -1
  18. package/node_modules/tiny-mcp-client/dist/index.d.ts +36 -5
  19. package/node_modules/tiny-mcp-client/dist/index.js +1312 -1095
  20. package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
  21. package/node_modules/toolcraft-schema/package.json +1 -1
  22. package/package.json +2 -2
@@ -3395,1184 +3395,1336 @@ var SubscriptionManager = class {
3395
3395
  }
3396
3396
  };
3397
3397
 
3398
- // ../mcp-oauth/dist/client/auth-store-session-store.js
3399
- import crypto from "node:crypto";
3400
- import path4 from "node:path";
3401
-
3402
- // ../auth-store/dist/encrypted-file-store.js
3403
- import { createCipheriv, createDecipheriv, randomBytes, randomUUID as randomUUID2, scrypt } from "node:crypto";
3404
- import { promises as fs } from "node:fs";
3405
- import { homedir, hostname, userInfo } from "node:os";
3406
- import path2 from "node:path";
3398
+ // ../mcp-oauth/dist/client/loopback-authorization.js
3399
+ import http from "node:http";
3407
3400
 
3408
- // ../auth-store/dist/error-codes.js
3409
- function hasOwnErrorCode(error, code) {
3410
- return error instanceof Error && Object.prototype.hasOwnProperty.call(error, "code") && error.code === code;
3401
+ // ../mcp-oauth/dist/client/authorization-state.js
3402
+ import crypto from "node:crypto";
3403
+ function createAuthorizationState(input) {
3404
+ const payload = {
3405
+ v: 1,
3406
+ n: crypto.randomBytes(16).toString("base64url"),
3407
+ i: input.issuer,
3408
+ r: input.requireIssuer
3409
+ };
3410
+ return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
3411
3411
  }
3412
-
3413
- // ../auth-store/dist/transaction-lock.js
3414
- import { randomUUID } from "node:crypto";
3415
- import path from "node:path";
3416
- async function withSecretStoreFileLock(fs2, lockDirectory, operation, options = {}) {
3417
- const timeoutMs = options.timeoutMs ?? 3e4;
3418
- if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 2147483647)
3419
- throw new Error("Invalid secret-store transaction lock timeout");
3420
- options.signal?.throwIfAborted();
3421
- const deadline = performance.now() + timeoutMs;
3422
- const directory = path.resolve(lockDirectory);
3423
- await assertLockDirectoryPath(fs2, directory);
3424
- await fs2.mkdir(directory, { recursive: true, mode: 448 });
3425
- await assertLockDirectoryPath(fs2, directory);
3426
- const name = `${process.pid}-${randomUUID()}.claim`;
3427
- const claimPath = path.join(directory, name);
3428
- const temporaryPath = `${claimPath}.tmp`;
3429
- let claimed = true;
3430
- let temporaryCreated = false;
3431
- let outcome;
3412
+ function parseAuthorizationState(value) {
3413
+ if (value === null || value.length === 0) {
3414
+ return null;
3415
+ }
3432
3416
  try {
3433
- try {
3434
- await fs2.writeFile(claimPath, JSON.stringify({ ticket: null }), { encoding: "utf8", flag: "wx", mode: 384 });
3435
- } catch (error) {
3436
- if (hasOwnErrorCode(error, "EEXIST"))
3437
- claimed = false;
3438
- throw error;
3439
- }
3440
- const existing = await readClaims(fs2, directory, name);
3441
- const ticket = existing.reduce((max, claim) => Math.max(max, claim.ticket ?? 0), 0) + 1;
3442
- if (!Number.isSafeInteger(ticket))
3443
- throw new Error("Secret-store transaction lock ticket overflow");
3444
- temporaryCreated = true;
3445
- try {
3446
- await fs2.writeFile(temporaryPath, JSON.stringify({ ticket }), { encoding: "utf8", flag: "wx", mode: 384 });
3447
- } catch (error) {
3448
- if (hasOwnErrorCode(error, "EEXIST"))
3449
- temporaryCreated = false;
3450
- throw error;
3451
- }
3452
- await fs2.rename(temporaryPath, claimPath);
3453
- temporaryCreated = false;
3454
- for (; ; ) {
3455
- options.signal?.throwIfAborted();
3456
- const peers = await readClaims(fs2, directory, name);
3457
- if (!peers.some((peer) => peer.ticket === null || peer.ticket < ticket || peer.ticket === ticket && peer.name < name))
3458
- break;
3459
- const remaining = deadline - performance.now();
3460
- if (remaining <= 0)
3461
- throw new Error("Timed out waiting for secret-store transaction lock");
3462
- await new Promise((resolve, reject) => {
3463
- const abort = () => {
3464
- clearTimeout(timer);
3465
- options.signal?.removeEventListener("abort", abort);
3466
- reject(options.signal?.reason);
3467
- };
3468
- const timer = setTimeout(() => {
3469
- options.signal?.removeEventListener("abort", abort);
3470
- resolve();
3471
- }, Math.min(10, remaining));
3472
- options.signal?.addEventListener("abort", abort, { once: true });
3473
- if (options.signal?.aborted)
3474
- abort();
3475
- });
3417
+ const decoded = Buffer.from(value, "base64url").toString("utf8");
3418
+ const parsed = JSON.parse(decoded);
3419
+ if (!isObjectRecord(parsed)) {
3420
+ return null;
3476
3421
  }
3477
- options.signal?.throwIfAborted();
3478
- outcome = { result: await operation() };
3479
- } catch (error) {
3480
- outcome = { error };
3481
- }
3482
- const cleanup = [];
3483
- for (const target of [...temporaryCreated ? [temporaryPath] : [], ...claimed ? [claimPath] : []]) {
3484
- try {
3485
- await fs2.unlink(target);
3486
- } catch (error) {
3487
- if (!hasOwnErrorCode(error, "ENOENT"))
3488
- cleanup.push(error);
3422
+ const version = getOwnEntry(parsed, "v");
3423
+ const nonce = getOwnEntry(parsed, "n");
3424
+ const issuer = getOwnEntry(parsed, "i");
3425
+ const requireIssuer = getOwnEntry(parsed, "r");
3426
+ if (version !== 1 || typeof nonce !== "string" || nonce.length === 0 || typeof issuer !== "string" || issuer.length === 0 || typeof requireIssuer !== "boolean") {
3427
+ return null;
3489
3428
  }
3429
+ return {
3430
+ issuer,
3431
+ requireIssuer
3432
+ };
3433
+ } catch {
3434
+ return null;
3490
3435
  }
3491
- if (cleanup.length)
3492
- throw new AggregateError([..."error" in outcome ? [outcome.error] : [], ...cleanup], "Secret-store transaction lock cleanup failed");
3493
- if ("error" in outcome)
3494
- throw outcome.error;
3495
- return outcome.result;
3496
3436
  }
3497
- async function assertNoSymbolicLink(fs2, target) {
3437
+ function isObjectRecord(value) {
3438
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3439
+ }
3440
+ function getOwnEntry(record2, key2) {
3441
+ return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
3442
+ }
3443
+
3444
+ // ../mcp-oauth/dist/client/loopback-authorization.js
3445
+ async function createLoopbackAuthorizationSession(options = {}) {
3446
+ options.signal?.throwIfAborted();
3447
+ const timeoutMs = options.timeoutMs ?? 12e4;
3448
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
3449
+ throw new Error("OAuth authorization timeoutMs must be a positive supported timer interval");
3450
+ const target = loopbackTarget(options);
3451
+ const server = options.createServer ? options.createServer() : http.createServer();
3452
+ const controller = new AbortController();
3453
+ let closed = false;
3454
+ let used = false;
3455
+ const callerAbort = () => controller.abort(options.signal?.reason);
3456
+ const teardown = () => {
3457
+ if (closed)
3458
+ return;
3459
+ closed = true;
3460
+ clearTimeout(timer);
3461
+ options.signal?.removeEventListener("abort", callerAbort);
3462
+ server.closeAllConnections?.();
3463
+ server.close();
3464
+ };
3465
+ const timer = setTimeout(() => controller.abort(new Error("OAuth authorization timed out")), timeoutMs);
3466
+ timer.unref?.();
3467
+ controller.signal.addEventListener("abort", teardown, { once: true });
3468
+ options.signal?.addEventListener("abort", callerAbort, { once: true });
3469
+ if (options.signal?.aborted)
3470
+ callerAbort();
3471
+ let port;
3498
3472
  try {
3499
- if ((await fs2.lstat(target)).isSymbolicLink())
3500
- throw new Error("Refusing secret-store transaction lock through symbolic link");
3473
+ port = await startServer(server, target.port, target.host, controller.signal);
3501
3474
  } catch (error) {
3502
- if (!hasOwnErrorCode(error, "ENOENT"))
3503
- throw error;
3504
- }
3505
- }
3506
- async function assertLockDirectoryPath(fs2, directory) {
3507
- const root = path.parse(directory).root;
3508
- const segments = directory.slice(root.length).split(path.sep).filter(Boolean);
3509
- let current = root;
3510
- for (const [index, segment] of segments.entries()) {
3511
- current = path.join(current, segment);
3512
- if (index > 0 || segments.length === 1)
3513
- await assertNoSymbolicLink(fs2, current);
3475
+ controller.abort(error);
3476
+ throw error;
3514
3477
  }
3515
- }
3516
- async function readClaims(fs2, directory, ownName) {
3517
- const claims = [];
3518
- for (const name of await fs2.readdir(directory)) {
3519
- if (name === ownName || !name.endsWith(".claim"))
3520
- continue;
3521
- const pidText = name.slice(0, name.indexOf("-"));
3522
- const pid = Number(pidText);
3523
- if (!Number.isSafeInteger(pid) || pid < 1 || String(pid) !== pidText)
3524
- throw new Error("Malformed secret-store transaction lock owner");
3525
- const target = path.join(directory, name);
3526
- await assertNoSymbolicLink(fs2, target);
3527
- let alive = true;
3528
- try {
3529
- process.kill(pid, 0);
3530
- } catch (error) {
3531
- alive = !hasOwnErrorCode(error, "ESRCH");
3532
- }
3533
- if (!alive) {
3478
+ const redirectUri = options.redirectUri ?? `http://127.0.0.1:${port}${target.callbackPath}`;
3479
+ return {
3480
+ redirectUri,
3481
+ async waitForCode(authorizationUrl) {
3482
+ controller.signal.throwIfAborted();
3483
+ if (used)
3484
+ throw new Error("OAuth authorization session has already been used");
3485
+ used = true;
3534
3486
  try {
3535
- await fs2.unlink(target);
3536
- } catch (error) {
3537
- if (!hasOwnErrorCode(error, "ENOENT"))
3538
- throw error;
3487
+ return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
3488
+ } finally {
3489
+ clearTimeout(timer);
3539
3490
  }
3540
- continue;
3541
- }
3542
- let ticket = null;
3543
- let raw;
3544
- try {
3545
- raw = await fs2.readFile(target, "utf8");
3546
- } catch (error) {
3547
- if (hasOwnErrorCode(error, "ENOENT"))
3548
- continue;
3549
- throw error;
3491
+ },
3492
+ close() {
3493
+ controller.abort(new Error("OAuth authorization session closed"));
3550
3494
  }
3495
+ };
3496
+ }
3497
+ function loopbackTarget(options) {
3498
+ if (options.redirectUri !== void 0) {
3499
+ let url;
3551
3500
  try {
3552
- const value = JSON.parse(raw);
3553
- if (value !== null && typeof value === "object" && "ticket" in value && typeof value.ticket === "number" && Number.isSafeInteger(value.ticket) && value.ticket > 0)
3554
- ticket = value.ticket;
3555
- } catch {
3501
+ url = new URL(options.redirectUri);
3502
+ } catch (cause) {
3503
+ throw new Error("Invalid OAuth loopback redirect URI", { cause });
3556
3504
  }
3557
- claims.push({ name, ticket });
3505
+ const forbiddenQuery = ["code", "state", "error", "error_description", "iss"];
3506
+ if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password || url.hash || url.port === "0" || forbiddenQuery.some((name) => url.searchParams.has(name)) || [...options.redirectUri].some((char) => char.codePointAt(0) <= 32) || options.callbackPath !== void 0 && options.callbackPath !== url.pathname)
3507
+ throw new Error("Invalid OAuth loopback redirect URI");
3508
+ return { port: url.port ? Number(url.port) : 80, host: url.hostname === "[::1]" ? "::1" : url.hostname, callbackPath: url.pathname };
3558
3509
  }
3559
- return claims;
3510
+ const callbackPath = options.callbackPath ?? "/callback";
3511
+ const parsed = new URL(callbackPath, "http://127.0.0.1");
3512
+ if (!callbackPath.startsWith("/") || parsed.origin !== "http://127.0.0.1" || parsed.pathname !== callbackPath || parsed.search || parsed.hash)
3513
+ throw new Error("Invalid OAuth loopback callback path");
3514
+ return { port: 0, host: "127.0.0.1", callbackPath };
3560
3515
  }
3561
-
3562
- // ../auth-store/dist/encrypted-file-store.js
3563
- var derivedKeyCache = /* @__PURE__ */ new Map();
3564
- var ENCRYPTION_ALGORITHM = "aes-256-gcm";
3565
- var ENCRYPTION_VERSION = 1;
3566
- var ENCRYPTION_KEY_BYTES = 32;
3567
- var ENCRYPTION_IV_BYTES = 12;
3568
- var ENCRYPTION_AUTH_TAG_BYTES = 16;
3569
- var ENCRYPTION_FILE_MODE = 384;
3570
- var EncryptedFileStore = class {
3571
- fs;
3572
- filePath;
3573
- symbolicLinkCheckStartPath;
3574
- salt;
3575
- getMachineIdentity;
3576
- getRandomBytes;
3577
- keyPromise = null;
3578
- throwOnInvalidDocument;
3579
- constructor(input) {
3580
- this.fs = input.fs ?? fs;
3581
- this.salt = input.salt;
3582
- if (input.filePath === void 0) {
3583
- const homeDirectory = (input.getHomeDirectory ?? homedir)();
3584
- const defaultDirectory = input.defaultDirectory ?? ".auth-store";
3585
- const defaultFileName = input.defaultFileName ?? "credentials.enc";
3586
- assertSafeDefaultDirectory(defaultDirectory);
3587
- assertSafeDefaultFileName(defaultFileName);
3588
- this.filePath = path2.join(homeDirectory, defaultDirectory, defaultFileName);
3589
- this.symbolicLinkCheckStartPath = resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory);
3590
- } else {
3591
- this.filePath = input.filePath;
3592
- this.symbolicLinkCheckStartPath = null;
3593
- }
3594
- this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
3595
- this.getRandomBytes = input.getRandomBytes ?? randomBytes;
3596
- this.throwOnInvalidDocument = input.throwOnInvalidDocument ?? false;
3597
- }
3598
- async get() {
3599
- await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
3600
- let rawDocument;
3601
- try {
3602
- rawDocument = await this.fs.readFile(this.filePath, "utf8");
3603
- } catch (error) {
3604
- if (isNotFoundError(error)) {
3605
- return null;
3606
- }
3607
- throw error;
3608
- }
3609
- const document = parseEncryptedDocument(rawDocument);
3610
- if (!document) {
3611
- if (this.throwOnInvalidDocument)
3612
- throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
3613
- return null;
3614
- }
3615
- const key2 = await this.getEncryptionKey();
3616
- try {
3617
- const iv = Buffer.from(document.iv, "base64");
3618
- const authTag = Buffer.from(document.authTag, "base64");
3619
- const ciphertext = Buffer.from(document.ciphertext, "base64");
3620
- if (iv.byteLength !== ENCRYPTION_IV_BYTES || authTag.byteLength !== ENCRYPTION_AUTH_TAG_BYTES) {
3621
- if (this.throwOnInvalidDocument)
3622
- throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
3623
- return null;
3624
- }
3625
- const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key2, iv);
3626
- decipher.setAuthTag(authTag);
3627
- const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
3628
- return plaintext.toString("utf8");
3629
- } catch {
3630
- if (this.throwOnInvalidDocument)
3631
- throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
3632
- return null;
3633
- }
3634
- }
3635
- async withLock(operation, options = {}) {
3636
- await this.assertCredentialPathHasNoSymbolicLinks(`${this.filePath}.lock`);
3637
- if (this.fs.readdir === void 0)
3638
- throw new Error("Secret-store transaction locks require filesystem readdir support");
3639
- return withSecretStoreFileLock(this.fs, `${this.filePath}.lock`, operation, options);
3640
- }
3641
- async set(value) {
3642
- await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
3643
- const key2 = await this.getEncryptionKey();
3644
- const iv = this.getRandomBytes(ENCRYPTION_IV_BYTES);
3645
- const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key2, iv);
3646
- const ciphertext = Buffer.concat([
3647
- cipher.update(value, "utf8"),
3648
- cipher.final()
3649
- ]);
3650
- const authTag = cipher.getAuthTag();
3651
- const document = {
3652
- version: ENCRYPTION_VERSION,
3653
- iv: iv.toString("base64"),
3654
- authTag: authTag.toString("base64"),
3655
- ciphertext: ciphertext.toString("base64")
3516
+ async function startServer(server, port, host, signal) {
3517
+ signal.throwIfAborted();
3518
+ return new Promise((resolve, reject) => {
3519
+ const cleanup = () => {
3520
+ server.off("error", handleError);
3521
+ signal.removeEventListener("abort", aborted);
3656
3522
  };
3657
- await this.fs.mkdir(path2.dirname(this.filePath), { recursive: true });
3658
- await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
3659
- const temporaryPath = `${this.filePath}.${process.pid}.${randomUUID2()}.tmp`;
3660
- let temporaryCreated = false;
3523
+ const handleError = (error) => {
3524
+ cleanup();
3525
+ reject(error);
3526
+ };
3527
+ const aborted = () => {
3528
+ cleanup();
3529
+ reject(signal.reason);
3530
+ };
3531
+ server.once("error", handleError);
3532
+ signal.addEventListener("abort", aborted, { once: true });
3661
3533
  try {
3662
- await this.assertCredentialPathHasNoSymbolicLinks(temporaryPath);
3663
- await this.fs.writeFile(temporaryPath, JSON.stringify(document), {
3664
- encoding: "utf8",
3665
- flag: "wx",
3666
- mode: ENCRYPTION_FILE_MODE
3534
+ server.listen(port, host, () => {
3535
+ cleanup();
3536
+ if (signal.aborted) {
3537
+ server.close();
3538
+ reject(signal.reason);
3539
+ return;
3540
+ }
3541
+ const address = server.address();
3542
+ if (address === null || typeof address === "string") {
3543
+ reject(new Error("OAuth listener has no TCP address"));
3544
+ return;
3545
+ }
3546
+ resolve(address.port);
3667
3547
  });
3668
- temporaryCreated = true;
3669
- await this.fs.chmod(temporaryPath, ENCRYPTION_FILE_MODE);
3670
- await this.fs.rename(temporaryPath, this.filePath);
3671
3548
  } catch (error) {
3672
- if (temporaryCreated || !isAlreadyExistsError(error)) {
3673
- await removeIfPresent(this.fs, temporaryPath).catch(() => void 0);
3674
- }
3675
- throw error;
3549
+ cleanup();
3550
+ reject(error);
3676
3551
  }
3677
- }
3678
- async delete() {
3679
- await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
3680
- try {
3681
- await this.fs.unlink(this.filePath);
3682
- } catch (error) {
3683
- if (!isNotFoundError(error)) {
3684
- throw error;
3552
+ });
3553
+ }
3554
+ function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath, signal) {
3555
+ signal.throwIfAborted();
3556
+ const expectedAuthorization = readExpectedAuthorizationCallback(authorizationUrl);
3557
+ return new Promise((resolve, reject) => {
3558
+ let settled = false;
3559
+ const settle = (fn) => {
3560
+ if (settled)
3561
+ return;
3562
+ settled = true;
3563
+ server.off("request", request);
3564
+ signal.removeEventListener("abort", aborted);
3565
+ fn();
3566
+ };
3567
+ const aborted = () => settle(() => reject(signal.reason));
3568
+ const request = (req, res) => {
3569
+ let url;
3570
+ try {
3571
+ url = new URL(req.url ?? "/", "http://127.0.0.1");
3572
+ } catch {
3573
+ res.writeHead(400);
3574
+ res.end("Invalid callback URL");
3575
+ return;
3685
3576
  }
3686
- }
3687
- }
3688
- async assertCredentialPathHasNoSymbolicLinks(targetPath) {
3689
- const resolvedPath = path2.resolve(targetPath);
3690
- const protectedPaths = getProtectedCredentialPaths(resolvedPath, this.symbolicLinkCheckStartPath);
3691
- for (const currentPath of protectedPaths) {
3577
+ if (url.pathname !== callbackPath) {
3578
+ res.writeHead(404);
3579
+ res.end("Not found");
3580
+ return;
3581
+ }
3582
+ const callbackParameters = {
3583
+ code: url.searchParams.get("code"),
3584
+ error: url.searchParams.get("error"),
3585
+ errorDescription: url.searchParams.get("error_description"),
3586
+ state: url.searchParams.get("state"),
3587
+ iss: url.searchParams.get("iss")
3588
+ };
3692
3589
  try {
3693
- const stats = await this.fs.lstat(currentPath);
3694
- if (stats.isSymbolicLink()) {
3695
- throw new Error(`Refusing to use encrypted credential path through symbolic link: ${currentPath}`);
3696
- }
3590
+ validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
3591
+ if (callbackParameters.error !== null)
3592
+ throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
3593
+ const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
3594
+ res.writeHead(200, { "Content-Type": "text/html" });
3595
+ res.end(buildSuccessPage(options.landingPage));
3596
+ settle(() => resolve(code));
3697
3597
  } catch (error) {
3698
- if (isNotFoundError(error)) {
3699
- return;
3700
- }
3701
- throw error;
3598
+ res.writeHead(400);
3599
+ res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
3600
+ settle(() => reject(error));
3702
3601
  }
3602
+ };
3603
+ server.on("request", request);
3604
+ signal.addEventListener("abort", aborted, { once: true });
3605
+ if (options.readLine !== void 0) {
3606
+ void Promise.resolve().then(() => settled ? void 0 : options.readLine()).then((input) => {
3607
+ if (settled)
3608
+ return;
3609
+ const callbackParameters = extractCallbackParametersFromInput(input);
3610
+ if (callbackParameters === null)
3611
+ throw new Error("OAuth callback missing authorization code");
3612
+ validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
3613
+ if (callbackParameters.error !== null)
3614
+ throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
3615
+ const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
3616
+ settle(() => resolve(code));
3617
+ }).catch((error) => settle(() => reject(error)));
3703
3618
  }
3704
- }
3705
- getEncryptionKey() {
3706
- if (!this.keyPromise) {
3707
- const retryableKeyPromise = deriveEncryptionKey(this.getMachineIdentity, this.salt).catch((error) => {
3708
- if (this.keyPromise === retryableKeyPromise) {
3709
- this.keyPromise = null;
3710
- }
3711
- throw error;
3712
- });
3713
- this.keyPromise = retryableKeyPromise;
3619
+ if (options.openBrowser !== void 0) {
3620
+ void Promise.resolve().then(() => settled ? void 0 : options.openBrowser(authorizationUrl)).catch((error) => settle(() => reject(error)));
3714
3621
  }
3715
- return this.keyPromise;
3716
- }
3717
- };
3718
- function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
3719
- const [firstSegment] = defaultDirectory.split(/[\\/]+/).filter(Boolean);
3720
- return path2.resolve(homeDirectory, firstSegment ?? ".");
3721
- }
3722
- function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
3723
- if (symbolicLinkCheckStartPath === null) {
3724
- return getExplicitProtectedCredentialPaths(resolvedPath);
3725
- }
3726
- const resolvedStartPath = path2.resolve(symbolicLinkCheckStartPath);
3727
- if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
3728
- return [path2.dirname(resolvedPath), resolvedPath];
3729
- }
3730
- const protectedPaths = [resolvedStartPath];
3731
- let currentPath = resolvedStartPath;
3732
- for (const segment of path2.relative(resolvedStartPath, resolvedPath).split(path2.sep).filter(Boolean)) {
3733
- currentPath = path2.join(currentPath, segment);
3734
- protectedPaths.push(currentPath);
3735
- }
3736
- return protectedPaths;
3622
+ });
3737
3623
  }
3738
- function assertSafeDefaultDirectory(defaultDirectory) {
3739
- if (path2.isAbsolute(defaultDirectory) || path2.win32.isAbsolute(defaultDirectory)) {
3740
- throw new Error("defaultDirectory must be a relative path inside the home directory");
3624
+ function extractCallbackParametersFromInput(input) {
3625
+ const trimmed = input.replaceAll("\r", "").replaceAll("\n", "").trim();
3626
+ if (trimmed.length === 0) {
3627
+ return null;
3741
3628
  }
3742
- for (const segment of splitPathSegments(defaultDirectory)) {
3743
- if (segment === "..") {
3744
- throw new Error("defaultDirectory must be a relative path inside the home directory");
3745
- }
3746
- }
3747
- }
3748
- function assertSafeDefaultFileName(defaultFileName) {
3749
- if (defaultFileName.trim().length === 0 || defaultFileName === "." || defaultFileName === ".." || splitPathSegments(defaultFileName).length !== 1) {
3750
- throw new Error("defaultFileName must be a file name without path separators");
3751
- }
3752
- }
3753
- function splitPathSegments(value) {
3754
- return value.split("/").flatMap((segment) => segment.split("\\")).filter((segment) => segment.length > 0);
3755
- }
3756
- function getExplicitProtectedCredentialPaths(resolvedPath) {
3757
- const parsed = path2.parse(resolvedPath);
3758
- const segments = resolvedPath.slice(parsed.root.length).split(path2.sep).filter((segment) => segment.length > 0);
3759
- if (segments.length <= 1) {
3760
- return [resolvedPath];
3761
- }
3762
- const protectedPaths = [];
3763
- let currentPath = parsed.root;
3764
- for (const [index, segment] of segments.entries()) {
3765
- currentPath = path2.join(currentPath, segment);
3766
- if (index === 0) {
3767
- continue;
3768
- }
3769
- protectedPaths.push(currentPath);
3770
- }
3771
- return protectedPaths;
3772
- }
3773
- function isPathInsideOrEqual(childPath, parentPath) {
3774
- const relativePath = path2.relative(parentPath, childPath);
3775
- return relativePath === "" || !relativePath.startsWith("..") && !path2.isAbsolute(relativePath);
3776
- }
3777
- async function removeIfPresent(fileSystem, filePath) {
3778
3629
  try {
3779
- await fileSystem.unlink(filePath);
3780
- } catch (error) {
3781
- if (!isNotFoundError(error)) {
3782
- throw error;
3783
- }
3630
+ const url = new URL(trimmed);
3631
+ return {
3632
+ code: url.searchParams.get("code"),
3633
+ error: url.searchParams.get("error"),
3634
+ errorDescription: url.searchParams.get("error_description"),
3635
+ state: url.searchParams.get("state"),
3636
+ iss: url.searchParams.get("iss")
3637
+ };
3638
+ } catch {
3639
+ return {
3640
+ code: trimmed,
3641
+ error: null,
3642
+ errorDescription: null,
3643
+ state: null,
3644
+ iss: null
3645
+ };
3784
3646
  }
3785
3647
  }
3786
- function defaultMachineIdentity() {
3648
+ function readExpectedAuthorizationCallback(authorizationUrl) {
3649
+ const url = new URL(authorizationUrl);
3650
+ const state = url.searchParams.get("state");
3651
+ const parsedState = parseAuthorizationState(state);
3787
3652
  return {
3788
- hostname: hostname(),
3789
- username: userInfo().username
3653
+ state,
3654
+ issuer: parsedState?.issuer ?? null,
3655
+ requireIssuer: parsedState?.requireIssuer ?? false
3790
3656
  };
3791
3657
  }
3792
- async function deriveEncryptionKey(getMachineIdentity, salt) {
3793
- const machineIdentity = await getMachineIdentity();
3794
- const secret = `${machineIdentity.hostname}:${machineIdentity.username}`;
3795
- const cacheKey = JSON.stringify([machineIdentity.hostname, machineIdentity.username, salt]);
3796
- const cached = derivedKeyCache.get(cacheKey);
3797
- if (cached) {
3798
- return cached;
3658
+ function validateAuthorizationCallbackParameters(callback, expected) {
3659
+ validateAuthorizationCallbackBinding(callback, expected);
3660
+ if (callback.code === null || callback.code.length === 0) {
3661
+ throw new Error("OAuth callback missing authorization code");
3799
3662
  }
3800
- const keyPromise = new Promise((resolve, reject) => {
3801
- scrypt(secret, salt, ENCRYPTION_KEY_BYTES, (error, derivedKey) => {
3802
- if (error) {
3803
- reject(error);
3804
- return;
3805
- }
3806
- resolve(Buffer.from(derivedKey));
3807
- });
3808
- });
3809
- derivedKeyCache.set(cacheKey, keyPromise);
3810
- return keyPromise.catch((error) => {
3811
- if (derivedKeyCache.get(cacheKey) === keyPromise) {
3812
- derivedKeyCache.delete(cacheKey);
3813
- }
3814
- throw error;
3815
- });
3663
+ return callback.code;
3816
3664
  }
3817
- function parseEncryptedDocument(raw) {
3818
- try {
3819
- const parsed = JSON.parse(raw);
3820
- if (!isRecord2(parsed)) {
3821
- return null;
3665
+ function validateAuthorizationCallbackBinding(callback, expected) {
3666
+ if (expected.state !== null) {
3667
+ if (callback.state === null || callback.state.length === 0) {
3668
+ throw new Error("OAuth callback missing state");
3822
3669
  }
3823
- const version = getOwnEntry(parsed, "version");
3824
- const iv = getOwnEntry(parsed, "iv");
3825
- const authTag = getOwnEntry(parsed, "authTag");
3826
- const ciphertext = getOwnEntry(parsed, "ciphertext");
3827
- if (version !== ENCRYPTION_VERSION) {
3828
- return null;
3670
+ if (callback.state !== expected.state) {
3671
+ throw new Error("OAuth callback state mismatch");
3829
3672
  }
3830
- if (typeof iv !== "string" || typeof authTag !== "string" || typeof ciphertext !== "string") {
3831
- return null;
3673
+ }
3674
+ if (expected.requireIssuer) {
3675
+ if (callback.iss === null || callback.iss.length === 0) {
3676
+ throw new Error("OAuth callback missing issuer");
3832
3677
  }
3833
- return {
3834
- version,
3835
- iv,
3836
- authTag,
3837
- ciphertext
3838
- };
3839
- } catch {
3840
- return null;
3678
+ }
3679
+ if (callback.iss !== null && callback.iss.length > 0 && expected.issuer !== null && callback.iss !== expected.issuer) {
3680
+ throw new Error("OAuth callback issuer mismatch");
3841
3681
  }
3842
3682
  }
3843
- function isRecord2(value) {
3844
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
3683
+ function createAuthorizationError(error, description) {
3684
+ return new Error(`OAuth authorization failed: ${error} \u2014 ${description}`);
3845
3685
  }
3846
- function getOwnEntry(record2, key2) {
3847
- return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
3686
+ function escapeHtml(text) {
3687
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3848
3688
  }
3849
- function isNotFoundError(error) {
3850
- return hasOwnErrorCode(error, "ENOENT");
3689
+ function buildSuccessPage(landingPage) {
3690
+ const title = landingPage?.title ?? "Connected";
3691
+ const body = landingPage?.body ?? "You can close this tab and return to your terminal.";
3692
+ return [
3693
+ "<!DOCTYPE html>",
3694
+ `<html><head><meta charset=utf-8><title>${escapeHtml(title)}</title></head>`,
3695
+ '<body style="font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0">',
3696
+ '<div style="text-align:center">',
3697
+ `<h1>${escapeHtml(title)}</h1>`,
3698
+ `<p style="color:#666">${escapeHtml(body)}</p>`,
3699
+ "</div></body></html>"
3700
+ ].join("");
3851
3701
  }
3852
- function isAlreadyExistsError(error) {
3853
- return hasOwnErrorCode(error, "EEXIST");
3702
+
3703
+ // ../mcp-oauth/dist/client/scope.js
3704
+ function normalizeOAuthScope(scope) {
3705
+ if (scope === void 0)
3706
+ return void 0;
3707
+ if (typeof scope !== "string" || [...scope].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126 || char === '"' || char === "\\"))
3708
+ throw new Error("Invalid OAuth scope syntax");
3709
+ const normalized = [...new Set(scope.split(" ").filter(Boolean))].sort().join(" ");
3710
+ return normalized || void 0;
3854
3711
  }
3855
3712
 
3856
- // ../auth-store/dist/keychain-store.js
3857
- import { spawn } from "node:child_process";
3858
- import { createHash } from "node:crypto";
3859
- import { promises as nodeFs } from "node:fs";
3860
- import { homedir as homedir2 } from "node:os";
3861
- import path3 from "node:path";
3862
- var SECURITY_CLI = "security";
3863
- var KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE = 44;
3864
- var KeychainStore = class {
3865
- runCommand;
3866
- service;
3867
- account;
3868
- lockFs;
3869
- lockDirectory;
3870
- constructor(input) {
3871
- this.runCommand = input.runCommand ?? runSecurityCommand;
3872
- this.service = input.service.trim();
3873
- this.account = input.account.trim();
3874
- this.lockFs = input.lock?.fs ?? nodeFs;
3875
- this.lockDirectory = input.lock?.directory ?? path3.join(homedir2(), ".auth-store", "keychain-locks");
3876
- if (this.service.length === 0) {
3877
- throw new Error("Keychain service must not be empty");
3878
- }
3879
- if (this.account.length === 0) {
3880
- throw new Error("Keychain account must not be empty");
3881
- }
3882
- }
3883
- async get() {
3884
- const result = await this.executeSecurityCommand(["find-generic-password", "-s", this.service, "-a", this.account, "-w"], "read secret from macOS Keychain");
3885
- if (getCommandExitCode(result) === 0) {
3886
- return stripTrailingLineBreak(getCommandOutput(result, "stdout"));
3887
- }
3888
- if (isKeychainEntryNotFound(result)) {
3889
- return null;
3890
- }
3891
- throw createSecurityCliFailure("read secret from macOS Keychain", result);
3713
+ // ../mcp-oauth/dist/client/token-auth-method.js
3714
+ function normalizeOAuthTokenEndpointAuthMethod(value) {
3715
+ if (value === void 0 || value === null)
3716
+ return void 0;
3717
+ if (value !== "none" && value !== "client_secret_post" && value !== "client_secret_basic")
3718
+ throw new Error("Unsupported OAuth token endpoint authentication method");
3719
+ return value;
3720
+ }
3721
+
3722
+ // ../mcp-oauth/dist/client/client-registration.js
3723
+ function parseOAuthClientRegistration(value) {
3724
+ const invalid = () => new Error("Invalid OAuth client registration metadata");
3725
+ let nodes = 0;
3726
+ function copy(input, depth) {
3727
+ if (++nodes > 2e4 || depth > 64)
3728
+ throw invalid();
3729
+ if (input === null || typeof input === "boolean" || typeof input === "string")
3730
+ return input;
3731
+ if (typeof input === "number" && Number.isFinite(input))
3732
+ return input;
3733
+ if (typeof input !== "object" || input === null)
3734
+ throw invalid();
3735
+ const descriptors = Object.getOwnPropertyDescriptors(input);
3736
+ if (Array.isArray(input)) {
3737
+ const length = descriptors.length?.value;
3738
+ if (length > 2e4)
3739
+ throw invalid();
3740
+ const result2 = [];
3741
+ for (let index = 0; index < length; index++) {
3742
+ const descriptor = descriptors[String(index)];
3743
+ if (descriptor === void 0 || !Object.hasOwn(descriptor, "value"))
3744
+ throw invalid();
3745
+ result2.push(copy(descriptor.value, depth + 1));
3746
+ }
3747
+ return result2;
3748
+ }
3749
+ if (Object.getPrototypeOf(input) !== Object.prototype && Object.getPrototypeOf(input) !== null)
3750
+ throw invalid();
3751
+ return Object.fromEntries(Object.entries(descriptors).filter(([, descriptor]) => descriptor.enumerable).map(([key2, descriptor]) => {
3752
+ if (!Object.hasOwn(descriptor, "value"))
3753
+ throw invalid();
3754
+ return [key2, copy(descriptor.value, depth + 1)];
3755
+ }));
3756
+ }
3757
+ let result;
3758
+ try {
3759
+ result = copy(value, 0);
3760
+ } catch {
3761
+ throw invalid();
3762
+ }
3763
+ if (typeof result !== "object" || result === null || Array.isArray(result))
3764
+ throw invalid();
3765
+ const record2 = result;
3766
+ if (!Object.hasOwn(record2, "client_id") || typeof record2.client_id !== "string" || record2.client_id.trim() === "")
3767
+ throw new Error("OAuth client registration response missing client_id");
3768
+ for (const key2 of [
3769
+ "client_id",
3770
+ "client_secret",
3771
+ "token_endpoint_auth_method",
3772
+ "application_type",
3773
+ "client_name",
3774
+ "client_uri",
3775
+ "logo_uri",
3776
+ "scope",
3777
+ "tos_uri",
3778
+ "policy_uri",
3779
+ "jwks_uri",
3780
+ "software_id",
3781
+ "software_version",
3782
+ "software_statement",
3783
+ "registration_access_token",
3784
+ "registration_client_uri",
3785
+ "issuer"
3786
+ ]) {
3787
+ if (Object.hasOwn(record2, key2) && record2[key2] !== null && typeof record2[key2] !== "string")
3788
+ throw invalid();
3892
3789
  }
3893
- async withLock(operation, options = {}) {
3894
- const identity = createHash("sha256").update(JSON.stringify([this.service, this.account])).digest("hex");
3895
- return withSecretStoreFileLock(this.lockFs, path3.join(this.lockDirectory, identity), operation, options);
3790
+ if (typeof record2.client_secret === "string" && record2.client_secret.trim() === "")
3791
+ throw invalid();
3792
+ for (const key2 of ["redirect_uris", "grant_types", "response_types", "contacts"]) {
3793
+ const entry = record2[key2];
3794
+ if (Object.hasOwn(record2, key2) && entry !== null && (!Array.isArray(entry) || entry.some((item) => typeof item !== "string")))
3795
+ throw invalid();
3896
3796
  }
3897
- async set(value) {
3898
- if (value.includes("\n") || value.includes("\r")) {
3899
- throw new Error("Keychain secrets cannot contain line breaks");
3900
- }
3901
- const result = await this.executeSecurityCommand([
3902
- "add-generic-password",
3903
- "-s",
3904
- this.service,
3905
- "-a",
3906
- this.account,
3907
- "-U",
3908
- "-w",
3909
- value
3910
- ], "store secret in macOS Keychain");
3911
- if (getCommandExitCode(result) !== 0) {
3912
- throw createSecurityCliFailure("store secret in macOS Keychain", result);
3913
- }
3797
+ for (const key2 of ["client_id_issued_at", "client_secret_expires_at"]) {
3798
+ const entry = record2[key2];
3799
+ if (Object.hasOwn(record2, key2) && entry !== null && (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 0))
3800
+ throw invalid();
3914
3801
  }
3915
- async delete() {
3916
- const result = await this.executeSecurityCommand(["delete-generic-password", "-s", this.service, "-a", this.account], "delete secret from macOS Keychain");
3917
- if (getCommandExitCode(result) === 0 || isKeychainEntryNotFound(result)) {
3918
- return;
3919
- }
3920
- throw createSecurityCliFailure("delete secret from macOS Keychain", result);
3802
+ try {
3803
+ normalizeOAuthScope(Object.hasOwn(record2, "scope") && record2.scope !== null ? record2.scope : void 0);
3804
+ } catch {
3805
+ throw invalid();
3921
3806
  }
3922
- async executeSecurityCommand(args, operation, options) {
3807
+ if (Buffer.byteLength(JSON.stringify(record2), "utf8") > 64 * 1024)
3808
+ throw invalid();
3809
+ return record2;
3810
+ }
3811
+ function normalizeStoredOAuthClient(value) {
3812
+ if (typeof value !== "object" || value === null || Array.isArray(value))
3813
+ return null;
3814
+ const record2 = value;
3815
+ const clientId = Object.hasOwn(record2, "clientId") ? record2.clientId : void 0;
3816
+ const clientSecret = Object.hasOwn(record2, "clientSecret") ? record2.clientSecret : void 0;
3817
+ if (typeof clientId !== "string" || clientId.trim() === "" || clientSecret !== void 0 && (typeof clientSecret !== "string" || clientSecret.trim() === ""))
3818
+ return null;
3819
+ const client = { clientId: clientId.trim(), ...clientSecret === void 0 ? {} : { clientSecret: clientSecret.trim() } };
3820
+ const requestedRedirectUri = Object.hasOwn(record2, "requestedRedirectUri") ? record2.requestedRedirectUri : void 0;
3821
+ if (requestedRedirectUri !== void 0) {
3923
3822
  try {
3924
- if (options === void 0) {
3925
- return await this.runCommand(SECURITY_CLI, args);
3926
- }
3927
- return await this.runCommand(SECURITY_CLI, args, options);
3928
- } catch (error) {
3929
- const message = error instanceof Error ? error.message : String(error);
3930
- throw new Error(`Failed to ${operation}: ${message}`);
3823
+ if (typeof requestedRedirectUri !== "string")
3824
+ throw new Error("Invalid redirect identity");
3825
+ loopbackTarget({ redirectUri: requestedRedirectUri });
3826
+ } catch {
3827
+ throw new Error("Invalid stored OAuth registration redirect identity");
3828
+ }
3829
+ client.requestedRedirectUri = requestedRedirectUri;
3830
+ }
3831
+ const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record2, "tokenEndpointAuthMethod") ? record2.tokenEndpointAuthMethod : void 0);
3832
+ if (Object.hasOwn(record2, "registration") && record2.registration !== void 0) {
3833
+ const registration = parseOAuthClientRegistration(record2.registration);
3834
+ const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : void 0;
3835
+ if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
3836
+ throw new Error("OAuth client registration does not match the client identity");
3837
+ client.registration = registration;
3838
+ const registrationMethod = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(registration, "token_endpoint_auth_method") ? registration.token_endpoint_auth_method : void 0);
3839
+ if (method !== void 0 && registrationMethod !== void 0 && method !== registrationMethod)
3840
+ throw new Error("OAuth token endpoint authentication conflicts with the client registration");
3841
+ if (registrationMethod !== void 0)
3842
+ client.tokenEndpointAuthMethod = registrationMethod;
3843
+ }
3844
+ if (method !== void 0)
3845
+ client.tokenEndpointAuthMethod = method;
3846
+ return client;
3847
+ }
3848
+ function registrationMatchesRedirect(client, requestedUri, fresh = false) {
3849
+ if (client.requestedRedirectUri !== void 0)
3850
+ return client.requestedRedirectUri === requestedUri;
3851
+ const redirects = client.registration?.redirect_uris;
3852
+ if (redirects === void 0 || redirects === null || redirects.length === 0)
3853
+ return true;
3854
+ return redirects.some((returnedUri) => {
3855
+ if (returnedUri === requestedUri)
3856
+ return true;
3857
+ if (!fresh)
3858
+ return false;
3859
+ let returned, requested;
3860
+ try {
3861
+ returned = new URL(returnedUri);
3862
+ requested = new URL(requestedUri);
3863
+ } catch {
3864
+ return false;
3931
3865
  }
3932
- }
3933
- };
3934
- function runSecurityCommand(command, args, options) {
3935
- return new Promise((resolve) => {
3936
- const child = spawn(command, args, {
3937
- stdio: [options?.stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
3938
- });
3939
- let stdout = "";
3940
- let stderr = "";
3941
- let stdinErrorMessage;
3942
- const appendStderr = (message) => {
3943
- stderr = stderr.length === 0 ? message : `${stderr}${stderr.endsWith("\n") ? "" : "\n"}${message}`;
3944
- };
3945
- const appendStdinError = () => {
3946
- if (stdinErrorMessage === void 0) {
3947
- return;
3948
- }
3949
- appendStderr(stdinErrorMessage);
3950
- stdinErrorMessage = void 0;
3951
- };
3952
- child.stdout?.setEncoding("utf8");
3953
- child.stdout?.on("data", (chunk) => {
3954
- stdout += chunk.toString();
3955
- });
3956
- child.stderr?.setEncoding("utf8");
3957
- child.stderr?.on("data", (chunk) => {
3958
- stderr += chunk.toString();
3959
- });
3960
- if (options?.stdin !== void 0) {
3961
- child.stdin?.once("error", (error) => {
3962
- stdinErrorMessage = error instanceof Error ? error.message : String(error);
3963
- });
3964
- child.stdin?.end(options.stdin);
3965
- }
3966
- child.on("error", (error) => {
3967
- const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
3968
- appendStdinError();
3969
- appendStderr(message);
3970
- resolve({
3971
- stdout,
3972
- stderr,
3973
- exitCode: 127
3974
- });
3975
- });
3976
- child.on("close", (code) => {
3977
- appendStdinError();
3978
- resolve({
3979
- stdout,
3980
- stderr,
3981
- exitCode: code ?? 1
3982
- });
3983
- });
3866
+ if (requested.protocol !== "http:" || returned.protocol !== "http:" || !["127.0.0.1", "[::1]", "localhost"].includes(requested.hostname))
3867
+ return false;
3868
+ const sameHost = returned.hostname === requested.hostname;
3869
+ const normalizedIpv4 = requested.hostname === "127.0.0.1" && returned.hostname === "localhost" && returned.port === "";
3870
+ if (!sameHost && !normalizedIpv4)
3871
+ return false;
3872
+ returned.hostname = requested.hostname;
3873
+ returned.port = requested.port;
3874
+ return returned.href === requested.href;
3984
3875
  });
3985
3876
  }
3986
- function stripTrailingLineBreak(value) {
3987
- if (value.endsWith("\r\n")) {
3988
- return value.slice(0, -2);
3989
- }
3990
- if (value.endsWith("\n") || value.endsWith("\r")) {
3991
- return value.slice(0, -1);
3992
- }
3993
- return value;
3994
- }
3995
- function isKeychainEntryNotFound(result) {
3996
- return getCommandExitCode(result) === KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE;
3997
- }
3998
- function createSecurityCliFailure(operation, result) {
3999
- const exitCode = getCommandExitCode(result);
4000
- const details = getCommandOutput(result, "stderr").trim() || getCommandOutput(result, "stdout").trim();
4001
- if (details) {
4002
- return new Error(`Failed to ${operation}: security exited with code ${exitCode}: ${details}`);
4003
- }
4004
- return new Error(`Failed to ${operation}: security exited with code ${exitCode}`);
4005
- }
4006
- function getCommandExitCode(result) {
4007
- const value = getOwnEntry2(result, "exitCode");
4008
- return typeof value === "number" && Number.isInteger(value) ? value : 1;
4009
- }
4010
- function getCommandOutput(result, key2) {
4011
- const value = getOwnEntry2(result, key2);
4012
- return typeof value === "string" ? value : "";
4013
- }
4014
- function getOwnEntry2(record2, key2) {
4015
- return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
3877
+
3878
+ // ../mcp-oauth/dist/client/auth-store-session-store.js
3879
+ import crypto2 from "node:crypto";
3880
+ import path4 from "node:path";
3881
+
3882
+ // ../auth-store/dist/encrypted-file-store.js
3883
+ import { createCipheriv, createDecipheriv, randomBytes, randomUUID as randomUUID2, scrypt } from "node:crypto";
3884
+ import { promises as fs } from "node:fs";
3885
+ import { homedir, hostname, userInfo } from "node:os";
3886
+ import path2 from "node:path";
3887
+
3888
+ // ../auth-store/dist/error-codes.js
3889
+ function hasOwnErrorCode(error, code) {
3890
+ return error instanceof Error && Object.prototype.hasOwnProperty.call(error, "code") && error.code === code;
4016
3891
  }
4017
3892
 
4018
- // ../auth-store/dist/create-secret-store.js
4019
- var DEFAULT_BACKEND_ENV_VAR = "AUTH_BACKEND";
4020
- var MACOS_PLATFORM = "darwin";
4021
- var storeFactories = {
4022
- file: (input) => {
4023
- if (!input.fileStore) {
4024
- throw new Error("fileStore configuration is required for file backend");
3893
+ // ../auth-store/dist/transaction-lock.js
3894
+ import { randomUUID } from "node:crypto";
3895
+ import path from "node:path";
3896
+ async function withSecretStoreFileLock(fs2, lockDirectory, operation, options = {}) {
3897
+ const timeoutMs = options.timeoutMs ?? 3e4;
3898
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 2147483647)
3899
+ throw new Error("Invalid secret-store transaction lock timeout");
3900
+ options.signal?.throwIfAborted();
3901
+ const deadline = performance.now() + timeoutMs;
3902
+ const directory = path.resolve(lockDirectory);
3903
+ await assertLockDirectoryPath(fs2, directory);
3904
+ await fs2.mkdir(directory, { recursive: true, mode: 448 });
3905
+ await assertLockDirectoryPath(fs2, directory);
3906
+ const name = `${process.pid}-${randomUUID()}.claim`;
3907
+ const claimPath = path.join(directory, name);
3908
+ const temporaryPath = `${claimPath}.tmp`;
3909
+ let claimed = true;
3910
+ let temporaryCreated = false;
3911
+ let outcome;
3912
+ try {
3913
+ try {
3914
+ await fs2.writeFile(claimPath, JSON.stringify({ ticket: null }), { encoding: "utf8", flag: "wx", mode: 384 });
3915
+ } catch (error) {
3916
+ if (hasOwnErrorCode(error, "EEXIST"))
3917
+ claimed = false;
3918
+ throw error;
4025
3919
  }
4026
- return new EncryptedFileStore(input.fileStore);
4027
- },
4028
- keychain: (input) => {
4029
- if (!input.keychainStore) {
4030
- throw new Error("keychainStore configuration is required for keychain backend");
3920
+ const existing = await readClaims(fs2, directory, name);
3921
+ const ticket = existing.reduce((max, claim) => Math.max(max, claim.ticket ?? 0), 0) + 1;
3922
+ if (!Number.isSafeInteger(ticket))
3923
+ throw new Error("Secret-store transaction lock ticket overflow");
3924
+ temporaryCreated = true;
3925
+ try {
3926
+ await fs2.writeFile(temporaryPath, JSON.stringify({ ticket }), { encoding: "utf8", flag: "wx", mode: 384 });
3927
+ } catch (error) {
3928
+ if (hasOwnErrorCode(error, "EEXIST"))
3929
+ temporaryCreated = false;
3930
+ throw error;
4031
3931
  }
4032
- return new KeychainStore(input.keychainStore);
3932
+ await fs2.rename(temporaryPath, claimPath);
3933
+ temporaryCreated = false;
3934
+ for (; ; ) {
3935
+ options.signal?.throwIfAborted();
3936
+ const peers = await readClaims(fs2, directory, name);
3937
+ if (!peers.some((peer) => peer.ticket === null || peer.ticket < ticket || peer.ticket === ticket && peer.name < name))
3938
+ break;
3939
+ const remaining = deadline - performance.now();
3940
+ if (remaining <= 0)
3941
+ throw new Error("Timed out waiting for secret-store transaction lock");
3942
+ await new Promise((resolve, reject) => {
3943
+ const abort = () => {
3944
+ clearTimeout(timer);
3945
+ options.signal?.removeEventListener("abort", abort);
3946
+ reject(options.signal?.reason);
3947
+ };
3948
+ const timer = setTimeout(() => {
3949
+ options.signal?.removeEventListener("abort", abort);
3950
+ resolve();
3951
+ }, Math.min(10, remaining));
3952
+ options.signal?.addEventListener("abort", abort, { once: true });
3953
+ if (options.signal?.aborted)
3954
+ abort();
3955
+ });
3956
+ }
3957
+ options.signal?.throwIfAborted();
3958
+ outcome = { result: await operation() };
3959
+ } catch (error) {
3960
+ outcome = { error };
4033
3961
  }
4034
- };
4035
- function createSecretStore(input) {
4036
- const backend = resolveBackend(input);
4037
- const platform = input.platform ?? process.platform;
4038
- if (backend === "keychain" && platform !== MACOS_PLATFORM) {
4039
- throw new Error(`Keychain backend is only supported on macOS. Current platform: ${platform}`);
3962
+ const cleanup = [];
3963
+ for (const target of [...temporaryCreated ? [temporaryPath] : [], ...claimed ? [claimPath] : []]) {
3964
+ try {
3965
+ await fs2.unlink(target);
3966
+ } catch (error) {
3967
+ if (!hasOwnErrorCode(error, "ENOENT"))
3968
+ cleanup.push(error);
3969
+ }
4040
3970
  }
4041
- const store = storeFactories[backend](input);
4042
- return { backend, store };
3971
+ if (cleanup.length)
3972
+ throw new AggregateError([..."error" in outcome ? [outcome.error] : [], ...cleanup], "Secret-store transaction lock cleanup failed");
3973
+ if ("error" in outcome)
3974
+ throw outcome.error;
3975
+ return outcome.result;
4043
3976
  }
4044
- function resolveBackend(input) {
4045
- const envVar = input.backendEnvVar ?? DEFAULT_BACKEND_ENV_VAR;
4046
- const configuredBackend = input.backend ?? getOwnEnvValue(input.env, envVar) ?? getOwnEnvValue(process.env, envVar);
4047
- const backend = configuredBackend?.trim();
4048
- if (backend === "keychain") {
4049
- return "keychain";
4050
- }
4051
- if (backend === void 0 || backend === "file") {
4052
- return "file";
3977
+ async function assertNoSymbolicLink(fs2, target) {
3978
+ try {
3979
+ if ((await fs2.lstat(target)).isSymbolicLink())
3980
+ throw new Error("Refusing secret-store transaction lock through symbolic link");
3981
+ } catch (error) {
3982
+ if (!hasOwnErrorCode(error, "ENOENT"))
3983
+ throw error;
4053
3984
  }
4054
- throw new Error(`Unsupported auth store backend: ${backend}`);
4055
- }
4056
- function getOwnEnvValue(env, key2) {
4057
- return env !== void 0 && Object.prototype.hasOwnProperty.call(env, key2) ? env[key2] : void 0;
4058
3985
  }
4059
-
4060
- // ../mcp-oauth/dist/resource-indicator.js
4061
- function canonicalizeResourceIndicator(value) {
4062
- let url;
4063
- try {
4064
- url = value instanceof URL ? new URL(value.toString()) : new URL(value);
4065
- } catch {
4066
- throw new Error("Resource indicator must be an absolute URL");
3986
+ async function assertLockDirectoryPath(fs2, directory) {
3987
+ const root = path.parse(directory).root;
3988
+ const segments = directory.slice(root.length).split(path.sep).filter(Boolean);
3989
+ let current = root;
3990
+ for (const [index, segment] of segments.entries()) {
3991
+ current = path.join(current, segment);
3992
+ if (index > 0 || segments.length === 1)
3993
+ await assertNoSymbolicLink(fs2, current);
4067
3994
  }
4068
- url.hash = "";
4069
- return url.toString();
4070
3995
  }
4071
-
4072
- // ../mcp-oauth/dist/client/auth-store-session-store.js
4073
- var DEFAULT_FILE_SALT = "poe-code:mcp-oauth:v1";
4074
- var DEFAULT_FILE_DIRECTORY = ".poe-code/mcp-oauth";
4075
- var DEFAULT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth";
4076
- var DEFAULT_CLIENT_FILE_SALT = "poe-code:mcp-oauth:clients:v1";
4077
- var DEFAULT_CLIENT_FILE_DIRECTORY = ".poe-code/mcp-oauth/clients";
4078
- var DEFAULT_CLIENT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth-clients";
4079
- var MAX_JS_DATE_MS = 864e13;
4080
- function createAuthStoreSessionStore(options = {}, namespace) {
4081
- assertPersistenceNamespace(namespace);
4082
- return {
4083
- async withLock(resource, operation, lockOptions) {
4084
- const store = createResourceSecretStore(resource, options, namespace);
4085
- if (store.withLock === void 0)
4086
- throw new Error("OAuth secret-store backend does not support transaction locks");
4087
- return store.withLock(operation, lockOptions);
4088
- },
4089
- async load(resource) {
4090
- const store = createResourceSecretStore(resource, options, namespace);
4091
- const value = await store.get();
4092
- if (value === null) {
4093
- return null;
4094
- }
4095
- let parsed;
4096
- try {
4097
- parsed = JSON.parse(value);
4098
- } catch {
4099
- throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
4100
- }
4101
- if (isStoredOAuthSession(parsed)) {
4102
- return parsed;
3996
+ async function readClaims(fs2, directory, ownName) {
3997
+ const claims = [];
3998
+ for (const name of await fs2.readdir(directory)) {
3999
+ if (name === ownName || !name.endsWith(".claim"))
4000
+ continue;
4001
+ const pidText = name.slice(0, name.indexOf("-"));
4002
+ const pid = Number(pidText);
4003
+ if (!Number.isSafeInteger(pid) || pid < 1 || String(pid) !== pidText)
4004
+ throw new Error("Malformed secret-store transaction lock owner");
4005
+ const target = path.join(directory, name);
4006
+ await assertNoSymbolicLink(fs2, target);
4007
+ let alive = true;
4008
+ try {
4009
+ process.kill(pid, 0);
4010
+ } catch (error) {
4011
+ alive = !hasOwnErrorCode(error, "ESRCH");
4012
+ }
4013
+ if (!alive) {
4014
+ try {
4015
+ await fs2.unlink(target);
4016
+ } catch (error) {
4017
+ if (!hasOwnErrorCode(error, "ENOENT"))
4018
+ throw error;
4103
4019
  }
4104
- throw new Error("Stored OAuth session must match the expected shape");
4105
- },
4106
- async save(resource, session) {
4107
- const store = createResourceSecretStore(resource, options, namespace);
4108
- await store.set(JSON.stringify(session));
4109
- },
4110
- async clear(resource) {
4111
- const store = createResourceSecretStore(resource, options, namespace);
4112
- await store.delete();
4020
+ continue;
4113
4021
  }
4114
- };
4022
+ let ticket = null;
4023
+ let raw;
4024
+ try {
4025
+ raw = await fs2.readFile(target, "utf8");
4026
+ } catch (error) {
4027
+ if (hasOwnErrorCode(error, "ENOENT"))
4028
+ continue;
4029
+ throw error;
4030
+ }
4031
+ try {
4032
+ const value = JSON.parse(raw);
4033
+ if (value !== null && typeof value === "object" && "ticket" in value && typeof value.ticket === "number" && Number.isSafeInteger(value.ticket) && value.ticket > 0)
4034
+ ticket = value.ticket;
4035
+ } catch {
4036
+ }
4037
+ claims.push({ name, ticket });
4038
+ }
4039
+ return claims;
4115
4040
  }
4116
- function createAuthStoreClientStore(options, namespace) {
4117
- assertPersistenceNamespace(namespace);
4118
- return {
4119
- async load(issuer) {
4120
- const store = createIssuerSecretStore(issuer, options, namespace);
4121
- const value = await store.get();
4122
- if (value === null) {
4041
+
4042
+ // ../auth-store/dist/encrypted-file-store.js
4043
+ var derivedKeyCache = /* @__PURE__ */ new Map();
4044
+ var ENCRYPTION_ALGORITHM = "aes-256-gcm";
4045
+ var ENCRYPTION_VERSION = 1;
4046
+ var ENCRYPTION_KEY_BYTES = 32;
4047
+ var ENCRYPTION_IV_BYTES = 12;
4048
+ var ENCRYPTION_AUTH_TAG_BYTES = 16;
4049
+ var ENCRYPTION_FILE_MODE = 384;
4050
+ var EncryptedFileStore = class {
4051
+ fs;
4052
+ filePath;
4053
+ symbolicLinkCheckStartPath;
4054
+ salt;
4055
+ getMachineIdentity;
4056
+ getRandomBytes;
4057
+ keyPromise = null;
4058
+ throwOnInvalidDocument;
4059
+ constructor(input) {
4060
+ this.fs = input.fs ?? fs;
4061
+ this.salt = input.salt;
4062
+ if (input.filePath === void 0) {
4063
+ const homeDirectory = (input.getHomeDirectory ?? homedir)();
4064
+ const defaultDirectory = input.defaultDirectory ?? ".auth-store";
4065
+ const defaultFileName = input.defaultFileName ?? "credentials.enc";
4066
+ assertSafeDefaultDirectory(defaultDirectory);
4067
+ assertSafeDefaultFileName(defaultFileName);
4068
+ this.filePath = path2.join(homeDirectory, defaultDirectory, defaultFileName);
4069
+ this.symbolicLinkCheckStartPath = resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory);
4070
+ } else {
4071
+ this.filePath = input.filePath;
4072
+ this.symbolicLinkCheckStartPath = null;
4073
+ }
4074
+ this.getMachineIdentity = input.getMachineIdentity ?? defaultMachineIdentity;
4075
+ this.getRandomBytes = input.getRandomBytes ?? randomBytes;
4076
+ this.throwOnInvalidDocument = input.throwOnInvalidDocument ?? false;
4077
+ }
4078
+ async get() {
4079
+ await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
4080
+ let rawDocument;
4081
+ try {
4082
+ rawDocument = await this.fs.readFile(this.filePath, "utf8");
4083
+ } catch (error) {
4084
+ if (isNotFoundError(error)) {
4123
4085
  return null;
4124
4086
  }
4125
- let parsed;
4126
- try {
4127
- parsed = JSON.parse(value);
4128
- } catch {
4129
- throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
4087
+ throw error;
4088
+ }
4089
+ const document = parseEncryptedDocument(rawDocument);
4090
+ if (!document) {
4091
+ if (this.throwOnInvalidDocument)
4092
+ throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
4093
+ return null;
4094
+ }
4095
+ const key2 = await this.getEncryptionKey();
4096
+ try {
4097
+ const iv = Buffer.from(document.iv, "base64");
4098
+ const authTag = Buffer.from(document.authTag, "base64");
4099
+ const ciphertext = Buffer.from(document.ciphertext, "base64");
4100
+ if (iv.byteLength !== ENCRYPTION_IV_BYTES || authTag.byteLength !== ENCRYPTION_AUTH_TAG_BYTES) {
4101
+ if (this.throwOnInvalidDocument)
4102
+ throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
4103
+ return null;
4130
4104
  }
4131
- const clientId = isObjectRecord(parsed) ? getOwnString(parsed, "clientId") : void 0;
4132
- if (clientId !== void 0) {
4133
- const client = { clientId };
4134
- if (isObjectRecord(parsed) && Object.prototype.hasOwnProperty.call(parsed, "clientSecret")) {
4135
- client.clientSecret = getOwnEntry3(parsed, "clientSecret");
4105
+ const decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key2, iv);
4106
+ decipher.setAuthTag(authTag);
4107
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
4108
+ return plaintext.toString("utf8");
4109
+ } catch {
4110
+ if (this.throwOnInvalidDocument)
4111
+ throw new Error("Invalid encrypted credential document; reset the store explicitly to recover");
4112
+ return null;
4113
+ }
4114
+ }
4115
+ async withLock(operation, options = {}) {
4116
+ await this.assertCredentialPathHasNoSymbolicLinks(`${this.filePath}.lock`);
4117
+ if (this.fs.readdir === void 0)
4118
+ throw new Error("Secret-store transaction locks require filesystem readdir support");
4119
+ return withSecretStoreFileLock(this.fs, `${this.filePath}.lock`, operation, options);
4120
+ }
4121
+ async set(value) {
4122
+ await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
4123
+ const key2 = await this.getEncryptionKey();
4124
+ const iv = this.getRandomBytes(ENCRYPTION_IV_BYTES);
4125
+ const cipher = createCipheriv(ENCRYPTION_ALGORITHM, key2, iv);
4126
+ const ciphertext = Buffer.concat([
4127
+ cipher.update(value, "utf8"),
4128
+ cipher.final()
4129
+ ]);
4130
+ const authTag = cipher.getAuthTag();
4131
+ const document = {
4132
+ version: ENCRYPTION_VERSION,
4133
+ iv: iv.toString("base64"),
4134
+ authTag: authTag.toString("base64"),
4135
+ ciphertext: ciphertext.toString("base64")
4136
+ };
4137
+ await this.fs.mkdir(path2.dirname(this.filePath), { recursive: true });
4138
+ await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
4139
+ const temporaryPath = `${this.filePath}.${process.pid}.${randomUUID2()}.tmp`;
4140
+ let temporaryCreated = false;
4141
+ try {
4142
+ await this.assertCredentialPathHasNoSymbolicLinks(temporaryPath);
4143
+ await this.fs.writeFile(temporaryPath, JSON.stringify(document), {
4144
+ encoding: "utf8",
4145
+ flag: "wx",
4146
+ mode: ENCRYPTION_FILE_MODE
4147
+ });
4148
+ temporaryCreated = true;
4149
+ await this.fs.chmod(temporaryPath, ENCRYPTION_FILE_MODE);
4150
+ await this.fs.rename(temporaryPath, this.filePath);
4151
+ } catch (error) {
4152
+ if (temporaryCreated || !isAlreadyExistsError(error)) {
4153
+ await removeIfPresent(this.fs, temporaryPath).catch(() => void 0);
4154
+ }
4155
+ throw error;
4156
+ }
4157
+ }
4158
+ async delete() {
4159
+ await this.assertCredentialPathHasNoSymbolicLinks(this.filePath);
4160
+ try {
4161
+ await this.fs.unlink(this.filePath);
4162
+ } catch (error) {
4163
+ if (!isNotFoundError(error)) {
4164
+ throw error;
4165
+ }
4166
+ }
4167
+ }
4168
+ async assertCredentialPathHasNoSymbolicLinks(targetPath) {
4169
+ const resolvedPath = path2.resolve(targetPath);
4170
+ const protectedPaths = getProtectedCredentialPaths(resolvedPath, this.symbolicLinkCheckStartPath);
4171
+ for (const currentPath of protectedPaths) {
4172
+ try {
4173
+ const stats = await this.fs.lstat(currentPath);
4174
+ if (stats.isSymbolicLink()) {
4175
+ throw new Error(`Refusing to use encrypted credential path through symbolic link: ${currentPath}`);
4136
4176
  }
4137
- return client;
4177
+ } catch (error) {
4178
+ if (isNotFoundError(error)) {
4179
+ return;
4180
+ }
4181
+ throw error;
4138
4182
  }
4139
- throw new Error("Stored OAuth client must be a JSON object with clientId");
4140
- },
4141
- async save(issuer, client) {
4142
- const store = createIssuerSecretStore(issuer, options, namespace);
4143
- await store.set(JSON.stringify(client));
4144
- },
4145
- async clear(issuer) {
4146
- const store = createIssuerSecretStore(issuer, options, namespace);
4147
- await store.delete();
4148
4183
  }
4149
- };
4150
- }
4151
- function createNamedSecretStore(key2, options, defaults, namespace) {
4152
- const hash = crypto.createHash("sha256").update(namespace === void 0 ? key2 : JSON.stringify([namespace, key2])).digest("hex");
4153
- const configuredFilePath = options.fileStore?.filePath;
4154
- const parsedFilePath = configuredFilePath === void 0 ? null : path4.parse(configuredFilePath);
4155
- const fileStore = {
4156
- ...options.fileStore,
4157
- throwOnInvalidDocument: true,
4158
- filePath: parsedFilePath === null ? void 0 : path4.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
4159
- salt: options.fileStore?.salt ?? defaults.salt,
4160
- defaultDirectory: options.fileStore?.defaultDirectory || defaults.directory,
4161
- defaultFileName: parsedFilePath === null ? `${hash}.enc` : `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`
4162
- };
4163
- const keychainStore = {
4164
- ...options.keychainStore,
4165
- service: options.keychainStore?.service ?? defaults.service,
4166
- account: `${options.keychainStore?.account ?? defaults.accountPrefix}:${hash}`
4167
- };
4168
- return createSecretStore({ ...options, fileStore, keychainStore }).store;
4169
- }
4170
- function createResourceSecretStore(resource, options, namespace) {
4171
- return createNamedSecretStore(canonicalizeResourceIndicator(resource), options, {
4172
- salt: DEFAULT_FILE_SALT,
4173
- directory: DEFAULT_FILE_DIRECTORY,
4174
- service: DEFAULT_KEYCHAIN_SERVICE,
4175
- accountPrefix: "provider"
4176
- }, namespace);
4177
- }
4178
- function createIssuerSecretStore(issuer, options, namespace) {
4179
- return createNamedSecretStore(issuer, options, {
4180
- salt: DEFAULT_CLIENT_FILE_SALT,
4181
- directory: DEFAULT_CLIENT_FILE_DIRECTORY,
4182
- service: DEFAULT_CLIENT_KEYCHAIN_SERVICE,
4183
- accountPrefix: "issuer"
4184
- }, namespace);
4185
- }
4186
- function assertPersistenceNamespace(namespace) {
4187
- if (namespace !== void 0 && (typeof namespace !== "string" || namespace.trim() === "" || Buffer.byteLength(namespace, "utf8") > 1024))
4188
- throw new Error("OAuth persistence namespace must be a nonempty string within 1024 bytes");
4189
- }
4190
- function isObjectRecord(value) {
4191
- return typeof value === "object" && value !== null && !Array.isArray(value);
4192
- }
4193
- function getOwnEntry3(record2, key2) {
4194
- return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
4195
- }
4196
- function getOwnString(record2, key2) {
4197
- const value = getOwnEntry3(record2, key2);
4198
- return typeof value === "string" ? value : void 0;
4199
- }
4200
- function isStoredOAuthSession(value) {
4201
- if (!isObjectRecord(value)) {
4202
- return false;
4203
4184
  }
4204
- return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && isStoredOAuthClient(getOwnEntry3(value, "client")) && isStoredOAuthDiscovery(getOwnEntry3(value, "discovery")) && (getOwnEntry3(value, "requestedScope") === void 0 || isNonBlankOwnString(value, "requestedScope")) && (getOwnEntry3(value, "refreshState") === void 0 || getOwnEntry3(value, "refreshState") === "pending" && getOwnEntry3(value, "tokens") === void 0) && isStoredOAuthTokensOrMissing(getOwnEntry3(value, "tokens"));
4185
+ getEncryptionKey() {
4186
+ if (!this.keyPromise) {
4187
+ const retryableKeyPromise = deriveEncryptionKey(this.getMachineIdentity, this.salt).catch((error) => {
4188
+ if (this.keyPromise === retryableKeyPromise) {
4189
+ this.keyPromise = null;
4190
+ }
4191
+ throw error;
4192
+ });
4193
+ this.keyPromise = retryableKeyPromise;
4194
+ }
4195
+ return this.keyPromise;
4196
+ }
4197
+ };
4198
+ function resolveDefaultDirectoryCheckStart(homeDirectory, defaultDirectory) {
4199
+ const [firstSegment] = defaultDirectory.split(/[\\/]+/).filter(Boolean);
4200
+ return path2.resolve(homeDirectory, firstSegment ?? ".");
4205
4201
  }
4206
- function isStoredOAuthClient(value) {
4207
- if (!isObjectRecord(value) || !isNonBlankOwnString(value, "clientId")) {
4208
- return false;
4202
+ function getProtectedCredentialPaths(resolvedPath, symbolicLinkCheckStartPath) {
4203
+ if (symbolicLinkCheckStartPath === null) {
4204
+ return getExplicitProtectedCredentialPaths(resolvedPath);
4209
4205
  }
4210
- const clientSecret = getOwnEntry3(value, "clientSecret");
4211
- return clientSecret === void 0 || typeof clientSecret === "string" && clientSecret.trim().length > 0;
4212
- }
4213
- function isStoredOAuthDiscovery(value) {
4214
- if (!isObjectRecord(value)) {
4215
- return false;
4206
+ const resolvedStartPath = path2.resolve(symbolicLinkCheckStartPath);
4207
+ if (!isPathInsideOrEqual(resolvedPath, resolvedStartPath)) {
4208
+ return [path2.dirname(resolvedPath), resolvedPath];
4209
+ }
4210
+ const protectedPaths = [resolvedStartPath];
4211
+ let currentPath = resolvedStartPath;
4212
+ for (const segment of path2.relative(resolvedStartPath, resolvedPath).split(path2.sep).filter(Boolean)) {
4213
+ currentPath = path2.join(currentPath, segment);
4214
+ protectedPaths.push(currentPath);
4216
4215
  }
4217
- return isNonBlankOwnString(value, "resourceMetadataUrl") && isObjectRecord(getOwnEntry3(value, "resourceMetadata")) && isObjectRecord(getOwnEntry3(value, "authorizationServerMetadata"));
4216
+ return protectedPaths;
4218
4217
  }
4219
- function isStoredOAuthTokensOrMissing(value) {
4220
- if (value === void 0) {
4221
- return true;
4218
+ function assertSafeDefaultDirectory(defaultDirectory) {
4219
+ if (path2.isAbsolute(defaultDirectory) || path2.win32.isAbsolute(defaultDirectory)) {
4220
+ throw new Error("defaultDirectory must be a relative path inside the home directory");
4222
4221
  }
4223
- if (!isObjectRecord(value)) {
4224
- return false;
4222
+ for (const segment of splitPathSegments(defaultDirectory)) {
4223
+ if (segment === "..") {
4224
+ throw new Error("defaultDirectory must be a relative path inside the home directory");
4225
+ }
4225
4226
  }
4226
- if (!isNonBlankOwnString(value, "accessToken") || getOwnString(value, "tokenType") !== "Bearer") {
4227
- return false;
4227
+ }
4228
+ function assertSafeDefaultFileName(defaultFileName) {
4229
+ if (defaultFileName.trim().length === 0 || defaultFileName === "." || defaultFileName === ".." || splitPathSegments(defaultFileName).length !== 1) {
4230
+ throw new Error("defaultFileName must be a file name without path separators");
4228
4231
  }
4229
- const expiresAt = getOwnEntry3(value, "expiresAt");
4230
- if (expiresAt !== null && (typeof expiresAt !== "number" || !Number.isSafeInteger(expiresAt) || expiresAt > MAX_JS_DATE_MS || !Number.isFinite(new Date(expiresAt).getTime()))) {
4231
- return false;
4232
+ }
4233
+ function splitPathSegments(value) {
4234
+ return value.split("/").flatMap((segment) => segment.split("\\")).filter((segment) => segment.length > 0);
4235
+ }
4236
+ function getExplicitProtectedCredentialPaths(resolvedPath) {
4237
+ const parsed = path2.parse(resolvedPath);
4238
+ const segments = resolvedPath.slice(parsed.root.length).split(path2.sep).filter((segment) => segment.length > 0);
4239
+ if (segments.length <= 1) {
4240
+ return [resolvedPath];
4232
4241
  }
4233
- const refreshToken = getOwnEntry3(value, "refreshToken");
4234
- if (refreshToken !== void 0 && (typeof refreshToken !== "string" || refreshToken.trim().length === 0)) {
4235
- return false;
4242
+ const protectedPaths = [];
4243
+ let currentPath = parsed.root;
4244
+ for (const [index, segment] of segments.entries()) {
4245
+ currentPath = path2.join(currentPath, segment);
4246
+ if (index === 0) {
4247
+ continue;
4248
+ }
4249
+ protectedPaths.push(currentPath);
4236
4250
  }
4237
- const scope = getOwnEntry3(value, "scope");
4238
- return scope === void 0 || typeof scope === "string" && scope.trim().length > 0;
4239
- }
4240
- function isNonBlankOwnString(record2, key2) {
4241
- const value = getOwnString(record2, key2);
4242
- return value !== void 0 && value.trim().length > 0;
4251
+ return protectedPaths;
4243
4252
  }
4244
-
4245
- // ../mcp-oauth/dist/client/scope.js
4246
- function normalizeOAuthScope(scope) {
4247
- if (scope === void 0)
4248
- return void 0;
4249
- if (typeof scope !== "string" || [...scope].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126 || char === '"' || char === "\\"))
4250
- throw new Error("Invalid OAuth scope syntax");
4251
- const normalized = [...new Set(scope.split(" ").filter(Boolean))].sort().join(" ");
4252
- return normalized || void 0;
4253
+ function isPathInsideOrEqual(childPath, parentPath) {
4254
+ const relativePath = path2.relative(parentPath, childPath);
4255
+ return relativePath === "" || !relativePath.startsWith("..") && !path2.isAbsolute(relativePath);
4253
4256
  }
4254
-
4255
- // ../mcp-oauth/dist/client/default-oauth-client-provider.js
4256
- import { isIP } from "node:net";
4257
-
4258
- // ../mcp-oauth/dist/http-fetch.js
4259
- async function fetchMcpResponse(fetchImplementation, input, init = {}) {
4260
- const response = await fetchImplementation(input, { ...init, redirect: "error" });
4261
- if (response.redirected || response.type === "opaqueredirect") {
4262
- void response.body?.cancel().catch(() => void 0);
4263
- throw new Error("MCP HTTP redirects are not allowed");
4257
+ async function removeIfPresent(fileSystem, filePath) {
4258
+ try {
4259
+ await fileSystem.unlink(filePath);
4260
+ } catch (error) {
4261
+ if (!isNotFoundError(error)) {
4262
+ throw error;
4263
+ }
4264
4264
  }
4265
- return response;
4266
4265
  }
4267
-
4268
- // ../mcp-oauth/dist/client/default-oauth-client-provider.js
4269
- import { URL as URL2 } from "node:url";
4270
-
4271
- // ../mcp-oauth/dist/client/loopback-authorization.js
4272
- import http from "node:http";
4273
-
4274
- // ../mcp-oauth/dist/client/authorization-state.js
4275
- import crypto2 from "node:crypto";
4276
- function createAuthorizationState(input) {
4277
- const payload = {
4278
- v: 1,
4279
- n: crypto2.randomBytes(16).toString("base64url"),
4280
- i: input.issuer,
4281
- r: input.requireIssuer
4266
+ function defaultMachineIdentity() {
4267
+ return {
4268
+ hostname: hostname(),
4269
+ username: userInfo().username
4282
4270
  };
4283
- return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
4284
4271
  }
4285
- function parseAuthorizationState(value) {
4286
- if (value === null || value.length === 0) {
4287
- return null;
4272
+ async function deriveEncryptionKey(getMachineIdentity, salt) {
4273
+ const machineIdentity = await getMachineIdentity();
4274
+ const secret = `${machineIdentity.hostname}:${machineIdentity.username}`;
4275
+ const cacheKey = JSON.stringify([machineIdentity.hostname, machineIdentity.username, salt]);
4276
+ const cached = derivedKeyCache.get(cacheKey);
4277
+ if (cached) {
4278
+ return cached;
4288
4279
  }
4280
+ const keyPromise = new Promise((resolve, reject) => {
4281
+ scrypt(secret, salt, ENCRYPTION_KEY_BYTES, (error, derivedKey) => {
4282
+ if (error) {
4283
+ reject(error);
4284
+ return;
4285
+ }
4286
+ resolve(Buffer.from(derivedKey));
4287
+ });
4288
+ });
4289
+ derivedKeyCache.set(cacheKey, keyPromise);
4290
+ return keyPromise.catch((error) => {
4291
+ if (derivedKeyCache.get(cacheKey) === keyPromise) {
4292
+ derivedKeyCache.delete(cacheKey);
4293
+ }
4294
+ throw error;
4295
+ });
4296
+ }
4297
+ function parseEncryptedDocument(raw) {
4289
4298
  try {
4290
- const decoded = Buffer.from(value, "base64url").toString("utf8");
4291
- const parsed = JSON.parse(decoded);
4292
- if (!isObjectRecord2(parsed)) {
4299
+ const parsed = JSON.parse(raw);
4300
+ if (!isRecord2(parsed)) {
4293
4301
  return null;
4294
4302
  }
4295
- const version = getOwnEntry4(parsed, "v");
4296
- const nonce = getOwnEntry4(parsed, "n");
4297
- const issuer = getOwnEntry4(parsed, "i");
4298
- const requireIssuer = getOwnEntry4(parsed, "r");
4299
- if (version !== 1 || typeof nonce !== "string" || nonce.length === 0 || typeof issuer !== "string" || issuer.length === 0 || typeof requireIssuer !== "boolean") {
4303
+ const version = getOwnEntry2(parsed, "version");
4304
+ const iv = getOwnEntry2(parsed, "iv");
4305
+ const authTag = getOwnEntry2(parsed, "authTag");
4306
+ const ciphertext = getOwnEntry2(parsed, "ciphertext");
4307
+ if (version !== ENCRYPTION_VERSION) {
4308
+ return null;
4309
+ }
4310
+ if (typeof iv !== "string" || typeof authTag !== "string" || typeof ciphertext !== "string") {
4300
4311
  return null;
4301
4312
  }
4302
4313
  return {
4303
- issuer,
4304
- requireIssuer
4314
+ version,
4315
+ iv,
4316
+ authTag,
4317
+ ciphertext
4305
4318
  };
4306
4319
  } catch {
4307
4320
  return null;
4308
4321
  }
4309
4322
  }
4310
- function isObjectRecord2(value) {
4311
- return typeof value === "object" && value !== null && !Array.isArray(value);
4323
+ function isRecord2(value) {
4324
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
4312
4325
  }
4313
- function getOwnEntry4(record2, key2) {
4326
+ function getOwnEntry2(record2, key2) {
4314
4327
  return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
4315
4328
  }
4329
+ function isNotFoundError(error) {
4330
+ return hasOwnErrorCode(error, "ENOENT");
4331
+ }
4332
+ function isAlreadyExistsError(error) {
4333
+ return hasOwnErrorCode(error, "EEXIST");
4334
+ }
4316
4335
 
4317
- // ../mcp-oauth/dist/client/loopback-authorization.js
4318
- async function createLoopbackAuthorizationSession(options = {}) {
4319
- options.signal?.throwIfAborted();
4320
- const timeoutMs = options.timeoutMs ?? 12e4;
4321
- if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
4322
- throw new Error("OAuth authorization timeoutMs must be a positive supported timer interval");
4323
- const target = loopbackTarget(options);
4324
- const server = options.createServer ? options.createServer() : http.createServer();
4325
- const controller = new AbortController();
4326
- let closed = false;
4327
- let used = false;
4328
- const callerAbort = () => controller.abort(options.signal?.reason);
4329
- const teardown = () => {
4330
- if (closed)
4331
- return;
4332
- closed = true;
4333
- clearTimeout(timer);
4334
- options.signal?.removeEventListener("abort", callerAbort);
4335
- server.closeAllConnections?.();
4336
- server.close();
4337
- };
4338
- const timer = setTimeout(() => controller.abort(new Error("OAuth authorization timed out")), timeoutMs);
4339
- timer.unref?.();
4340
- controller.signal.addEventListener("abort", teardown, { once: true });
4341
- options.signal?.addEventListener("abort", callerAbort, { once: true });
4342
- if (options.signal?.aborted)
4343
- callerAbort();
4344
- let port;
4345
- try {
4346
- port = await startServer(server, target.port, target.host, controller.signal);
4347
- } catch (error) {
4348
- controller.abort(error);
4349
- throw error;
4336
+ // ../auth-store/dist/keychain-store.js
4337
+ import { spawn } from "node:child_process";
4338
+ import { createHash } from "node:crypto";
4339
+ import { promises as nodeFs } from "node:fs";
4340
+ import { homedir as homedir2 } from "node:os";
4341
+ import path3 from "node:path";
4342
+ var SECURITY_CLI = "security";
4343
+ var KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE = 44;
4344
+ var KeychainStore = class {
4345
+ runCommand;
4346
+ service;
4347
+ account;
4348
+ lockFs;
4349
+ lockDirectory;
4350
+ constructor(input) {
4351
+ this.runCommand = input.runCommand ?? runSecurityCommand;
4352
+ this.service = input.service.trim();
4353
+ this.account = input.account.trim();
4354
+ this.lockFs = input.lock?.fs ?? nodeFs;
4355
+ this.lockDirectory = input.lock?.directory ?? path3.join(homedir2(), ".auth-store", "keychain-locks");
4356
+ if (this.service.length === 0) {
4357
+ throw new Error("Keychain service must not be empty");
4358
+ }
4359
+ if (this.account.length === 0) {
4360
+ throw new Error("Keychain account must not be empty");
4361
+ }
4362
+ }
4363
+ async get() {
4364
+ const result = await this.executeSecurityCommand(["find-generic-password", "-s", this.service, "-a", this.account, "-w"], "read secret from macOS Keychain");
4365
+ if (getCommandExitCode(result) === 0) {
4366
+ return stripTrailingLineBreak(getCommandOutput(result, "stdout"));
4367
+ }
4368
+ if (isKeychainEntryNotFound(result)) {
4369
+ return null;
4370
+ }
4371
+ throw createSecurityCliFailure("read secret from macOS Keychain", result);
4372
+ }
4373
+ async withLock(operation, options = {}) {
4374
+ const identity = createHash("sha256").update(JSON.stringify([this.service, this.account])).digest("hex");
4375
+ return withSecretStoreFileLock(this.lockFs, path3.join(this.lockDirectory, identity), operation, options);
4376
+ }
4377
+ async set(value) {
4378
+ if (value.includes("\n") || value.includes("\r")) {
4379
+ throw new Error("Keychain secrets cannot contain line breaks");
4380
+ }
4381
+ const result = await this.executeSecurityCommand([
4382
+ "add-generic-password",
4383
+ "-s",
4384
+ this.service,
4385
+ "-a",
4386
+ this.account,
4387
+ "-U",
4388
+ "-w",
4389
+ value
4390
+ ], "store secret in macOS Keychain");
4391
+ if (getCommandExitCode(result) !== 0) {
4392
+ throw createSecurityCliFailure("store secret in macOS Keychain", result);
4393
+ }
4350
4394
  }
4351
- const redirectUri = options.redirectUri ?? `http://127.0.0.1:${port}${target.callbackPath}`;
4352
- return {
4353
- redirectUri,
4354
- async waitForCode(authorizationUrl) {
4355
- controller.signal.throwIfAborted();
4356
- if (used)
4357
- throw new Error("OAuth authorization session has already been used");
4358
- used = true;
4359
- try {
4360
- return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
4361
- } finally {
4362
- clearTimeout(timer);
4363
- }
4364
- },
4365
- close() {
4366
- controller.abort(new Error("OAuth authorization session closed"));
4395
+ async delete() {
4396
+ const result = await this.executeSecurityCommand(["delete-generic-password", "-s", this.service, "-a", this.account], "delete secret from macOS Keychain");
4397
+ if (getCommandExitCode(result) === 0 || isKeychainEntryNotFound(result)) {
4398
+ return;
4367
4399
  }
4368
- };
4369
- }
4370
- function loopbackTarget(options) {
4371
- if (options.redirectUri !== void 0) {
4372
- let url;
4400
+ throw createSecurityCliFailure("delete secret from macOS Keychain", result);
4401
+ }
4402
+ async executeSecurityCommand(args, operation, options) {
4373
4403
  try {
4374
- url = new URL(options.redirectUri);
4375
- } catch (cause) {
4376
- throw new Error("Invalid OAuth loopback redirect URI", { cause });
4404
+ if (options === void 0) {
4405
+ return await this.runCommand(SECURITY_CLI, args);
4406
+ }
4407
+ return await this.runCommand(SECURITY_CLI, args, options);
4408
+ } catch (error) {
4409
+ const message = error instanceof Error ? error.message : String(error);
4410
+ throw new Error(`Failed to ${operation}: ${message}`);
4377
4411
  }
4378
- const forbiddenQuery = ["code", "state", "error", "error_description", "iss"];
4379
- if (url.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || url.username || url.password || url.hash || url.port === "0" || forbiddenQuery.some((name) => url.searchParams.has(name)) || [...options.redirectUri].some((char) => char.codePointAt(0) <= 32) || options.callbackPath !== void 0 && options.callbackPath !== url.pathname)
4380
- throw new Error("Invalid OAuth loopback redirect URI");
4381
- return { port: url.port ? Number(url.port) : 80, host: url.hostname === "[::1]" ? "::1" : url.hostname, callbackPath: url.pathname };
4382
4412
  }
4383
- const callbackPath = options.callbackPath ?? "/callback";
4384
- const parsed = new URL(callbackPath, "http://127.0.0.1");
4385
- if (!callbackPath.startsWith("/") || parsed.origin !== "http://127.0.0.1" || parsed.pathname !== callbackPath || parsed.search || parsed.hash)
4386
- throw new Error("Invalid OAuth loopback callback path");
4387
- return { port: 0, host: "127.0.0.1", callbackPath };
4388
- }
4389
- async function startServer(server, port, host, signal) {
4390
- signal.throwIfAborted();
4391
- return new Promise((resolve, reject) => {
4392
- const cleanup = () => {
4393
- server.off("error", handleError);
4394
- signal.removeEventListener("abort", aborted);
4395
- };
4396
- const handleError = (error) => {
4397
- cleanup();
4398
- reject(error);
4413
+ };
4414
+ function runSecurityCommand(command, args, options) {
4415
+ return new Promise((resolve) => {
4416
+ const child = spawn(command, args, {
4417
+ stdio: [options?.stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
4418
+ });
4419
+ let stdout = "";
4420
+ let stderr = "";
4421
+ let stdinErrorMessage;
4422
+ const appendStderr = (message) => {
4423
+ stderr = stderr.length === 0 ? message : `${stderr}${stderr.endsWith("\n") ? "" : "\n"}${message}`;
4399
4424
  };
4400
- const aborted = () => {
4401
- cleanup();
4402
- reject(signal.reason);
4425
+ const appendStdinError = () => {
4426
+ if (stdinErrorMessage === void 0) {
4427
+ return;
4428
+ }
4429
+ appendStderr(stdinErrorMessage);
4430
+ stdinErrorMessage = void 0;
4403
4431
  };
4404
- server.once("error", handleError);
4405
- signal.addEventListener("abort", aborted, { once: true });
4406
- try {
4407
- server.listen(port, host, () => {
4408
- cleanup();
4409
- if (signal.aborted) {
4410
- server.close();
4411
- reject(signal.reason);
4412
- return;
4413
- }
4414
- const address = server.address();
4415
- if (address === null || typeof address === "string") {
4416
- reject(new Error("OAuth listener has no TCP address"));
4417
- return;
4418
- }
4419
- resolve(address.port);
4432
+ child.stdout?.setEncoding("utf8");
4433
+ child.stdout?.on("data", (chunk) => {
4434
+ stdout += chunk.toString();
4435
+ });
4436
+ child.stderr?.setEncoding("utf8");
4437
+ child.stderr?.on("data", (chunk) => {
4438
+ stderr += chunk.toString();
4439
+ });
4440
+ if (options?.stdin !== void 0) {
4441
+ child.stdin?.once("error", (error) => {
4442
+ stdinErrorMessage = error instanceof Error ? error.message : String(error);
4420
4443
  });
4421
- } catch (error) {
4422
- cleanup();
4423
- reject(error);
4444
+ child.stdin?.end(options.stdin);
4424
4445
  }
4446
+ child.on("error", (error) => {
4447
+ const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
4448
+ appendStdinError();
4449
+ appendStderr(message);
4450
+ resolve({
4451
+ stdout,
4452
+ stderr,
4453
+ exitCode: 127
4454
+ });
4455
+ });
4456
+ child.on("close", (code) => {
4457
+ appendStdinError();
4458
+ resolve({
4459
+ stdout,
4460
+ stderr,
4461
+ exitCode: code ?? 1
4462
+ });
4463
+ });
4425
4464
  });
4426
4465
  }
4427
- function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath, signal) {
4428
- signal.throwIfAborted();
4429
- const expectedAuthorization = readExpectedAuthorizationCallback(authorizationUrl);
4430
- return new Promise((resolve, reject) => {
4431
- let settled = false;
4432
- const settle = (fn) => {
4433
- if (settled)
4434
- return;
4435
- settled = true;
4436
- server.off("request", request);
4437
- signal.removeEventListener("abort", aborted);
4438
- fn();
4439
- };
4440
- const aborted = () => settle(() => reject(signal.reason));
4441
- const request = (req, res) => {
4442
- let url;
4443
- try {
4444
- url = new URL(req.url ?? "/", "http://127.0.0.1");
4445
- } catch {
4446
- res.writeHead(400);
4447
- res.end("Invalid callback URL");
4448
- return;
4449
- }
4450
- if (url.pathname !== callbackPath) {
4451
- res.writeHead(404);
4452
- res.end("Not found");
4453
- return;
4454
- }
4455
- const callbackParameters = {
4456
- code: url.searchParams.get("code"),
4457
- error: url.searchParams.get("error"),
4458
- errorDescription: url.searchParams.get("error_description"),
4459
- state: url.searchParams.get("state"),
4460
- iss: url.searchParams.get("iss")
4461
- };
4462
- try {
4463
- validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
4464
- if (callbackParameters.error !== null)
4465
- throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
4466
- const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
4467
- res.writeHead(200, { "Content-Type": "text/html" });
4468
- res.end(buildSuccessPage(options.landingPage));
4469
- settle(() => resolve(code));
4470
- } catch (error) {
4471
- res.writeHead(400);
4472
- res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
4473
- settle(() => reject(error));
4474
- }
4475
- };
4476
- server.on("request", request);
4477
- signal.addEventListener("abort", aborted, { once: true });
4478
- if (options.readLine !== void 0) {
4479
- void Promise.resolve().then(() => settled ? void 0 : options.readLine()).then((input) => {
4480
- if (settled)
4481
- return;
4482
- const callbackParameters = extractCallbackParametersFromInput(input);
4483
- if (callbackParameters === null)
4484
- throw new Error("OAuth callback missing authorization code");
4485
- validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
4486
- if (callbackParameters.error !== null)
4487
- throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
4488
- const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
4489
- settle(() => resolve(code));
4490
- }).catch((error) => settle(() => reject(error)));
4466
+ function stripTrailingLineBreak(value) {
4467
+ if (value.endsWith("\r\n")) {
4468
+ return value.slice(0, -2);
4469
+ }
4470
+ if (value.endsWith("\n") || value.endsWith("\r")) {
4471
+ return value.slice(0, -1);
4472
+ }
4473
+ return value;
4474
+ }
4475
+ function isKeychainEntryNotFound(result) {
4476
+ return getCommandExitCode(result) === KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE;
4477
+ }
4478
+ function createSecurityCliFailure(operation, result) {
4479
+ const exitCode = getCommandExitCode(result);
4480
+ const details = getCommandOutput(result, "stderr").trim() || getCommandOutput(result, "stdout").trim();
4481
+ if (details) {
4482
+ return new Error(`Failed to ${operation}: security exited with code ${exitCode}: ${details}`);
4483
+ }
4484
+ return new Error(`Failed to ${operation}: security exited with code ${exitCode}`);
4485
+ }
4486
+ function getCommandExitCode(result) {
4487
+ const value = getOwnEntry3(result, "exitCode");
4488
+ return typeof value === "number" && Number.isInteger(value) ? value : 1;
4489
+ }
4490
+ function getCommandOutput(result, key2) {
4491
+ const value = getOwnEntry3(result, key2);
4492
+ return typeof value === "string" ? value : "";
4493
+ }
4494
+ function getOwnEntry3(record2, key2) {
4495
+ return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
4496
+ }
4497
+
4498
+ // ../auth-store/dist/create-secret-store.js
4499
+ var DEFAULT_BACKEND_ENV_VAR = "AUTH_BACKEND";
4500
+ var MACOS_PLATFORM = "darwin";
4501
+ var storeFactories = {
4502
+ file: (input) => {
4503
+ if (!input.fileStore) {
4504
+ throw new Error("fileStore configuration is required for file backend");
4491
4505
  }
4492
- if (options.openBrowser !== void 0) {
4493
- void Promise.resolve().then(() => settled ? void 0 : options.openBrowser(authorizationUrl)).catch((error) => settle(() => reject(error)));
4506
+ return new EncryptedFileStore(input.fileStore);
4507
+ },
4508
+ keychain: (input) => {
4509
+ if (!input.keychainStore) {
4510
+ throw new Error("keychainStore configuration is required for keychain backend");
4494
4511
  }
4495
- });
4512
+ return new KeychainStore(input.keychainStore);
4513
+ }
4514
+ };
4515
+ function createSecretStore(input) {
4516
+ const backend = resolveBackend(input);
4517
+ const platform = input.platform ?? process.platform;
4518
+ if (backend === "keychain" && platform !== MACOS_PLATFORM) {
4519
+ throw new Error(`Keychain backend is only supported on macOS. Current platform: ${platform}`);
4520
+ }
4521
+ const store = storeFactories[backend](input);
4522
+ return { backend, store };
4496
4523
  }
4497
- function extractCallbackParametersFromInput(input) {
4498
- const trimmed = input.replaceAll("\r", "").replaceAll("\n", "").trim();
4499
- if (trimmed.length === 0) {
4500
- return null;
4524
+ function resolveBackend(input) {
4525
+ const envVar = input.backendEnvVar ?? DEFAULT_BACKEND_ENV_VAR;
4526
+ const configuredBackend = input.backend ?? getOwnEnvValue(input.env, envVar) ?? getOwnEnvValue(process.env, envVar);
4527
+ const backend = configuredBackend?.trim();
4528
+ if (backend === "keychain") {
4529
+ return "keychain";
4530
+ }
4531
+ if (backend === void 0 || backend === "file") {
4532
+ return "file";
4501
4533
  }
4534
+ throw new Error(`Unsupported auth store backend: ${backend}`);
4535
+ }
4536
+ function getOwnEnvValue(env, key2) {
4537
+ return env !== void 0 && Object.prototype.hasOwnProperty.call(env, key2) ? env[key2] : void 0;
4538
+ }
4539
+
4540
+ // ../mcp-oauth/dist/resource-indicator.js
4541
+ function canonicalizeResourceIndicator(value) {
4542
+ let url;
4502
4543
  try {
4503
- const url = new URL(trimmed);
4504
- return {
4505
- code: url.searchParams.get("code"),
4506
- error: url.searchParams.get("error"),
4507
- errorDescription: url.searchParams.get("error_description"),
4508
- state: url.searchParams.get("state"),
4509
- iss: url.searchParams.get("iss")
4510
- };
4544
+ url = value instanceof URL ? new URL(value.toString()) : new URL(value);
4511
4545
  } catch {
4512
- return {
4513
- code: trimmed,
4514
- error: null,
4515
- errorDescription: null,
4516
- state: null,
4517
- iss: null
4518
- };
4546
+ throw new Error("Resource indicator must be an absolute URL");
4519
4547
  }
4548
+ url.hash = "";
4549
+ return url.toString();
4520
4550
  }
4521
- function readExpectedAuthorizationCallback(authorizationUrl) {
4522
- const url = new URL(authorizationUrl);
4523
- const state = url.searchParams.get("state");
4524
- const parsedState = parseAuthorizationState(state);
4551
+
4552
+ // ../mcp-oauth/dist/client/auth-store-session-store.js
4553
+ var DEFAULT_FILE_SALT = "poe-code:mcp-oauth:v1";
4554
+ var DEFAULT_FILE_DIRECTORY = ".poe-code/mcp-oauth";
4555
+ var DEFAULT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth";
4556
+ var DEFAULT_CLIENT_FILE_SALT = "poe-code:mcp-oauth:clients:v1";
4557
+ var DEFAULT_CLIENT_FILE_DIRECTORY = ".poe-code/mcp-oauth/clients";
4558
+ var DEFAULT_CLIENT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth-clients";
4559
+ var MAX_JS_DATE_MS = 864e13;
4560
+ function createAuthStoreSessionStore(options = {}, namespace) {
4561
+ assertPersistenceNamespace(namespace);
4525
4562
  return {
4526
- state,
4527
- issuer: parsedState?.issuer ?? null,
4528
- requireIssuer: parsedState?.requireIssuer ?? false
4563
+ async withLock(resource, operation, lockOptions) {
4564
+ const store = createResourceSecretStore(resource, options, namespace);
4565
+ if (store.withLock === void 0)
4566
+ throw new Error("OAuth secret-store backend does not support transaction locks");
4567
+ return store.withLock(operation, lockOptions);
4568
+ },
4569
+ async load(resource) {
4570
+ const store = createResourceSecretStore(resource, options, namespace);
4571
+ const value = await store.get();
4572
+ if (value === null) {
4573
+ return null;
4574
+ }
4575
+ let parsed;
4576
+ try {
4577
+ parsed = JSON.parse(value);
4578
+ } catch {
4579
+ throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
4580
+ }
4581
+ if (isStoredOAuthSession(parsed)) {
4582
+ return parsed;
4583
+ }
4584
+ throw new Error("Stored OAuth session must match the expected shape");
4585
+ },
4586
+ async save(resource, session) {
4587
+ const store = createResourceSecretStore(resource, options, namespace);
4588
+ await store.set(JSON.stringify(session));
4589
+ },
4590
+ async clear(resource) {
4591
+ const store = createResourceSecretStore(resource, options, namespace);
4592
+ await store.delete();
4593
+ }
4529
4594
  };
4530
4595
  }
4531
- function validateAuthorizationCallbackParameters(callback, expected) {
4532
- validateAuthorizationCallbackBinding(callback, expected);
4533
- if (callback.code === null || callback.code.length === 0) {
4534
- throw new Error("OAuth callback missing authorization code");
4596
+ function createAuthStoreClientStore(options, namespace) {
4597
+ assertPersistenceNamespace(namespace);
4598
+ return {
4599
+ async load(issuer) {
4600
+ const store = createIssuerSecretStore(issuer, options, namespace);
4601
+ const value = await store.get();
4602
+ if (value === null) {
4603
+ return null;
4604
+ }
4605
+ let parsed;
4606
+ try {
4607
+ parsed = JSON.parse(value);
4608
+ } catch {
4609
+ throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
4610
+ }
4611
+ if (isObjectRecord2(parsed) && typeof getOwnEntry4(parsed, "clientId") === "string")
4612
+ return parsed;
4613
+ throw new Error("Stored OAuth client must be a JSON object with clientId");
4614
+ },
4615
+ async save(issuer, client) {
4616
+ const store = createIssuerSecretStore(issuer, options, namespace);
4617
+ await store.set(JSON.stringify(client));
4618
+ },
4619
+ async clear(issuer) {
4620
+ const store = createIssuerSecretStore(issuer, options, namespace);
4621
+ await store.delete();
4622
+ }
4623
+ };
4624
+ }
4625
+ function createNamedSecretStore(key2, options, defaults, namespace) {
4626
+ const hash = crypto2.createHash("sha256").update(namespace === void 0 ? key2 : JSON.stringify([namespace, key2])).digest("hex");
4627
+ const configuredFilePath = options.fileStore?.filePath;
4628
+ const parsedFilePath = configuredFilePath === void 0 ? null : path4.parse(configuredFilePath);
4629
+ const fileStore = {
4630
+ ...options.fileStore,
4631
+ throwOnInvalidDocument: true,
4632
+ filePath: parsedFilePath === null ? void 0 : path4.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
4633
+ salt: options.fileStore?.salt ?? defaults.salt,
4634
+ defaultDirectory: options.fileStore?.defaultDirectory || defaults.directory,
4635
+ defaultFileName: parsedFilePath === null ? `${hash}.enc` : `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`
4636
+ };
4637
+ const keychainStore = {
4638
+ ...options.keychainStore,
4639
+ service: options.keychainStore?.service ?? defaults.service,
4640
+ account: `${options.keychainStore?.account ?? defaults.accountPrefix}:${hash}`
4641
+ };
4642
+ return createSecretStore({ ...options, fileStore, keychainStore }).store;
4643
+ }
4644
+ function createResourceSecretStore(resource, options, namespace) {
4645
+ return createNamedSecretStore(canonicalizeResourceIndicator(resource), options, {
4646
+ salt: DEFAULT_FILE_SALT,
4647
+ directory: DEFAULT_FILE_DIRECTORY,
4648
+ service: DEFAULT_KEYCHAIN_SERVICE,
4649
+ accountPrefix: "provider"
4650
+ }, namespace);
4651
+ }
4652
+ function createIssuerSecretStore(issuer, options, namespace) {
4653
+ return createNamedSecretStore(issuer, options, {
4654
+ salt: DEFAULT_CLIENT_FILE_SALT,
4655
+ directory: DEFAULT_CLIENT_FILE_DIRECTORY,
4656
+ service: DEFAULT_CLIENT_KEYCHAIN_SERVICE,
4657
+ accountPrefix: "issuer"
4658
+ }, namespace);
4659
+ }
4660
+ function assertPersistenceNamespace(namespace) {
4661
+ if (namespace !== void 0 && (typeof namespace !== "string" || namespace.trim() === "" || Buffer.byteLength(namespace, "utf8") > 1024))
4662
+ throw new Error("OAuth persistence namespace must be a nonempty string within 1024 bytes");
4663
+ }
4664
+ function isObjectRecord2(value) {
4665
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4666
+ }
4667
+ function getOwnEntry4(record2, key2) {
4668
+ return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
4669
+ }
4670
+ function getOwnString(record2, key2) {
4671
+ const value = getOwnEntry4(record2, key2);
4672
+ return typeof value === "string" ? value : void 0;
4673
+ }
4674
+ function isStoredOAuthSession(value) {
4675
+ if (!isObjectRecord2(value)) {
4676
+ return false;
4535
4677
  }
4536
- return callback.code;
4678
+ return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && normalizeStoredOAuthClient(getOwnEntry4(value, "client")) !== null && isStoredOAuthDiscovery(getOwnEntry4(value, "discovery")) && (getOwnEntry4(value, "requestedScope") === void 0 || isNonBlankOwnString(value, "requestedScope")) && (getOwnEntry4(value, "refreshState") === void 0 || getOwnEntry4(value, "refreshState") === "pending" && getOwnEntry4(value, "tokens") === void 0) && isStoredOAuthTokensOrMissing(getOwnEntry4(value, "tokens"));
4537
4679
  }
4538
- function validateAuthorizationCallbackBinding(callback, expected) {
4539
- if (expected.state !== null) {
4540
- if (callback.state === null || callback.state.length === 0) {
4541
- throw new Error("OAuth callback missing state");
4542
- }
4543
- if (callback.state !== expected.state) {
4544
- throw new Error("OAuth callback state mismatch");
4545
- }
4680
+ function isStoredOAuthDiscovery(value) {
4681
+ if (!isObjectRecord2(value)) {
4682
+ return false;
4546
4683
  }
4547
- if (expected.requireIssuer) {
4548
- if (callback.iss === null || callback.iss.length === 0) {
4549
- throw new Error("OAuth callback missing issuer");
4550
- }
4684
+ return isNonBlankOwnString(value, "resourceMetadataUrl") && isObjectRecord2(getOwnEntry4(value, "resourceMetadata")) && isObjectRecord2(getOwnEntry4(value, "authorizationServerMetadata"));
4685
+ }
4686
+ function isStoredOAuthTokensOrMissing(value) {
4687
+ if (value === void 0) {
4688
+ return true;
4551
4689
  }
4552
- if (callback.iss !== null && callback.iss.length > 0 && expected.issuer !== null && callback.iss !== expected.issuer) {
4553
- throw new Error("OAuth callback issuer mismatch");
4690
+ if (!isObjectRecord2(value)) {
4691
+ return false;
4554
4692
  }
4693
+ if (!isNonBlankOwnString(value, "accessToken") || getOwnString(value, "tokenType") !== "Bearer") {
4694
+ return false;
4695
+ }
4696
+ const expiresAt = getOwnEntry4(value, "expiresAt");
4697
+ if (expiresAt !== null && (typeof expiresAt !== "number" || !Number.isSafeInteger(expiresAt) || expiresAt > MAX_JS_DATE_MS || !Number.isFinite(new Date(expiresAt).getTime()))) {
4698
+ return false;
4699
+ }
4700
+ const refreshToken = getOwnEntry4(value, "refreshToken");
4701
+ if (refreshToken !== void 0 && (typeof refreshToken !== "string" || refreshToken.trim().length === 0)) {
4702
+ return false;
4703
+ }
4704
+ const scope = getOwnEntry4(value, "scope");
4705
+ return scope === void 0 || typeof scope === "string" && scope.trim().length > 0;
4555
4706
  }
4556
- function createAuthorizationError(error, description) {
4557
- return new Error(`OAuth authorization failed: ${error} \u2014 ${description}`);
4558
- }
4559
- function escapeHtml(text) {
4560
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
4707
+ function isNonBlankOwnString(record2, key2) {
4708
+ const value = getOwnString(record2, key2);
4709
+ return value !== void 0 && value.trim().length > 0;
4561
4710
  }
4562
- function buildSuccessPage(landingPage) {
4563
- const title = landingPage?.title ?? "Connected";
4564
- const body = landingPage?.body ?? "You can close this tab and return to your terminal.";
4565
- return [
4566
- "<!DOCTYPE html>",
4567
- `<html><head><meta charset=utf-8><title>${escapeHtml(title)}</title></head>`,
4568
- '<body style="font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0">',
4569
- '<div style="text-align:center">',
4570
- `<h1>${escapeHtml(title)}</h1>`,
4571
- `<p style="color:#666">${escapeHtml(body)}</p>`,
4572
- "</div></body></html>"
4573
- ].join("");
4711
+
4712
+ // ../mcp-oauth/dist/client/default-oauth-client-provider.js
4713
+ import { isIP } from "node:net";
4714
+
4715
+ // ../mcp-oauth/dist/http-fetch.js
4716
+ async function fetchMcpResponse(fetchImplementation, input, init = {}) {
4717
+ const response = await fetchImplementation(input, { ...init, redirect: "error" });
4718
+ if (response.redirected || response.type === "opaqueredirect") {
4719
+ void response.body?.cancel().catch(() => void 0);
4720
+ throw new Error("MCP HTTP redirects are not allowed");
4721
+ }
4722
+ return response;
4574
4723
  }
4575
4724
 
4725
+ // ../mcp-oauth/dist/client/default-oauth-client-provider.js
4726
+ import { URL as URL2 } from "node:url";
4727
+
4576
4728
  // ../mcp-oauth/dist/client/pkce.js
4577
4729
  import crypto3 from "node:crypto";
4578
4730
  function generateCodeVerifier() {
@@ -4665,6 +4817,7 @@ async function exchangeAuthorizationCode(input) {
4665
4817
  tokenEndpoint: input.tokenEndpoint,
4666
4818
  clientId: input.clientId,
4667
4819
  clientSecret: input.clientSecret,
4820
+ tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
4668
4821
  params: {
4669
4822
  grant_type: "authorization_code",
4670
4823
  code: input.code,
@@ -4683,6 +4836,7 @@ async function refreshAccessToken(input) {
4683
4836
  tokenEndpoint: input.tokenEndpoint,
4684
4837
  clientId: input.clientId,
4685
4838
  clientSecret: input.clientSecret,
4839
+ tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
4686
4840
  params: {
4687
4841
  grant_type: "refresh_token",
4688
4842
  refresh_token: input.refreshToken,
@@ -4694,21 +4848,26 @@ async function refreshAccessToken(input) {
4694
4848
  });
4695
4849
  }
4696
4850
  async function requestTokens(input) {
4697
- const body = new URLSearchParams({
4698
- client_id: input.clientId,
4699
- ...input.params
4700
- });
4701
- if (input.clientSecret !== void 0) {
4702
- body.set("client_secret", input.clientSecret);
4851
+ const method = normalizeOAuthTokenEndpointAuthMethod(input.tokenEndpointAuthMethod) ?? (input.clientSecret === void 0 ? "none" : "client_secret_post");
4852
+ if (method !== "none" && (input.clientSecret === void 0 || input.clientSecret.trim() === ""))
4853
+ throw new Error("OAuth token endpoint authentication requires a client secret");
4854
+ const body = new URLSearchParams(input.params);
4855
+ const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded" });
4856
+ if (method === "client_secret_basic") {
4857
+ const encoded = new URLSearchParams({ credential: input.clientId }).toString().slice("credential=".length);
4858
+ const encodedSecret = new URLSearchParams({ credential: input.clientSecret }).toString().slice("credential=".length);
4859
+ headers.set("Authorization", `Basic ${Buffer.from(`${encoded}:${encodedSecret}`).toString("base64")}`);
4860
+ } else {
4861
+ body.set("client_id", input.clientId);
4862
+ if (method === "client_secret_post")
4863
+ body.set("client_secret", input.clientSecret);
4703
4864
  }
4704
4865
  input.signal?.throwIfAborted();
4705
4866
  const deadline = AbortSignal.timeout(3e4);
4706
4867
  const signal = input.signal === void 0 ? deadline : AbortSignal.any([input.signal, deadline]);
4707
4868
  const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
4708
4869
  method: "POST",
4709
- headers: {
4710
- "Content-Type": "application/x-www-form-urlencoded"
4711
- },
4870
+ headers,
4712
4871
  body: body.toString(),
4713
4872
  signal
4714
4873
  });
@@ -4861,6 +5020,9 @@ function createDefaultOAuthClientProvider(options) {
4861
5020
  assertPersistenceNamespace(options.persistenceNamespace);
4862
5021
  const clientMetadata = getClientMetadata(options.client);
4863
5022
  const requestedScope = clientMetadata?.scope;
5023
+ const configuredClient = normalizeConfiguredClient(options.client);
5024
+ const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
5025
+ const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
4864
5026
  const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
4865
5027
  const clientStore = options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
4866
5028
  const now = options.now ?? Date.now;
@@ -4877,8 +5039,8 @@ function createDefaultOAuthClientProvider(options) {
4877
5039
  }
4878
5040
  const initialGrant = options.initialGrant === void 0 ? void 0 : {
4879
5041
  resource: canonicalizeResourceIndicator(options.initialGrant.resource),
4880
- tokens: normalizeStoredTokens(options.initialGrant.tokens),
4881
- client: normalizeConfiguredClient(options.client)
5042
+ tokens: normalizeImportedTokens(options.initialGrant.tokens, now),
5043
+ client: configuredClient
4882
5044
  };
4883
5045
  if (initialGrant !== void 0 && (initialGrant.tokens === void 0 || initialGrant.client === null))
4884
5046
  throw new Error("OAuth initial grant requires valid tokens and the original client ID");
@@ -4955,6 +5117,10 @@ function createDefaultOAuthClientProvider(options) {
4955
5117
  const canonicalResource = canonicalizeResourceIndicator(resource);
4956
5118
  return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
4957
5119
  let session = await loadSession(canonicalResource);
5120
+ if (session !== null)
5121
+ assertRegistrationIssuer(session.client, session.authorizationServer);
5122
+ if (configuredClient !== null && discovery !== void 0)
5123
+ assertRegistrationIssuer(configuredClient, discovery.authorizationServer);
4958
5124
  if (session !== null && initialGrant?.resource === canonicalResource)
4959
5125
  initialGrantConsumed = true;
4960
5126
  signal?.throwIfAborted();
@@ -4982,13 +5148,15 @@ function createDefaultOAuthClientProvider(options) {
4982
5148
  if (forceRefresh && rejectedTokens !== void 0 && (rejectedTokens === null || session?.tokens === void 0 || !sameTokenGrant(session.tokens, rejectedTokens)))
4983
5149
  forceRefresh = false;
4984
5150
  const sessionDiscovery = resolveDiscovery(discovery, session);
4985
- if ((options.client.mode === "static" || initialGrant !== void 0) && session !== null && (session.tokens !== void 0 || session.refreshState === "pending")) {
4986
- const configured = normalizeConfiguredClient(options.client);
5151
+ if ((options.client.mode === "static" || configuredClient?.registration !== void 0 || initialGrant !== void 0) && session !== null && (session.tokens !== void 0 || session.refreshState === "pending")) {
5152
+ const configured = configuredClient;
4987
5153
  if (configured === null || configured.clientId !== session.client.clientId || configured.clientSecret !== session.client.clientSecret)
4988
5154
  throw new Error("Stored session belongs to a different OAuth client; use separate persistence or explicitly reset it");
4989
5155
  }
4990
5156
  if (requestedScope !== void 0 && session?.tokens !== void 0 && normalizeOAuthScope(session.tokens.scope ?? session.requestedScope) !== requestedScope)
4991
5157
  throw new Error("Stored session does not match the requested OAuth scope; authorize again or select separate persistence");
5158
+ if (configuredTokenMethod !== void 0 && session !== null && (session.tokens !== void 0 || session.refreshState === "pending") && (session.client.tokenEndpointAuthMethod ?? (session.client.clientSecret === void 0 ? "none" : "client_secret_post")) !== configuredTokenMethod)
5159
+ throw new Error("Stored session does not match the requested OAuth token endpoint authentication; select separate persistence or reset it");
4992
5160
  if (session?.refreshState === "pending") {
4993
5161
  if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === void 0)
4994
5162
  throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
@@ -4998,6 +5166,11 @@ function createDefaultOAuthClientProvider(options) {
4998
5166
  return session;
4999
5167
  }
5000
5168
  if (session?.tokens?.refreshToken !== void 0 && sessionDiscovery !== void 0 && (forceRefresh || isExpired(session.tokens, now))) {
5169
+ if (hasExpiredClientSecret(session.client, now)) {
5170
+ if (!allowInteractive || options.allowInteractive === false || options.client.mode === "static" || configuredClient?.registration !== void 0)
5171
+ throw new Error("OAuth client secret has expired; authorize again or update the imported registration");
5172
+ return authorizeSession(canonicalResource, clearSessionTokens(session), sessionDiscovery, fetch2, signal);
5173
+ }
5001
5174
  session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
5002
5175
  if (session?.tokens !== void 0 && !isExpired(session.tokens, now)) {
5003
5176
  return session;
@@ -5021,6 +5194,7 @@ function createDefaultOAuthClientProvider(options) {
5021
5194
  if (session.tokens?.refreshToken === void 0) {
5022
5195
  return session;
5023
5196
  }
5197
+ assertTokenEndpointAuthentication(session.client, discovery.authorizationServerMetadata);
5024
5198
  const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
5025
5199
  await saveSession(resource, pendingSession);
5026
5200
  signal?.throwIfAborted();
@@ -5032,6 +5206,7 @@ function createDefaultOAuthClientProvider(options) {
5032
5206
  tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
5033
5207
  clientId: session.client.clientId,
5034
5208
  clientSecret: session.client.clientSecret,
5209
+ tokenEndpointAuthMethod: session.client.tokenEndpointAuthMethod,
5035
5210
  refreshToken: session.tokens.refreshToken,
5036
5211
  resource,
5037
5212
  fetch: fetch2,
@@ -5095,6 +5270,7 @@ function createDefaultOAuthClientProvider(options) {
5095
5270
  let resolvedClient = null;
5096
5271
  try {
5097
5272
  resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch2, signal);
5273
+ assertTokenEndpointAuthentication(resolvedClient.client, discovery.authorizationServerMetadata);
5098
5274
  const sessionWithoutTokens = {
5099
5275
  resource,
5100
5276
  authorizationServer: discovery.authorizationServer,
@@ -5118,6 +5294,7 @@ function createDefaultOAuthClientProvider(options) {
5118
5294
  tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
5119
5295
  clientId: resolvedClient.client.clientId,
5120
5296
  clientSecret: resolvedClient.client.clientSecret,
5297
+ tokenEndpointAuthMethod: resolvedClient.client.tokenEndpointAuthMethod,
5121
5298
  code,
5122
5299
  codeVerifier: verifier,
5123
5300
  redirectUri: loopback.redirectUri,
@@ -5157,11 +5334,13 @@ function createDefaultOAuthClientProvider(options) {
5157
5334
  }
5158
5335
  async function resolveClient(existingSession, discovery, redirectUri, fetch2, parentSignal) {
5159
5336
  parentSignal?.throwIfAborted();
5160
- const configuredClient = normalizeConfiguredClient(options.client);
5161
- if (options.client.mode === "static") {
5337
+ if (options.client.mode === "static" || configuredClient?.registration !== void 0) {
5162
5338
  if (configuredClient === null) {
5163
5339
  throw new Error("OAuth client_id must not be blank");
5164
5340
  }
5341
+ assertRegistrationIssuer(configuredClient, discovery.authorizationServer);
5342
+ if (hasExpiredClientSecret(configuredClient, now))
5343
+ throw new Error("OAuth client secret has expired; update the imported registration");
5165
5344
  return {
5166
5345
  kind: "static",
5167
5346
  fromStoredRegistration: false,
@@ -5176,7 +5355,14 @@ function createDefaultOAuthClientProvider(options) {
5176
5355
  client: configuredClient
5177
5356
  };
5178
5357
  }
5179
- const storedClient = await loadRegisteredClient(discovery.authorizationServer);
5358
+ let storedClient = await loadRegisteredClient(discovery.authorizationServer);
5359
+ if (storedClient !== null) {
5360
+ assertRegistrationIssuer(storedClient, discovery.authorizationServer);
5361
+ if (hasExpiredClientSecret(storedClient, now) || !registrationMatchesRedirect(storedClient, redirectUri)) {
5362
+ await clearRegisteredClient(discovery.authorizationServer);
5363
+ storedClient = null;
5364
+ }
5365
+ }
5180
5366
  if (storedClient !== null) {
5181
5367
  return {
5182
5368
  kind: "dynamic",
@@ -5185,7 +5371,7 @@ function createDefaultOAuthClientProvider(options) {
5185
5371
  };
5186
5372
  }
5187
5373
  if (registrationEndpoint === void 0) {
5188
- if (existingSession !== null && existingSession.client.clientId.length > 0) {
5374
+ if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
5189
5375
  return {
5190
5376
  kind: "dynamic",
5191
5377
  fromStoredRegistration: true,
@@ -5194,7 +5380,7 @@ function createDefaultOAuthClientProvider(options) {
5194
5380
  }
5195
5381
  throw new Error("Authorization server metadata is missing registration_endpoint");
5196
5382
  }
5197
- if (existingSession !== null && existingSession.client.clientId.length > 0) {
5383
+ if (existingSession !== null && existingSession.client.clientId.length > 0 && !hasExpiredClientSecret(existingSession.client, now) && registrationMatchesRedirect(existingSession.client, redirectUri)) {
5198
5384
  const isConfiguredStaticFallback = configuredClient !== null && existingSession.client.clientId === configuredClient.clientId && existingSession.client.clientSecret === configuredClient.clientSecret;
5199
5385
  if (!isConfiguredStaticFallback) {
5200
5386
  await saveRegisteredClient(discovery.authorizationServer, existingSession.client);
@@ -5205,7 +5391,11 @@ function createDefaultOAuthClientProvider(options) {
5205
5391
  };
5206
5392
  }
5207
5393
  }
5208
- const registrationBody = buildClientRegistrationBody(clientMetadata, redirectUri);
5394
+ const supported = getSupportedTokenAuthMethods(discovery.authorizationServerMetadata);
5395
+ const registrationMethod = requestedTokenMethod ?? (supported === void 0 ? "none" : ["none", "client_secret_basic", "client_secret_post"].find((method) => supported.includes(method)));
5396
+ if (registrationMethod === void 0 || supported !== void 0 && !supported.includes(registrationMethod))
5397
+ throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
5398
+ const registrationBody = buildClientRegistrationBody(clientMetadata, redirectUri, registrationMethod);
5209
5399
  const deadline = AbortSignal.timeout(3e4);
5210
5400
  const signal = parentSignal === void 0 ? deadline : AbortSignal.any([parentSignal, deadline]);
5211
5401
  const response = await fetchMcpResponse(fetch2, registrationEndpoint, {
@@ -5217,27 +5407,33 @@ function createDefaultOAuthClientProvider(options) {
5217
5407
  signal
5218
5408
  });
5219
5409
  const payload = await readOAuthJsonObjectResponse(response, signal);
5220
- const clientId = getOwnString2(payload, "client_id");
5221
- if (clientId === void 0 || clientId.trim().length === 0) {
5222
- throw new Error("OAuth client registration response missing client_id");
5223
- }
5224
- const clientSecret = getOwnString2(payload, "client_secret");
5410
+ const registration = parseOAuthClientRegistration(payload);
5411
+ const registeredSecret = getOwnString2(registration, "client_secret");
5412
+ const responseMethod = normalizeOAuthTokenEndpointAuthMethod(getOwnEntry6(registration, "token_endpoint_auth_method")) ?? requestedTokenMethod ?? (supported === void 0 ? void 0 : normalizeOAuthTokenEndpointAuthMethod(registrationMethod));
5225
5413
  const registeredClient = {
5226
- clientId: clientId.trim(),
5227
- clientSecret: clientSecret !== void 0 && clientSecret.trim().length > 0 ? clientSecret.trim() : void 0
5414
+ clientId: registration.client_id.trim(),
5415
+ ...registeredSecret === void 0 ? {} : { clientSecret: registeredSecret.trim() },
5416
+ ...responseMethod === void 0 ? {} : { tokenEndpointAuthMethod: responseMethod },
5417
+ registration
5228
5418
  };
5229
- await saveRegisteredClient(discovery.authorizationServer, registeredClient);
5419
+ assertRegistrationIssuer(registeredClient, discovery.authorizationServer);
5420
+ if (hasExpiredClientSecret(registeredClient, now))
5421
+ throw new Error("OAuth client secret has expired in the registration response");
5422
+ if (!registrationMatchesRedirect(registeredClient, redirectUri, true))
5423
+ throw new Error("OAuth registration response does not match the requested redirect URI");
5424
+ const clientWithRedirect = { ...registeredClient, requestedRedirectUri: redirectUri };
5425
+ await saveRegisteredClient(discovery.authorizationServer, clientWithRedirect);
5230
5426
  return {
5231
5427
  kind: "dynamic",
5232
5428
  fromStoredRegistration: false,
5233
- client: registeredClient
5429
+ client: clientWithRedirect
5234
5430
  };
5235
5431
  }
5236
5432
  async function loadSession(resource) {
5237
5433
  return normalizeLoadedSession(await sessionStore.load(resource));
5238
5434
  }
5239
5435
  async function saveSession(resource, session) {
5240
- await sessionStore.save(resource, session);
5436
+ await sessionStore.save(resource, structuredClone(session));
5241
5437
  }
5242
5438
  async function clearSession(resource) {
5243
5439
  await sessionStore.clear(resource);
@@ -5250,7 +5446,7 @@ function createDefaultOAuthClientProvider(options) {
5250
5446
  return null;
5251
5447
  }
5252
5448
  const client = await clientStore.load(issuer);
5253
- const normalizedClient = client === null ? null : normalizeStoredClient(client);
5449
+ const normalizedClient = client === null ? null : normalizeStoredOAuthClient(client);
5254
5450
  if (client !== null && normalizedClient === null) {
5255
5451
  await clientStore.clear(issuer);
5256
5452
  return null;
@@ -5323,7 +5519,7 @@ function normalizeLoadedSession(session) {
5323
5519
  const refreshState = getOwnEntry6(session, "refreshState");
5324
5520
  if (refreshState !== void 0 && (refreshState !== "pending" || getOwnEntry6(session, "tokens") !== void 0))
5325
5521
  throw new Error("Stored OAuth refresh state is invalid");
5326
- const client = normalizeStoredClient(getOwnEntry6(session, "client"));
5522
+ const client = normalizeStoredOAuthClient(getOwnEntry6(session, "client"));
5327
5523
  if (client === null) {
5328
5524
  return { ...session, client: { clientId: "" }, tokens: void 0 };
5329
5525
  }
@@ -5333,24 +5529,18 @@ function normalizeLoadedSession(session) {
5333
5529
  tokens: normalizeStoredTokens(getOwnEntry6(session, "tokens"))
5334
5530
  };
5335
5531
  }
5336
- function normalizeStoredClient(value) {
5337
- if (!isObjectRecord3(value)) {
5338
- return null;
5339
- }
5340
- const clientId = getOwnString2(value, "clientId");
5341
- if (clientId === void 0 || clientId.trim().length === 0) {
5342
- return null;
5343
- }
5344
- const normalizedClientId = clientId.trim();
5345
- const clientSecret = getOwnEntry6(value, "clientSecret");
5346
- if (clientSecret === void 0) {
5347
- return { clientId: normalizedClientId };
5348
- }
5349
- if (typeof clientSecret !== "string" || clientSecret.trim().length === 0) {
5350
- return null;
5351
- }
5352
- const normalizedClientSecret = clientSecret.trim();
5353
- return { clientId: normalizedClientId, clientSecret: normalizedClientSecret };
5532
+ function normalizeImportedTokens(value, now) {
5533
+ if (!isObjectRecord3(value))
5534
+ return void 0;
5535
+ const absolute = getOwnEntry6(value, "expiresAt");
5536
+ const lifetime = getOwnEntry6(value, "expiresIn");
5537
+ const issuedAt = getOwnEntry6(value, "issuedAt");
5538
+ if (lifetime !== void 0 && (typeof lifetime !== "number" || !Number.isSafeInteger(lifetime) || lifetime < 0))
5539
+ throw new Error("OAuth initial grant has invalid relative expiry");
5540
+ if (issuedAt !== void 0 && (typeof issuedAt !== "number" || !Number.isSafeInteger(issuedAt) || Math.abs(issuedAt) > MAX_JS_DATE_MS3))
5541
+ throw new Error("OAuth initial grant has invalid issuance time");
5542
+ const expiresAt = absolute !== void 0 && absolute !== null ? absolute : lifetime === void 0 ? null : (issuedAt === void 0 ? now() : issuedAt) + lifetime * 1e3;
5543
+ return normalizeStoredTokens({ ...value, expiresAt });
5354
5544
  }
5355
5545
  function normalizeStoredTokens(value) {
5356
5546
  if (value === void 0 || !isObjectRecord3(value)) {
@@ -5387,12 +5577,12 @@ function getClientMetadata(client) {
5387
5577
  };
5388
5578
  }
5389
5579
  function normalizeConfiguredClient(client) {
5390
- const clientId = normalizeOptionalOAuthString(client.clientId);
5391
- if (clientId === void 0) {
5580
+ const registration = client.registration === void 0 ? void 0 : parseOAuthClientRegistration(client.registration);
5581
+ const clientId = normalizeOptionalOAuthString(client.clientId) ?? registration?.client_id.trim();
5582
+ if (clientId === void 0)
5392
5583
  return null;
5393
- }
5394
- const clientSecret = normalizeOptionalOAuthString(client.clientSecret);
5395
- return clientSecret === void 0 ? { clientId } : { clientId, clientSecret };
5584
+ const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === void 0 ? void 0 : getOwnString2(registration, "client_secret")?.trim());
5585
+ return normalizeStoredOAuthClient({ clientId, clientSecret, registration, tokenEndpointAuthMethod: client.tokenEndpointAuthMethod });
5396
5586
  }
5397
5587
  function normalizeOptionalOAuthString(value) {
5398
5588
  if (value === void 0) {
@@ -5492,12 +5682,39 @@ function assertRequestMatchesResource(requestUrl, resource) {
5492
5682
  throw new Error(`OAuth request URL ${requestUrl} does not match discovered resource ${resource}`);
5493
5683
  }
5494
5684
  }
5495
- function buildClientRegistrationBody(metadata, redirectUri) {
5685
+ function assertRegistrationIssuer(client, issuer) {
5686
+ const registrationIssuer = client.registration === void 0 ? void 0 : getOwnString2(client.registration, "issuer");
5687
+ if (registrationIssuer !== void 0 && registrationIssuer !== issuer)
5688
+ throw new Error("OAuth client registration issuer does not match the authorization server");
5689
+ }
5690
+ function hasExpiredClientSecret(client, now) {
5691
+ if (client.clientSecret === void 0 || client.tokenEndpointAuthMethod === "none" || client.registration === void 0)
5692
+ return false;
5693
+ const expiry = getOwnEntry6(client.registration, "client_secret_expires_at");
5694
+ return typeof expiry === "number" && expiry !== 0 && expiry <= now() / 1e3;
5695
+ }
5696
+ function getSupportedTokenAuthMethods(metadata) {
5697
+ const value = getOwnEntry6(metadata, "token_endpoint_auth_methods_supported");
5698
+ if (value === void 0)
5699
+ return void 0;
5700
+ if (!Array.isArray(value) || value.length > 128 || value.some((method) => typeof method !== "string"))
5701
+ throw new Error("Invalid OAuth token endpoint authentication metadata");
5702
+ return value;
5703
+ }
5704
+ function assertTokenEndpointAuthentication(client, metadata) {
5705
+ const method = client.tokenEndpointAuthMethod ?? (client.clientSecret === void 0 ? "none" : "client_secret_post");
5706
+ if (method !== "none" && client.clientSecret === void 0)
5707
+ throw new Error("OAuth token endpoint authentication requires a client secret");
5708
+ const supported = getSupportedTokenAuthMethods(metadata);
5709
+ if (supported !== void 0 && !supported.includes(method))
5710
+ throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
5711
+ }
5712
+ function buildClientRegistrationBody(metadata, redirectUri, tokenEndpointAuthMethod) {
5496
5713
  const body = {
5497
5714
  redirect_uris: [redirectUri],
5498
5715
  grant_types: ["authorization_code", "refresh_token"],
5499
5716
  response_types: ["code"],
5500
- token_endpoint_auth_method: "none"
5717
+ token_endpoint_auth_method: tokenEndpointAuthMethod
5501
5718
  };
5502
5719
  const clientName = metadata === void 0 ? void 0 : getOwnString2(metadata, "clientName");
5503
5720
  const scope = metadata === void 0 ? void 0 : getOwnString2(metadata, "scope");