mcp-expose 1.0.0 → 1.1.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/README.md +181 -32
  3. package/dist/adonisjs/index.cjs +366 -6
  4. package/dist/adonisjs/index.cjs.map +1 -1
  5. package/dist/adonisjs/index.d.cts +2 -1
  6. package/dist/adonisjs/index.d.ts +2 -1
  7. package/dist/adonisjs/index.js +366 -6
  8. package/dist/adonisjs/index.js.map +1 -1
  9. package/dist/express/index.cjs +364 -7
  10. package/dist/express/index.cjs.map +1 -1
  11. package/dist/express/index.d.cts +4 -2
  12. package/dist/express/index.d.ts +4 -2
  13. package/dist/express/index.js +364 -7
  14. package/dist/express/index.js.map +1 -1
  15. package/dist/fastify/index.cjs +373 -6
  16. package/dist/fastify/index.cjs.map +1 -1
  17. package/dist/fastify/index.d.cts +2 -1
  18. package/dist/fastify/index.d.ts +2 -1
  19. package/dist/fastify/index.js +373 -6
  20. package/dist/fastify/index.js.map +1 -1
  21. package/dist/hono/index.cjs +367 -6
  22. package/dist/hono/index.cjs.map +1 -1
  23. package/dist/hono/index.d.cts +2 -1
  24. package/dist/hono/index.d.ts +2 -1
  25. package/dist/hono/index.js +367 -6
  26. package/dist/hono/index.js.map +1 -1
  27. package/dist/index.cjs +371 -31
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +10 -5
  30. package/dist/index.d.ts +10 -5
  31. package/dist/index.js +367 -31
  32. package/dist/index.js.map +1 -1
  33. package/dist/koa/index.cjs +366 -6
  34. package/dist/koa/index.cjs.map +1 -1
  35. package/dist/koa/index.d.cts +2 -1
  36. package/dist/koa/index.d.ts +2 -1
  37. package/dist/koa/index.js +366 -6
  38. package/dist/koa/index.js.map +1 -1
  39. package/dist/nestjs/index.cjs +381 -7
  40. package/dist/nestjs/index.cjs.map +1 -1
  41. package/dist/nestjs/index.d.cts +21 -7
  42. package/dist/nestjs/index.d.ts +21 -7
  43. package/dist/nestjs/index.js +382 -8
  44. package/dist/nestjs/index.js.map +1 -1
  45. package/dist/oauth/index.cjs +214 -0
  46. package/dist/oauth/index.cjs.map +1 -0
  47. package/dist/oauth/index.d.cts +69 -0
  48. package/dist/oauth/index.d.ts +69 -0
  49. package/dist/oauth/index.js +177 -0
  50. package/dist/oauth/index.js.map +1 -0
  51. package/dist/oauth-8RmQamGP.d.cts +75 -0
  52. package/dist/oauth-8RmQamGP.d.ts +75 -0
  53. package/dist/{server-fzkb-wJo.d.ts → server--mQVQq3A.d.cts} +81 -1
  54. package/dist/{server-fzkb-wJo.d.cts → server-BPDTsT2o.d.ts} +81 -1
  55. package/package.json +22 -2
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/oauth/index.ts
32
+ var oauth_exports = {};
33
+ __export(oauth_exports, {
34
+ discoverAuthorizationServer: () => discoverAuthorizationServer,
35
+ introspectionVerifier: () => introspectionVerifier,
36
+ jwtVerifier: () => jwtVerifier
37
+ });
38
+ module.exports = __toCommonJS(oauth_exports);
39
+
40
+ // src/core/oauth.ts
41
+ function parseScopes(value) {
42
+ if (Array.isArray(value)) return value.map(String);
43
+ if (typeof value === "string") return value.split(" ").filter(Boolean);
44
+ return [];
45
+ }
46
+ __name(parseScopes, "parseScopes");
47
+
48
+ // src/oauth/index.ts
49
+ var ASYMMETRIC = [
50
+ "RS256",
51
+ "RS384",
52
+ "RS512",
53
+ "PS256",
54
+ "PS384",
55
+ "PS512",
56
+ "ES256",
57
+ "ES384",
58
+ "ES512",
59
+ "EdDSA",
60
+ "Ed25519"
61
+ ];
62
+ var josePromise;
63
+ function loadJose() {
64
+ josePromise ??= import("jose").catch((err) => {
65
+ josePromise = void 0;
66
+ throw new Error(`mcp-expose: jwtVerifier() needs the "jose" package. Run: npm install jose (${String(err)})`);
67
+ });
68
+ return josePromise;
69
+ }
70
+ __name(loadJose, "loadJose");
71
+ async function discoverAuthorizationServer(issuer) {
72
+ const url = new URL(issuer);
73
+ const path = url.pathname.replace(/\/+$/, "");
74
+ const candidates = [
75
+ new URL(`/.well-known/oauth-authorization-server${path}`, url.origin).href,
76
+ new URL(`/.well-known/openid-configuration${path}`, url.origin).href,
77
+ ...path ? [
78
+ `${url.origin}${path}/.well-known/openid-configuration`
79
+ ] : []
80
+ ];
81
+ for (const candidate of candidates) {
82
+ try {
83
+ const res = await fetch(candidate, {
84
+ headers: {
85
+ accept: "application/json"
86
+ }
87
+ });
88
+ if (res.ok) return await res.json();
89
+ } catch {
90
+ }
91
+ }
92
+ throw new Error(`mcp-expose: could not load authorization server metadata for ${issuer}`);
93
+ }
94
+ __name(discoverAuthorizationServer, "discoverAuthorizationServer");
95
+ function toAuthInfo(token, claims, scopeClaim) {
96
+ const scopes = scopeClaim ? parseScopes(claims[scopeClaim]) : parseScopes(claims.scope ?? claims.scp);
97
+ return {
98
+ token,
99
+ scopes,
100
+ subject: typeof claims.sub === "string" ? claims.sub : void 0,
101
+ clientId: [
102
+ claims.client_id,
103
+ claims.azp,
104
+ claims.cid
105
+ ].find((v) => typeof v === "string"),
106
+ expiresAt: typeof claims.exp === "number" ? claims.exp : void 0,
107
+ claims
108
+ };
109
+ }
110
+ __name(toAuthInfo, "toAuthInfo");
111
+ function jwtVerifier(options) {
112
+ let keySet;
113
+ const getKeys = /* @__PURE__ */ __name(() => {
114
+ keySet ??= (async () => {
115
+ const jose = await loadJose();
116
+ if (options.jwks) return jose.createLocalJWKSet(options.jwks);
117
+ const jwksUri = options.jwksUri ?? (await discoverAuthorizationServer(options.issuer)).jwks_uri;
118
+ if (!jwksUri) throw new Error(`mcp-expose: no jwks_uri in the metadata of ${options.issuer}`);
119
+ return jose.createRemoteJWKSet(new URL(jwksUri));
120
+ })().catch((err) => {
121
+ keySet = void 0;
122
+ throw err;
123
+ });
124
+ return keySet;
125
+ }, "getKeys");
126
+ return async (token, ctx) => {
127
+ const jose = await loadJose();
128
+ const { payload } = await jose.jwtVerify(token, await getKeys(), {
129
+ issuer: options.issuer,
130
+ audience: options.audience ?? ctx.resource,
131
+ algorithms: options.algorithms ?? ASYMMETRIC,
132
+ clockTolerance: options.clockTolerance ?? 30
133
+ });
134
+ const claims = payload;
135
+ if (options.validate && !await options.validate(claims)) return void 0;
136
+ return toAuthInfo(token, claims, options.scopeClaim);
137
+ };
138
+ }
139
+ __name(jwtVerifier, "jwtVerifier");
140
+ function introspectionVerifier(options) {
141
+ if (!options.introspectionEndpoint && !options.issuer) {
142
+ throw new Error("mcp-expose: introspectionVerifier needs introspectionEndpoint or issuer");
143
+ }
144
+ let endpoint;
145
+ const getEndpoint = /* @__PURE__ */ __name(() => {
146
+ endpoint ??= (async () => {
147
+ if (options.introspectionEndpoint) return options.introspectionEndpoint;
148
+ const meta = await discoverAuthorizationServer(options.issuer);
149
+ if (typeof meta.introspection_endpoint !== "string") {
150
+ throw new Error(`mcp-expose: no introspection_endpoint in the metadata of ${options.issuer}`);
151
+ }
152
+ return meta.introspection_endpoint;
153
+ })().catch((err) => {
154
+ endpoint = void 0;
155
+ throw err;
156
+ });
157
+ return endpoint;
158
+ }, "getEndpoint");
159
+ const cacheMs = (options.cacheSeconds ?? 60) * 1e3;
160
+ const cache = /* @__PURE__ */ new Map();
161
+ return async (token, ctx) => {
162
+ const now = Date.now();
163
+ const hit = cache.get(token);
164
+ if (hit && hit.until > now) return hit.info;
165
+ cache.delete(token);
166
+ const headers = {
167
+ "content-type": "application/x-www-form-urlencoded",
168
+ accept: "application/json",
169
+ ...options.headers
170
+ };
171
+ if (options.clientId) {
172
+ const basic = `${encodeURIComponent(options.clientId)}:${encodeURIComponent(options.clientSecret ?? "")}`;
173
+ headers.authorization = `Basic ${btoa(basic)}`;
174
+ }
175
+ const res = await fetch(await getEndpoint(), {
176
+ method: "POST",
177
+ headers,
178
+ body: new URLSearchParams({
179
+ token,
180
+ token_type_hint: "access_token"
181
+ }).toString()
182
+ });
183
+ if (!res.ok) throw new Error(`token introspection failed: HTTP ${res.status}`);
184
+ const data = await res.json();
185
+ if (data.active !== true) return void 0;
186
+ if (options.issuer && typeof data.iss === "string" && data.iss !== options.issuer) return void 0;
187
+ const expected = [
188
+ options.audience ?? ctx.resource
189
+ ].flat();
190
+ const aud = data.aud === void 0 ? void 0 : [
191
+ data.aud
192
+ ].flat().map(String);
193
+ if (aud ? !aud.some((a) => expected.includes(a)) : options.requireAudience) return void 0;
194
+ if (options.validate && !await options.validate(data)) return void 0;
195
+ const info = toAuthInfo(token, data);
196
+ if (cacheMs > 0) {
197
+ const until = Math.min(now + cacheMs, info.expiresAt ? info.expiresAt * 1e3 : Infinity);
198
+ if (cache.size > 1e4) cache.clear();
199
+ cache.set(token, {
200
+ info,
201
+ until
202
+ });
203
+ }
204
+ return info;
205
+ };
206
+ }
207
+ __name(introspectionVerifier, "introspectionVerifier");
208
+ // Annotate the CommonJS export names for ESM import in node:
209
+ 0 && (module.exports = {
210
+ discoverAuthorizationServer,
211
+ introspectionVerifier,
212
+ jwtVerifier
213
+ });
214
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/oauth/index.ts","../../src/core/oauth.ts"],"sourcesContent":["/**\n * Ready-made token verifiers for the `oauth` option:\n *\n * - `jwtVerifier()` JWT access tokens, checked against the issuer's JWKS\n * (Auth0, Okta, Keycloak, Entra ID, Clerk, Cognito, WorkOS, ...).\n * Needs the `jose` package.\n * - `introspectionVerifier()` opaque tokens, checked with RFC 7662 token introspection.\n *\n * Both check the audience: a token is only accepted when it was issued for this\n * MCP server (`oauth.resource`), so tokens meant for other services are refused.\n */\nimport { parseScopes } from '../core/oauth.js';\nimport type { AuthInfo, TokenVerifier } from '../core/oauth.js';\nimport type * as JoseModule from 'jose';\n\nexport type { AuthInfo, TokenVerifier, VerifyTokenContext } from '../core/oauth.js';\n\nexport interface JwtVerifierOptions {\n /** Expected `iss` claim, e.g. `https://your-tenant.auth0.com/`. */\n issuer: string;\n /**\n * Expected `aud` claim(s). Defaults to `oauth.resource`, the URL of your MCP endpoint,\n * as the MCP spec requires. Set it when your authorization server uses a different\n * audience identifier (e.g. an Auth0 API identifier).\n */\n audience?: string | string[];\n /** JWKS URL. Discovered from the issuer's metadata when omitted. */\n jwksUri?: string;\n /** A static JWKS (`{ keys: [...] }`) instead of fetching one. */\n jwks?: { keys: Record<string, unknown>[] };\n /** Allowed signing algorithms. @default asymmetric algorithms only (RS*, PS*, ES*, EdDSA) */\n algorithms?: string[];\n /** Allowed clock skew in seconds. @default 30 */\n clockTolerance?: number;\n /** Claim that holds the scopes. @default 'scope', then 'scp' */\n scopeClaim?: string;\n /** Extra checks on the verified claims; return false to reject the token. */\n validate?: (claims: Record<string, unknown>) => boolean | Promise<boolean>;\n}\n\nconst ASYMMETRIC = [\n 'RS256',\n 'RS384',\n 'RS512',\n 'PS256',\n 'PS384',\n 'PS512',\n 'ES256',\n 'ES384',\n 'ES512',\n 'EdDSA',\n 'Ed25519',\n];\n\ntype Jose = typeof JoseModule;\nlet josePromise: Promise<Jose> | undefined;\nfunction loadJose(): Promise<Jose> {\n josePromise ??= import('jose').catch((err: unknown) => {\n josePromise = undefined;\n throw new Error(`mcp-expose: jwtVerifier() needs the \"jose\" package. Run: npm install jose (${String(err)})`);\n });\n return josePromise;\n}\n\n/**\n * Authorization server metadata (RFC 8414), falling back to OpenID Connect discovery.\n * Tries the path-inserted and appended forms, as MCP clients do.\n */\nexport async function discoverAuthorizationServer(issuer: string): Promise<Record<string, unknown>> {\n const url = new URL(issuer);\n const path = url.pathname.replace(/\\/+$/, '');\n const candidates = [\n new URL(`/.well-known/oauth-authorization-server${path}`, url.origin).href,\n new URL(`/.well-known/openid-configuration${path}`, url.origin).href,\n ...(path ? [`${url.origin}${path}/.well-known/openid-configuration`] : []),\n ];\n for (const candidate of candidates) {\n try {\n const res = await fetch(candidate, { headers: { accept: 'application/json' } });\n if (res.ok) return (await res.json()) as Record<string, unknown>;\n } catch {\n /* try the next one */\n }\n }\n throw new Error(`mcp-expose: could not load authorization server metadata for ${issuer}`);\n}\n\nfunction toAuthInfo(token: string, claims: Record<string, unknown>, scopeClaim?: string): AuthInfo {\n const scopes = scopeClaim ? parseScopes(claims[scopeClaim]) : parseScopes(claims.scope ?? claims.scp);\n return {\n token,\n scopes,\n subject: typeof claims.sub === 'string' ? claims.sub : undefined,\n clientId: [claims.client_id, claims.azp, claims.cid].find((v): v is string => typeof v === 'string'),\n expiresAt: typeof claims.exp === 'number' ? claims.exp : undefined,\n claims,\n };\n}\n\n/**\n * Verify JWT access tokens with the issuer's public keys:\n *\n * oauth: {\n * resource: 'https://api.example.com/mcp',\n * authorizationServers: ['https://example.auth0.com/'],\n * verifyToken: jwtVerifier({ issuer: 'https://example.auth0.com/' }),\n * }\n *\n * Checks signature, `iss`, `aud` (defaults to the resource), `exp` and `nbf`.\n */\nexport function jwtVerifier(options: JwtVerifierOptions): TokenVerifier {\n let keySet: Promise<Parameters<Jose['jwtVerify']>[1]> | undefined;\n const getKeys = () => {\n keySet ??= (async () => {\n const jose = await loadJose();\n if (options.jwks) return jose.createLocalJWKSet(options.jwks as Parameters<Jose['createLocalJWKSet']>[0]);\n const jwksUri = options.jwksUri ?? ((await discoverAuthorizationServer(options.issuer)).jwks_uri as string);\n if (!jwksUri) throw new Error(`mcp-expose: no jwks_uri in the metadata of ${options.issuer}`);\n return jose.createRemoteJWKSet(new URL(jwksUri));\n })().catch((err: unknown) => {\n keySet = undefined; // retry discovery on the next request\n throw err;\n });\n return keySet;\n };\n\n return async (token, ctx) => {\n const jose = await loadJose();\n const { payload } = await jose.jwtVerify(token, await getKeys(), {\n issuer: options.issuer,\n audience: options.audience ?? ctx.resource,\n algorithms: options.algorithms ?? ASYMMETRIC,\n clockTolerance: options.clockTolerance ?? 30,\n });\n const claims = payload as Record<string, unknown>;\n if (options.validate && !(await options.validate(claims))) return undefined;\n return toAuthInfo(token, claims, options.scopeClaim);\n };\n}\n\nexport interface IntrospectionVerifierOptions {\n /** Token introspection endpoint. Discovered from `issuer` when omitted. */\n introspectionEndpoint?: string;\n /** Issuer URL, used to discover the endpoint and to check `iss` when present. */\n issuer?: string;\n /** Credentials of this resource server at the authorization server (HTTP Basic). */\n clientId?: string;\n clientSecret?: string;\n /** Or any headers, e.g. `{ authorization: 'Bearer <token>' }`. */\n headers?: Record<string, string>;\n /**\n * Expected audience. Defaults to `oauth.resource`. Checked when the response\n * has an `aud` field; set `requireAudience: true` to reject responses without one.\n */\n audience?: string | string[];\n requireAudience?: boolean;\n /** Cache positive results for this many seconds (never past the token expiry). @default 60 */\n cacheSeconds?: number;\n /** Extra checks on the introspection response; return false to reject. */\n validate?: (response: Record<string, unknown>) => boolean | Promise<boolean>;\n}\n\n/** Verify opaque (or any) tokens with RFC 7662 token introspection. */\nexport function introspectionVerifier(options: IntrospectionVerifierOptions): TokenVerifier {\n if (!options.introspectionEndpoint && !options.issuer) {\n throw new Error('mcp-expose: introspectionVerifier needs introspectionEndpoint or issuer');\n }\n let endpoint: Promise<string> | undefined;\n const getEndpoint = () => {\n endpoint ??= (async () => {\n if (options.introspectionEndpoint) return options.introspectionEndpoint;\n const meta = await discoverAuthorizationServer(options.issuer!);\n if (typeof meta.introspection_endpoint !== 'string') {\n throw new Error(`mcp-expose: no introspection_endpoint in the metadata of ${options.issuer}`);\n }\n return meta.introspection_endpoint;\n })().catch((err: unknown) => {\n endpoint = undefined;\n throw err;\n });\n return endpoint;\n };\n\n const cacheMs = (options.cacheSeconds ?? 60) * 1000;\n const cache = new Map<string, { info: AuthInfo; until: number }>();\n\n return async (token, ctx) => {\n const now = Date.now();\n const hit = cache.get(token);\n if (hit && hit.until > now) return hit.info;\n cache.delete(token);\n\n const headers: Record<string, string> = {\n 'content-type': 'application/x-www-form-urlencoded',\n accept: 'application/json',\n ...options.headers,\n };\n if (options.clientId) {\n const basic = `${encodeURIComponent(options.clientId)}:${encodeURIComponent(options.clientSecret ?? '')}`;\n headers.authorization = `Basic ${btoa(basic)}`;\n }\n const res = await fetch(await getEndpoint(), {\n method: 'POST',\n headers,\n body: new URLSearchParams({ token, token_type_hint: 'access_token' }).toString(),\n });\n if (!res.ok) throw new Error(`token introspection failed: HTTP ${res.status}`);\n const data = (await res.json()) as Record<string, unknown>;\n if (data.active !== true) return undefined;\n\n if (options.issuer && typeof data.iss === 'string' && data.iss !== options.issuer) return undefined;\n const expected = [options.audience ?? ctx.resource].flat();\n const aud = data.aud === undefined ? undefined : [data.aud].flat().map(String);\n if (aud ? !aud.some((a) => expected.includes(a)) : options.requireAudience) return undefined;\n if (options.validate && !(await options.validate(data))) return undefined;\n\n const info = toAuthInfo(token, data);\n if (cacheMs > 0) {\n const until = Math.min(now + cacheMs, info.expiresAt ? info.expiresAt * 1000 : Infinity);\n if (cache.size > 10_000) cache.clear();\n cache.set(token, { info, until });\n }\n return info;\n };\n}\n","/**\n * OAuth 2.1 support per the MCP authorization spec: the MCP endpoint acts as an\n * OAuth *resource server*. It publishes Protected Resource Metadata (RFC 9728),\n * answers unauthenticated requests with a `WWW-Authenticate` challenge that\n * tells the client where to log in, and checks bearer tokens with a verifier\n * you provide (see `jwtVerifier` / `introspectionVerifier` in `mcp-expose/oauth`).\n *\n * Runtime-agnostic: no `node:*` imports.\n */\n\n/** Who the access token belongs to and what it allows. Available to tools as `ctx.auth`. */\nexport interface AuthInfo {\n /** The raw access token. */\n token: string;\n /** Scopes granted to the token. */\n scopes: string[];\n /** Subject (user id), e.g. the JWT `sub` claim. */\n subject?: string;\n /** OAuth client the token was issued to. */\n clientId?: string;\n /** Expiry, in seconds since the epoch. Expired tokens are rejected. */\n expiresAt?: number;\n /** All claims / introspection fields. */\n claims?: Record<string, unknown>;\n}\n\nexport interface VerifyTokenContext {\n /** The canonical resource URL (`oauth.resource`). Tokens must be issued for it (audience). */\n resource: string;\n /** Lower-cased headers of the MCP request. */\n headers: Record<string, string>;\n}\n\n/**\n * Checks an access token. Return its AuthInfo, or `undefined` / throw when the\n * token is invalid. It MUST check that the token was issued for this server\n * (audience = `ctx.resource`), or tokens meant for other services would be accepted.\n */\nexport type TokenVerifier = (\n token: string,\n ctx: VerifyTokenContext,\n) => AuthInfo | undefined | null | Promise<AuthInfo | undefined | null>;\n\nexport interface OAuthOptions {\n /**\n * Canonical, public URL of the MCP endpoint, e.g. `https://api.example.com/mcp`.\n * Clients request tokens for this resource, and it must match the URL they connect to.\n */\n resource: string;\n /** Issuer URLs of the authorization servers that issue tokens for this server. */\n authorizationServers: string[];\n /** Checks bearer tokens. */\n verifyToken: TokenVerifier;\n /** Scopes every token needs to use the MCP endpoint at all. */\n requiredScopes?: string[];\n /** Scopes advertised in the metadata. @default requiredScopes plus every tool's `scopes` */\n scopesSupported?: string[];\n /** Human-readable name shown by clients. */\n resourceName?: string;\n /** URL of documentation for developers. */\n resourceDocumentation?: string;\n /** Extra fields merged into the Protected Resource Metadata document. */\n metadata?: Record<string, unknown>;\n}\n\nexport const PROTECTED_RESOURCE_WELL_KNOWN = '/.well-known/oauth-protected-resource';\n\n/**\n * Paths the metadata is served on, per RFC 9728: the well-known prefix with the\n * resource path appended (`/.well-known/oauth-protected-resource/mcp`), plus the\n * bare well-known path that older clients fall back to.\n */\nexport function protectedResourceMetadataPaths(resource: string): string[] {\n const pathname = new URL(resource).pathname.replace(/\\/+$/, '');\n return pathname\n ? [PROTECTED_RESOURCE_WELL_KNOWN + pathname, PROTECTED_RESOURCE_WELL_KNOWN]\n : [PROTECTED_RESOURCE_WELL_KNOWN];\n}\n\n/** Absolute URL of the metadata document, used in `WWW-Authenticate`. */\nexport function protectedResourceMetadataUrl(resource: string): string {\n return new URL(protectedResourceMetadataPaths(resource)[0] as string, resource).href;\n}\n\nexport function validateOAuthOptions(o: OAuthOptions): void {\n let url: URL;\n try {\n url = new URL(o.resource);\n } catch {\n throw new Error(`mcp-expose: oauth.resource must be an absolute URL, got ${JSON.stringify(o.resource)}`);\n }\n if (url.hash) throw new Error('mcp-expose: oauth.resource must not contain a fragment');\n if (!o.authorizationServers?.length) throw new Error('mcp-expose: oauth.authorizationServers must not be empty');\n if (typeof o.verifyToken !== 'function') throw new Error('mcp-expose: oauth.verifyToken is required');\n}\n\nconst quote = (v: string) => `\"${v.replace(/[\"\\\\]/g, '\\\\$&')}\"`;\n\n/** Build a `WWW-Authenticate: Bearer ...` challenge (RFC 6750 + RFC 9728). */\nexport function bearerChallenge(\n resource: string,\n params: { error?: string; errorDescription?: string; scope?: string[] } = {},\n): string {\n const parts: string[] = [];\n if (params.error) parts.push(`error=${quote(params.error)}`);\n if (params.errorDescription) parts.push(`error_description=${quote(params.errorDescription)}`);\n if (params.scope?.length) parts.push(`scope=${quote(params.scope.join(' '))}`);\n parts.push(`resource_metadata=${quote(protectedResourceMetadataUrl(resource))}`);\n return `Bearer ${parts.join(', ')}`;\n}\n\n/** Extract the token from `Authorization: Bearer <token>`. Tokens in the query string are never accepted. */\nexport function bearerToken(headers: Record<string, string>): string | undefined {\n const m = /^Bearer[ ]+([^\\s,]+)\\s*$/i.exec(headers.authorization ?? '');\n return m?.[1];\n}\n\nexport type AuthOutcome =\n | { ok: true; auth: AuthInfo }\n | { ok: false; status: 401 | 403; challenge: string; error: string; description: string };\n\n/** Authenticate one MCP request. */\nexport async function authenticate(o: OAuthOptions, headers: Record<string, string>): Promise<AuthOutcome> {\n const token = bearerToken(headers);\n if (!token) {\n // No credentials: a bare challenge pointing at the metadata starts the client's login flow.\n return {\n ok: false,\n status: 401,\n error: 'unauthorized',\n description: 'Authorization required',\n challenge: bearerChallenge(o.resource, { scope: o.requiredScopes }),\n };\n }\n\n let auth: AuthInfo | undefined | null;\n try {\n auth = await o.verifyToken(token, { resource: o.resource, headers });\n } catch {\n auth = undefined;\n }\n if (!auth) return deny(o, 401, 'invalid_token', 'The access token is invalid');\n if (auth.expiresAt !== undefined && auth.expiresAt * 1000 <= Date.now()) {\n return deny(o, 401, 'invalid_token', 'The access token has expired');\n }\n const missing = missingScopes(auth, o.requiredScopes);\n if (missing.length) {\n // Keep the scopes already granted in the challenge, so re-authorizing never drops access.\n const scope = [...new Set([...(auth.scopes ?? []), ...(o.requiredScopes ?? [])])];\n return deny(o, 403, 'insufficient_scope', `Missing scope: ${missing.join(' ')}`, scope);\n }\n return { ok: true, auth: { ...auth, scopes: auth.scopes ?? [] } };\n}\n\n/** A failed AuthOutcome with an RFC 6750 error challenge. */\nexport function deny(\n o: OAuthOptions,\n status: 401 | 403,\n error: string,\n description: string,\n scope?: string[],\n): AuthOutcome & { ok: false } {\n return {\n ok: false,\n status,\n error,\n description,\n challenge: bearerChallenge(o.resource, { error, errorDescription: description, scope }),\n };\n}\n\nexport function missingScopes(auth: AuthInfo | undefined, required: string[] | undefined): string[] {\n if (!required?.length) return [];\n const granted = new Set(auth?.scopes ?? []);\n return required.filter((s) => !granted.has(s));\n}\n\n/** Parse a space-separated `scope` string or an array of scopes. */\nexport function parseScopes(value: unknown): string[] {\n if (Array.isArray(value)) return value.map(String);\n if (typeof value === 'string') return value.split(' ').filter(Boolean);\n return [];\n}\n"],"mappings":"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","names":["parseScopes","value","Array","isArray","map","String","split","filter","Boolean","ASYMMETRIC","josePromise","loadJose","catch","err","undefined","Error","String","discoverAuthorizationServer","issuer","url","URL","path","pathname","replace","candidates","origin","href","candidate","res","fetch","headers","accept","ok","json","toAuthInfo","token","claims","scopeClaim","scopes","parseScopes","scope","scp","subject","sub","clientId","client_id","azp","cid","find","v","expiresAt","exp","jwtVerifier","options","keySet","getKeys","jose","jwks","createLocalJWKSet","jwksUri","jwks_uri","createRemoteJWKSet","ctx","payload","jwtVerify","audience","resource","algorithms","clockTolerance","validate","introspectionVerifier","introspectionEndpoint","endpoint","getEndpoint","meta","introspection_endpoint","cacheMs","cacheSeconds","cache","Map","now","Date","hit","get","until","info","delete","basic","encodeURIComponent","clientSecret","authorization","btoa","method","body","URLSearchParams","token_type_hint","toString","status","data","active","iss","expected","flat","aud","map","some","a","includes","requireAudience","Math","min","Infinity","size","clear","set"]}
@@ -0,0 +1,69 @@
1
+ import { T as TokenVerifier } from '../oauth-8RmQamGP.cjs';
2
+ export { A as AuthInfo, V as VerifyTokenContext } from '../oauth-8RmQamGP.cjs';
3
+
4
+ interface JwtVerifierOptions {
5
+ /** Expected `iss` claim, e.g. `https://your-tenant.auth0.com/`. */
6
+ issuer: string;
7
+ /**
8
+ * Expected `aud` claim(s). Defaults to `oauth.resource`, the URL of your MCP endpoint,
9
+ * as the MCP spec requires. Set it when your authorization server uses a different
10
+ * audience identifier (e.g. an Auth0 API identifier).
11
+ */
12
+ audience?: string | string[];
13
+ /** JWKS URL. Discovered from the issuer's metadata when omitted. */
14
+ jwksUri?: string;
15
+ /** A static JWKS (`{ keys: [...] }`) instead of fetching one. */
16
+ jwks?: {
17
+ keys: Record<string, unknown>[];
18
+ };
19
+ /** Allowed signing algorithms. @default asymmetric algorithms only (RS*, PS*, ES*, EdDSA) */
20
+ algorithms?: string[];
21
+ /** Allowed clock skew in seconds. @default 30 */
22
+ clockTolerance?: number;
23
+ /** Claim that holds the scopes. @default 'scope', then 'scp' */
24
+ scopeClaim?: string;
25
+ /** Extra checks on the verified claims; return false to reject the token. */
26
+ validate?: (claims: Record<string, unknown>) => boolean | Promise<boolean>;
27
+ }
28
+ /**
29
+ * Authorization server metadata (RFC 8414), falling back to OpenID Connect discovery.
30
+ * Tries the path-inserted and appended forms, as MCP clients do.
31
+ */
32
+ declare function discoverAuthorizationServer(issuer: string): Promise<Record<string, unknown>>;
33
+ /**
34
+ * Verify JWT access tokens with the issuer's public keys:
35
+ *
36
+ * oauth: {
37
+ * resource: 'https://api.example.com/mcp',
38
+ * authorizationServers: ['https://example.auth0.com/'],
39
+ * verifyToken: jwtVerifier({ issuer: 'https://example.auth0.com/' }),
40
+ * }
41
+ *
42
+ * Checks signature, `iss`, `aud` (defaults to the resource), `exp` and `nbf`.
43
+ */
44
+ declare function jwtVerifier(options: JwtVerifierOptions): TokenVerifier;
45
+ interface IntrospectionVerifierOptions {
46
+ /** Token introspection endpoint. Discovered from `issuer` when omitted. */
47
+ introspectionEndpoint?: string;
48
+ /** Issuer URL, used to discover the endpoint and to check `iss` when present. */
49
+ issuer?: string;
50
+ /** Credentials of this resource server at the authorization server (HTTP Basic). */
51
+ clientId?: string;
52
+ clientSecret?: string;
53
+ /** Or any headers, e.g. `{ authorization: 'Bearer <token>' }`. */
54
+ headers?: Record<string, string>;
55
+ /**
56
+ * Expected audience. Defaults to `oauth.resource`. Checked when the response
57
+ * has an `aud` field; set `requireAudience: true` to reject responses without one.
58
+ */
59
+ audience?: string | string[];
60
+ requireAudience?: boolean;
61
+ /** Cache positive results for this many seconds (never past the token expiry). @default 60 */
62
+ cacheSeconds?: number;
63
+ /** Extra checks on the introspection response; return false to reject. */
64
+ validate?: (response: Record<string, unknown>) => boolean | Promise<boolean>;
65
+ }
66
+ /** Verify opaque (or any) tokens with RFC 7662 token introspection. */
67
+ declare function introspectionVerifier(options: IntrospectionVerifierOptions): TokenVerifier;
68
+
69
+ export { type IntrospectionVerifierOptions, type JwtVerifierOptions, TokenVerifier, discoverAuthorizationServer, introspectionVerifier, jwtVerifier };
@@ -0,0 +1,69 @@
1
+ import { T as TokenVerifier } from '../oauth-8RmQamGP.js';
2
+ export { A as AuthInfo, V as VerifyTokenContext } from '../oauth-8RmQamGP.js';
3
+
4
+ interface JwtVerifierOptions {
5
+ /** Expected `iss` claim, e.g. `https://your-tenant.auth0.com/`. */
6
+ issuer: string;
7
+ /**
8
+ * Expected `aud` claim(s). Defaults to `oauth.resource`, the URL of your MCP endpoint,
9
+ * as the MCP spec requires. Set it when your authorization server uses a different
10
+ * audience identifier (e.g. an Auth0 API identifier).
11
+ */
12
+ audience?: string | string[];
13
+ /** JWKS URL. Discovered from the issuer's metadata when omitted. */
14
+ jwksUri?: string;
15
+ /** A static JWKS (`{ keys: [...] }`) instead of fetching one. */
16
+ jwks?: {
17
+ keys: Record<string, unknown>[];
18
+ };
19
+ /** Allowed signing algorithms. @default asymmetric algorithms only (RS*, PS*, ES*, EdDSA) */
20
+ algorithms?: string[];
21
+ /** Allowed clock skew in seconds. @default 30 */
22
+ clockTolerance?: number;
23
+ /** Claim that holds the scopes. @default 'scope', then 'scp' */
24
+ scopeClaim?: string;
25
+ /** Extra checks on the verified claims; return false to reject the token. */
26
+ validate?: (claims: Record<string, unknown>) => boolean | Promise<boolean>;
27
+ }
28
+ /**
29
+ * Authorization server metadata (RFC 8414), falling back to OpenID Connect discovery.
30
+ * Tries the path-inserted and appended forms, as MCP clients do.
31
+ */
32
+ declare function discoverAuthorizationServer(issuer: string): Promise<Record<string, unknown>>;
33
+ /**
34
+ * Verify JWT access tokens with the issuer's public keys:
35
+ *
36
+ * oauth: {
37
+ * resource: 'https://api.example.com/mcp',
38
+ * authorizationServers: ['https://example.auth0.com/'],
39
+ * verifyToken: jwtVerifier({ issuer: 'https://example.auth0.com/' }),
40
+ * }
41
+ *
42
+ * Checks signature, `iss`, `aud` (defaults to the resource), `exp` and `nbf`.
43
+ */
44
+ declare function jwtVerifier(options: JwtVerifierOptions): TokenVerifier;
45
+ interface IntrospectionVerifierOptions {
46
+ /** Token introspection endpoint. Discovered from `issuer` when omitted. */
47
+ introspectionEndpoint?: string;
48
+ /** Issuer URL, used to discover the endpoint and to check `iss` when present. */
49
+ issuer?: string;
50
+ /** Credentials of this resource server at the authorization server (HTTP Basic). */
51
+ clientId?: string;
52
+ clientSecret?: string;
53
+ /** Or any headers, e.g. `{ authorization: 'Bearer <token>' }`. */
54
+ headers?: Record<string, string>;
55
+ /**
56
+ * Expected audience. Defaults to `oauth.resource`. Checked when the response
57
+ * has an `aud` field; set `requireAudience: true` to reject responses without one.
58
+ */
59
+ audience?: string | string[];
60
+ requireAudience?: boolean;
61
+ /** Cache positive results for this many seconds (never past the token expiry). @default 60 */
62
+ cacheSeconds?: number;
63
+ /** Extra checks on the introspection response; return false to reject. */
64
+ validate?: (response: Record<string, unknown>) => boolean | Promise<boolean>;
65
+ }
66
+ /** Verify opaque (or any) tokens with RFC 7662 token introspection. */
67
+ declare function introspectionVerifier(options: IntrospectionVerifierOptions): TokenVerifier;
68
+
69
+ export { type IntrospectionVerifierOptions, type JwtVerifierOptions, TokenVerifier, discoverAuthorizationServer, introspectionVerifier, jwtVerifier };
@@ -0,0 +1,177 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/core/oauth.ts
5
+ function parseScopes(value) {
6
+ if (Array.isArray(value)) return value.map(String);
7
+ if (typeof value === "string") return value.split(" ").filter(Boolean);
8
+ return [];
9
+ }
10
+ __name(parseScopes, "parseScopes");
11
+
12
+ // src/oauth/index.ts
13
+ var ASYMMETRIC = [
14
+ "RS256",
15
+ "RS384",
16
+ "RS512",
17
+ "PS256",
18
+ "PS384",
19
+ "PS512",
20
+ "ES256",
21
+ "ES384",
22
+ "ES512",
23
+ "EdDSA",
24
+ "Ed25519"
25
+ ];
26
+ var josePromise;
27
+ function loadJose() {
28
+ josePromise ??= import("jose").catch((err) => {
29
+ josePromise = void 0;
30
+ throw new Error(`mcp-expose: jwtVerifier() needs the "jose" package. Run: npm install jose (${String(err)})`);
31
+ });
32
+ return josePromise;
33
+ }
34
+ __name(loadJose, "loadJose");
35
+ async function discoverAuthorizationServer(issuer) {
36
+ const url = new URL(issuer);
37
+ const path = url.pathname.replace(/\/+$/, "");
38
+ const candidates = [
39
+ new URL(`/.well-known/oauth-authorization-server${path}`, url.origin).href,
40
+ new URL(`/.well-known/openid-configuration${path}`, url.origin).href,
41
+ ...path ? [
42
+ `${url.origin}${path}/.well-known/openid-configuration`
43
+ ] : []
44
+ ];
45
+ for (const candidate of candidates) {
46
+ try {
47
+ const res = await fetch(candidate, {
48
+ headers: {
49
+ accept: "application/json"
50
+ }
51
+ });
52
+ if (res.ok) return await res.json();
53
+ } catch {
54
+ }
55
+ }
56
+ throw new Error(`mcp-expose: could not load authorization server metadata for ${issuer}`);
57
+ }
58
+ __name(discoverAuthorizationServer, "discoverAuthorizationServer");
59
+ function toAuthInfo(token, claims, scopeClaim) {
60
+ const scopes = scopeClaim ? parseScopes(claims[scopeClaim]) : parseScopes(claims.scope ?? claims.scp);
61
+ return {
62
+ token,
63
+ scopes,
64
+ subject: typeof claims.sub === "string" ? claims.sub : void 0,
65
+ clientId: [
66
+ claims.client_id,
67
+ claims.azp,
68
+ claims.cid
69
+ ].find((v) => typeof v === "string"),
70
+ expiresAt: typeof claims.exp === "number" ? claims.exp : void 0,
71
+ claims
72
+ };
73
+ }
74
+ __name(toAuthInfo, "toAuthInfo");
75
+ function jwtVerifier(options) {
76
+ let keySet;
77
+ const getKeys = /* @__PURE__ */ __name(() => {
78
+ keySet ??= (async () => {
79
+ const jose = await loadJose();
80
+ if (options.jwks) return jose.createLocalJWKSet(options.jwks);
81
+ const jwksUri = options.jwksUri ?? (await discoverAuthorizationServer(options.issuer)).jwks_uri;
82
+ if (!jwksUri) throw new Error(`mcp-expose: no jwks_uri in the metadata of ${options.issuer}`);
83
+ return jose.createRemoteJWKSet(new URL(jwksUri));
84
+ })().catch((err) => {
85
+ keySet = void 0;
86
+ throw err;
87
+ });
88
+ return keySet;
89
+ }, "getKeys");
90
+ return async (token, ctx) => {
91
+ const jose = await loadJose();
92
+ const { payload } = await jose.jwtVerify(token, await getKeys(), {
93
+ issuer: options.issuer,
94
+ audience: options.audience ?? ctx.resource,
95
+ algorithms: options.algorithms ?? ASYMMETRIC,
96
+ clockTolerance: options.clockTolerance ?? 30
97
+ });
98
+ const claims = payload;
99
+ if (options.validate && !await options.validate(claims)) return void 0;
100
+ return toAuthInfo(token, claims, options.scopeClaim);
101
+ };
102
+ }
103
+ __name(jwtVerifier, "jwtVerifier");
104
+ function introspectionVerifier(options) {
105
+ if (!options.introspectionEndpoint && !options.issuer) {
106
+ throw new Error("mcp-expose: introspectionVerifier needs introspectionEndpoint or issuer");
107
+ }
108
+ let endpoint;
109
+ const getEndpoint = /* @__PURE__ */ __name(() => {
110
+ endpoint ??= (async () => {
111
+ if (options.introspectionEndpoint) return options.introspectionEndpoint;
112
+ const meta = await discoverAuthorizationServer(options.issuer);
113
+ if (typeof meta.introspection_endpoint !== "string") {
114
+ throw new Error(`mcp-expose: no introspection_endpoint in the metadata of ${options.issuer}`);
115
+ }
116
+ return meta.introspection_endpoint;
117
+ })().catch((err) => {
118
+ endpoint = void 0;
119
+ throw err;
120
+ });
121
+ return endpoint;
122
+ }, "getEndpoint");
123
+ const cacheMs = (options.cacheSeconds ?? 60) * 1e3;
124
+ const cache = /* @__PURE__ */ new Map();
125
+ return async (token, ctx) => {
126
+ const now = Date.now();
127
+ const hit = cache.get(token);
128
+ if (hit && hit.until > now) return hit.info;
129
+ cache.delete(token);
130
+ const headers = {
131
+ "content-type": "application/x-www-form-urlencoded",
132
+ accept: "application/json",
133
+ ...options.headers
134
+ };
135
+ if (options.clientId) {
136
+ const basic = `${encodeURIComponent(options.clientId)}:${encodeURIComponent(options.clientSecret ?? "")}`;
137
+ headers.authorization = `Basic ${btoa(basic)}`;
138
+ }
139
+ const res = await fetch(await getEndpoint(), {
140
+ method: "POST",
141
+ headers,
142
+ body: new URLSearchParams({
143
+ token,
144
+ token_type_hint: "access_token"
145
+ }).toString()
146
+ });
147
+ if (!res.ok) throw new Error(`token introspection failed: HTTP ${res.status}`);
148
+ const data = await res.json();
149
+ if (data.active !== true) return void 0;
150
+ if (options.issuer && typeof data.iss === "string" && data.iss !== options.issuer) return void 0;
151
+ const expected = [
152
+ options.audience ?? ctx.resource
153
+ ].flat();
154
+ const aud = data.aud === void 0 ? void 0 : [
155
+ data.aud
156
+ ].flat().map(String);
157
+ if (aud ? !aud.some((a) => expected.includes(a)) : options.requireAudience) return void 0;
158
+ if (options.validate && !await options.validate(data)) return void 0;
159
+ const info = toAuthInfo(token, data);
160
+ if (cacheMs > 0) {
161
+ const until = Math.min(now + cacheMs, info.expiresAt ? info.expiresAt * 1e3 : Infinity);
162
+ if (cache.size > 1e4) cache.clear();
163
+ cache.set(token, {
164
+ info,
165
+ until
166
+ });
167
+ }
168
+ return info;
169
+ };
170
+ }
171
+ __name(introspectionVerifier, "introspectionVerifier");
172
+ export {
173
+ discoverAuthorizationServer,
174
+ introspectionVerifier,
175
+ jwtVerifier
176
+ };
177
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/oauth.ts","../../src/oauth/index.ts"],"sourcesContent":["/**\n * OAuth 2.1 support per the MCP authorization spec: the MCP endpoint acts as an\n * OAuth *resource server*. It publishes Protected Resource Metadata (RFC 9728),\n * answers unauthenticated requests with a `WWW-Authenticate` challenge that\n * tells the client where to log in, and checks bearer tokens with a verifier\n * you provide (see `jwtVerifier` / `introspectionVerifier` in `mcp-expose/oauth`).\n *\n * Runtime-agnostic: no `node:*` imports.\n */\n\n/** Who the access token belongs to and what it allows. Available to tools as `ctx.auth`. */\nexport interface AuthInfo {\n /** The raw access token. */\n token: string;\n /** Scopes granted to the token. */\n scopes: string[];\n /** Subject (user id), e.g. the JWT `sub` claim. */\n subject?: string;\n /** OAuth client the token was issued to. */\n clientId?: string;\n /** Expiry, in seconds since the epoch. Expired tokens are rejected. */\n expiresAt?: number;\n /** All claims / introspection fields. */\n claims?: Record<string, unknown>;\n}\n\nexport interface VerifyTokenContext {\n /** The canonical resource URL (`oauth.resource`). Tokens must be issued for it (audience). */\n resource: string;\n /** Lower-cased headers of the MCP request. */\n headers: Record<string, string>;\n}\n\n/**\n * Checks an access token. Return its AuthInfo, or `undefined` / throw when the\n * token is invalid. It MUST check that the token was issued for this server\n * (audience = `ctx.resource`), or tokens meant for other services would be accepted.\n */\nexport type TokenVerifier = (\n token: string,\n ctx: VerifyTokenContext,\n) => AuthInfo | undefined | null | Promise<AuthInfo | undefined | null>;\n\nexport interface OAuthOptions {\n /**\n * Canonical, public URL of the MCP endpoint, e.g. `https://api.example.com/mcp`.\n * Clients request tokens for this resource, and it must match the URL they connect to.\n */\n resource: string;\n /** Issuer URLs of the authorization servers that issue tokens for this server. */\n authorizationServers: string[];\n /** Checks bearer tokens. */\n verifyToken: TokenVerifier;\n /** Scopes every token needs to use the MCP endpoint at all. */\n requiredScopes?: string[];\n /** Scopes advertised in the metadata. @default requiredScopes plus every tool's `scopes` */\n scopesSupported?: string[];\n /** Human-readable name shown by clients. */\n resourceName?: string;\n /** URL of documentation for developers. */\n resourceDocumentation?: string;\n /** Extra fields merged into the Protected Resource Metadata document. */\n metadata?: Record<string, unknown>;\n}\n\nexport const PROTECTED_RESOURCE_WELL_KNOWN = '/.well-known/oauth-protected-resource';\n\n/**\n * Paths the metadata is served on, per RFC 9728: the well-known prefix with the\n * resource path appended (`/.well-known/oauth-protected-resource/mcp`), plus the\n * bare well-known path that older clients fall back to.\n */\nexport function protectedResourceMetadataPaths(resource: string): string[] {\n const pathname = new URL(resource).pathname.replace(/\\/+$/, '');\n return pathname\n ? [PROTECTED_RESOURCE_WELL_KNOWN + pathname, PROTECTED_RESOURCE_WELL_KNOWN]\n : [PROTECTED_RESOURCE_WELL_KNOWN];\n}\n\n/** Absolute URL of the metadata document, used in `WWW-Authenticate`. */\nexport function protectedResourceMetadataUrl(resource: string): string {\n return new URL(protectedResourceMetadataPaths(resource)[0] as string, resource).href;\n}\n\nexport function validateOAuthOptions(o: OAuthOptions): void {\n let url: URL;\n try {\n url = new URL(o.resource);\n } catch {\n throw new Error(`mcp-expose: oauth.resource must be an absolute URL, got ${JSON.stringify(o.resource)}`);\n }\n if (url.hash) throw new Error('mcp-expose: oauth.resource must not contain a fragment');\n if (!o.authorizationServers?.length) throw new Error('mcp-expose: oauth.authorizationServers must not be empty');\n if (typeof o.verifyToken !== 'function') throw new Error('mcp-expose: oauth.verifyToken is required');\n}\n\nconst quote = (v: string) => `\"${v.replace(/[\"\\\\]/g, '\\\\$&')}\"`;\n\n/** Build a `WWW-Authenticate: Bearer ...` challenge (RFC 6750 + RFC 9728). */\nexport function bearerChallenge(\n resource: string,\n params: { error?: string; errorDescription?: string; scope?: string[] } = {},\n): string {\n const parts: string[] = [];\n if (params.error) parts.push(`error=${quote(params.error)}`);\n if (params.errorDescription) parts.push(`error_description=${quote(params.errorDescription)}`);\n if (params.scope?.length) parts.push(`scope=${quote(params.scope.join(' '))}`);\n parts.push(`resource_metadata=${quote(protectedResourceMetadataUrl(resource))}`);\n return `Bearer ${parts.join(', ')}`;\n}\n\n/** Extract the token from `Authorization: Bearer <token>`. Tokens in the query string are never accepted. */\nexport function bearerToken(headers: Record<string, string>): string | undefined {\n const m = /^Bearer[ ]+([^\\s,]+)\\s*$/i.exec(headers.authorization ?? '');\n return m?.[1];\n}\n\nexport type AuthOutcome =\n | { ok: true; auth: AuthInfo }\n | { ok: false; status: 401 | 403; challenge: string; error: string; description: string };\n\n/** Authenticate one MCP request. */\nexport async function authenticate(o: OAuthOptions, headers: Record<string, string>): Promise<AuthOutcome> {\n const token = bearerToken(headers);\n if (!token) {\n // No credentials: a bare challenge pointing at the metadata starts the client's login flow.\n return {\n ok: false,\n status: 401,\n error: 'unauthorized',\n description: 'Authorization required',\n challenge: bearerChallenge(o.resource, { scope: o.requiredScopes }),\n };\n }\n\n let auth: AuthInfo | undefined | null;\n try {\n auth = await o.verifyToken(token, { resource: o.resource, headers });\n } catch {\n auth = undefined;\n }\n if (!auth) return deny(o, 401, 'invalid_token', 'The access token is invalid');\n if (auth.expiresAt !== undefined && auth.expiresAt * 1000 <= Date.now()) {\n return deny(o, 401, 'invalid_token', 'The access token has expired');\n }\n const missing = missingScopes(auth, o.requiredScopes);\n if (missing.length) {\n // Keep the scopes already granted in the challenge, so re-authorizing never drops access.\n const scope = [...new Set([...(auth.scopes ?? []), ...(o.requiredScopes ?? [])])];\n return deny(o, 403, 'insufficient_scope', `Missing scope: ${missing.join(' ')}`, scope);\n }\n return { ok: true, auth: { ...auth, scopes: auth.scopes ?? [] } };\n}\n\n/** A failed AuthOutcome with an RFC 6750 error challenge. */\nexport function deny(\n o: OAuthOptions,\n status: 401 | 403,\n error: string,\n description: string,\n scope?: string[],\n): AuthOutcome & { ok: false } {\n return {\n ok: false,\n status,\n error,\n description,\n challenge: bearerChallenge(o.resource, { error, errorDescription: description, scope }),\n };\n}\n\nexport function missingScopes(auth: AuthInfo | undefined, required: string[] | undefined): string[] {\n if (!required?.length) return [];\n const granted = new Set(auth?.scopes ?? []);\n return required.filter((s) => !granted.has(s));\n}\n\n/** Parse a space-separated `scope` string or an array of scopes. */\nexport function parseScopes(value: unknown): string[] {\n if (Array.isArray(value)) return value.map(String);\n if (typeof value === 'string') return value.split(' ').filter(Boolean);\n return [];\n}\n","/**\n * Ready-made token verifiers for the `oauth` option:\n *\n * - `jwtVerifier()` JWT access tokens, checked against the issuer's JWKS\n * (Auth0, Okta, Keycloak, Entra ID, Clerk, Cognito, WorkOS, ...).\n * Needs the `jose` package.\n * - `introspectionVerifier()` opaque tokens, checked with RFC 7662 token introspection.\n *\n * Both check the audience: a token is only accepted when it was issued for this\n * MCP server (`oauth.resource`), so tokens meant for other services are refused.\n */\nimport { parseScopes } from '../core/oauth.js';\nimport type { AuthInfo, TokenVerifier } from '../core/oauth.js';\nimport type * as JoseModule from 'jose';\n\nexport type { AuthInfo, TokenVerifier, VerifyTokenContext } from '../core/oauth.js';\n\nexport interface JwtVerifierOptions {\n /** Expected `iss` claim, e.g. `https://your-tenant.auth0.com/`. */\n issuer: string;\n /**\n * Expected `aud` claim(s). Defaults to `oauth.resource`, the URL of your MCP endpoint,\n * as the MCP spec requires. Set it when your authorization server uses a different\n * audience identifier (e.g. an Auth0 API identifier).\n */\n audience?: string | string[];\n /** JWKS URL. Discovered from the issuer's metadata when omitted. */\n jwksUri?: string;\n /** A static JWKS (`{ keys: [...] }`) instead of fetching one. */\n jwks?: { keys: Record<string, unknown>[] };\n /** Allowed signing algorithms. @default asymmetric algorithms only (RS*, PS*, ES*, EdDSA) */\n algorithms?: string[];\n /** Allowed clock skew in seconds. @default 30 */\n clockTolerance?: number;\n /** Claim that holds the scopes. @default 'scope', then 'scp' */\n scopeClaim?: string;\n /** Extra checks on the verified claims; return false to reject the token. */\n validate?: (claims: Record<string, unknown>) => boolean | Promise<boolean>;\n}\n\nconst ASYMMETRIC = [\n 'RS256',\n 'RS384',\n 'RS512',\n 'PS256',\n 'PS384',\n 'PS512',\n 'ES256',\n 'ES384',\n 'ES512',\n 'EdDSA',\n 'Ed25519',\n];\n\ntype Jose = typeof JoseModule;\nlet josePromise: Promise<Jose> | undefined;\nfunction loadJose(): Promise<Jose> {\n josePromise ??= import('jose').catch((err: unknown) => {\n josePromise = undefined;\n throw new Error(`mcp-expose: jwtVerifier() needs the \"jose\" package. Run: npm install jose (${String(err)})`);\n });\n return josePromise;\n}\n\n/**\n * Authorization server metadata (RFC 8414), falling back to OpenID Connect discovery.\n * Tries the path-inserted and appended forms, as MCP clients do.\n */\nexport async function discoverAuthorizationServer(issuer: string): Promise<Record<string, unknown>> {\n const url = new URL(issuer);\n const path = url.pathname.replace(/\\/+$/, '');\n const candidates = [\n new URL(`/.well-known/oauth-authorization-server${path}`, url.origin).href,\n new URL(`/.well-known/openid-configuration${path}`, url.origin).href,\n ...(path ? [`${url.origin}${path}/.well-known/openid-configuration`] : []),\n ];\n for (const candidate of candidates) {\n try {\n const res = await fetch(candidate, { headers: { accept: 'application/json' } });\n if (res.ok) return (await res.json()) as Record<string, unknown>;\n } catch {\n /* try the next one */\n }\n }\n throw new Error(`mcp-expose: could not load authorization server metadata for ${issuer}`);\n}\n\nfunction toAuthInfo(token: string, claims: Record<string, unknown>, scopeClaim?: string): AuthInfo {\n const scopes = scopeClaim ? parseScopes(claims[scopeClaim]) : parseScopes(claims.scope ?? claims.scp);\n return {\n token,\n scopes,\n subject: typeof claims.sub === 'string' ? claims.sub : undefined,\n clientId: [claims.client_id, claims.azp, claims.cid].find((v): v is string => typeof v === 'string'),\n expiresAt: typeof claims.exp === 'number' ? claims.exp : undefined,\n claims,\n };\n}\n\n/**\n * Verify JWT access tokens with the issuer's public keys:\n *\n * oauth: {\n * resource: 'https://api.example.com/mcp',\n * authorizationServers: ['https://example.auth0.com/'],\n * verifyToken: jwtVerifier({ issuer: 'https://example.auth0.com/' }),\n * }\n *\n * Checks signature, `iss`, `aud` (defaults to the resource), `exp` and `nbf`.\n */\nexport function jwtVerifier(options: JwtVerifierOptions): TokenVerifier {\n let keySet: Promise<Parameters<Jose['jwtVerify']>[1]> | undefined;\n const getKeys = () => {\n keySet ??= (async () => {\n const jose = await loadJose();\n if (options.jwks) return jose.createLocalJWKSet(options.jwks as Parameters<Jose['createLocalJWKSet']>[0]);\n const jwksUri = options.jwksUri ?? ((await discoverAuthorizationServer(options.issuer)).jwks_uri as string);\n if (!jwksUri) throw new Error(`mcp-expose: no jwks_uri in the metadata of ${options.issuer}`);\n return jose.createRemoteJWKSet(new URL(jwksUri));\n })().catch((err: unknown) => {\n keySet = undefined; // retry discovery on the next request\n throw err;\n });\n return keySet;\n };\n\n return async (token, ctx) => {\n const jose = await loadJose();\n const { payload } = await jose.jwtVerify(token, await getKeys(), {\n issuer: options.issuer,\n audience: options.audience ?? ctx.resource,\n algorithms: options.algorithms ?? ASYMMETRIC,\n clockTolerance: options.clockTolerance ?? 30,\n });\n const claims = payload as Record<string, unknown>;\n if (options.validate && !(await options.validate(claims))) return undefined;\n return toAuthInfo(token, claims, options.scopeClaim);\n };\n}\n\nexport interface IntrospectionVerifierOptions {\n /** Token introspection endpoint. Discovered from `issuer` when omitted. */\n introspectionEndpoint?: string;\n /** Issuer URL, used to discover the endpoint and to check `iss` when present. */\n issuer?: string;\n /** Credentials of this resource server at the authorization server (HTTP Basic). */\n clientId?: string;\n clientSecret?: string;\n /** Or any headers, e.g. `{ authorization: 'Bearer <token>' }`. */\n headers?: Record<string, string>;\n /**\n * Expected audience. Defaults to `oauth.resource`. Checked when the response\n * has an `aud` field; set `requireAudience: true` to reject responses without one.\n */\n audience?: string | string[];\n requireAudience?: boolean;\n /** Cache positive results for this many seconds (never past the token expiry). @default 60 */\n cacheSeconds?: number;\n /** Extra checks on the introspection response; return false to reject. */\n validate?: (response: Record<string, unknown>) => boolean | Promise<boolean>;\n}\n\n/** Verify opaque (or any) tokens with RFC 7662 token introspection. */\nexport function introspectionVerifier(options: IntrospectionVerifierOptions): TokenVerifier {\n if (!options.introspectionEndpoint && !options.issuer) {\n throw new Error('mcp-expose: introspectionVerifier needs introspectionEndpoint or issuer');\n }\n let endpoint: Promise<string> | undefined;\n const getEndpoint = () => {\n endpoint ??= (async () => {\n if (options.introspectionEndpoint) return options.introspectionEndpoint;\n const meta = await discoverAuthorizationServer(options.issuer!);\n if (typeof meta.introspection_endpoint !== 'string') {\n throw new Error(`mcp-expose: no introspection_endpoint in the metadata of ${options.issuer}`);\n }\n return meta.introspection_endpoint;\n })().catch((err: unknown) => {\n endpoint = undefined;\n throw err;\n });\n return endpoint;\n };\n\n const cacheMs = (options.cacheSeconds ?? 60) * 1000;\n const cache = new Map<string, { info: AuthInfo; until: number }>();\n\n return async (token, ctx) => {\n const now = Date.now();\n const hit = cache.get(token);\n if (hit && hit.until > now) return hit.info;\n cache.delete(token);\n\n const headers: Record<string, string> = {\n 'content-type': 'application/x-www-form-urlencoded',\n accept: 'application/json',\n ...options.headers,\n };\n if (options.clientId) {\n const basic = `${encodeURIComponent(options.clientId)}:${encodeURIComponent(options.clientSecret ?? '')}`;\n headers.authorization = `Basic ${btoa(basic)}`;\n }\n const res = await fetch(await getEndpoint(), {\n method: 'POST',\n headers,\n body: new URLSearchParams({ token, token_type_hint: 'access_token' }).toString(),\n });\n if (!res.ok) throw new Error(`token introspection failed: HTTP ${res.status}`);\n const data = (await res.json()) as Record<string, unknown>;\n if (data.active !== true) return undefined;\n\n if (options.issuer && typeof data.iss === 'string' && data.iss !== options.issuer) return undefined;\n const expected = [options.audience ?? ctx.resource].flat();\n const aud = data.aud === undefined ? undefined : [data.aud].flat().map(String);\n if (aud ? !aud.some((a) => expected.includes(a)) : options.requireAudience) return undefined;\n if (options.validate && !(await options.validate(data))) return undefined;\n\n const info = toAuthInfo(token, data);\n if (cacheMs > 0) {\n const until = Math.min(now + cacheMs, info.expiresAt ? info.expiresAt * 1000 : Infinity);\n if (cache.size > 10_000) cache.clear();\n cache.set(token, { info, until });\n }\n return info;\n };\n}\n"],"mappings":"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","names":["parseScopes","value","Array","isArray","map","String","split","filter","Boolean","ASYMMETRIC","josePromise","loadJose","catch","err","undefined","Error","String","discoverAuthorizationServer","issuer","url","URL","path","pathname","replace","candidates","origin","href","candidate","res","fetch","headers","accept","ok","json","toAuthInfo","token","claims","scopeClaim","scopes","parseScopes","scope","scp","subject","sub","clientId","client_id","azp","cid","find","v","expiresAt","exp","jwtVerifier","options","keySet","getKeys","jose","jwks","createLocalJWKSet","jwksUri","jwks_uri","createRemoteJWKSet","ctx","payload","jwtVerify","audience","resource","algorithms","clockTolerance","validate","introspectionVerifier","introspectionEndpoint","endpoint","getEndpoint","meta","introspection_endpoint","cacheMs","cacheSeconds","cache","Map","now","Date","hit","get","until","info","delete","basic","encodeURIComponent","clientSecret","authorization","btoa","method","body","URLSearchParams","token_type_hint","toString","status","data","active","iss","expected","flat","aud","map","some","a","includes","requireAudience","Math","min","Infinity","size","clear","set"]}