@12-apps/mcp 3.2.0 → 3.2.1

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.
@@ -0,0 +1,1247 @@
1
+ import {
2
+ buildAuthorizationServerMetadata,
3
+ buildProtectedResourceMetadata
4
+ } from "./chunk-WJJNKKNS.js";
5
+ import {
6
+ __name
7
+ } from "./chunk-7QVYU63E.js";
8
+
9
+ // src/oauth/config.ts
10
+ var MCP_SUPPORTED_SCOPES = ["mcp:read", "mcp:write"];
11
+ var DEFAULT_MCP_RESOURCE_PATH = "/api/mcp";
12
+ function issuer(origin) {
13
+ return origin;
14
+ }
15
+ __name(issuer, "issuer");
16
+ function resourceAudience(origin, resourcePath = DEFAULT_MCP_RESOURCE_PATH) {
17
+ return `${origin}${resourcePath}`;
18
+ }
19
+ __name(resourceAudience, "resourceAudience");
20
+ var HOST_ONLY = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*(?::\d{1,5})?$/i;
21
+ var SLASH = 47;
22
+ function stripTrailingSlashes(value) {
23
+ let end = value.length;
24
+ while (end > 0 && value.charCodeAt(end - 1) === SLASH) end -= 1;
25
+ return value.slice(0, end);
26
+ }
27
+ __name(stripTrailingSlashes, "stripTrailingSlashes");
28
+ function normalizeOrigins(origins) {
29
+ return origins.map((origin) => stripTrailingSlashes(origin.trim())).filter(Boolean);
30
+ }
31
+ __name(normalizeOrigins, "normalizeOrigins");
32
+ function trustedOriginsFromEnv(name) {
33
+ const raw = typeof process === "undefined" ? void 0 : process.env?.[name];
34
+ return raw ? normalizeOrigins(raw.split(",")) : [];
35
+ }
36
+ __name(trustedOriginsFromEnv, "trustedOriginsFromEnv");
37
+ function claimedForwardedOrigin(getHeader) {
38
+ const host = getHeader("x-forwarded-host")?.split(",")[0]?.trim();
39
+ if (!host || !HOST_ONLY.test(host)) return null;
40
+ const proto = getHeader("x-forwarded-proto")?.split(",")[0]?.trim();
41
+ return `${proto === "http" ? "http" : "https"}://${host}`;
42
+ }
43
+ __name(claimedForwardedOrigin, "claimedForwardedOrigin");
44
+ function resolveTrustedOrigin(getHeader, fallbackOrigin, trustedOrigins = []) {
45
+ const [canonical, ...rest] = normalizeOrigins(trustedOrigins);
46
+ if (!canonical) return fallbackOrigin;
47
+ const allowed = [canonical, ...rest];
48
+ const claimed = claimedForwardedOrigin(getHeader);
49
+ return claimed && allowed.includes(claimed) ? claimed : canonical;
50
+ }
51
+ __name(resolveTrustedOrigin, "resolveTrustedOrigin");
52
+ function originFromRequest(request, trustedOrigins = []) {
53
+ const fallback = new URL(request.url).origin;
54
+ return resolveTrustedOrigin((name) => request.headers.get(name), fallback, trustedOrigins) ?? fallback;
55
+ }
56
+ __name(originFromRequest, "originFromRequest");
57
+
58
+ // src/oauth/keys.ts
59
+ import { exportJWK, importPKCS8 } from "jose";
60
+ var SIGNING_ALG = "ES256";
61
+ async function parseSigningKey(pem, kid) {
62
+ const privateKey = await importPKCS8(pem, SIGNING_ALG, { extractable: true });
63
+ const jwk = await exportJWK(privateKey);
64
+ const { d: _private, ...publicHalf } = jwk;
65
+ void _private;
66
+ const publicJwk = {
67
+ ...publicHalf,
68
+ kty: "EC",
69
+ crv: "P-256",
70
+ alg: SIGNING_ALG,
71
+ use: "sig",
72
+ kid
73
+ };
74
+ return { privateKey, publicJwk, kid };
75
+ }
76
+ __name(parseSigningKey, "parseSigningKey");
77
+ function signingKeyProvider(read) {
78
+ let cache = null;
79
+ return async () => {
80
+ const { pem, kid } = read();
81
+ if (!pem || !kid) return null;
82
+ const cacheKey = `${kid} ${pem}`;
83
+ if (cache?.key === cacheKey) return cache.promise;
84
+ const promise = parseSigningKey(pem, kid);
85
+ cache = { key: cacheKey, promise };
86
+ return promise;
87
+ };
88
+ }
89
+ __name(signingKeyProvider, "signingKeyProvider");
90
+ var DEFAULT_SIGNING_KEY_ENV = "MCP_OAUTH_SIGNING_KEY";
91
+ var DEFAULT_SIGNING_KEY_ID_ENV = "MCP_OAUTH_SIGNING_KEY_ID";
92
+ function loadSigningKeyFromEnv(keyEnv = DEFAULT_SIGNING_KEY_ENV, kidEnv = DEFAULT_SIGNING_KEY_ID_ENV) {
93
+ return signingKeyProvider(() => ({
94
+ pem: typeof process === "undefined" ? void 0 : process.env?.[keyEnv],
95
+ kid: typeof process === "undefined" ? void 0 : process.env?.[kidEnv]
96
+ }));
97
+ }
98
+ __name(loadSigningKeyFromEnv, "loadSigningKeyFromEnv");
99
+
100
+ // src/oauth/authorization-code.ts
101
+ import { SignJWT, jwtVerify, importJWK } from "jose";
102
+ var AUTHORIZATION_CODE_AUDIENCE = "oauth:code";
103
+ var AUTHORIZATION_CODE_TTL_SECONDS = 60;
104
+ var CLOCK_TOLERANCE_SECONDS = 5;
105
+ var AuthorizationCodeError = class extends Error {
106
+ static {
107
+ __name(this, "AuthorizationCodeError");
108
+ }
109
+ code;
110
+ constructor(message) {
111
+ super(message ?? "invalid_grant");
112
+ this.name = "AuthorizationCodeError";
113
+ this.code = "invalid_grant";
114
+ }
115
+ };
116
+ function nowSeconds(now) {
117
+ return Math.floor((now ?? Date.now()) / 1e3);
118
+ }
119
+ __name(nowSeconds, "nowSeconds");
120
+ function stringClaim(payload, key) {
121
+ const value = payload[key];
122
+ return typeof value === "string" ? value : null;
123
+ }
124
+ __name(stringClaim, "stringClaim");
125
+ function extractBoundFields(payload) {
126
+ const sub = stringClaim(payload, "sub");
127
+ const email = stringClaim(payload, "email");
128
+ const clientId = stringClaim(payload, "client_id");
129
+ const redirectUri = stringClaim(payload, "redirect_uri");
130
+ const codeChallenge = stringClaim(payload, "code_challenge");
131
+ const scope = stringClaim(payload, "scope");
132
+ const jti = stringClaim(payload, "jti");
133
+ if (!sub || !email || !clientId || !redirectUri || !codeChallenge || scope === null || !jti) {
134
+ throw new AuthorizationCodeError("code is missing required bound fields");
135
+ }
136
+ return { sub, email, clientId, redirectUri, codeChallenge, scope, jti };
137
+ }
138
+ __name(extractBoundFields, "extractBoundFields");
139
+ async function mintCode(loadSigningKey, input, options) {
140
+ const key = await loadSigningKey();
141
+ if (!key) return null;
142
+ const iat = nowSeconds(options?.now);
143
+ const exp = iat + AUTHORIZATION_CODE_TTL_SECONDS;
144
+ const claims = {
145
+ email: input.email,
146
+ client_id: input.clientId,
147
+ redirect_uri: input.redirectUri,
148
+ code_challenge: input.codeChallenge,
149
+ scope: input.scope
150
+ };
151
+ return new SignJWT(claims).setProtectedHeader({ alg: SIGNING_ALG, kid: key.kid }).setIssuer(issuer(input.origin)).setAudience(AUTHORIZATION_CODE_AUDIENCE).setSubject(input.sub).setIssuedAt(iat).setExpirationTime(exp).setJti(crypto.randomUUID()).sign(key.privateKey);
152
+ }
153
+ __name(mintCode, "mintCode");
154
+ async function verifyCode(loadSigningKey, code, options) {
155
+ const key = await loadSigningKey();
156
+ if (!key) {
157
+ throw new AuthorizationCodeError("no signing key configured");
158
+ }
159
+ const publicKey = await importJWK(key.publicJwk, SIGNING_ALG);
160
+ let payload;
161
+ try {
162
+ const result = await jwtVerify(code, publicKey, {
163
+ algorithms: [SIGNING_ALG],
164
+ issuer: issuer(options.origin),
165
+ audience: AUTHORIZATION_CODE_AUDIENCE,
166
+ clockTolerance: CLOCK_TOLERANCE_SECONDS,
167
+ currentDate: options.now === void 0 ? void 0 : new Date(options.now)
168
+ });
169
+ payload = result.payload;
170
+ } catch {
171
+ throw new AuthorizationCodeError("code verification failed");
172
+ }
173
+ return extractBoundFields(payload);
174
+ }
175
+ __name(verifyCode, "verifyCode");
176
+
177
+ // src/oauth/clients.ts
178
+ import { createHash, randomBytes, randomUUID } from "crypto";
179
+ var DEFAULT_GRANT_TYPES = ["authorization_code", "refresh_token"];
180
+ var CLIENT_SECRET_BYTES = 32;
181
+ function hashSecret(secret) {
182
+ return createHash("sha256").update(secret).digest("hex");
183
+ }
184
+ __name(hashSecret, "hashSecret");
185
+ async function registerClient(store, input) {
186
+ const clientId = randomUUID();
187
+ const authMethod = input.tokenEndpointAuthMethod ?? "none";
188
+ const grantTypes = input.grantTypes ?? [...DEFAULT_GRANT_TYPES];
189
+ const clientSecret = authMethod === "client_secret_basic" ? randomBytes(CLIENT_SECRET_BYTES).toString("hex") : void 0;
190
+ const row = await store.create({
191
+ clientId,
192
+ clientSecretHash: clientSecret ? hashSecret(clientSecret) : null,
193
+ redirectUris: input.redirectUris,
194
+ clientName: input.clientName ?? null,
195
+ tokenEndpointAuthMethod: authMethod,
196
+ grantTypes,
197
+ scopes: input.scopes
198
+ });
199
+ return {
200
+ clientId: row.clientId,
201
+ ...clientSecret ? { clientSecret } : {},
202
+ redirectUris: row.redirectUris,
203
+ clientName: row.clientName,
204
+ tokenEndpointAuthMethod: row.tokenEndpointAuthMethod,
205
+ grantTypes: row.grantTypes,
206
+ scopes: row.scopes
207
+ };
208
+ }
209
+ __name(registerClient, "registerClient");
210
+ function matchesRedirectUri(client, redirectUri) {
211
+ if (!redirectUri) return false;
212
+ return client.redirectUris.includes(redirectUri);
213
+ }
214
+ __name(matchesRedirectUri, "matchesRedirectUri");
215
+ var DEFAULT_PROVIDER_ROOTS = [
216
+ { roots: ["claude.ai", "anthropic.com"], provider: "claude" },
217
+ { roots: ["chatgpt.com", "openai.com"], provider: "chatgpt" }
218
+ ];
219
+ function hostMatchesRoot(host, root) {
220
+ return host === root || host.endsWith(`.${root}`);
221
+ }
222
+ __name(hostMatchesRoot, "hostMatchesRoot");
223
+ function providerFromRedirectUris(redirectUris, rules = DEFAULT_PROVIDER_ROOTS) {
224
+ for (const uri of redirectUris) {
225
+ let host;
226
+ try {
227
+ host = new URL(uri).host.toLowerCase();
228
+ } catch {
229
+ continue;
230
+ }
231
+ const match = rules.find((rule) => rule.roots.some((root) => hostMatchesRoot(host, root)));
232
+ if (match) return match.provider;
233
+ }
234
+ return null;
235
+ }
236
+ __name(providerFromRedirectUris, "providerFromRedirectUris");
237
+
238
+ // src/oauth/pkce.ts
239
+ var SUPPORTED_CHALLENGE_METHOD = "S256";
240
+ var UnsupportedChallengeMethodError = class extends Error {
241
+ static {
242
+ __name(this, "UnsupportedChallengeMethodError");
243
+ }
244
+ method;
245
+ constructor(method) {
246
+ super(
247
+ `unsupported code_challenge_method '${method}' \u2014 only ${SUPPORTED_CHALLENGE_METHOD} is allowed`
248
+ );
249
+ this.name = "UnsupportedChallengeMethodError";
250
+ this.method = method;
251
+ }
252
+ };
253
+ function base64UrlEncode(bytes) {
254
+ return Buffer.from(bytes).toString("base64url");
255
+ }
256
+ __name(base64UrlEncode, "base64UrlEncode");
257
+ async function computeChallenge(verifier) {
258
+ const data = new TextEncoder().encode(verifier);
259
+ const digest = await crypto.subtle.digest("SHA-256", data);
260
+ return base64UrlEncode(new Uint8Array(digest));
261
+ }
262
+ __name(computeChallenge, "computeChallenge");
263
+ function constantTimeEquals(a, b) {
264
+ if (a.length !== b.length) return false;
265
+ let mismatch = 0;
266
+ for (let i = 0; i < a.length; i += 1) {
267
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
268
+ }
269
+ return mismatch === 0;
270
+ }
271
+ __name(constantTimeEquals, "constantTimeEquals");
272
+ async function verifyChallenge(verifier, storedChallenge, method = SUPPORTED_CHALLENGE_METHOD) {
273
+ if (method !== SUPPORTED_CHALLENGE_METHOD) {
274
+ throw new UnsupportedChallengeMethodError(method);
275
+ }
276
+ if (!storedChallenge) return false;
277
+ const computed = await computeChallenge(verifier);
278
+ return constantTimeEquals(computed, storedChallenge);
279
+ }
280
+ __name(verifyChallenge, "verifyChallenge");
281
+
282
+ // src/oauth/code-replay.ts
283
+ var RETENTION_MS = 9e4;
284
+ function inProcessCodeReplayStore() {
285
+ const usedJtis = /* @__PURE__ */ new Map();
286
+ return {
287
+ consume(jti, nowMs) {
288
+ for (const [seen, expiresAt] of usedJtis) {
289
+ if (expiresAt <= nowMs) usedJtis.delete(seen);
290
+ }
291
+ if (usedJtis.has(jti)) return false;
292
+ usedJtis.set(jti, nowMs + RETENTION_MS);
293
+ return true;
294
+ }
295
+ };
296
+ }
297
+ __name(inProcessCodeReplayStore, "inProcessCodeReplayStore");
298
+
299
+ // src/oauth/access-token.ts
300
+ import { SignJWT as SignJWT2, jwtVerify as jwtVerify2, importJWK as importJWK2 } from "jose";
301
+ var ACCESS_TOKEN_TTL_SECONDS = 15 * 60;
302
+ var CLOCK_TOLERANCE_SECONDS2 = 5;
303
+ var AccessTokenError = class extends Error {
304
+ static {
305
+ __name(this, "AccessTokenError");
306
+ }
307
+ code;
308
+ constructor(code, message) {
309
+ super(message ?? code);
310
+ this.name = "AccessTokenError";
311
+ this.code = code;
312
+ }
313
+ };
314
+ function nowSeconds2(now) {
315
+ return Math.floor((now ?? Date.now()) / 1e3);
316
+ }
317
+ __name(nowSeconds2, "nowSeconds");
318
+ async function signAccessToken(loadSigningKey, input, options) {
319
+ const key = await loadSigningKey();
320
+ if (!key) return null;
321
+ const iat = nowSeconds2(options?.now);
322
+ const exp = iat + (input.ttlSeconds ?? ACCESS_TOKEN_TTL_SECONDS);
323
+ const scope = input.scopes.join(" ");
324
+ return new SignJWT2({ email: input.email, scope }).setProtectedHeader({ alg: SIGNING_ALG, kid: key.kid }).setIssuer(issuer(input.origin)).setAudience(resourceAudience(input.origin, input.resourcePath ?? DEFAULT_MCP_RESOURCE_PATH)).setSubject(input.subject).setIssuedAt(iat).setExpirationTime(exp).setJti(crypto.randomUUID()).sign(key.privateKey);
325
+ }
326
+ __name(signAccessToken, "signAccessToken");
327
+ function parseScopes(scope) {
328
+ if (typeof scope !== "string" || scope.trim() === "") return [];
329
+ return [...new Set(scope.trim().split(/\s+/))];
330
+ }
331
+ __name(parseScopes, "parseScopes");
332
+ async function verifiedPayload(loadSigningKey, token, options) {
333
+ const key = await loadSigningKey();
334
+ if (!key) throw new AccessTokenError("invalid_token", "no signing key configured");
335
+ try {
336
+ const { payload } = await jwtVerify2(token, await importJWK2(key.publicJwk, SIGNING_ALG), {
337
+ algorithms: [SIGNING_ALG],
338
+ issuer: issuer(options.origin),
339
+ audience: resourceAudience(options.origin, options.resourcePath ?? DEFAULT_MCP_RESOURCE_PATH),
340
+ clockTolerance: CLOCK_TOLERANCE_SECONDS2,
341
+ currentDate: options.now === void 0 ? void 0 : new Date(options.now)
342
+ });
343
+ return payload;
344
+ } catch {
345
+ throw new AccessTokenError("invalid_token", "token verification failed");
346
+ }
347
+ }
348
+ __name(verifiedPayload, "verifiedPayload");
349
+ async function verifyAccessToken(loadSigningKey, token, options) {
350
+ const payload = await verifiedPayload(loadSigningKey, token, options);
351
+ const email = typeof payload.email === "string" ? payload.email : null;
352
+ const subject = typeof payload.sub === "string" ? payload.sub : null;
353
+ if (!email || !subject) {
354
+ throw new AccessTokenError("invalid_token", "missing subject or email claim");
355
+ }
356
+ const scopes = parseScopes(payload.scope);
357
+ if (options.requiredScope && !scopes.includes(options.requiredScope)) {
358
+ throw new AccessTokenError(
359
+ "insufficient_scope",
360
+ `token lacks required scope '${options.requiredScope}'`
361
+ );
362
+ }
363
+ return { email, subject, scopes };
364
+ }
365
+ __name(verifyAccessToken, "verifyAccessToken");
366
+
367
+ // src/oauth/refresh.ts
368
+ import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
369
+ var REFRESH_TOKEN_BYTES = 32;
370
+ var REFRESH_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
371
+ var RefreshTokenError = class extends Error {
372
+ static {
373
+ __name(this, "RefreshTokenError");
374
+ }
375
+ code;
376
+ constructor(code, message) {
377
+ super(message ?? code);
378
+ this.name = "RefreshTokenError";
379
+ this.code = code;
380
+ }
381
+ };
382
+ function hashToken(token) {
383
+ return createHash2("sha256").update(token).digest("hex");
384
+ }
385
+ __name(hashToken, "hashToken");
386
+ function generateToken() {
387
+ return randomBytes2(REFRESH_TOKEN_BYTES).toString("hex");
388
+ }
389
+ __name(generateToken, "generateToken");
390
+ function expiryOf(context) {
391
+ return new Date(Date.now() + (context.ttlMs ?? REFRESH_TOKEN_TTL_MS));
392
+ }
393
+ __name(expiryOf, "expiryOf");
394
+ async function issueRefreshToken(context, binding) {
395
+ const refreshToken = generateToken();
396
+ const row = {
397
+ tokenHash: hashToken(refreshToken),
398
+ userEmail: binding.userEmail,
399
+ userSub: binding.userSub,
400
+ clientId: binding.clientId,
401
+ scopes: binding.scopes,
402
+ expiresAt: expiryOf(context),
403
+ rotatedFrom: null
404
+ };
405
+ await context.store.create(row);
406
+ return { refreshToken, scopes: binding.scopes };
407
+ }
408
+ __name(issueRefreshToken, "issueRefreshToken");
409
+ function buildLineageIndex(family) {
410
+ const byHash = /* @__PURE__ */ new Map();
411
+ const childrenOf = /* @__PURE__ */ new Map();
412
+ for (const row of family) {
413
+ byHash.set(row.tokenHash, row);
414
+ if (!row.rotatedFrom) continue;
415
+ const siblings = childrenOf.get(row.rotatedFrom) ?? [];
416
+ siblings.push(row.tokenHash);
417
+ childrenOf.set(row.rotatedFrom, siblings);
418
+ }
419
+ return { byHash, childrenOf };
420
+ }
421
+ __name(buildLineageIndex, "buildLineageIndex");
422
+ function collectLineage(index, seedHash) {
423
+ const lineage = /* @__PURE__ */ new Set();
424
+ const queue = [seedHash];
425
+ while (queue.length > 0) {
426
+ const hash = queue.shift();
427
+ if (!hash || lineage.has(hash)) continue;
428
+ lineage.add(hash);
429
+ const parent = index.byHash.get(hash)?.rotatedFrom ?? null;
430
+ if (parent && !lineage.has(parent)) queue.push(parent);
431
+ const children = (index.childrenOf.get(hash) ?? []).filter((child) => !lineage.has(child));
432
+ queue.push(...children);
433
+ }
434
+ return lineage;
435
+ }
436
+ __name(collectLineage, "collectLineage");
437
+ async function revokeLineage(context, scopedTo, seedHash) {
438
+ const family = await context.store.listFamily(scopedTo.userEmail, scopedTo.clientId);
439
+ const lineage = collectLineage(buildLineageIndex(family), seedHash);
440
+ await context.store.revokeHashes([...lineage], /* @__PURE__ */ new Date());
441
+ }
442
+ __name(revokeLineage, "revokeLineage");
443
+ function narrowedScopes(current, requested) {
444
+ const scopes = requested ?? current.scopes;
445
+ const original = new Set(current.scopes);
446
+ for (const scope of scopes) {
447
+ if (!original.has(scope)) {
448
+ throw new RefreshTokenError(
449
+ "invalid_scope",
450
+ `scope '${scope}' broadens the refresh token grant`
451
+ );
452
+ }
453
+ }
454
+ return scopes;
455
+ }
456
+ __name(narrowedScopes, "narrowedScopes");
457
+ async function rotateRefreshToken(context, plaintext, expectedClientId, newScopes) {
458
+ const tokenHash = hashToken(plaintext);
459
+ const current = await context.store.findByHash(tokenHash);
460
+ if (!current) {
461
+ throw new RefreshTokenError("invalid_grant", "unknown refresh token");
462
+ }
463
+ if (current.clientId !== expectedClientId) {
464
+ throw new RefreshTokenError(
465
+ "invalid_grant",
466
+ "refresh token was not issued to this client"
467
+ );
468
+ }
469
+ if (current.expiresAt.getTime() <= Date.now()) {
470
+ throw new RefreshTokenError("invalid_grant", "refresh token expired");
471
+ }
472
+ if (current.revokedAt || await context.store.hasSuccessor(tokenHash)) {
473
+ await replay(context, current, tokenHash);
474
+ }
475
+ const scopes = narrowedScopes(current, newScopes);
476
+ const successorPlaintext = generateToken();
477
+ const claimed = await context.store.rotate(
478
+ {
479
+ tokenHash: hashToken(successorPlaintext),
480
+ userEmail: current.userEmail,
481
+ userSub: current.userSub,
482
+ clientId: current.clientId,
483
+ scopes,
484
+ expiresAt: expiryOf(context),
485
+ rotatedFrom: tokenHash
486
+ },
487
+ tokenHash,
488
+ /* @__PURE__ */ new Date()
489
+ );
490
+ if (!claimed) await replay(context, current, tokenHash);
491
+ return { refreshToken: successorPlaintext, scopes };
492
+ }
493
+ __name(rotateRefreshToken, "rotateRefreshToken");
494
+ async function replay(context, current, tokenHash) {
495
+ await revokeLineage(context, current, tokenHash);
496
+ throw new RefreshTokenError(
497
+ "invalid_grant",
498
+ "refresh token already used (replay) \u2014 lineage revoked"
499
+ );
500
+ }
501
+ __name(replay, "replay");
502
+ async function getRefreshTokenIdentity(context, plaintext) {
503
+ const row = await context.store.findByHash(hashToken(plaintext));
504
+ return row ? { userEmail: row.userEmail, userSub: row.userSub } : null;
505
+ }
506
+ __name(getRefreshTokenIdentity, "getRefreshTokenIdentity");
507
+
508
+ // src/oauth/context.ts
509
+ var DEFAULT_OAUTH_PATHS = {
510
+ // the origin host's paths, and the ones the RFC 8414 document has always advertised.
511
+ authorize: "/api/oauth/authorize",
512
+ token: "/api/oauth/token",
513
+ register: "/api/oauth/register",
514
+ jwks: "/.well-known/jwks.json",
515
+ authorizationServerMetadata: "/.well-known/oauth-authorization-server",
516
+ protectedResourceMetadata: "/.well-known/oauth-protected-resource"
517
+ };
518
+ function resolveSurface(config) {
519
+ return {
520
+ scopes: config.scopes ?? [...MCP_SUPPORTED_SCOPES],
521
+ resourcePath: config.resourcePath ?? DEFAULT_MCP_RESOURCE_PATH,
522
+ paths: { ...DEFAULT_OAUTH_PATHS, ...config.paths },
523
+ loginPath: config.loginPath ?? "/login",
524
+ loginCallbackParam: config.loginCallbackParam ?? "callbackUrl",
525
+ accessTokenTtlSeconds: config.accessTokenTtlSeconds ?? ACCESS_TOKEN_TTL_SECONDS,
526
+ refreshTokenTtlMs: config.refreshTokenTtlMs ?? REFRESH_TOKEN_TTL_MS
527
+ };
528
+ }
529
+ __name(resolveSurface, "resolveSurface");
530
+ function resolveMcpOauthConfig(config) {
531
+ const enabled = config.enabled ?? true;
532
+ const trustedOrigins = config.trustedOrigins ?? [];
533
+ return {
534
+ stores: config.stores,
535
+ resolveSession: config.resolveSession,
536
+ // Mounting is the opt-in, so the gate defaults to ON; a host that ships the
537
+ // surface dark passes its own flag (the origin host: `MCP_BEARER_ENABLED`).
538
+ enabled: typeof enabled === "function" ? enabled : () => enabled,
539
+ // `null` from the provider means "not provisioned": nothing is minted and the
540
+ // JWKS answers 503 rather than falling back to a weaker mode.
541
+ signingKey: config.signingKey ?? loadSigningKeyFromEnv(),
542
+ trustedOrigins,
543
+ ...resolveSurface(config),
544
+ // `'in-process'` is an ACKNOWLEDGEMENT, not a default — see the field's docs.
545
+ codeReplay: config.codeReplay === "in-process" ? inProcessCodeReplayStore() : config.codeReplay,
546
+ approve: resolveApprover(config),
547
+ ...config.connections ? { connections: config.connections } : {},
548
+ originOf: /* @__PURE__ */ __name((request) => originFromRequest(request, trustedOrigins), "originOf")
549
+ };
550
+ }
551
+ __name(resolveMcpOauthConfig, "resolveMcpOauthConfig");
552
+ function resolveApprover(config) {
553
+ const preApproved = new Set(config.preApprovedClientIds ?? []);
554
+ const { resolveApproval } = config;
555
+ return async (request, client, scopes) => {
556
+ if (preApproved.has(client.clientId)) return true;
557
+ if (!resolveApproval) return false;
558
+ return resolveApproval(request, client, scopes);
559
+ };
560
+ }
561
+ __name(resolveApprover, "resolveApprover");
562
+ function notFound() {
563
+ return new Response("Not Found", { status: 404 });
564
+ }
565
+ __name(notFound, "notFound");
566
+
567
+ // src/oauth/authorize.ts
568
+ function parseParams(url) {
569
+ const q = url.searchParams;
570
+ return {
571
+ responseType: q.get("response_type"),
572
+ clientId: q.get("client_id"),
573
+ redirectUri: q.get("redirect_uri"),
574
+ codeChallenge: q.get("code_challenge"),
575
+ codeChallengeMethod: q.get("code_challenge_method"),
576
+ scope: q.get("scope"),
577
+ state: q.get("state")
578
+ };
579
+ }
580
+ __name(parseParams, "parseParams");
581
+ function redirectTo(location) {
582
+ return new Response(null, { status: 302, headers: { location } });
583
+ }
584
+ __name(redirectTo, "redirectTo");
585
+ function badRequest(message) {
586
+ return new Response(message, {
587
+ status: 400,
588
+ headers: { "content-type": "text/plain; charset=utf-8" }
589
+ });
590
+ }
591
+ __name(badRequest, "badRequest");
592
+ function errorRedirect(redirectUri, error, state) {
593
+ const target = new URL(redirectUri);
594
+ target.searchParams.set("error", error);
595
+ if (state !== null) target.searchParams.set("state", state);
596
+ return redirectTo(target.toString());
597
+ }
598
+ __name(errorRedirect, "errorRedirect");
599
+ function scopeIsSupported(scope, allowed) {
600
+ if (!scope) return true;
601
+ const requested = scope.split(/\s+/).filter(Boolean);
602
+ const allowedSet = new Set(allowed);
603
+ return requested.every((candidate) => allowedSet.has(candidate));
604
+ }
605
+ __name(scopeIsSupported, "scopeIsSupported");
606
+ async function validateClientAndRedirect(context, params) {
607
+ if (!params.clientId) return badRequest("invalid_request: missing client_id");
608
+ if (!params.redirectUri) return badRequest("invalid_request: missing redirect_uri");
609
+ const client = await context.stores.clients.findByClientId(params.clientId);
610
+ if (!client) return badRequest("invalid_client: unknown client_id");
611
+ if (!matchesRedirectUri(client, params.redirectUri)) {
612
+ return badRequest("invalid_request: redirect_uri is not registered");
613
+ }
614
+ return { client, redirectUri: params.redirectUri };
615
+ }
616
+ __name(validateClientAndRedirect, "validateClientAndRedirect");
617
+ function validateAuthorizeRequest(params, redirectUri, clientScopes) {
618
+ const { state } = params;
619
+ if (params.responseType !== "code") {
620
+ return errorRedirect(redirectUri, "unsupported_response_type", state);
621
+ }
622
+ if (!params.codeChallenge || params.codeChallengeMethod !== SUPPORTED_CHALLENGE_METHOD) {
623
+ return errorRedirect(redirectUri, "invalid_request", state);
624
+ }
625
+ if (!scopeIsSupported(params.scope, clientScopes)) {
626
+ return errorRedirect(redirectUri, "invalid_scope", state);
627
+ }
628
+ return null;
629
+ }
630
+ __name(validateAuthorizeRequest, "validateAuthorizeRequest");
631
+ async function authenticateAndMint(context, request, url, origin, validated) {
632
+ const { redirectUri, state } = validated;
633
+ const session = await context.resolveSession(request);
634
+ if (!session?.email) {
635
+ const loginUrl = new URL(context.loginPath, origin);
636
+ loginUrl.searchParams.set(context.loginCallbackParam, url.pathname + url.search);
637
+ return redirectTo(loginUrl.toString());
638
+ }
639
+ const scopes = validated.scope.split(/\s+/).filter(Boolean);
640
+ if (!await context.approve(request, validated.client, scopes)) {
641
+ return errorRedirect(redirectUri, "access_denied", state);
642
+ }
643
+ const code = await mintCode(context.signingKey, {
644
+ // The subject is the OAuth `sub` the host resolved, NOT a DB id: downstream
645
+ // guards resolve the user by EMAIL, and the code carries only what the session
646
+ // verified.
647
+ sub: session.subject || session.email,
648
+ email: session.email,
649
+ clientId: validated.clientId,
650
+ redirectUri,
651
+ codeChallenge: validated.codeChallenge,
652
+ scope: validated.scope,
653
+ origin
654
+ });
655
+ if (!code) {
656
+ return errorRedirect(redirectUri, "server_error", state);
657
+ }
658
+ const success = new URL(redirectUri);
659
+ success.searchParams.set("code", code);
660
+ if (state !== null) success.searchParams.set("state", state);
661
+ return redirectTo(success.toString());
662
+ }
663
+ __name(authenticateAndMint, "authenticateAndMint");
664
+ async function authorizeEndpoint(context, request) {
665
+ const url = new URL(request.url);
666
+ const origin = context.originOf(request);
667
+ const params = parseParams(url);
668
+ const clientResult = await validateClientAndRedirect(context, params);
669
+ if (clientResult instanceof Response) return clientResult;
670
+ const { client, redirectUri } = clientResult;
671
+ const requestError = validateAuthorizeRequest(params, redirectUri, client.scopes);
672
+ if (requestError) return requestError;
673
+ return authenticateAndMint(context, request, url, origin, {
674
+ client,
675
+ clientId: params.clientId,
676
+ redirectUri,
677
+ codeChallenge: params.codeChallenge,
678
+ scope: params.scope ?? "",
679
+ state: params.state
680
+ });
681
+ }
682
+ __name(authorizeEndpoint, "authorizeEndpoint");
683
+
684
+ // src/oauth/register.ts
685
+ var SUPPORTED_AUTH_METHODS = [
686
+ "none",
687
+ "client_secret_basic"
688
+ ];
689
+ var SUPPORTED_GRANT_TYPES = ["authorization_code", "refresh_token"];
690
+ var DEFAULT_GRANT_TYPES2 = ["authorization_code", "refresh_token"];
691
+ var DEFAULT_AUTH_METHOD = "none";
692
+ var JSON_HEADERS = {
693
+ "content-type": "application/json; charset=utf-8",
694
+ "cache-control": "no-store"
695
+ };
696
+ function registrationError(error, status, description) {
697
+ const body = { error };
698
+ if (description) body.error_description = description;
699
+ return new Response(JSON.stringify(body), { status, headers: { ...JSON_HEADERS } });
700
+ }
701
+ __name(registrationError, "registrationError");
702
+ function isAbsoluteUri(value) {
703
+ try {
704
+ const url = new URL(value);
705
+ return Boolean(url.protocol) && Boolean(url.host);
706
+ } catch {
707
+ return false;
708
+ }
709
+ }
710
+ __name(isAbsoluteUri, "isAbsoluteUri");
711
+ function accept(value) {
712
+ return { ok: true, value };
713
+ }
714
+ __name(accept, "accept");
715
+ function reject(response) {
716
+ return { ok: false, response };
717
+ }
718
+ __name(reject, "reject");
719
+ function validateRedirectUris(raw) {
720
+ if (!Array.isArray(raw) || raw.length === 0 || !raw.every((uri) => typeof uri === "string" && isAbsoluteUri(uri))) {
721
+ return reject(
722
+ registrationError(
723
+ "invalid_redirect_uri",
724
+ 400,
725
+ "redirect_uris must be a non-empty array of absolute URIs"
726
+ )
727
+ );
728
+ }
729
+ return accept([...raw]);
730
+ }
731
+ __name(validateRedirectUris, "validateRedirectUris");
732
+ function validateAuthMethod(raw) {
733
+ if (raw === void 0 || raw === null) return accept(DEFAULT_AUTH_METHOD);
734
+ if (typeof raw !== "string" || !SUPPORTED_AUTH_METHODS.includes(raw)) {
735
+ return reject(
736
+ registrationError(
737
+ "invalid_client_metadata",
738
+ 400,
739
+ `unsupported token_endpoint_auth_method (supported: ${SUPPORTED_AUTH_METHODS.join(", ")})`
740
+ )
741
+ );
742
+ }
743
+ return accept(raw);
744
+ }
745
+ __name(validateAuthMethod, "validateAuthMethod");
746
+ function validateGrantTypes(raw) {
747
+ if (raw === void 0 || raw === null) return accept([...DEFAULT_GRANT_TYPES2]);
748
+ if (!Array.isArray(raw) || raw.length === 0 || !raw.every(
749
+ (grant) => typeof grant === "string" && SUPPORTED_GRANT_TYPES.includes(grant)
750
+ )) {
751
+ return reject(
752
+ registrationError(
753
+ "invalid_client_metadata",
754
+ 400,
755
+ `unsupported grant_types (supported: ${SUPPORTED_GRANT_TYPES.join(", ")})`
756
+ )
757
+ );
758
+ }
759
+ return accept([...raw]);
760
+ }
761
+ __name(validateGrantTypes, "validateGrantTypes");
762
+ function validateScopes(raw, supportedScopes) {
763
+ if (raw === void 0 || raw === null) return accept([...supportedScopes]);
764
+ if (typeof raw !== "string") {
765
+ return reject(
766
+ registrationError("invalid_client_metadata", 400, "scope must be a space-delimited string")
767
+ );
768
+ }
769
+ const requested = raw.split(/\s+/).filter(Boolean);
770
+ const supported = new Set(supportedScopes);
771
+ if (!requested.every((scope) => supported.has(scope))) {
772
+ return reject(
773
+ registrationError(
774
+ "invalid_client_metadata",
775
+ 400,
776
+ `scope must be a subset of: ${supportedScopes.join(" ")}`
777
+ )
778
+ );
779
+ }
780
+ return accept(requested.length > 0 ? requested : [...supportedScopes]);
781
+ }
782
+ __name(validateScopes, "validateScopes");
783
+ function validateMetadata(metadata, supportedScopes) {
784
+ const redirectUris = validateRedirectUris(metadata.redirect_uris);
785
+ if (!redirectUris.ok) return redirectUris;
786
+ const authMethod = validateAuthMethod(metadata.token_endpoint_auth_method);
787
+ if (!authMethod.ok) return authMethod;
788
+ const grantTypes = validateGrantTypes(metadata.grant_types);
789
+ if (!grantTypes.ok) return grantTypes;
790
+ const scopes = validateScopes(metadata.scope, supportedScopes);
791
+ if (!scopes.ok) return scopes;
792
+ const clientNameRaw = metadata.client_name;
793
+ const clientName = typeof clientNameRaw === "string" ? clientNameRaw : null;
794
+ return {
795
+ ok: true,
796
+ input: {
797
+ redirectUris: redirectUris.value,
798
+ clientName,
799
+ tokenEndpointAuthMethod: authMethod.value,
800
+ grantTypes: grantTypes.value,
801
+ scopes: scopes.value
802
+ }
803
+ };
804
+ }
805
+ __name(validateMetadata, "validateMetadata");
806
+ function registrationDisabled() {
807
+ return new Response(
808
+ JSON.stringify({
809
+ error: "access_denied",
810
+ error_description: "dynamic client registration is disabled"
811
+ }),
812
+ { status: 403, headers: { ...JSON_HEADERS } }
813
+ );
814
+ }
815
+ __name(registrationDisabled, "registrationDisabled");
816
+ async function registerEndpoint(context, request) {
817
+ let metadata;
818
+ try {
819
+ const parsed = await request.json();
820
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
821
+ return registrationError(
822
+ "invalid_client_metadata",
823
+ 400,
824
+ "request body must be a JSON object"
825
+ );
826
+ }
827
+ metadata = parsed;
828
+ } catch {
829
+ return registrationError("invalid_client_metadata", 400, "request body must be valid JSON");
830
+ }
831
+ const validated = validateMetadata(metadata, context.scopes);
832
+ if (!validated.ok) return validated.response;
833
+ const registered = await registerClient(context.stores.clients, validated.input);
834
+ const responseBody = {
835
+ client_id: registered.clientId,
836
+ ...registered.clientSecret ? { client_secret: registered.clientSecret } : {},
837
+ client_id_issued_at: Math.floor(Date.now() / 1e3),
838
+ token_endpoint_auth_method: registered.tokenEndpointAuthMethod,
839
+ redirect_uris: registered.redirectUris,
840
+ grant_types: registered.grantTypes,
841
+ scope: registered.scopes.join(" "),
842
+ ...registered.clientName ? { client_name: registered.clientName } : {}
843
+ };
844
+ return new Response(JSON.stringify(responseBody), {
845
+ status: 201,
846
+ headers: { ...JSON_HEADERS }
847
+ });
848
+ }
849
+ __name(registerEndpoint, "registerEndpoint");
850
+
851
+ // src/oauth/token-response.ts
852
+ import { createHash as createHash3, timingSafeEqual } from "crypto";
853
+ var JSON_HEADERS2 = {
854
+ "content-type": "application/json; charset=utf-8",
855
+ "cache-control": "no-store"
856
+ };
857
+ function tokenError(error, status, description, headers = {}) {
858
+ const body = { error };
859
+ if (description) body.error_description = description;
860
+ return new Response(JSON.stringify(body), {
861
+ status,
862
+ headers: { ...JSON_HEADERS2, ...headers }
863
+ });
864
+ }
865
+ __name(tokenError, "tokenError");
866
+ function tokenSuccess(payload) {
867
+ return new Response(JSON.stringify(payload), { status: 200, headers: { ...JSON_HEADERS2 } });
868
+ }
869
+ __name(tokenSuccess, "tokenSuccess");
870
+ function hashesEqual(a, b) {
871
+ const bufA = Buffer.from(a, "hex");
872
+ const bufB = Buffer.from(b, "hex");
873
+ if (bufA.length !== bufB.length || bufA.length === 0) return false;
874
+ return timingSafeEqual(bufA, bufB);
875
+ }
876
+ __name(hashesEqual, "hashesEqual");
877
+ function sha256Hex(value) {
878
+ return createHash3("sha256").update(value).digest("hex");
879
+ }
880
+ __name(sha256Hex, "sha256Hex");
881
+ function readClientCredentials(request, form) {
882
+ const authorization = request.headers.get("authorization");
883
+ if (authorization && authorization.startsWith("Basic ")) {
884
+ const decoded = Buffer.from(authorization.slice(6), "base64").toString("utf8");
885
+ const separator = decoded.indexOf(":");
886
+ if (separator !== -1) {
887
+ return {
888
+ clientId: decoded.slice(0, separator),
889
+ clientSecret: decoded.slice(separator + 1)
890
+ };
891
+ }
892
+ }
893
+ return { clientId: form.get("client_id"), clientSecret: form.get("client_secret") };
894
+ }
895
+ __name(readClientCredentials, "readClientCredentials");
896
+ var CLIENT_AUTH_CHALLENGE = { "www-authenticate": 'Basic realm="oauth-token"' };
897
+ async function authenticateClient(clients, credentials, expectedClientId) {
898
+ const clientId = credentials.clientId;
899
+ if (!clientId) {
900
+ return tokenError("invalid_client", 401, "missing client_id", CLIENT_AUTH_CHALLENGE);
901
+ }
902
+ if (expectedClientId && clientId !== expectedClientId) {
903
+ return tokenError(
904
+ "invalid_client",
905
+ 401,
906
+ "client_id does not match the grant",
907
+ CLIENT_AUTH_CHALLENGE
908
+ );
909
+ }
910
+ const client = await clients.findByClientId(clientId);
911
+ if (!client) {
912
+ return tokenError("invalid_client", 401, "unknown client", CLIENT_AUTH_CHALLENGE);
913
+ }
914
+ if (client.tokenEndpointAuthMethod === "client_secret_basic") {
915
+ const secret = credentials.clientSecret;
916
+ if (!secret || !client.clientSecretHash) {
917
+ return tokenError(
918
+ "invalid_client",
919
+ 401,
920
+ "client authentication required",
921
+ CLIENT_AUTH_CHALLENGE
922
+ );
923
+ }
924
+ if (!hashesEqual(sha256Hex(secret), client.clientSecretHash)) {
925
+ return tokenError(
926
+ "invalid_client",
927
+ 401,
928
+ "invalid client credentials",
929
+ CLIENT_AUTH_CHALLENGE
930
+ );
931
+ }
932
+ }
933
+ return null;
934
+ }
935
+ __name(authenticateClient, "authenticateClient");
936
+
937
+ // src/oauth/token-grants.ts
938
+ var DEFAULT_ACTIVITY_THROTTLE_MS = 6e4;
939
+ async function recordHostConnection(context, email, clientId) {
940
+ const recording = context.connections;
941
+ const store = context.stores.connections;
942
+ if (!recording || !store) return;
943
+ try {
944
+ await writeConnectionActivity(context, { recording, store }, email, clientId);
945
+ } catch {
946
+ }
947
+ }
948
+ __name(recordHostConnection, "recordHostConnection");
949
+ async function writeConnectionActivity(context, ports, email, clientId) {
950
+ const { recording, store } = ports;
951
+ const [userId, client] = await Promise.all([
952
+ recording.resolveUserId(email),
953
+ context.stores.clients.findByClientId(clientId)
954
+ ]);
955
+ if (!userId) return;
956
+ const throttleMs = recording.activityThrottleMs ?? DEFAULT_ACTIVITY_THROTTLE_MS;
957
+ const lastActiveAt = await store.lastActiveAt(userId, clientId);
958
+ const now = /* @__PURE__ */ new Date();
959
+ if (lastActiveAt && now.getTime() - lastActiveAt.getTime() < throttleMs) return;
960
+ await store.recordActivity({
961
+ userId,
962
+ oauthClientId: clientId,
963
+ clientName: client?.clientName ?? null,
964
+ // Attribute to a provider from the client's redirect URIs (claude.ai →
965
+ // claude, chatgpt.com → chatgpt) so an account page lights the right card.
966
+ host: client ? providerFromRedirectUris(client.redirectUris, recording.providerRules) : null,
967
+ at: now
968
+ });
969
+ }
970
+ __name(writeConnectionActivity, "writeConnectionActivity");
971
+ function readAuthorizationCodeParams(form) {
972
+ const code = form.get("code");
973
+ const redirectUri = form.get("redirect_uri");
974
+ const codeVerifier = form.get("code_verifier");
975
+ if (!code) return tokenError("invalid_request", 400, "missing code");
976
+ if (!redirectUri) return tokenError("invalid_request", 400, "missing redirect_uri");
977
+ if (!codeVerifier) return tokenError("invalid_request", 400, "missing code_verifier");
978
+ return { code, redirectUri, codeVerifier };
979
+ }
980
+ __name(readAuthorizationCodeParams, "readAuthorizationCodeParams");
981
+ async function redeemCode(context, params, credentials, origin) {
982
+ const { code, redirectUri, codeVerifier } = params;
983
+ let verified;
984
+ try {
985
+ verified = await verifyCode(context.signingKey, code, { origin });
986
+ } catch (error) {
987
+ if (error instanceof AuthorizationCodeError) {
988
+ return tokenError("invalid_grant", 400, "invalid or expired authorization code");
989
+ }
990
+ throw error;
991
+ }
992
+ const authError = await authenticateClient(
993
+ context.stores.clients,
994
+ credentials,
995
+ verified.clientId
996
+ );
997
+ if (authError) return authError;
998
+ if (redirectUri !== verified.redirectUri) {
999
+ return tokenError("invalid_grant", 400, "redirect_uri mismatch");
1000
+ }
1001
+ if (!await verifyChallenge(codeVerifier, verified.codeChallenge)) {
1002
+ return tokenError("invalid_grant", 400, "PKCE verification failed");
1003
+ }
1004
+ if (!await context.codeReplay.consume(verified.jti, Date.now())) {
1005
+ return tokenError("invalid_grant", 400, "authorization code already used");
1006
+ }
1007
+ return verified;
1008
+ }
1009
+ __name(redeemCode, "redeemCode");
1010
+ async function handleAuthorizationCode(context, form, credentials, origin) {
1011
+ const params = readAuthorizationCodeParams(form);
1012
+ if (params instanceof Response) return params;
1013
+ const verified = await redeemCode(context, params, credentials, origin);
1014
+ if (verified instanceof Response) return verified;
1015
+ const scopes = verified.scope.split(/\s+/).filter(Boolean);
1016
+ const accessToken = await signAccessToken(context.signingKey, {
1017
+ email: verified.email,
1018
+ subject: verified.sub,
1019
+ scopes,
1020
+ origin,
1021
+ resourcePath: context.resourcePath,
1022
+ ttlSeconds: context.accessTokenTtlSeconds
1023
+ });
1024
+ if (!accessToken) {
1025
+ return tokenError("invalid_request", 400, "token issuance unavailable");
1026
+ }
1027
+ const refresh = await issueRefreshToken(
1028
+ { store: context.stores.refreshTokens, ttlMs: context.refreshTokenTtlMs },
1029
+ {
1030
+ userEmail: verified.email,
1031
+ userSub: verified.sub,
1032
+ clientId: verified.clientId,
1033
+ scopes
1034
+ }
1035
+ );
1036
+ await recordHostConnection(context, verified.email, verified.clientId);
1037
+ return tokenSuccess({
1038
+ access_token: accessToken,
1039
+ token_type: "Bearer",
1040
+ expires_in: context.accessTokenTtlSeconds ?? ACCESS_TOKEN_TTL_SECONDS,
1041
+ refresh_token: refresh.refreshToken,
1042
+ scope: refresh.scopes.join(" ")
1043
+ });
1044
+ }
1045
+ __name(handleAuthorizationCode, "handleAuthorizationCode");
1046
+ async function handleRefreshToken(context, form, credentials, origin) {
1047
+ const refreshToken = form.get("refresh_token");
1048
+ const requestedScope = form.get("scope");
1049
+ if (!refreshToken) return tokenError("invalid_request", 400, "missing refresh_token");
1050
+ const authError = await authenticateClient(context.stores.clients, credentials);
1051
+ if (authError) return authError;
1052
+ const clientId = credentials.clientId;
1053
+ const newScopes = requestedScope ? requestedScope.split(/\s+/).filter(Boolean) : void 0;
1054
+ const refreshContext = {
1055
+ store: context.stores.refreshTokens,
1056
+ ttlMs: context.refreshTokenTtlMs
1057
+ };
1058
+ let rotated;
1059
+ try {
1060
+ rotated = await rotateRefreshToken(refreshContext, refreshToken, clientId, newScopes);
1061
+ } catch (error) {
1062
+ if (error instanceof RefreshTokenError) {
1063
+ return tokenError(error.code, 400, error.message);
1064
+ }
1065
+ throw error;
1066
+ }
1067
+ const identity = await getRefreshTokenIdentity(refreshContext, rotated.refreshToken);
1068
+ if (!identity) return tokenError("invalid_grant", 400, "refresh token binding not found");
1069
+ const accessToken = await signAccessToken(context.signingKey, {
1070
+ email: identity.userEmail,
1071
+ subject: identity.userSub,
1072
+ scopes: rotated.scopes,
1073
+ origin,
1074
+ resourcePath: context.resourcePath,
1075
+ ttlSeconds: context.accessTokenTtlSeconds
1076
+ });
1077
+ if (!accessToken) return tokenError("invalid_request", 400, "token issuance unavailable");
1078
+ await recordHostConnection(context, identity.userEmail, clientId);
1079
+ return tokenSuccess({
1080
+ access_token: accessToken,
1081
+ token_type: "Bearer",
1082
+ expires_in: context.accessTokenTtlSeconds ?? ACCESS_TOKEN_TTL_SECONDS,
1083
+ refresh_token: rotated.refreshToken,
1084
+ scope: rotated.scopes.join(" ")
1085
+ });
1086
+ }
1087
+ __name(handleRefreshToken, "handleRefreshToken");
1088
+ async function tokenEndpoint(context, request) {
1089
+ const origin = context.originOf(request);
1090
+ let form;
1091
+ try {
1092
+ form = new URLSearchParams(await request.text());
1093
+ } catch {
1094
+ return tokenError("invalid_request", 400, "malformed request body");
1095
+ }
1096
+ const grantType = form.get("grant_type");
1097
+ if (!grantType) return tokenError("invalid_request", 400, "missing grant_type");
1098
+ const credentials = readClientCredentials(request, form);
1099
+ switch (grantType) {
1100
+ case "authorization_code":
1101
+ return handleAuthorizationCode(context, form, credentials, origin);
1102
+ case "refresh_token":
1103
+ return handleRefreshToken(context, form, credentials, origin);
1104
+ default:
1105
+ return tokenError(
1106
+ "unsupported_grant_type",
1107
+ 400,
1108
+ `grant_type '${grantType}' is not supported`
1109
+ );
1110
+ }
1111
+ }
1112
+ __name(tokenEndpoint, "tokenEndpoint");
1113
+
1114
+ // src/oauth/create-api-mcp-oauth.ts
1115
+ function jsonResponse(body, status = 200, headers = {}) {
1116
+ return new Response(JSON.stringify(body), {
1117
+ status,
1118
+ headers: { "content-type": "application/json; charset=utf-8", ...headers }
1119
+ });
1120
+ }
1121
+ __name(jsonResponse, "jsonResponse");
1122
+ async function jwksResponse(context) {
1123
+ const key = await context.signingKey();
1124
+ if (!key) return jsonResponse({ error: "signing_key_unavailable" }, 503);
1125
+ return jsonResponse({ keys: [key.publicJwk] }, 200, {
1126
+ // Public, cacheable key set; hosts may cache it and re-fetch on a `kid` miss
1127
+ // (rotation). A short max-age keeps the rotation overlap tight.
1128
+ "cache-control": "public, max-age=300"
1129
+ });
1130
+ }
1131
+ __name(jwksResponse, "jwksResponse");
1132
+ function discoveryHandlers(context) {
1133
+ return {
1134
+ authorizationServerMetadata: /* @__PURE__ */ __name(async (request) => jsonResponse(
1135
+ buildAuthorizationServerMetadata({
1136
+ issuer: issuer(context.originOf(request)),
1137
+ scopesSupported: [...context.scopes],
1138
+ // The RESOLVED paths, so what a connector reads before its first request
1139
+ // is where the endpoints actually are.
1140
+ paths: context.paths
1141
+ })
1142
+ ), "authorizationServerMetadata"),
1143
+ protectedResourceMetadata: /* @__PURE__ */ __name(async (request) => {
1144
+ const origin = context.originOf(request);
1145
+ return jsonResponse(
1146
+ buildProtectedResourceMetadata({
1147
+ resource: resourceAudience(origin, context.resourcePath),
1148
+ authorizationServers: [issuer(origin)],
1149
+ scopesSupported: [...context.scopes]
1150
+ })
1151
+ );
1152
+ }, "protectedResourceMetadata")
1153
+ };
1154
+ }
1155
+ __name(discoveryHandlers, "discoveryHandlers");
1156
+ function buildHandlers(context) {
1157
+ const gated = /* @__PURE__ */ __name((handler) => async (request) => context.enabled() ? handler(request) : notFound(), "gated");
1158
+ const discovery = discoveryHandlers(context);
1159
+ return {
1160
+ authorize: gated((request) => authorizeEndpoint(context, request)),
1161
+ token: gated((request) => tokenEndpoint(context, request)),
1162
+ register: /* @__PURE__ */ __name(async (request) => context.enabled() ? registerEndpoint(context, request) : registrationDisabled(), "register"),
1163
+ jwks: gated(() => jwksResponse(context)),
1164
+ authorizationServerMetadata: gated(discovery.authorizationServerMetadata),
1165
+ protectedResourceMetadata: gated(discovery.protectedResourceMetadata)
1166
+ };
1167
+ }
1168
+ __name(buildHandlers, "buildHandlers");
1169
+ function buildRoutes(context, handlers) {
1170
+ const { paths } = context;
1171
+ return [
1172
+ {
1173
+ method: "GET",
1174
+ path: paths.authorizationServerMetadata,
1175
+ handle: handlers.authorizationServerMetadata
1176
+ },
1177
+ {
1178
+ method: "GET",
1179
+ path: paths.protectedResourceMetadata,
1180
+ handle: handlers.protectedResourceMetadata
1181
+ },
1182
+ { method: "GET", path: paths.jwks, handle: handlers.jwks },
1183
+ { method: "GET", path: paths.authorize, handle: handlers.authorize },
1184
+ { method: "POST", path: paths.token, handle: handlers.token },
1185
+ { method: "POST", path: paths.register, handle: handlers.register }
1186
+ ];
1187
+ }
1188
+ __name(buildRoutes, "buildRoutes");
1189
+ function createApiMcpOauth(config) {
1190
+ const context = resolveMcpOauthConfig(config);
1191
+ const handlers = buildHandlers(context);
1192
+ return {
1193
+ routes: buildRoutes(context, handlers),
1194
+ handlers,
1195
+ verifyBearer: /* @__PURE__ */ __name((token, request, options) => verifyAccessToken(context.signingKey, token, {
1196
+ ...options,
1197
+ origin: context.originOf(request),
1198
+ resourcePath: context.resourcePath
1199
+ }), "verifyBearer"),
1200
+ context
1201
+ };
1202
+ }
1203
+ __name(createApiMcpOauth, "createApiMcpOauth");
1204
+
1205
+ export {
1206
+ MCP_SUPPORTED_SCOPES,
1207
+ DEFAULT_MCP_RESOURCE_PATH,
1208
+ issuer,
1209
+ resourceAudience,
1210
+ trustedOriginsFromEnv,
1211
+ resolveTrustedOrigin,
1212
+ originFromRequest,
1213
+ SIGNING_ALG,
1214
+ signingKeyProvider,
1215
+ DEFAULT_SIGNING_KEY_ENV,
1216
+ DEFAULT_SIGNING_KEY_ID_ENV,
1217
+ loadSigningKeyFromEnv,
1218
+ AUTHORIZATION_CODE_AUDIENCE,
1219
+ AUTHORIZATION_CODE_TTL_SECONDS,
1220
+ AuthorizationCodeError,
1221
+ mintCode,
1222
+ verifyCode,
1223
+ hashSecret,
1224
+ registerClient,
1225
+ matchesRedirectUri,
1226
+ DEFAULT_PROVIDER_ROOTS,
1227
+ providerFromRedirectUris,
1228
+ SUPPORTED_CHALLENGE_METHOD,
1229
+ UnsupportedChallengeMethodError,
1230
+ computeChallenge,
1231
+ verifyChallenge,
1232
+ inProcessCodeReplayStore,
1233
+ ACCESS_TOKEN_TTL_SECONDS,
1234
+ AccessTokenError,
1235
+ signAccessToken,
1236
+ verifyAccessToken,
1237
+ REFRESH_TOKEN_TTL_MS,
1238
+ RefreshTokenError,
1239
+ hashToken,
1240
+ issueRefreshToken,
1241
+ rotateRefreshToken,
1242
+ getRefreshTokenIdentity,
1243
+ DEFAULT_OAUTH_PATHS,
1244
+ resolveMcpOauthConfig,
1245
+ createApiMcpOauth
1246
+ };
1247
+ //# sourceMappingURL=chunk-UIILEGAC.js.map