tiny-http-mcp-server 0.1.34 → 0.1.36

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.
@@ -3395,6 +3395,311 @@ var SubscriptionManager = class {
3395
3395
  }
3396
3396
  };
3397
3397
 
3398
+ // ../mcp-oauth/dist/client/loopback-authorization.js
3399
+ import http from "node:http";
3400
+
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
+ }
3412
+ function parseAuthorizationState(value) {
3413
+ if (value === null || value.length === 0) {
3414
+ return null;
3415
+ }
3416
+ try {
3417
+ const decoded = Buffer.from(value, "base64url").toString("utf8");
3418
+ const parsed = JSON.parse(decoded);
3419
+ if (!isObjectRecord(parsed)) {
3420
+ return null;
3421
+ }
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;
3428
+ }
3429
+ return {
3430
+ issuer,
3431
+ requireIssuer
3432
+ };
3433
+ } catch {
3434
+ return null;
3435
+ }
3436
+ }
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;
3472
+ try {
3473
+ port = await startServer(server, target.port, target.host, controller.signal);
3474
+ } catch (error) {
3475
+ controller.abort(error);
3476
+ throw error;
3477
+ }
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;
3486
+ try {
3487
+ return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
3488
+ } finally {
3489
+ clearTimeout(timer);
3490
+ }
3491
+ },
3492
+ close() {
3493
+ controller.abort(new Error("OAuth authorization session closed"));
3494
+ }
3495
+ };
3496
+ }
3497
+ function loopbackTarget(options) {
3498
+ if (options.redirectUri !== void 0) {
3499
+ let url;
3500
+ try {
3501
+ url = new URL(options.redirectUri);
3502
+ } catch (cause) {
3503
+ throw new Error("Invalid OAuth loopback redirect URI", { cause });
3504
+ }
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 };
3509
+ }
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 };
3515
+ }
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);
3522
+ };
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 });
3533
+ try {
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);
3547
+ });
3548
+ } catch (error) {
3549
+ cleanup();
3550
+ reject(error);
3551
+ }
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;
3576
+ }
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
+ };
3589
+ try {
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));
3597
+ } catch (error) {
3598
+ res.writeHead(400);
3599
+ res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
3600
+ settle(() => reject(error));
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)));
3618
+ }
3619
+ if (options.openBrowser !== void 0) {
3620
+ void Promise.resolve().then(() => settled ? void 0 : options.openBrowser(authorizationUrl)).catch((error) => settle(() => reject(error)));
3621
+ }
3622
+ });
3623
+ }
3624
+ function extractCallbackParametersFromInput(input) {
3625
+ const trimmed = input.replaceAll("\r", "").replaceAll("\n", "").trim();
3626
+ if (trimmed.length === 0) {
3627
+ return null;
3628
+ }
3629
+ try {
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
+ };
3646
+ }
3647
+ }
3648
+ function readExpectedAuthorizationCallback(authorizationUrl) {
3649
+ const url = new URL(authorizationUrl);
3650
+ const state = url.searchParams.get("state");
3651
+ const parsedState = parseAuthorizationState(state);
3652
+ return {
3653
+ state,
3654
+ issuer: parsedState?.issuer ?? null,
3655
+ requireIssuer: parsedState?.requireIssuer ?? false
3656
+ };
3657
+ }
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");
3662
+ }
3663
+ return callback.code;
3664
+ }
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");
3669
+ }
3670
+ if (callback.state !== expected.state) {
3671
+ throw new Error("OAuth callback state mismatch");
3672
+ }
3673
+ }
3674
+ if (expected.requireIssuer) {
3675
+ if (callback.iss === null || callback.iss.length === 0) {
3676
+ throw new Error("OAuth callback missing issuer");
3677
+ }
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");
3681
+ }
3682
+ }
3683
+ function createAuthorizationError(error, description) {
3684
+ return new Error(`OAuth authorization failed: ${error} \u2014 ${description}`);
3685
+ }
3686
+ function escapeHtml(text) {
3687
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3688
+ }
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("");
3701
+ }
3702
+
3398
3703
  // ../mcp-oauth/dist/client/scope.js
3399
3704
  function normalizeOAuthScope(scope) {
3400
3705
  if (scope === void 0)
@@ -3512,6 +3817,17 @@ function normalizeStoredOAuthClient(value) {
3512
3817
  if (typeof clientId !== "string" || clientId.trim() === "" || clientSecret !== void 0 && (typeof clientSecret !== "string" || clientSecret.trim() === ""))
3513
3818
  return null;
3514
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) {
3822
+ try {
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
+ }
3515
3831
  const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record2, "tokenEndpointAuthMethod") ? record2.tokenEndpointAuthMethod : void 0);
3516
3832
  if (Object.hasOwn(record2, "registration") && record2.registration !== void 0) {
3517
3833
  const registration = parseOAuthClientRegistration(record2.registration);
@@ -3529,9 +3845,38 @@ function normalizeStoredOAuthClient(value) {
3529
3845
  client.tokenEndpointAuthMethod = method;
3530
3846
  return client;
3531
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;
3865
+ }
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;
3875
+ });
3876
+ }
3532
3877
 
3533
3878
  // ../mcp-oauth/dist/client/auth-store-session-store.js
3534
- import crypto from "node:crypto";
3879
+ import crypto2 from "node:crypto";
3535
3880
  import path4 from "node:path";
3536
3881
 
3537
3882
  // ../auth-store/dist/encrypted-file-store.js
@@ -3955,10 +4300,10 @@ function parseEncryptedDocument(raw) {
3955
4300
  if (!isRecord2(parsed)) {
3956
4301
  return null;
3957
4302
  }
3958
- const version = getOwnEntry(parsed, "version");
3959
- const iv = getOwnEntry(parsed, "iv");
3960
- const authTag = getOwnEntry(parsed, "authTag");
3961
- const ciphertext = getOwnEntry(parsed, "ciphertext");
4303
+ const version = getOwnEntry2(parsed, "version");
4304
+ const iv = getOwnEntry2(parsed, "iv");
4305
+ const authTag = getOwnEntry2(parsed, "authTag");
4306
+ const ciphertext = getOwnEntry2(parsed, "ciphertext");
3962
4307
  if (version !== ENCRYPTION_VERSION) {
3963
4308
  return null;
3964
4309
  }
@@ -3978,7 +4323,7 @@ function parseEncryptedDocument(raw) {
3978
4323
  function isRecord2(value) {
3979
4324
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
3980
4325
  }
3981
- function getOwnEntry(record2, key2) {
4326
+ function getOwnEntry2(record2, key2) {
3982
4327
  return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
3983
4328
  }
3984
4329
  function isNotFoundError(error) {
@@ -4139,552 +4484,247 @@ function createSecurityCliFailure(operation, result) {
4139
4484
  return new Error(`Failed to ${operation}: security exited with code ${exitCode}`);
4140
4485
  }
4141
4486
  function getCommandExitCode(result) {
4142
- const value = getOwnEntry2(result, "exitCode");
4487
+ const value = getOwnEntry3(result, "exitCode");
4143
4488
  return typeof value === "number" && Number.isInteger(value) ? value : 1;
4144
4489
  }
4145
4490
  function getCommandOutput(result, key2) {
4146
- const value = getOwnEntry2(result, key2);
4147
- return typeof value === "string" ? value : "";
4148
- }
4149
- function getOwnEntry2(record2, key2) {
4150
- return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
4151
- }
4152
-
4153
- // ../auth-store/dist/create-secret-store.js
4154
- var DEFAULT_BACKEND_ENV_VAR = "AUTH_BACKEND";
4155
- var MACOS_PLATFORM = "darwin";
4156
- var storeFactories = {
4157
- file: (input) => {
4158
- if (!input.fileStore) {
4159
- throw new Error("fileStore configuration is required for file backend");
4160
- }
4161
- return new EncryptedFileStore(input.fileStore);
4162
- },
4163
- keychain: (input) => {
4164
- if (!input.keychainStore) {
4165
- throw new Error("keychainStore configuration is required for keychain backend");
4166
- }
4167
- return new KeychainStore(input.keychainStore);
4168
- }
4169
- };
4170
- function createSecretStore(input) {
4171
- const backend = resolveBackend(input);
4172
- const platform = input.platform ?? process.platform;
4173
- if (backend === "keychain" && platform !== MACOS_PLATFORM) {
4174
- throw new Error(`Keychain backend is only supported on macOS. Current platform: ${platform}`);
4175
- }
4176
- const store = storeFactories[backend](input);
4177
- return { backend, store };
4178
- }
4179
- function resolveBackend(input) {
4180
- const envVar = input.backendEnvVar ?? DEFAULT_BACKEND_ENV_VAR;
4181
- const configuredBackend = input.backend ?? getOwnEnvValue(input.env, envVar) ?? getOwnEnvValue(process.env, envVar);
4182
- const backend = configuredBackend?.trim();
4183
- if (backend === "keychain") {
4184
- return "keychain";
4185
- }
4186
- if (backend === void 0 || backend === "file") {
4187
- return "file";
4188
- }
4189
- throw new Error(`Unsupported auth store backend: ${backend}`);
4190
- }
4191
- function getOwnEnvValue(env, key2) {
4192
- return env !== void 0 && Object.prototype.hasOwnProperty.call(env, key2) ? env[key2] : void 0;
4193
- }
4194
-
4195
- // ../mcp-oauth/dist/resource-indicator.js
4196
- function canonicalizeResourceIndicator(value) {
4197
- let url;
4198
- try {
4199
- url = value instanceof URL ? new URL(value.toString()) : new URL(value);
4200
- } catch {
4201
- throw new Error("Resource indicator must be an absolute URL");
4202
- }
4203
- url.hash = "";
4204
- return url.toString();
4205
- }
4206
-
4207
- // ../mcp-oauth/dist/client/auth-store-session-store.js
4208
- var DEFAULT_FILE_SALT = "poe-code:mcp-oauth:v1";
4209
- var DEFAULT_FILE_DIRECTORY = ".poe-code/mcp-oauth";
4210
- var DEFAULT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth";
4211
- var DEFAULT_CLIENT_FILE_SALT = "poe-code:mcp-oauth:clients:v1";
4212
- var DEFAULT_CLIENT_FILE_DIRECTORY = ".poe-code/mcp-oauth/clients";
4213
- var DEFAULT_CLIENT_KEYCHAIN_SERVICE = "poe-code-mcp-oauth-clients";
4214
- var MAX_JS_DATE_MS = 864e13;
4215
- function createAuthStoreSessionStore(options = {}, namespace) {
4216
- assertPersistenceNamespace(namespace);
4217
- return {
4218
- async withLock(resource, operation, lockOptions) {
4219
- const store = createResourceSecretStore(resource, options, namespace);
4220
- if (store.withLock === void 0)
4221
- throw new Error("OAuth secret-store backend does not support transaction locks");
4222
- return store.withLock(operation, lockOptions);
4223
- },
4224
- async load(resource) {
4225
- const store = createResourceSecretStore(resource, options, namespace);
4226
- const value = await store.get();
4227
- if (value === null) {
4228
- return null;
4229
- }
4230
- let parsed;
4231
- try {
4232
- parsed = JSON.parse(value);
4233
- } catch {
4234
- throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
4235
- }
4236
- if (isStoredOAuthSession(parsed)) {
4237
- return parsed;
4238
- }
4239
- throw new Error("Stored OAuth session must match the expected shape");
4240
- },
4241
- async save(resource, session) {
4242
- const store = createResourceSecretStore(resource, options, namespace);
4243
- await store.set(JSON.stringify(session));
4244
- },
4245
- async clear(resource) {
4246
- const store = createResourceSecretStore(resource, options, namespace);
4247
- await store.delete();
4248
- }
4249
- };
4250
- }
4251
- function createAuthStoreClientStore(options, namespace) {
4252
- assertPersistenceNamespace(namespace);
4253
- return {
4254
- async load(issuer) {
4255
- const store = createIssuerSecretStore(issuer, options, namespace);
4256
- const value = await store.get();
4257
- if (value === null) {
4258
- return null;
4259
- }
4260
- let parsed;
4261
- try {
4262
- parsed = JSON.parse(value);
4263
- } catch {
4264
- throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
4265
- }
4266
- if (isObjectRecord(parsed) && typeof getOwnEntry3(parsed, "clientId") === "string")
4267
- return parsed;
4268
- throw new Error("Stored OAuth client must be a JSON object with clientId");
4269
- },
4270
- async save(issuer, client) {
4271
- const store = createIssuerSecretStore(issuer, options, namespace);
4272
- await store.set(JSON.stringify(client));
4273
- },
4274
- async clear(issuer) {
4275
- const store = createIssuerSecretStore(issuer, options, namespace);
4276
- await store.delete();
4277
- }
4278
- };
4279
- }
4280
- function createNamedSecretStore(key2, options, defaults, namespace) {
4281
- const hash = crypto.createHash("sha256").update(namespace === void 0 ? key2 : JSON.stringify([namespace, key2])).digest("hex");
4282
- const configuredFilePath = options.fileStore?.filePath;
4283
- const parsedFilePath = configuredFilePath === void 0 ? null : path4.parse(configuredFilePath);
4284
- const fileStore = {
4285
- ...options.fileStore,
4286
- throwOnInvalidDocument: true,
4287
- filePath: parsedFilePath === null ? void 0 : path4.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
4288
- salt: options.fileStore?.salt ?? defaults.salt,
4289
- defaultDirectory: options.fileStore?.defaultDirectory || defaults.directory,
4290
- defaultFileName: parsedFilePath === null ? `${hash}.enc` : `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`
4291
- };
4292
- const keychainStore = {
4293
- ...options.keychainStore,
4294
- service: options.keychainStore?.service ?? defaults.service,
4295
- account: `${options.keychainStore?.account ?? defaults.accountPrefix}:${hash}`
4296
- };
4297
- return createSecretStore({ ...options, fileStore, keychainStore }).store;
4298
- }
4299
- function createResourceSecretStore(resource, options, namespace) {
4300
- return createNamedSecretStore(canonicalizeResourceIndicator(resource), options, {
4301
- salt: DEFAULT_FILE_SALT,
4302
- directory: DEFAULT_FILE_DIRECTORY,
4303
- service: DEFAULT_KEYCHAIN_SERVICE,
4304
- accountPrefix: "provider"
4305
- }, namespace);
4306
- }
4307
- function createIssuerSecretStore(issuer, options, namespace) {
4308
- return createNamedSecretStore(issuer, options, {
4309
- salt: DEFAULT_CLIENT_FILE_SALT,
4310
- directory: DEFAULT_CLIENT_FILE_DIRECTORY,
4311
- service: DEFAULT_CLIENT_KEYCHAIN_SERVICE,
4312
- accountPrefix: "issuer"
4313
- }, namespace);
4314
- }
4315
- function assertPersistenceNamespace(namespace) {
4316
- if (namespace !== void 0 && (typeof namespace !== "string" || namespace.trim() === "" || Buffer.byteLength(namespace, "utf8") > 1024))
4317
- throw new Error("OAuth persistence namespace must be a nonempty string within 1024 bytes");
4318
- }
4319
- function isObjectRecord(value) {
4320
- return typeof value === "object" && value !== null && !Array.isArray(value);
4491
+ const value = getOwnEntry3(result, key2);
4492
+ return typeof value === "string" ? value : "";
4321
4493
  }
4322
4494
  function getOwnEntry3(record2, key2) {
4323
4495
  return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
4324
4496
  }
4325
- function getOwnString(record2, key2) {
4326
- const value = getOwnEntry3(record2, key2);
4327
- return typeof value === "string" ? value : void 0;
4328
- }
4329
- function isStoredOAuthSession(value) {
4330
- if (!isObjectRecord(value)) {
4331
- return false;
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");
4505
+ }
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");
4511
+ }
4512
+ return new KeychainStore(input.keychainStore);
4332
4513
  }
4333
- return isNonBlankOwnString(value, "resource") && isNonBlankOwnString(value, "authorizationServer") && normalizeStoredOAuthClient(getOwnEntry3(value, "client")) !== null && 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"));
4334
- }
4335
- function isStoredOAuthDiscovery(value) {
4336
- if (!isObjectRecord(value)) {
4337
- return false;
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}`);
4338
4520
  }
4339
- return isNonBlankOwnString(value, "resourceMetadataUrl") && isObjectRecord(getOwnEntry3(value, "resourceMetadata")) && isObjectRecord(getOwnEntry3(value, "authorizationServerMetadata"));
4521
+ const store = storeFactories[backend](input);
4522
+ return { backend, store };
4340
4523
  }
4341
- function isStoredOAuthTokensOrMissing(value) {
4342
- if (value === void 0) {
4343
- return true;
4344
- }
4345
- if (!isObjectRecord(value)) {
4346
- return false;
4347
- }
4348
- if (!isNonBlankOwnString(value, "accessToken") || getOwnString(value, "tokenType") !== "Bearer") {
4349
- return false;
4350
- }
4351
- const expiresAt = getOwnEntry3(value, "expiresAt");
4352
- if (expiresAt !== null && (typeof expiresAt !== "number" || !Number.isSafeInteger(expiresAt) || expiresAt > MAX_JS_DATE_MS || !Number.isFinite(new Date(expiresAt).getTime()))) {
4353
- return false;
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";
4354
4530
  }
4355
- const refreshToken = getOwnEntry3(value, "refreshToken");
4356
- if (refreshToken !== void 0 && (typeof refreshToken !== "string" || refreshToken.trim().length === 0)) {
4357
- return false;
4531
+ if (backend === void 0 || backend === "file") {
4532
+ return "file";
4358
4533
  }
4359
- const scope = getOwnEntry3(value, "scope");
4360
- return scope === void 0 || typeof scope === "string" && scope.trim().length > 0;
4361
- }
4362
- function isNonBlankOwnString(record2, key2) {
4363
- const value = getOwnString(record2, key2);
4364
- return value !== void 0 && value.trim().length > 0;
4534
+ throw new Error(`Unsupported auth store backend: ${backend}`);
4365
4535
  }
4366
-
4367
- // ../mcp-oauth/dist/client/default-oauth-client-provider.js
4368
- import { isIP } from "node:net";
4369
-
4370
- // ../mcp-oauth/dist/http-fetch.js
4371
- async function fetchMcpResponse(fetchImplementation, input, init = {}) {
4372
- const response = await fetchImplementation(input, { ...init, redirect: "error" });
4373
- if (response.redirected || response.type === "opaqueredirect") {
4374
- void response.body?.cancel().catch(() => void 0);
4375
- throw new Error("MCP HTTP redirects are not allowed");
4376
- }
4377
- return response;
4536
+ function getOwnEnvValue(env, key2) {
4537
+ return env !== void 0 && Object.prototype.hasOwnProperty.call(env, key2) ? env[key2] : void 0;
4378
4538
  }
4379
4539
 
4380
- // ../mcp-oauth/dist/client/default-oauth-client-provider.js
4381
- import { URL as URL2 } from "node:url";
4382
-
4383
- // ../mcp-oauth/dist/client/loopback-authorization.js
4384
- import http from "node:http";
4385
-
4386
- // ../mcp-oauth/dist/client/authorization-state.js
4387
- import crypto2 from "node:crypto";
4388
- function createAuthorizationState(input) {
4389
- const payload = {
4390
- v: 1,
4391
- n: crypto2.randomBytes(16).toString("base64url"),
4392
- i: input.issuer,
4393
- r: input.requireIssuer
4394
- };
4395
- return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
4396
- }
4397
- function parseAuthorizationState(value) {
4398
- if (value === null || value.length === 0) {
4399
- return null;
4400
- }
4540
+ // ../mcp-oauth/dist/resource-indicator.js
4541
+ function canonicalizeResourceIndicator(value) {
4542
+ let url;
4401
4543
  try {
4402
- const decoded = Buffer.from(value, "base64url").toString("utf8");
4403
- const parsed = JSON.parse(decoded);
4404
- if (!isObjectRecord2(parsed)) {
4405
- return null;
4406
- }
4407
- const version = getOwnEntry4(parsed, "v");
4408
- const nonce = getOwnEntry4(parsed, "n");
4409
- const issuer = getOwnEntry4(parsed, "i");
4410
- const requireIssuer = getOwnEntry4(parsed, "r");
4411
- if (version !== 1 || typeof nonce !== "string" || nonce.length === 0 || typeof issuer !== "string" || issuer.length === 0 || typeof requireIssuer !== "boolean") {
4412
- return null;
4413
- }
4414
- return {
4415
- issuer,
4416
- requireIssuer
4417
- };
4544
+ url = value instanceof URL ? new URL(value.toString()) : new URL(value);
4418
4545
  } catch {
4419
- return null;
4546
+ throw new Error("Resource indicator must be an absolute URL");
4420
4547
  }
4421
- }
4422
- function isObjectRecord2(value) {
4423
- return typeof value === "object" && value !== null && !Array.isArray(value);
4424
- }
4425
- function getOwnEntry4(record2, key2) {
4426
- return Object.prototype.hasOwnProperty.call(record2, key2) ? record2[key2] : void 0;
4548
+ url.hash = "";
4549
+ return url.toString();
4427
4550
  }
4428
4551
 
4429
- // ../mcp-oauth/dist/client/loopback-authorization.js
4430
- async function createLoopbackAuthorizationSession(options = {}) {
4431
- options.signal?.throwIfAborted();
4432
- const timeoutMs = options.timeoutMs ?? 12e4;
4433
- if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)
4434
- throw new Error("OAuth authorization timeoutMs must be a positive supported timer interval");
4435
- const target = loopbackTarget(options);
4436
- const server = options.createServer ? options.createServer() : http.createServer();
4437
- const controller = new AbortController();
4438
- let closed = false;
4439
- let used = false;
4440
- const callerAbort = () => controller.abort(options.signal?.reason);
4441
- const teardown = () => {
4442
- if (closed)
4443
- return;
4444
- closed = true;
4445
- clearTimeout(timer);
4446
- options.signal?.removeEventListener("abort", callerAbort);
4447
- server.closeAllConnections?.();
4448
- server.close();
4449
- };
4450
- const timer = setTimeout(() => controller.abort(new Error("OAuth authorization timed out")), timeoutMs);
4451
- timer.unref?.();
4452
- controller.signal.addEventListener("abort", teardown, { once: true });
4453
- options.signal?.addEventListener("abort", callerAbort, { once: true });
4454
- if (options.signal?.aborted)
4455
- callerAbort();
4456
- let port;
4457
- try {
4458
- port = await startServer(server, target.port, target.host, controller.signal);
4459
- } catch (error) {
4460
- controller.abort(error);
4461
- throw error;
4462
- }
4463
- const redirectUri = options.redirectUri ?? `http://127.0.0.1:${port}${target.callbackPath}`;
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);
4464
4562
  return {
4465
- redirectUri,
4466
- async waitForCode(authorizationUrl) {
4467
- controller.signal.throwIfAborted();
4468
- if (used)
4469
- throw new Error("OAuth authorization session has already been used");
4470
- used = true;
4471
- try {
4472
- return await waitForAuthorizationCode(server, authorizationUrl, options, target.callbackPath, controller.signal);
4473
- } finally {
4474
- clearTimeout(timer);
4475
- }
4476
- },
4477
- close() {
4478
- controller.abort(new Error("OAuth authorization session closed"));
4479
- }
4480
- };
4481
- }
4482
- function loopbackTarget(options) {
4483
- if (options.redirectUri !== void 0) {
4484
- let url;
4485
- try {
4486
- url = new URL(options.redirectUri);
4487
- } catch (cause) {
4488
- throw new Error("Invalid OAuth loopback redirect URI", { cause });
4489
- }
4490
- const forbiddenQuery = ["code", "state", "error", "error_description", "iss"];
4491
- 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)
4492
- throw new Error("Invalid OAuth loopback redirect URI");
4493
- return { port: url.port ? Number(url.port) : 80, host: url.hostname === "[::1]" ? "::1" : url.hostname, callbackPath: url.pathname };
4494
- }
4495
- const callbackPath = options.callbackPath ?? "/callback";
4496
- const parsed = new URL(callbackPath, "http://127.0.0.1");
4497
- if (!callbackPath.startsWith("/") || parsed.origin !== "http://127.0.0.1" || parsed.pathname !== callbackPath || parsed.search || parsed.hash)
4498
- throw new Error("Invalid OAuth loopback callback path");
4499
- return { port: 0, host: "127.0.0.1", callbackPath };
4500
- }
4501
- async function startServer(server, port, host, signal) {
4502
- signal.throwIfAborted();
4503
- return new Promise((resolve, reject) => {
4504
- const cleanup = () => {
4505
- server.off("error", handleError);
4506
- signal.removeEventListener("abort", aborted);
4507
- };
4508
- const handleError = (error) => {
4509
- cleanup();
4510
- reject(error);
4511
- };
4512
- const aborted = () => {
4513
- cleanup();
4514
- reject(signal.reason);
4515
- };
4516
- server.once("error", handleError);
4517
- signal.addEventListener("abort", aborted, { once: true });
4518
- try {
4519
- server.listen(port, host, () => {
4520
- cleanup();
4521
- if (signal.aborted) {
4522
- server.close();
4523
- reject(signal.reason);
4524
- return;
4525
- }
4526
- const address = server.address();
4527
- if (address === null || typeof address === "string") {
4528
- reject(new Error("OAuth listener has no TCP address"));
4529
- return;
4530
- }
4531
- resolve(address.port);
4532
- });
4533
- } catch (error) {
4534
- cleanup();
4535
- reject(error);
4536
- }
4537
- });
4538
- }
4539
- function waitForAuthorizationCode(server, authorizationUrl, options, callbackPath, signal) {
4540
- signal.throwIfAborted();
4541
- const expectedAuthorization = readExpectedAuthorizationCallback(authorizationUrl);
4542
- return new Promise((resolve, reject) => {
4543
- let settled = false;
4544
- const settle = (fn) => {
4545
- if (settled)
4546
- return;
4547
- settled = true;
4548
- server.off("request", request);
4549
- signal.removeEventListener("abort", aborted);
4550
- fn();
4551
- };
4552
- const aborted = () => settle(() => reject(signal.reason));
4553
- const request = (req, res) => {
4554
- let url;
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;
4555
4576
  try {
4556
- url = new URL(req.url ?? "/", "http://127.0.0.1");
4577
+ parsed = JSON.parse(value);
4557
4578
  } catch {
4558
- res.writeHead(400);
4559
- res.end("Invalid callback URL");
4560
- return;
4579
+ throw new Error("Stored OAuth session must be valid JSON; reset the store explicitly to recover");
4561
4580
  }
4562
- if (url.pathname !== callbackPath) {
4563
- res.writeHead(404);
4564
- res.end("Not found");
4565
- return;
4581
+ if (isStoredOAuthSession(parsed)) {
4582
+ return parsed;
4566
4583
  }
4567
- const callbackParameters = {
4568
- code: url.searchParams.get("code"),
4569
- error: url.searchParams.get("error"),
4570
- errorDescription: url.searchParams.get("error_description"),
4571
- state: url.searchParams.get("state"),
4572
- iss: url.searchParams.get("iss")
4573
- };
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
+ }
4594
+ };
4595
+ }
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;
4574
4606
  try {
4575
- validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
4576
- if (callbackParameters.error !== null)
4577
- throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
4578
- const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
4579
- res.writeHead(200, { "Content-Type": "text/html" });
4580
- res.end(buildSuccessPage(options.landingPage));
4581
- settle(() => resolve(code));
4582
- } catch (error) {
4583
- res.writeHead(400);
4584
- res.end(error instanceof Error ? error.message : "Invalid OAuth callback");
4585
- settle(() => reject(error));
4607
+ parsed = JSON.parse(value);
4608
+ } catch {
4609
+ throw new Error("Stored OAuth client must be valid JSON; reset the store explicitly to recover");
4586
4610
  }
4587
- };
4588
- server.on("request", request);
4589
- signal.addEventListener("abort", aborted, { once: true });
4590
- if (options.readLine !== void 0) {
4591
- void Promise.resolve().then(() => settled ? void 0 : options.readLine()).then((input) => {
4592
- if (settled)
4593
- return;
4594
- const callbackParameters = extractCallbackParametersFromInput(input);
4595
- if (callbackParameters === null)
4596
- throw new Error("OAuth callback missing authorization code");
4597
- validateAuthorizationCallbackBinding(callbackParameters, expectedAuthorization);
4598
- if (callbackParameters.error !== null)
4599
- throw createAuthorizationError(callbackParameters.error, callbackParameters.errorDescription ?? callbackParameters.error);
4600
- const code = validateAuthorizationCallbackParameters(callbackParameters, expectedAuthorization);
4601
- settle(() => resolve(code));
4602
- }).catch((error) => settle(() => reject(error)));
4603
- }
4604
- if (options.openBrowser !== void 0) {
4605
- void Promise.resolve().then(() => settled ? void 0 : options.openBrowser(authorizationUrl)).catch((error) => settle(() => reject(error)));
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();
4606
4622
  }
4607
- });
4608
- }
4609
- function extractCallbackParametersFromInput(input) {
4610
- const trimmed = input.replaceAll("\r", "").replaceAll("\n", "").trim();
4611
- if (trimmed.length === 0) {
4612
- return null;
4613
- }
4614
- try {
4615
- const url = new URL(trimmed);
4616
- return {
4617
- code: url.searchParams.get("code"),
4618
- error: url.searchParams.get("error"),
4619
- errorDescription: url.searchParams.get("error_description"),
4620
- state: url.searchParams.get("state"),
4621
- iss: url.searchParams.get("iss")
4622
- };
4623
- } catch {
4624
- return {
4625
- code: trimmed,
4626
- error: null,
4627
- errorDescription: null,
4628
- state: null,
4629
- iss: null
4630
- };
4631
- }
4623
+ };
4632
4624
  }
4633
- function readExpectedAuthorizationCallback(authorizationUrl) {
4634
- const url = new URL(authorizationUrl);
4635
- const state = url.searchParams.get("state");
4636
- const parsedState = parseAuthorizationState(state);
4637
- return {
4638
- state,
4639
- issuer: parsedState?.issuer ?? null,
4640
- requireIssuer: parsedState?.requireIssuer ?? false
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
4641
  };
4642
+ return createSecretStore({ ...options, fileStore, keychainStore }).store;
4642
4643
  }
4643
- function validateAuthorizationCallbackParameters(callback, expected) {
4644
- validateAuthorizationCallbackBinding(callback, expected);
4645
- if (callback.code === null || callback.code.length === 0) {
4646
- throw new Error("OAuth callback missing authorization code");
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;
4647
4677
  }
4648
- 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"));
4649
4679
  }
4650
- function validateAuthorizationCallbackBinding(callback, expected) {
4651
- if (expected.state !== null) {
4652
- if (callback.state === null || callback.state.length === 0) {
4653
- throw new Error("OAuth callback missing state");
4654
- }
4655
- if (callback.state !== expected.state) {
4656
- throw new Error("OAuth callback state mismatch");
4657
- }
4680
+ function isStoredOAuthDiscovery(value) {
4681
+ if (!isObjectRecord2(value)) {
4682
+ return false;
4658
4683
  }
4659
- if (expected.requireIssuer) {
4660
- if (callback.iss === null || callback.iss.length === 0) {
4661
- throw new Error("OAuth callback missing issuer");
4662
- }
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;
4663
4689
  }
4664
- if (callback.iss !== null && callback.iss.length > 0 && expected.issuer !== null && callback.iss !== expected.issuer) {
4665
- throw new Error("OAuth callback issuer mismatch");
4690
+ if (!isObjectRecord2(value)) {
4691
+ return false;
4666
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;
4667
4706
  }
4668
- function createAuthorizationError(error, description) {
4669
- return new Error(`OAuth authorization failed: ${error} \u2014 ${description}`);
4670
- }
4671
- function escapeHtml(text) {
4672
- 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;
4673
4710
  }
4674
- function buildSuccessPage(landingPage) {
4675
- const title = landingPage?.title ?? "Connected";
4676
- const body = landingPage?.body ?? "You can close this tab and return to your terminal.";
4677
- return [
4678
- "<!DOCTYPE html>",
4679
- `<html><head><meta charset=utf-8><title>${escapeHtml(title)}</title></head>`,
4680
- '<body style="font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0">',
4681
- '<div style="text-align:center">',
4682
- `<h1>${escapeHtml(title)}</h1>`,
4683
- `<p style="color:#666">${escapeHtml(body)}</p>`,
4684
- "</div></body></html>"
4685
- ].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;
4686
4723
  }
4687
4724
 
4725
+ // ../mcp-oauth/dist/client/default-oauth-client-provider.js
4726
+ import { URL as URL2 } from "node:url";
4727
+
4688
4728
  // ../mcp-oauth/dist/client/pkce.js
4689
4729
  import crypto3 from "node:crypto";
4690
4730
  function generateCodeVerifier() {
@@ -4999,7 +5039,7 @@ function createDefaultOAuthClientProvider(options) {
4999
5039
  }
5000
5040
  const initialGrant = options.initialGrant === void 0 ? void 0 : {
5001
5041
  resource: canonicalizeResourceIndicator(options.initialGrant.resource),
5002
- tokens: normalizeStoredTokens(options.initialGrant.tokens),
5042
+ tokens: normalizeImportedTokens(options.initialGrant.tokens, now),
5003
5043
  client: configuredClient
5004
5044
  };
5005
5045
  if (initialGrant !== void 0 && (initialGrant.tokens === void 0 || initialGrant.client === null))
@@ -5077,6 +5117,10 @@ function createDefaultOAuthClientProvider(options) {
5077
5117
  const canonicalResource = canonicalizeResourceIndicator(resource);
5078
5118
  return withOAuthSessionTransaction(sessionStore, canonicalResource, async () => {
5079
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);
5080
5124
  if (session !== null && initialGrant?.resource === canonicalResource)
5081
5125
  initialGrantConsumed = true;
5082
5126
  signal?.throwIfAborted();
@@ -5122,6 +5166,11 @@ function createDefaultOAuthClientProvider(options) {
5122
5166
  return session;
5123
5167
  }
5124
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
+ }
5125
5174
  session = await refreshSession(canonicalResource, session, sessionDiscovery, fetch2, signal);
5126
5175
  if (session?.tokens !== void 0 && !isExpired(session.tokens, now)) {
5127
5176
  return session;
@@ -5289,6 +5338,9 @@ function createDefaultOAuthClientProvider(options) {
5289
5338
  if (configuredClient === null) {
5290
5339
  throw new Error("OAuth client_id must not be blank");
5291
5340
  }
5341
+ assertRegistrationIssuer(configuredClient, discovery.authorizationServer);
5342
+ if (hasExpiredClientSecret(configuredClient, now))
5343
+ throw new Error("OAuth client secret has expired; update the imported registration");
5292
5344
  return {
5293
5345
  kind: "static",
5294
5346
  fromStoredRegistration: false,
@@ -5303,7 +5355,14 @@ function createDefaultOAuthClientProvider(options) {
5303
5355
  client: configuredClient
5304
5356
  };
5305
5357
  }
5306
- 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
+ }
5307
5366
  if (storedClient !== null) {
5308
5367
  return {
5309
5368
  kind: "dynamic",
@@ -5312,7 +5371,7 @@ function createDefaultOAuthClientProvider(options) {
5312
5371
  };
5313
5372
  }
5314
5373
  if (registrationEndpoint === void 0) {
5315
- 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)) {
5316
5375
  return {
5317
5376
  kind: "dynamic",
5318
5377
  fromStoredRegistration: true,
@@ -5321,7 +5380,7 @@ function createDefaultOAuthClientProvider(options) {
5321
5380
  }
5322
5381
  throw new Error("Authorization server metadata is missing registration_endpoint");
5323
5382
  }
5324
- 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)) {
5325
5384
  const isConfiguredStaticFallback = configuredClient !== null && existingSession.client.clientId === configuredClient.clientId && existingSession.client.clientSecret === configuredClient.clientSecret;
5326
5385
  if (!isConfiguredStaticFallback) {
5327
5386
  await saveRegisteredClient(discovery.authorizationServer, existingSession.client);
@@ -5357,11 +5416,17 @@ function createDefaultOAuthClientProvider(options) {
5357
5416
  ...responseMethod === void 0 ? {} : { tokenEndpointAuthMethod: responseMethod },
5358
5417
  registration
5359
5418
  };
5360
- 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);
5361
5426
  return {
5362
5427
  kind: "dynamic",
5363
5428
  fromStoredRegistration: false,
5364
- client: registeredClient
5429
+ client: clientWithRedirect
5365
5430
  };
5366
5431
  }
5367
5432
  async function loadSession(resource) {
@@ -5464,6 +5529,19 @@ function normalizeLoadedSession(session) {
5464
5529
  tokens: normalizeStoredTokens(getOwnEntry6(session, "tokens"))
5465
5530
  };
5466
5531
  }
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 });
5544
+ }
5467
5545
  function normalizeStoredTokens(value) {
5468
5546
  if (value === void 0 || !isObjectRecord3(value)) {
5469
5547
  return void 0;
@@ -5604,6 +5682,17 @@ function assertRequestMatchesResource(requestUrl, resource) {
5604
5682
  throw new Error(`OAuth request URL ${requestUrl} does not match discovered resource ${resource}`);
5605
5683
  }
5606
5684
  }
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
+ }
5607
5696
  function getSupportedTokenAuthMethods(metadata) {
5608
5697
  const value = getOwnEntry6(metadata, "token_endpoint_auth_methods_supported");
5609
5698
  if (value === void 0)