@kieksme/mcp-http-kit 0.0.0-stage → 0.2.0

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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +197 -2
  3. package/SECURITY.md +19 -0
  4. package/dist/auth/bearer.d.ts +14 -0
  5. package/dist/auth/bearer.d.ts.map +1 -0
  6. package/dist/auth/bearer.js +43 -0
  7. package/dist/auth/bearer.js.map +1 -0
  8. package/dist/auth/context.d.ts +7 -0
  9. package/dist/auth/context.d.ts.map +1 -0
  10. package/dist/auth/context.js +46 -0
  11. package/dist/auth/context.js.map +1 -0
  12. package/dist/auth/index.d.ts +25 -0
  13. package/dist/auth/index.d.ts.map +1 -0
  14. package/dist/auth/index.js +34 -0
  15. package/dist/auth/index.js.map +1 -0
  16. package/dist/auth/oauth.d.ts +25 -0
  17. package/dist/auth/oauth.d.ts.map +1 -0
  18. package/dist/auth/oauth.js +161 -0
  19. package/dist/auth/oauth.js.map +1 -0
  20. package/dist/config.d.ts +30 -0
  21. package/dist/config.d.ts.map +1 -0
  22. package/dist/config.js +201 -0
  23. package/dist/config.js.map +1 -0
  24. package/dist/http/app.d.ts +32 -0
  25. package/dist/http/app.d.ts.map +1 -0
  26. package/dist/http/app.js +127 -0
  27. package/dist/http/app.js.map +1 -0
  28. package/dist/http/host-guard.d.ts +22 -0
  29. package/dist/http/host-guard.d.ts.map +1 -0
  30. package/dist/http/host-guard.js +46 -0
  31. package/dist/http/host-guard.js.map +1 -0
  32. package/dist/http/index.d.ts +4 -0
  33. package/dist/http/index.d.ts.map +1 -0
  34. package/dist/http/index.js +4 -0
  35. package/dist/http/index.js.map +1 -0
  36. package/dist/http/node.d.ts +36 -0
  37. package/dist/http/node.d.ts.map +1 -0
  38. package/dist/http/node.js +74 -0
  39. package/dist/http/node.js.map +1 -0
  40. package/dist/index.d.ts +8 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +7 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/start.d.ts +16 -0
  45. package/dist/start.d.ts.map +1 -0
  46. package/dist/start.js +84 -0
  47. package/dist/start.js.map +1 -0
  48. package/dist/test/helpers.d.ts +35 -0
  49. package/dist/test/helpers.d.ts.map +1 -0
  50. package/dist/test/helpers.js +171 -0
  51. package/dist/test/helpers.js.map +1 -0
  52. package/dist/test/index.d.ts +2 -0
  53. package/dist/test/index.d.ts.map +1 -0
  54. package/dist/test/index.js +2 -0
  55. package/dist/test/index.js.map +1 -0
  56. package/dist/types.d.ts +123 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +2 -0
  59. package/dist/types.js.map +1 -0
  60. package/dist/upstream.d.ts +17 -0
  61. package/dist/upstream.d.ts.map +1 -0
  62. package/dist/upstream.js +49 -0
  63. package/dist/upstream.js.map +1 -0
  64. package/docs/migration.md +125 -0
  65. package/package.json +87 -4
  66. package/templates/Dockerfile +46 -0
  67. package/templates/railway.json +13 -0
  68. package/templates/server.json +79 -0
  69. package/templates/smithery.yaml +31 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kieks.me
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,198 @@
1
- # Temporary Holding Version
1
+ # `@kieksme/mcp-http-kit`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Shared **Streamable HTTP** transport, **auth** (bearer + OAuth 2.1 resource server), and **deploy templates** for kieksme MCP servers.
4
+
5
+ Use this so every server speaks the same env scheme, passes Railway healthchecks, and can be listed on MCP registries without copy-pasting HTTP glue.
6
+
7
+ ## SDK decision (v2 only)
8
+
9
+ This kit targets the **MCP TypeScript SDK v2** packages:
10
+
11
+ | Package | Role |
12
+ | --- | --- |
13
+ | `@modelcontextprotocol/server` | `McpServer`, `createMcpHandler` |
14
+ | `@modelcontextprotocol/node` | `toNodeHandler`, Node Streamable HTTP |
15
+
16
+ **Why not v1 / dual adapters?** Upstream marked v2 as the stable release line implementing the **2026-07-28** spec; `@modelcontextprotocol/sdk` is the legacy monolith. Newer kieksme servers (`mcp-enhancv`, `mcp-balena`) already use v2. Older ones (`mcp-contabo`, `mcp-listmonk`, `mcp-swetrix`, `mcp-hashnode`) should migrate to v2 rather than the kit carrying a forever-v1 adapter. See [`docs/migration.md`](./docs/migration.md).
17
+
18
+ Peer dependencies: install `@modelcontextprotocol/server` and `@modelcontextprotocol/node` in each server.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pnpm add @kieksme/mcp-http-kit
24
+ pnpm add @modelcontextprotocol/server @modelcontextprotocol/node
25
+ ```
26
+
27
+ Node **22.14+**, ESM only, MIT.
28
+
29
+ ## Quick start
30
+
31
+ ```ts
32
+ import { McpServer } from '@modelcontextprotocol/server';
33
+ import { loadConfig, startMcpServer } from '@kieksme/mcp-http-kit';
34
+
35
+ function createServer(ctx) {
36
+ const server = new McpServer({ name: 'demo-mcp', version: '1.0.0' });
37
+ // ctx.auth.mode / ctx.auth.scopes available to tool handlers
38
+ return server;
39
+ }
40
+
41
+ const config = loadConfig(process.env, { realm: 'demo-mcp' });
42
+ await startMcpServer({ createServer, config });
43
+ ```
44
+
45
+ - `MCP_TRANSPORT=stdio` → stdio (default)
46
+ - `MCP_TRANSPORT=http` → stateless Streamable HTTP (`POST /mcp`)
47
+
48
+ Example: [`examples/echo-server`](./examples/echo-server).
49
+
50
+ ```bash
51
+ MCP_TRANSPORT=stdio pnpm example:echo
52
+ MCP_TRANSPORT=http MCP_AUTH_TOKEN=dev MCP_HTTP_HOST=127.0.0.1 pnpm example:echo
53
+ ```
54
+
55
+ ## Environment variables
56
+
57
+ | Variable | Default | Notes |
58
+ | --- | --- | --- |
59
+ | `MCP_TRANSPORT` | `stdio` | `stdio` \| `http` |
60
+ | `PORT` | `3000` | Preferred listen port (Railway). Fallback: `MCP_HTTP_PORT` |
61
+ | `MCP_HTTP_HOST` | `127.0.0.1` | Use `0.0.0.0` in containers |
62
+ | `MCP_HTTP_PATH` | `/mcp` | MCP endpoint |
63
+ | `MCP_HEALTH_PATH` | `/health` | Primary unauthenticated health path |
64
+ | `MCP_HEALTH_ALIASES` | — | Extra health paths (comma-separated), e.g. `/healthz` |
65
+ | `MCP_ALLOWED_HOSTS` | localhost trio | Required when binding beyond localhost |
66
+ | `MCP_ALLOWED_ORIGINS` | _(derived)_ | Optional absolute origins; else Origin hostname must be in hosts |
67
+ | `MCP_AUTH_MODE` | `bearer` | `bearer` \| `oauth` \| `none` |
68
+ | `MCP_AUTH_TOKEN` | — | Bearer secrets; **comma-separated** for rotation |
69
+ | `MCP_AUTH_ALLOW_NONE` | `false` | Opt-in for `none` off localhost (public read-only) |
70
+ | `MCP_AUTH_REALM` | `mcp` | WWW-Authenticate realm (or pass `loadConfig(..., { realm })`) |
71
+ | `MCP_HTTP_ENABLE_SESSION_METHODS` | `false` | If true, GET/DELETE on MCP path are forwarded (else **405**) |
72
+ | `MCP_OAUTH_RESOURCE` | — | Canonical resource URL (oauth) |
73
+ | `MCP_OAUTH_AUTHORIZATION_SERVERS` | — | Comma-separated AS issuer URLs |
74
+ | `MCP_OAUTH_AUDIENCES` | resource | Expected JWT `aud` / resource |
75
+ | `MCP_OAUTH_JWKS_URL` | _(discovered)_ | Optional JWKS override |
76
+ | `MCP_OAUTH_REQUIRED_SCOPES` | _(none)_ | All must be present |
77
+ | `MCP_OAUTH_SCOPES_SUPPORTED` | required | Advertised in RFC 9728 metadata |
78
+
79
+ **Deprecated aliases** (still work, warn once): `MCP_HTTP_AUTH_TOKEN`, `MCP_SERVER_AUTH_TOKEN`, `MCP_HTTP_ALLOWED_HOSTS`, `MCP_HTTP_ALLOWED_ORIGINS`, `MCP_HTTP_ENDPOINT`, `HEALTH_ENDPOINT`.
80
+
81
+ ## Auth modes
82
+
83
+ ### `bearer`
84
+
85
+ Constant-time compare against `MCP_AUTH_TOKEN` list. Missing/invalid → **401** + `WWW-Authenticate: Bearer realm="…"`.
86
+
87
+ ### `oauth`
88
+
89
+ MCP server acts as an **OAuth 2.1 resource server** only (never an authorization server):
90
+
91
+ 1. Serves RFC 9728 metadata at `/.well-known/oauth-protected-resource` (+ path-aware variant for `/mcp`)
92
+ 2. Validates JWT access tokens via JWKS (issuer, audience/resource, expiry, scopes)
93
+ 3. 401 responses include `resource_metadata="…"` so clients can discover the AS
94
+ 4. Scopes are available on `ctx.auth.scopes` and as SDK `authInfo` for `requireScopes`
95
+
96
+ ### `none`
97
+
98
+ Allowed on **localhost** binds, or with `MCP_AUTH_ALLOW_NONE=true` for intentional public read-only endpoints. Header-based upstream credentials are **refused** in this mode.
99
+
100
+ ## Request pipeline (HTTP)
101
+
102
+ 1. **`GET` health paths** (`MCP_HEALTH_PATH` + `MCP_HEALTH_ALIASES`) — no auth, **before** Host/Origin checks (Railway-friendly)
103
+ 2. **OAuth well-known** — no auth, before Host checks
104
+ 3. Host / Origin allowlist → **403**
105
+ 4. MCP path; non-POST → **405** (unless session methods enabled)
106
+ 5. Auth → **401**
107
+ 6. Stateless `createMcpHandler` (body size limit applied)
108
+
109
+ Graceful **SIGTERM** / **SIGINT** shutdown is wired by `startMcpServer`.
110
+
111
+ ### Health body hook
112
+
113
+ Default body is `{ "status": "ok" }`. Pass `getHealthInfo` to add fields (version, tool count, …). `status: "ok"` always wins:
114
+
115
+ ```ts
116
+ await startMcpServer({
117
+ createServer,
118
+ config,
119
+ getHealthInfo: () => ({ version: '1.2.3', tools: 12 }),
120
+ });
121
+ // → { "version": "1.2.3", "tools": 12, "status": "ok" }
122
+ ```
123
+
124
+ Aliases via env or `loadConfig(..., { healthPathAliases: ['/healthz'] })`.
125
+
126
+ ## Framework-agnostic core
127
+
128
+ ```ts
129
+ import { createMcpHttpApp, handleWithKitApp, createNodeHttpServer } from '@kieksme/mcp-http-kit';
130
+
131
+ const app = createMcpHttpApp({ createServer, config });
132
+ // Web Fetch API: app.fetch(request)
133
+ // Node: createNodeHttpServer({ app, config })
134
+ // Fastify/Express: handleWithKitApp(app, req.raw, reply.raw, config, req.body)
135
+ ```
136
+
137
+ ## Upstream credentials (per request)
138
+
139
+ ```ts
140
+ import { resolveUpstreamCredential } from '@kieksme/mcp-http-kit';
141
+
142
+ const { value } = resolveUpstreamCredential(ctx.headers, {
143
+ mode: 'header', // or 'env'
144
+ envVar: 'CONTABO_API_KEY',
145
+ headerName: 'X-Contabo-Api-Key',
146
+ authMode: ctx.auth.mode,
147
+ });
148
+ ```
149
+
150
+ ## Test helpers
151
+
152
+ ```ts
153
+ import { createAppTestClient, createKitContractAssertions } from '@kieksme/mcp-http-kit/test';
154
+ ```
155
+
156
+ Covered contracts: 401 without token, 403 bad Host/Origin, `/health` without auth (incl. bad Host), initialize + `tools/list`.
157
+
158
+ ## Railway deploy
159
+
160
+ 1. Copy `templates/Dockerfile` and `templates/railway.json`
161
+ 2. Set `MCP_TRANSPORT=http`, `MCP_HTTP_HOST=0.0.0.0`, `MCP_AUTH_TOKEN`, `MCP_ALLOWED_HOSTS=<your-public-host>`
162
+ 3. Railway sets `PORT`; the kit reads it first
163
+ 4. Healthcheck path is `/health` (no auth, Host-agnostic)
164
+
165
+ ## Registry / Smithery
166
+
167
+ - `templates/server.json` — official MCP registry (`io.github.kieksme/<name>`, npm + OCI + `remotes` streamable-http)
168
+ - `templates/smithery.yaml` — container runtime, HTTP start command
169
+
170
+ ## Public API
171
+
172
+ | Export | Purpose |
173
+ | --- | --- |
174
+ | `loadConfig` / `resolveHealthPaths` | Env → `KitConfig` (+ legacy aliases) |
175
+ | `startMcpServer` | stdio or HTTP lifecycle (`getHealthInfo` supported) |
176
+ | `createMcpHttpApp` | Web-standard `{ fetch, close }` |
177
+ | `createNodeHttpServer` / `createNodeRequestListener` | `node:http` |
178
+ | `handleWithKitApp` | Fastify/Express bridge |
179
+ | `authenticateRequest` / bearer + oauth helpers | Auth primitives |
180
+ | `resolveUpstreamCredential` | env \| header upstream keys |
181
+ | `@kieksme/mcp-http-kit/test` | Contract test helpers |
182
+
183
+ ### Git installs
184
+
185
+ `prepare` builds `dist/` when it is missing (typical for `github:` / git URL deps). Published npm tarballs already include `dist`, so prepare is a no-op there.
186
+
187
+ ## Scripts
188
+
189
+ ```bash
190
+ pnpm lint
191
+ pnpm typecheck
192
+ pnpm test
193
+ pnpm build
194
+ ```
195
+
196
+ ## License
197
+
198
+ MIT © kieks.me
package/SECURITY.md ADDED
@@ -0,0 +1,19 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes are applied to the latest published release of `@kieksme/mcp-http-kit`.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Please open a private security advisory on
10
+ [GitHub](https://github.com/kieksme/mcp-http-kit/security/advisories/new)
11
+ or email the maintainers. Do not file public issues for undisclosed vulnerabilities.
12
+
13
+ ## Hardening notes
14
+
15
+ - Prefer `MCP_AUTH_MODE=bearer` or `oauth` for any non-localhost deployment.
16
+ - `MCP_AUTH_MODE=none` is refused unless the process binds to localhost or
17
+ `MCP_AUTH_ALLOW_NONE=true` is set explicitly (intended for public read-only demos).
18
+ - Keep `MCP_ALLOWED_HOSTS` / `MCP_ALLOWED_ORIGINS` tight when binding beyond loopback.
19
+ - Never commit bearer tokens or OAuth client secrets; use environment variables / Railway secrets.
@@ -0,0 +1,14 @@
1
+ import type { AuthContext } from '../types.js';
2
+ export type BearerAuthResult = {
3
+ ok: true;
4
+ context: AuthContext;
5
+ } | {
6
+ ok: false;
7
+ status: 401;
8
+ wwwAuthenticate: string;
9
+ body: string;
10
+ };
11
+ export declare function extractBearerToken(authorization: string | null | undefined): string | undefined;
12
+ /** Constant-time check of Authorization: Bearer against one or more tokens. */
13
+ export declare function verifyBearerToken(authorization: string | null | undefined, tokens: string[], realm: string): BearerAuthResult;
14
+ //# sourceMappingURL=bearer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../../src/auth/bearer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAatE,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAI/F;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACxC,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,GACZ,gBAAgB,CAuBlB"}
@@ -0,0 +1,43 @@
1
+ import { timingSafeEqual } from 'node:crypto';
2
+ function safeEqual(a, b) {
3
+ const left = Buffer.from(a);
4
+ const right = Buffer.from(b);
5
+ if (left.length !== right.length) {
6
+ // Still compare against something of equal length to reduce timing skew on length.
7
+ timingSafeEqual(left, left);
8
+ return false;
9
+ }
10
+ return timingSafeEqual(left, right);
11
+ }
12
+ export function extractBearerToken(authorization) {
13
+ if (!authorization)
14
+ return undefined;
15
+ const match = /^Bearer\s+(.+)$/i.exec(authorization.trim());
16
+ return match?.[1]?.trim() || undefined;
17
+ }
18
+ /** Constant-time check of Authorization: Bearer against one or more tokens. */
19
+ export function verifyBearerToken(authorization, tokens, realm) {
20
+ const token = extractBearerToken(authorization);
21
+ if (!token) {
22
+ return {
23
+ ok: false,
24
+ status: 401,
25
+ wwwAuthenticate: `Bearer realm="${realm}"`,
26
+ body: 'Unauthorized',
27
+ };
28
+ }
29
+ const accepted = tokens.some(expected => safeEqual(token, expected));
30
+ if (!accepted) {
31
+ return {
32
+ ok: false,
33
+ status: 401,
34
+ wwwAuthenticate: `Bearer realm="${realm}", error="invalid_token"`,
35
+ body: 'Unauthorized',
36
+ };
37
+ }
38
+ return {
39
+ ok: true,
40
+ context: { mode: 'bearer', token, scopes: [] },
41
+ };
42
+ }
43
+ //# sourceMappingURL=bearer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer.js","sourceRoot":"","sources":["../../src/auth/bearer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAO9C,SAAS,SAAS,CAAC,CAAS,EAAE,CAAS;IACrC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;QACjC,mFAAmF;QACnF,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,aAAwC;IACzE,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IACrC,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5D,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;AACzC,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,iBAAiB,CAC/B,aAAwC,EACxC,MAAgB,EAChB,KAAa;IAEb,MAAM,KAAK,GAAG,kBAAkB,CAAC,aAAa,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,eAAe,EAAE,iBAAiB,KAAK,GAAG;YAC1C,IAAI,EAAE,cAAc;SACrB,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;IACrE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,eAAe,EAAE,iBAAiB,KAAK,0BAA0B;YACjE,IAAI,EAAE,cAAc;SACrB,CAAC;IACJ,CAAC;IACD,OAAO;QACL,EAAE,EAAE,IAAI;QACR,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;KAC/C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { AuthInfo } from '@modelcontextprotocol/server';
2
+ import type { AuthContext, KitConfig } from '../types.js';
3
+ /** Map kit auth into the SDK `AuthInfo` pass-through (scopes reach tool handlers). */
4
+ export declare function toAuthInfo(auth: AuthContext, config: KitConfig): AuthInfo | undefined;
5
+ /** Recover kit `AuthContext` from SDK `AuthInfo` (or fall back to none). */
6
+ export declare function fromAuthInfo(authInfo: AuthInfo | undefined): AuthContext;
7
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/auth/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAY,SAAS,EAAE,MAAM,aAAa,CAAC;AAKpE,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS,CAsBrF;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,CAkBxE"}
@@ -0,0 +1,46 @@
1
+ import { resourceMetadataUrl } from './oauth.js';
2
+ const KIT_AUTH_EXTRA_KEY = 'kieksme.auth';
3
+ /** Map kit auth into the SDK `AuthInfo` pass-through (scopes reach tool handlers). */
4
+ export function toAuthInfo(auth, config) {
5
+ if (auth.mode === 'none')
6
+ return undefined;
7
+ const token = auth.token ?? '';
8
+ const info = {
9
+ token,
10
+ clientId: auth.subject ?? auth.mode,
11
+ scopes: auth.scopes,
12
+ extra: {
13
+ [KIT_AUTH_EXTRA_KEY]: {
14
+ mode: auth.mode,
15
+ subject: auth.subject,
16
+ claims: auth.claims,
17
+ },
18
+ },
19
+ };
20
+ if (config.auth.mode === 'oauth') {
21
+ info.resource = new URL(config.auth.resource);
22
+ info.resourceMetadataUrl = resourceMetadataUrl(config.auth.resource);
23
+ const exp = auth.claims?.exp;
24
+ if (typeof exp === 'number')
25
+ info.expiresAt = exp;
26
+ }
27
+ return info;
28
+ }
29
+ /** Recover kit `AuthContext` from SDK `AuthInfo` (or fall back to none). */
30
+ export function fromAuthInfo(authInfo) {
31
+ if (!authInfo) {
32
+ return { mode: 'none', scopes: [] };
33
+ }
34
+ const stamped = authInfo.extra?.[KIT_AUTH_EXTRA_KEY];
35
+ const subject = stamped?.subject ?? (authInfo.clientId !== 'bearer' && authInfo.clientId !== 'none'
36
+ ? authInfo.clientId
37
+ : undefined);
38
+ return {
39
+ mode: stamped?.mode ?? (authInfo.scopes.length > 0 ? 'oauth' : 'bearer'),
40
+ token: authInfo.token,
41
+ scopes: authInfo.scopes,
42
+ ...(subject ? { subject } : {}),
43
+ ...(stamped?.claims ? { claims: stamped.claims } : {}),
44
+ };
45
+ }
46
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/auth/context.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,kBAAkB,GAAG,cAAc,CAAC;AAE1C,sFAAsF;AACtF,MAAM,UAAU,UAAU,CAAC,IAAiB,EAAE,MAAiB;IAC7D,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,SAAS,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAa;QACrB,KAAK;QACL,QAAQ,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI;QACnC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE;YACL,CAAC,kBAAkB,CAAC,EAAE;gBACpB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB;SACF;KACF,CAAC;IACF,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;QAC7B,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,QAA8B;IACzD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACtC,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,kBAAkB,CAEtC,CAAC;IACd,MAAM,OAAO,GACX,OAAO,EAAE,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,QAAQ,KAAK,MAAM;QACjF,CAAC,CAAC,QAAQ,CAAC,QAAQ;QACnB,CAAC,CAAC,SAAS,CAAC,CAAC;IACjB,OAAO;QACL,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;QACxE,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/B,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { AuthConfig, AuthContext } from '../types.js';
2
+ export type AuthenticateResult = {
3
+ ok: true;
4
+ context: AuthContext;
5
+ } | {
6
+ ok: false;
7
+ status: 401;
8
+ wwwAuthenticate: string;
9
+ body: string;
10
+ contentType?: string;
11
+ };
12
+ export type AuthenticateOptions = {
13
+ auth: AuthConfig;
14
+ realm: string;
15
+ authorizationHeader: string | null | undefined;
16
+ };
17
+ /**
18
+ * Authenticate an HTTP request according to kit auth mode.
19
+ * Callers should skip this for `/health` and well-known metadata routes.
20
+ */
21
+ export declare function authenticateRequest(options: AuthenticateOptions): Promise<AuthenticateResult>;
22
+ export declare function unauthorizedWwwAuthenticate(auth: AuthConfig, realm: string): string;
23
+ export { buildProtectedResourceMetadata, clearOAuthJwksCache, oauthWwwAuthenticate, resourceMetadataUrl, verifyOAuthAccessToken, } from './oauth.js';
24
+ export { extractBearerToken, verifyBearerToken } from './bearer.js';
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI3D,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5F,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,mBAAmB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAChD,CAAC;AAEF;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAsBnG;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnF;AAED,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { verifyBearerToken } from './bearer.js';
2
+ import { oauthWwwAuthenticate, verifyOAuthAccessToken } from './oauth.js';
3
+ /**
4
+ * Authenticate an HTTP request according to kit auth mode.
5
+ * Callers should skip this for `/health` and well-known metadata routes.
6
+ */
7
+ export async function authenticateRequest(options) {
8
+ const { auth, realm, authorizationHeader } = options;
9
+ if (auth.mode === 'none') {
10
+ return { ok: true, context: { mode: 'none', scopes: [] } };
11
+ }
12
+ if (auth.mode === 'bearer') {
13
+ return verifyBearerToken(authorizationHeader, auth.tokens, realm);
14
+ }
15
+ const result = await verifyOAuthAccessToken(authorizationHeader, auth);
16
+ if (!result.ok) {
17
+ return {
18
+ ok: false,
19
+ status: result.status,
20
+ wwwAuthenticate: result.wwwAuthenticate,
21
+ body: result.body,
22
+ contentType: 'application/json',
23
+ };
24
+ }
25
+ return result;
26
+ }
27
+ export function unauthorizedWwwAuthenticate(auth, realm) {
28
+ if (auth.mode === 'oauth')
29
+ return oauthWwwAuthenticate(auth, 'invalid_token', 'Authentication required');
30
+ return `Bearer realm="${realm}"`;
31
+ }
32
+ export { buildProtectedResourceMetadata, clearOAuthJwksCache, oauthWwwAuthenticate, resourceMetadataUrl, verifyOAuthAccessToken, } from './oauth.js';
33
+ export { extractBearerToken, verifyBearerToken } from './bearer.js';
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAY1E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAA4B;IACpE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC;IAErD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC;IAC7D,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC3B,OAAO,iBAAiB,CAAC,mBAAmB,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,sBAAsB,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC;IACvE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,eAAe,EAAE,MAAM,CAAC,eAAe;YACvC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,WAAW,EAAE,kBAAkB;SAChC,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,IAAgB,EAAE,KAAa;IACzE,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,oBAAoB,CAAC,IAAI,EAAE,eAAe,EAAE,yBAAyB,CAAC,CAAC;IACzG,OAAO,iBAAiB,KAAK,GAAG,CAAC;AACnC,CAAC;AAED,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { AuthContext, OAuthAuthConfig } from '../types.js';
2
+ export type OAuthAuthResult = {
3
+ ok: true;
4
+ context: AuthContext;
5
+ } | {
6
+ ok: false;
7
+ status: 401;
8
+ wwwAuthenticate: string;
9
+ body: string;
10
+ };
11
+ export type ProtectedResourceMetadata = {
12
+ resource: string;
13
+ authorization_servers: string[];
14
+ scopes_supported?: string[];
15
+ bearer_methods_supported: string[];
16
+ };
17
+ declare function resourceMetadataUrl(resource: string): string;
18
+ export declare function buildProtectedResourceMetadata(config: OAuthAuthConfig): ProtectedResourceMetadata;
19
+ export declare function oauthWwwAuthenticate(config: OAuthAuthConfig, error?: string, description?: string): string;
20
+ /** Validate a Bearer access token as an OAuth 2.1 resource server (JWT + JWKS). */
21
+ export declare function verifyOAuthAccessToken(authorization: string | null | undefined, config: OAuthAuthConfig): Promise<OAuthAuthResult>;
22
+ /** Clear JWKS cache (tests). */
23
+ export declare function clearOAuthJwksCache(): void;
24
+ export { resourceMetadataUrl };
25
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,wBAAwB,EAAE,MAAM,EAAE,CAAC;CACpC,CAAC;AAMF,iBAAS,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,eAAe,GAAG,yBAAyB,CAOjG;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,eAAe,EACvB,KAAK,CAAC,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAMR;AAqFD,mFAAmF;AACnF,wBAAsB,sBAAsB,CAC1C,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACxC,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,eAAe,CAAC,CA0D1B;AAED,gCAAgC;AAChC,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
@@ -0,0 +1,161 @@
1
+ import { createRemoteJWKSet, jwtVerify, } from 'jose';
2
+ const jwksCache = new Map();
3
+ function resourceMetadataUrl(resource) {
4
+ const url = new URL(resource);
5
+ // RFC 9728 path-aware well-known: insert /.well-known/oauth-protected-resource before path
6
+ const path = url.pathname === '/' ? '' : url.pathname.replace(/\/$/, '');
7
+ return `${url.origin}/.well-known/oauth-protected-resource${path}`;
8
+ }
9
+ export function buildProtectedResourceMetadata(config) {
10
+ return {
11
+ resource: config.resource,
12
+ authorization_servers: config.authorizationServers,
13
+ ...(config.scopesSupported.length > 0 ? { scopes_supported: config.scopesSupported } : {}),
14
+ bearer_methods_supported: ['header'],
15
+ };
16
+ }
17
+ export function oauthWwwAuthenticate(config, error, description) {
18
+ const metadata = resourceMetadataUrl(config.resource);
19
+ const parts = [`Bearer resource_metadata="${metadata}"`];
20
+ if (error)
21
+ parts.push(`error="${error}"`);
22
+ if (description)
23
+ parts.push(`error_description="${description.replace(/"/g, "'")}"`);
24
+ return parts.join(', ');
25
+ }
26
+ function parseScopes(payload) {
27
+ const scopeClaim = payload.scope;
28
+ if (typeof scopeClaim === 'string') {
29
+ return scopeClaim.split(/\s+/).filter(Boolean);
30
+ }
31
+ const scp = payload.scp;
32
+ if (Array.isArray(scp)) {
33
+ return scp.filter((s) => typeof s === 'string');
34
+ }
35
+ return [];
36
+ }
37
+ function audiencesMatch(payload, expected) {
38
+ const aud = payload.aud;
39
+ const values = Array.isArray(aud) ? aud : aud ? [aud] : [];
40
+ // Also accept `resource` / `rsc` style claims used by some AS implementations.
41
+ const resourceClaim = payload.resource ?? payload.rsc;
42
+ if (typeof resourceClaim === 'string')
43
+ values.push(resourceClaim);
44
+ if (Array.isArray(resourceClaim)) {
45
+ for (const entry of resourceClaim) {
46
+ if (typeof entry === 'string')
47
+ values.push(entry);
48
+ }
49
+ }
50
+ return expected.some(want => values.includes(want));
51
+ }
52
+ async function resolveJwks(config) {
53
+ if (config.jwksUrl) {
54
+ const cached = jwksCache.get(config.jwksUrl);
55
+ if (cached)
56
+ return cached.getKey;
57
+ const getKey = createRemoteJWKSet(new URL(config.jwksUrl));
58
+ jwksCache.set(config.jwksUrl, { url: config.jwksUrl, getKey });
59
+ return getKey;
60
+ }
61
+ const issuer = config.authorizationServers[0];
62
+ if (!issuer) {
63
+ throw new Error('OAuth config missing authorizationServers');
64
+ }
65
+ const issuerUrl = issuer.endsWith('/') ? issuer : `${issuer}/`;
66
+ // Prefer OIDC discovery, then OAuth AS metadata.
67
+ const candidates = [
68
+ new URL('.well-known/openid-configuration', issuerUrl).href,
69
+ new URL('.well-known/oauth-authorization-server', issuerUrl).href,
70
+ ];
71
+ for (const metadataUrl of candidates) {
72
+ try {
73
+ const response = await fetch(metadataUrl, {
74
+ headers: { accept: 'application/json' },
75
+ signal: AbortSignal.timeout(10_000),
76
+ });
77
+ if (!response.ok)
78
+ continue;
79
+ const body = (await response.json());
80
+ if (!body.jwks_uri)
81
+ continue;
82
+ const cached = jwksCache.get(body.jwks_uri);
83
+ if (cached)
84
+ return cached.getKey;
85
+ const getKey = createRemoteJWKSet(new URL(body.jwks_uri));
86
+ jwksCache.set(body.jwks_uri, { url: body.jwks_uri, getKey });
87
+ return getKey;
88
+ }
89
+ catch {
90
+ // try next candidate
91
+ }
92
+ }
93
+ throw new Error(`Unable to resolve JWKS for authorization server ${issuer}; set MCP_OAUTH_JWKS_URL`);
94
+ }
95
+ function unauthorized(config, error, description) {
96
+ return {
97
+ ok: false,
98
+ status: 401,
99
+ wwwAuthenticate: oauthWwwAuthenticate(config, error, description),
100
+ body: JSON.stringify({ error, error_description: description }),
101
+ };
102
+ }
103
+ /** Validate a Bearer access token as an OAuth 2.1 resource server (JWT + JWKS). */
104
+ export async function verifyOAuthAccessToken(authorization, config) {
105
+ const header = authorization?.trim();
106
+ if (!header?.toLowerCase().startsWith('bearer ')) {
107
+ return unauthorized(config, 'invalid_token', 'Authentication required');
108
+ }
109
+ const token = header.slice(7).trim();
110
+ if (!token) {
111
+ return unauthorized(config, 'invalid_token', 'Authentication required');
112
+ }
113
+ let payload;
114
+ try {
115
+ const getKey = await resolveJwks(config);
116
+ const verified = await jwtVerify(token, getKey, {
117
+ issuer: config.authorizationServers,
118
+ clockTolerance: 30,
119
+ });
120
+ payload = verified.payload;
121
+ }
122
+ catch (error) {
123
+ const message = error instanceof Error ? error.message : 'Token validation failed';
124
+ return unauthorized(config, 'invalid_token', message);
125
+ }
126
+ if (!audiencesMatch(payload, config.audiences)) {
127
+ return unauthorized(config, 'invalid_token', 'Token audience does not match this resource');
128
+ }
129
+ const scopes = parseScopes(payload);
130
+ if (config.requiredScopes.length > 0) {
131
+ const missing = config.requiredScopes.filter(scope => !scopes.includes(scope));
132
+ if (missing.length > 0) {
133
+ return {
134
+ ok: false,
135
+ status: 401,
136
+ wwwAuthenticate: oauthWwwAuthenticate(config, 'insufficient_scope', `Missing scopes: ${missing.join(' ')}`),
137
+ body: JSON.stringify({
138
+ error: 'insufficient_scope',
139
+ error_description: `Missing scopes: ${missing.join(' ')}`,
140
+ }),
141
+ };
142
+ }
143
+ }
144
+ const subject = typeof payload.sub === 'string' ? payload.sub : undefined;
145
+ return {
146
+ ok: true,
147
+ context: {
148
+ mode: 'oauth',
149
+ token,
150
+ scopes,
151
+ claims: payload,
152
+ ...(subject ? { subject } : {}),
153
+ },
154
+ };
155
+ }
156
+ /** Clear JWKS cache (tests). */
157
+ export function clearOAuthJwksCache() {
158
+ jwksCache.clear();
159
+ }
160
+ export { resourceMetadataUrl };
161
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}