@mcpfn/testing 0.0.4 → 0.0.6

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.
package/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # McpFn Testing
2
2
 
3
- `@mcpfn/testing` provides deterministic MCP regression testing over the official SDK's in-memory transport. It tests the protocol boundary rather than calling handlers directly.
3
+ `@mcpfn/testing` provides deterministic MCP regression testing over the official SDK's in-memory transport and against arbitrary stdio or Streamable HTTP MCP servers. It tests the protocol boundary rather than calling handlers directly; the server under test does not need to use McpFn.
4
4
 
5
5
  It includes:
6
6
 
7
7
  - the production McpFn client exposed as a fixture;
8
8
  - exact manifest assertions across tools, resources, templates, and prompts;
9
+ - deterministic generic and authenticated client-profile catalogs, fixtures, and snapshots;
9
10
  - resource, prompt, completion, subscription, and task client methods;
10
11
  - reusable API-key and OAuth resource-server regression matrices;
11
12
  - an in-memory authorization-code, PKCE, refresh, and revocation server;
@@ -18,9 +19,16 @@ It includes:
18
19
  - version 1 declarative scenarios for capabilities, tasks, events, and auth phases;
19
20
  - per-scenario timeout/cancellation, side-effect and incomplete metadata;
20
21
  - bounded, redacted scenario and target-suite reports;
22
+ - JSON and JUnit artifacts with package/runtime provenance and failure layers;
21
23
  - orchestration of the official `@modelcontextprotocol/conformance` runner.
22
24
 
23
- Official conformance validates protocol behavior. McpFn scenarios validate product behavior. Production MCP servers should run both. For a protected local endpoint, use `runAuthenticatedOfficialConformance({ url, headers })`; it requires a literal loopback upstream, binds a temporary loopback-only streaming proxy, pins every request to the configured upstream path, injects the configured headers without printing them, and always closes the proxy after the pinned official runner exits.
25
+ Official conformance validates protocol behavior. McpFn scenarios validate product behavior. Production MCP servers should run both. For a protected local endpoint, use `runAuthenticatedOfficialConformance({ url, credential })`; it requires a literal loopback upstream, binds a temporary loopback-only streaming proxy, pins every request to the configured upstream path, injects bounded credential headers without printing them, and closes the proxy and attempts credential revocation/disposal after the pinned official runner exits. Credential cleanup retries up to three times. If all attempts fail, it throws `McpFnConformanceCleanupError`; retain that error and call `await error.retryCleanup()` after the provider recovers. The error contains no raw credential fields, and a successful retry releases the retained lease.
26
+
27
+ Deterministic client-profile compatibility is a third, separate gate. It proves
28
+ the effective authenticated catalog, schema portability, trusted enrichment,
29
+ canonical validation, handler reachability, and structured error fidelity. It
30
+ does not replace protocol conformance or product scenarios and does not emulate
31
+ a proprietary hosted model.
24
32
 
25
33
  Use `runMcpFnTargetSuite({ target, scenarios, manifest })` when a test should
26
34
  exercise a subprocess or deployed target. It constructs the same session used
@@ -28,6 +36,7 @@ by applications, the inspector, and CLI. Scenario execution is serial and
28
36
  capability calls are never retried implicitly.
29
37
 
30
38
  ```ts
39
+ import { writeFile } from "node:fs/promises";
31
40
  import {
32
41
  McpFnTestClient,
33
42
  assertManifestContract,
@@ -54,6 +63,127 @@ try {
54
63
  }
55
64
  ```
56
65
 
66
+ ## Client-profile contract suite
67
+
68
+ `runMcpFnClientProfileContracts()` accepts one case per generic or configured
69
+ profile. Each case supplies a production `McpFnTarget`, client initialization
70
+ metadata/capabilities, an optional reviewed snapshot, and explicit fixtures.
71
+ This supports in-memory, stdio, authenticated Streamable HTTP, and custom
72
+ targets without a test-only server adapter.
73
+
74
+ ```ts
75
+ import {
76
+ createMcpFnClientProfileSnapshot,
77
+ runMcpFnClientProfileContracts,
78
+ } from "@mcpfn/testing";
79
+
80
+ const report = await runMcpFnClientProfileContracts({
81
+ profiles: [{
82
+ id: "generic",
83
+ version: "canonical",
84
+ target: genericTarget,
85
+ }, {
86
+ id: "consumer/trusted",
87
+ version: "1",
88
+ target: authenticatedTarget,
89
+ clientInfo: { name: "consumer", version: "2.0.0" },
90
+ capabilities: { roots: { listChanged: true } },
91
+ expectedSnapshot: reviewedSnapshot,
92
+ fixtures: [{
93
+ name: "captured unknown property",
94
+ tool: "lookup",
95
+ arguments: capturedAndRedactedArguments,
96
+ sideEffect: "read-only",
97
+ source: "captured-failure",
98
+ expect: {
99
+ isError: true,
100
+ errorCode: "MCPFN_INVALID_ARGUMENTS",
101
+ lifecycleStage: "input-validation",
102
+ validationIssue: {
103
+ keyword: "additionalProperties",
104
+ rejectedProperty: "unexpectedField",
105
+ },
106
+ },
107
+ }],
108
+ }],
109
+ });
110
+ ```
111
+
112
+ Fixtures are executed through `McpFnTestClient.connectTarget()`, the same
113
+ production session engine used by applications, inspector, and CLI. Fixture
114
+ argument values never appear in reports. `read-only` fixtures run by default;
115
+ `idempotent` and `non-idempotent` fixtures require explicit suite or CLI
116
+ authorization.
117
+
118
+ The profile fixture runner supports ordinary tool calls. After reading the
119
+ effective catalog for each profile, it rejects a configuration containing a
120
+ fixture for a task-required tool before executing that profile's fixtures,
121
+ then closes its target. Earlier profiles may already have run. Use the task
122
+ scenario APIs for task execution and terminal-result assertions.
123
+
124
+ `createMcpFnClientProfileSnapshot()`,
125
+ `validateMcpFnClientProfileSnapshot()`, and
126
+ `diffMcpFnClientProfileSnapshots()` create reviewable effective-catalog
127
+ baselines. A diff rejects contradictory aggregate and per-tool hashes in
128
+ either direction; it does not recompute an aggregate from the stored tool hashes.
129
+ Portability validation compiles each schema using its declared
130
+ draft-07, 2019-09, or 2020-12 dialect and recursively reports reviewed
131
+ compatibility-sensitive keywords. Invalid schemas, dialects, and references
132
+ are errors; valid compatibility reductions are warnings unless policy promotes
133
+ them to errors.
134
+
135
+ ## External authenticated targets
136
+
137
+ `authenticatedHttpTarget()` accepts a URL plus either a static credential or an
138
+ application-owned provider. The provider is responsible for acquiring the
139
+ credential and may revoke and dispose it. McpFn applies the headers only to the
140
+ fixed target, refuses redirect following, excludes credentials from target
141
+ descriptors and reports, and releases the credential exactly once even when
142
+ initialization fails.
143
+
144
+ ```ts
145
+ import { writeFile } from "node:fs/promises";
146
+ import {
147
+ authenticatedHttpTarget,
148
+ createMcpFnTargetSuiteJUnit,
149
+ disposeMcpFnTargetSuiteReport,
150
+ runMcpFnTargetSuite,
151
+ serializeMcpFnTargetSuiteReport,
152
+ } from "@mcpfn/testing";
153
+
154
+ const report = await runMcpFnTargetSuite({
155
+ target: authenticatedHttpTarget("https://mcp.example.com/mcp", {
156
+ credential: {
157
+ kind: "api-key",
158
+ headers: { "x-api-key": process.env.MCP_API_KEY! },
159
+ },
160
+ }),
161
+ scenarios,
162
+ });
163
+
164
+ try {
165
+ await writeFile(
166
+ "mcpfn-report.json",
167
+ serializeMcpFnTargetSuiteReport(report, { space: 2, trailingNewline: true }),
168
+ );
169
+ await writeFile("mcpfn-report.xml", createMcpFnTargetSuiteJUnit(report));
170
+ } finally {
171
+ disposeMcpFnTargetSuiteReport(report);
172
+ }
173
+ ```
174
+
175
+ Generated target-suite reports retain target-aware proof for deferred JSON and
176
+ JUnit composition. Use the serializers above for every required encoding, then
177
+ dispose the report in `finally`. A finalizer is only a fallback. Copies retain a
178
+ proof-required marker and fail closed because copied values cannot retain the
179
+ live credential validator. Every exact encoding must also fit the report's
180
+ original `maxReportBytes` cap; pretty JSON that exceeds it is rejected.
181
+
182
+ Use a provider instead of a static credential for short-lived OAuth access
183
+ tokens. Report failures identify `mcpfn-preflight`, `authorization-server`,
184
+ `resource-server`, `mcp-initialization`, `scenario`, or
185
+ `upstream-conformance` without serializing secret material.
186
+
57
187
  Scenarios run serially so stateful workflows and idempotency checks remain
58
188
  deterministic. Legacy arrays are readable; portable artifacts use
59
189
  `{ formatVersion: 1, kind: "mcpfn.scenarios", status, scenarios }`. A runner
@@ -98,6 +228,16 @@ OAuth adapters can additionally enable scope, expiry, resource-binding, and revo
98
228
 
99
229
  `createOAuthClientMetadataVariants()` returns authorization-code clients with basic, JWT-bearer-extension, device-code-extension, and generic-extension metadata. The compatibility assertion requires authorization-code support while deliberately accepting unrelated grants, which catches closed-world Client ID Metadata validation regressions.
100
230
 
231
+ `createHostedAuthorizationFixtures()` keeps registration metadata independent
232
+ from the generated authorization request. The ChatGPT-shaped pre-registration,
233
+ Claude-shaped Client ID Metadata Document, and dynamic-registration cases cover
234
+ authorization code with S256 PKCE and refresh. Advertised JWT bearer, device,
235
+ and custom grants remain compatible when authorization code is supported, while
236
+ an actual unsupported token request must return `unsupported_grant_type`.
237
+ Allowed fixtures exchange the code returned by the authorization server. Set
238
+ the optional token-request `code` only to exercise an independently authored
239
+ negative case such as an expired or unknown authorization code.
240
+
101
241
  ## Playwright fixture
102
242
 
103
243
  Install `@playwright/test` and import the ready-to-extend fixture from `@mcpfn/testing/playwright`:
@@ -120,3 +260,39 @@ test("accepts extensible OAuth client metadata", async ({ page, mcpfnOAuth }) =>
120
260
  The fixture starts a local mock server that publishes authorization-server discovery, consent UI, callback capture, client metadata variants, PKCE token exchange, refresh rotation, revocation, and an SDK-compatible access-token verifier. Extend the exported `test` with Skillplane's signed-in page or database fixtures; do not copy the OAuth machinery into the application.
121
261
 
122
262
  See [Testing and CI](https://github.com/21nCo/super-functions/blob/main/mcpfn/TESTING.md) for the complete layered strategy.
263
+
264
+ Client-profile lifecycle evidence uses `invalid-arguments-handler` when a registered invalid-argument fallback runs. This stage does not imply that the tool handler ran; normal tool execution uses `handler`. Projected schemas select their declared JSON Schema dialect, defaulting to draft 7 to match canonical registry validation. Named `$anchor` definitions are portability-sensitive for older clients.
265
+
266
+ Projected catalogs support local JSON Pointer and named-anchor references. Dynamic and recursive reference forms fail closed until dialect-correct dynamic-scope comparison is supported. A recovered invalid-input call emits failed input-validation evidence followed by the invalid-argument fallback stage.
267
+
268
+ When credentials come from environment variables, pass their names in `sensitiveEnvironmentVariables` to authenticated official conformance. Those names are removed case-insensitively before the upstream runner is spawned. The library cannot infer the source of arbitrary provider-returned headers.
269
+
270
+ Authenticated official conformance always captures stdout and stderr, including when
271
+ `stdio: "inherit"` is requested, so credential values can be removed before output
272
+ is returned. `outputDir` is rejected before credentials are acquired because the
273
+ upstream runner writes raw artifacts directly. Persist the returned redacted result
274
+ if an authenticated run needs an artifact. Credential cleanup may be retried after
275
+ failure; successful revoke/dispose steps are not repeated.
276
+
277
+ Authenticated targets provide credential-aware redaction to client diagnostic/event listeners and inspector snapshots and exports while their credentials are active. Export raw operation results before closing the client; application-facing protocol return values retain their original contents. Recorded inspector events are scrubbed before storage.
278
+
279
+ When authenticated conformance cleanup exhausts retries, `McpFnConformanceCleanupError.result` retains the original runner stdout, stderr, exit code (or 1 for an otherwise successful run), and failure. A separate `cleanupFailure` records cleanup exhaustion; the overall result is failed. The CLI persists this redacted result before exiting nonzero.
280
+
281
+
282
+ ### Retrying failed suite cleanup
283
+
284
+ If final session cleanup fails, `runMcpFnTargetSuite` rejects with
285
+ `McpFnTargetSuiteCleanupError`. Its `report` contains the bounded, redacted failed
286
+ snapshot. Retain the error and call `await error.retryCleanup()` to retry the
287
+ owned session cleanup. Concurrent retries share one operation; successful cleanup
288
+ releases ownership and later retries do nothing. A failed retry rejects with the
289
+ same safe error. Retrying does not rewrite the historical report as passing.
290
+ The CLI makes one cleanup retry and emits the failed report with exit code 1.
291
+
292
+ Direct `McpFnTestClient.connectTarget()` callers receive
293
+ `McpFnTestClientCleanupError` when connection failure is followed by cleanup
294
+ failure, and every connected client's public `close()` returns the same owner
295
+ contract if final cleanup fails. Retain that error and call
296
+ `await error.retryCleanup()`; the failed session remains owned until a retry
297
+ succeeds. Failed-connect cleanup preserves the original connection failure as
298
+ its `cause`; post-connect cleanup preserves the close failure.
package/dist/auth.cjs CHANGED
@@ -348,7 +348,7 @@ function uniqueScopes(value) {
348
348
  function json(status, body) {
349
349
  return Response.json(body, {
350
350
  status,
351
- headers: { "cache-control": "no-store" }
351
+ headers: { "cache-control": "no-store", pragma: "no-cache" }
352
352
  });
353
353
  }
354
354
  function oauthError(status, error, description) {
package/dist/auth.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth.ts","../src/auth-core.ts","../src/mock-oauth-server.ts"],"sourcesContent":["export * from \"./auth-core.js\";\nexport * from \"./mock-oauth-server.js\";\n","export type McpFnAuthCredentialKind = \"api-key\" | \"oauth\";\n\nexport interface McpFnAuthCredential {\n kind: McpFnAuthCredentialKind;\n headers: HeadersInit;\n label?: string;\n dispose?(): void | Promise<void>;\n}\n\nexport interface McpFnAuthCredentialRequest {\n kind: McpFnAuthCredentialKind;\n scopes?: string[];\n resource?: string;\n expiresInSeconds?: number;\n}\n\nexport interface McpFnAuthProviderCapabilities {\n expiration?: boolean;\n resourceBinding?: boolean;\n revocation?: boolean;\n scopes?: boolean;\n}\n\n/**\n * The application-owned seam. Skillplane only needs to map this interface to\n * its existing API-key or OAuth issuance and revocation helpers.\n */\nexport interface McpFnAuthProviderAdapter {\n capabilities?: McpFnAuthProviderCapabilities;\n issue(input: McpFnAuthCredentialRequest): Promise<McpFnAuthCredential>;\n revoke?(credential: McpFnAuthCredential): void | Promise<void>;\n}\n\nexport interface McpFnAuthProbeScenario {\n name:\n | \"missing-credential\"\n | \"invalid-credential\"\n | \"valid-credential\"\n | \"insufficient-scope\"\n | \"expired-credential\"\n | \"missing-resource\"\n | \"wrong-resource\"\n | \"revoked-credential\";\n kind: McpFnAuthCredentialKind;\n}\n\nexport interface McpFnAuthTarget {\n request(\n headers: Headers,\n scenario: McpFnAuthProbeScenario,\n ): Promise<Response>;\n}\n\nexport interface McpFnAuthRegressionOptions {\n kind: McpFnAuthCredentialKind;\n provider: McpFnAuthProviderAdapter;\n target: McpFnAuthTarget;\n requiredScopes?: string[];\n resource?: string;\n wrongResource?: string;\n invalidCredentialHeaders?: HeadersInit;\n /** OAuth failures are required to advertise protected-resource metadata. */\n protectedResourceMetadataUrl?: string;\n expectedAuthenticatedStatuses?: number[];\n verifyAuthenticatedResponse?(\n response: Response,\n credential: McpFnAuthCredential,\n ): void | Promise<void>;\n}\n\nexport interface McpFnAuthScenarioResult {\n name: McpFnAuthProbeScenario[\"name\"];\n status: \"passed\" | \"failed\";\n responseStatus?: number;\n error?: string;\n}\n\nexport interface McpFnBearerChallenge {\n scheme: string;\n parameters: Record<string, string>;\n}\n\nexport class McpFnAuthAssertionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"McpFnAuthAssertionError\";\n }\n}\n\nfunction expectedStatus(\n response: Response,\n expected: number | number[],\n name: string,\n): void {\n const statuses = Array.isArray(expected) ? expected : [expected];\n if (!statuses.includes(response.status)) {\n throw new McpFnAuthAssertionError(\n `${name} expected HTTP ${statuses.join(\" or \")}, received ${response.status}`,\n );\n }\n}\n\nexport function parseBearerChallenge(\n header: string | null,\n): McpFnBearerChallenge | undefined {\n if (!header) return undefined;\n const matches = [...header.matchAll(/(?:^|,)\\s*([A-Za-z][A-Za-z0-9_-]*)\\s+/g)];\n const selectedIndex = matches.findIndex((entry) => entry[1].toLowerCase() === \"bearer\");\n if (selectedIndex < 0) return undefined;\n const match = matches[selectedIndex];\n const start = (match.index ?? 0) + match[0].length;\n const end = matches[selectedIndex + 1]?.index ?? header.length;\n const parameters: Record<string, string> = {};\n const source = header.slice(start, end);\n const pattern = /([A-Za-z][A-Za-z0-9_-]*)\\s*=\\s*(?:\"((?:[^\"\\\\]|\\\\.)*)\"|([^,\\s]+))/g;\n for (const entry of source.matchAll(pattern)) {\n const quoted = entry[2]?.replace(/\\\\([\"\\\\])/g, \"$1\");\n parameters[entry[1]] = quoted ?? entry[3] ?? \"\";\n }\n return { scheme: match[1], parameters };\n}\n\nexport function assertBearerResourceChallenge(\n response: Response,\n expectedMetadataUrl?: string,\n): McpFnBearerChallenge {\n const challenge = parseBearerChallenge(response.headers.get(\"www-authenticate\"));\n if (!challenge || challenge.scheme.toLowerCase() !== \"bearer\") {\n throw new McpFnAuthAssertionError(\"Expected a Bearer WWW-Authenticate challenge\");\n }\n const metadataUrl = challenge.parameters.resource_metadata;\n if (!metadataUrl) {\n throw new McpFnAuthAssertionError(\n \"Bearer challenge is missing the resource_metadata parameter\",\n );\n }\n try {\n const parsed = new URL(metadataUrl);\n if (!parsed.protocol || !parsed.host) throw new Error(\"not absolute\");\n } catch {\n throw new McpFnAuthAssertionError(\n `Bearer challenge has an invalid resource_metadata URL: ${metadataUrl}`,\n );\n }\n if (expectedMetadataUrl && metadataUrl !== expectedMetadataUrl) {\n throw new McpFnAuthAssertionError(\n `Expected resource_metadata=${expectedMetadataUrl}, received ${metadataUrl}`,\n );\n }\n return challenge;\n}\n\nexport function bearerCredential(\n token: string,\n kind: McpFnAuthCredentialKind = \"oauth\",\n): McpFnAuthCredential {\n return {\n kind,\n headers: { authorization: `Bearer ${token}` },\n };\n}\n\nexport function apiKeyCredential(\n apiKey: string,\n options: { headerName?: string; scheme?: string } = {},\n): McpFnAuthCredential {\n const headerName = options.headerName ?? \"authorization\";\n const value = options.scheme === \"\"\n ? apiKey\n : `${options.scheme ?? \"Bearer\"} ${apiKey}`;\n return { kind: \"api-key\", headers: { [headerName]: value } };\n}\n\nfunction invalidHeaders(options: McpFnAuthRegressionOptions): Headers {\n return new Headers(options.invalidCredentialHeaders ?? {\n authorization: `Bearer mcpfn-invalid-${options.kind}`,\n });\n}\n\nasync function runOne(\n options: McpFnAuthRegressionOptions,\n scenario: McpFnAuthProbeScenario,\n prepare: () => Promise<McpFnAuthCredential | undefined>,\n expected: number | number[],\n): Promise<McpFnAuthScenarioResult> {\n let credential: McpFnAuthCredential | undefined;\n try {\n credential = await prepare();\n const headers = credential\n ? new Headers(credential.headers)\n : scenario.name === \"invalid-credential\"\n ? invalidHeaders(options)\n : new Headers();\n const response = await options.target.request(headers, scenario);\n expectedStatus(response, expected, scenario.name);\n if (\n options.kind === \"oauth\" &&\n scenario.name !== \"valid-credential\"\n ) {\n assertBearerResourceChallenge(\n response,\n options.protectedResourceMetadataUrl,\n );\n }\n if (scenario.name === \"valid-credential\" && credential) {\n await options.verifyAuthenticatedResponse?.(response.clone(), credential);\n }\n return {\n name: scenario.name,\n status: \"passed\",\n responseStatus: response.status,\n };\n } catch (error) {\n return {\n name: scenario.name,\n status: \"failed\",\n error: error instanceof Error ? error.message : String(error),\n };\n } finally {\n await credential?.dispose?.();\n }\n}\n\n/** Runs the common API-key or OAuth resource-server regression matrix. */\nexport async function runAuthRegressionSuite(\n options: McpFnAuthRegressionOptions,\n): Promise<McpFnAuthScenarioResult[]> {\n const issue = (overrides: Partial<McpFnAuthCredentialRequest> = {}) =>\n options.provider.issue({\n kind: options.kind,\n scopes: options.requiredScopes,\n resource: options.resource,\n ...overrides,\n });\n const results: McpFnAuthScenarioResult[] = [];\n results.push(await runOne(\n options,\n { name: \"missing-credential\", kind: options.kind },\n async () => undefined,\n 401,\n ));\n results.push(await runOne(\n options,\n { name: \"invalid-credential\", kind: options.kind },\n async () => undefined,\n 401,\n ));\n results.push(await runOne(\n options,\n { name: \"valid-credential\", kind: options.kind },\n () => issue(),\n options.expectedAuthenticatedStatuses ?? [200, 201, 202, 204],\n ));\n\n const capabilities = options.provider.capabilities ?? {};\n if (capabilities.scopes && options.requiredScopes?.length) {\n results.push(await runOne(\n options,\n { name: \"insufficient-scope\", kind: options.kind },\n () => issue({ scopes: [] }),\n 403,\n ));\n }\n if (capabilities.expiration) {\n results.push(await runOne(\n options,\n { name: \"expired-credential\", kind: options.kind },\n () => issue({ expiresInSeconds: -60 }),\n 401,\n ));\n }\n if (capabilities.resourceBinding && options.resource) {\n results.push(await runOne(\n options,\n { name: \"missing-resource\", kind: options.kind },\n () => issue({ resource: undefined }),\n 401,\n ));\n }\n if (capabilities.resourceBinding && options.wrongResource) {\n results.push(await runOne(\n options,\n { name: \"wrong-resource\", kind: options.kind },\n () => issue({ resource: options.wrongResource }),\n 401,\n ));\n }\n if (capabilities.revocation && options.provider.revoke) {\n results.push(await runOne(\n options,\n { name: \"revoked-credential\", kind: options.kind },\n async () => {\n const credential = await issue();\n await options.provider.revoke?.(credential);\n return credential;\n },\n 401,\n ));\n }\n return results;\n}\n\nexport async function assertAuthRegressionSuite(\n options: McpFnAuthRegressionOptions,\n): Promise<McpFnAuthScenarioResult[]> {\n const results = await runAuthRegressionSuite(options);\n const failures = results.filter((result) => result.status === \"failed\");\n if (failures.length) {\n throw new McpFnAuthAssertionError(\n failures.map((failure) => `${failure.name}: ${failure.error}`).join(\"\\n\"),\n );\n }\n return results;\n}\n\nexport interface McpFnFetchAuthTargetOptions {\n url: string | URL;\n fetch?: typeof globalThis.fetch;\n requestInit?: RequestInit | ((scenario: McpFnAuthProbeScenario) => RequestInit);\n}\n\n/** Creates a protected MCP initialize probe suitable for local or deployed endpoints. */\nexport function createFetchAuthTarget(\n options: McpFnFetchAuthTargetOptions,\n): McpFnAuthTarget {\n const fetchImplementation = options.fetch ?? globalThis.fetch;\n if (!fetchImplementation) {\n throw new Error(\"A fetch implementation is required\");\n }\n return {\n async request(authHeaders, scenario) {\n const configured = typeof options.requestInit === \"function\"\n ? options.requestInit(scenario)\n : options.requestInit ?? {};\n const headers = new Headers(configured.headers);\n headers.set(\"accept\", \"application/json, text/event-stream\");\n headers.set(\"content-type\", \"application/json\");\n authHeaders.forEach((value, name) => headers.set(name, value));\n return fetchImplementation(options.url, {\n method: \"POST\",\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: `mcpfn-auth-${scenario.name}`,\n method: \"initialize\",\n params: {\n protocolVersion: \"2025-11-25\",\n capabilities: {},\n clientInfo: { name: \"mcpfn-auth-test\", version: \"1.0.0\" },\n },\n }),\n ...configured,\n headers,\n });\n },\n };\n}\n\nexport const MCPFN_OAUTH_EXTENSION_GRANTS = {\n jwtBearer: \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n deviceCode: \"urn:ietf:params:oauth:grant-type:device_code\",\n generic: \"urn:example:params:oauth:grant-type:extension\",\n} as const;\n\nexport interface McpFnOAuthClientMetadata extends Record<string, unknown> {\n client_id: string;\n client_name?: string;\n redirect_uris: string[];\n response_types: string[];\n grant_types: string[];\n token_endpoint_auth_method: string;\n}\n\nexport interface McpFnOAuthClientMetadataFixtureOptions {\n clientId: string;\n redirectUris: string[];\n clientName?: string;\n extraGrantTypes?: string[];\n extraMetadata?: Record<string, unknown>;\n}\n\nexport interface McpFnOAuthClientMetadataVariants {\n basic: McpFnOAuthClientMetadata;\n jwtBearerExtension: McpFnOAuthClientMetadata;\n deviceCodeExtension: McpFnOAuthClientMetadata;\n genericExtension: McpFnOAuthClientMetadata;\n}\n\nexport interface McpFnNamedOAuthHostFixture {\n id: \"chatgpt\" | \"claude\";\n name: string;\n redirectUris: string[];\n responseTypes: [\"code\"];\n grantTypes: string[];\n tokenEndpointAuthMethod: \"none\";\n}\n\n/** Host-shaped fixtures; applications still configure their registered production URLs. */\nexport const MCPFN_NAMED_OAUTH_HOST_FIXTURES = {\n chatgpt: {\n id: \"chatgpt\",\n name: \"ChatGPT connector\",\n redirectUris: [\"https://chatgpt.com/connector_platform_oauth_redirect\"],\n responseTypes: [\"code\"],\n grantTypes: [\"authorization_code\", \"refresh_token\"],\n tokenEndpointAuthMethod: \"none\",\n },\n claude: {\n id: \"claude\",\n name: \"Claude connector\",\n redirectUris: [\"https://claude.example.com/oauth/callback\"],\n responseTypes: [\"code\"],\n grantTypes: [\n \"authorization_code\",\n \"refresh_token\",\n MCPFN_OAUTH_EXTENSION_GRANTS.jwtBearer,\n ],\n tokenEndpointAuthMethod: \"none\",\n },\n} as const satisfies Record<string, McpFnNamedOAuthHostFixture>;\n\nexport function createOAuthClientMetadataFixture(\n options: McpFnOAuthClientMetadataFixtureOptions,\n): McpFnOAuthClientMetadata {\n return {\n ...options.extraMetadata,\n client_id: new URL(options.clientId).toString(),\n client_name: options.clientName ?? \"McpFn OAuth test client\",\n redirect_uris: [...new Set(options.redirectUris.map((uri) => new URL(uri).toString()))],\n response_types: [\"code\"],\n grant_types: [\n \"authorization_code\",\n \"refresh_token\",\n ...new Set(options.extraGrantTypes ?? []),\n ],\n token_endpoint_auth_method: \"none\",\n };\n}\n\nexport function createOAuthClientMetadataVariants(\n baseUrl: string | URL,\n redirectUri = new URL(\"/callback\", baseUrl).toString(),\n): McpFnOAuthClientMetadataVariants {\n const base = new URL(baseUrl);\n const fixture = (name: string, extraGrantTypes: string[] = []) =>\n createOAuthClientMetadataFixture({\n clientId: new URL(`/client-metadata/${name}`, base).toString(),\n redirectUris: [redirectUri],\n clientName: `McpFn ${name} client`,\n extraGrantTypes,\n });\n return {\n basic: fixture(\"basic\"),\n jwtBearerExtension: fixture(\"jwt-bearer-extension\", [MCPFN_OAUTH_EXTENSION_GRANTS.jwtBearer]),\n deviceCodeExtension: fixture(\"device-code-extension\", [MCPFN_OAUTH_EXTENSION_GRANTS.deviceCode]),\n genericExtension: fixture(\"generic-extension\", [MCPFN_OAUTH_EXTENSION_GRANTS.generic]),\n };\n}\n\n/**\n * Validates compatibility with authorization-code + PKCE without applying a\n * closed-world allowlist to unrelated grant types advertised by the client.\n */\nexport function assertAuthorizationCodeClientMetadata(\n metadata: McpFnOAuthClientMetadata,\n expectedClientId?: string,\n): McpFnOAuthClientMetadata {\n if (expectedClientId && metadata.client_id !== new URL(expectedClientId).toString()) {\n throw new McpFnAuthAssertionError(\"OAuth client metadata client_id does not match its document URL\");\n }\n if (!metadata.response_types.includes(\"code\")) {\n throw new McpFnAuthAssertionError(\"OAuth client metadata does not support response_type=code\");\n }\n if (!metadata.grant_types.includes(\"authorization_code\")) {\n throw new McpFnAuthAssertionError(\"OAuth client metadata does not support authorization_code\");\n }\n if (!metadata.redirect_uris.length) {\n throw new McpFnAuthAssertionError(\"OAuth client metadata must declare at least one redirect URI\");\n }\n return metadata;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\n\nimport type { AuthInfo } from \"@modelcontextprotocol/sdk/server/auth/types.js\";\nimport { derivePkceS256Challenge, generatePkcePair } from \"@superfunctions/oauth-core\";\n\nimport {\n assertAuthorizationCodeClientMetadata,\n createOAuthClientMetadataVariants,\n type McpFnOAuthClientMetadataVariants,\n} from \"./auth-core.js\";\n\ninterface AuthorizationCodeRecord {\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n scopes: string[];\n resource?: string;\n expiresAt: number;\n used: boolean;\n}\n\ninterface AccessTokenRecord {\n clientId: string;\n scopes: string[];\n resource?: string;\n expiresAt: number;\n revoked: boolean;\n}\n\ninterface RefreshTokenRecord {\n clientId: string;\n scopes: string[];\n resource?: string;\n expiresAt: number;\n revoked: boolean;\n}\n\nexport interface McpFnMockOAuthServerOptions {\n issuer: string | URL;\n clientId?: string;\n redirectUris?: string[];\n scopesSupported?: string[];\n accessTokenLifetimeSeconds?: number;\n authorizationCodeLifetimeSeconds?: number;\n refreshTokenLifetimeSeconds?: number;\n clock?: () => number;\n}\n\nexport interface McpFnMockOAuthTokenSet {\n access_token: string;\n token_type: \"Bearer\";\n expires_in: number;\n refresh_token: string;\n scope: string;\n resource?: string;\n}\n\nexport interface McpFnPkcePair {\n verifier: string;\n challenge: string;\n method: \"S256\";\n}\n\nexport interface McpFnOAuthCallback {\n url: string;\n parameters: Record<string, string>;\n}\n\nexport function createPkcePair(): McpFnPkcePair {\n const pair = generatePkcePair(48);\n return {\n verifier: pair.codeVerifier,\n challenge: pair.codeChallenge,\n method: pair.codeChallengeMethod,\n };\n}\n\nfunction uniqueScopes(value: string | null | undefined): string[] {\n return [...new Set((value ?? \"\").split(/\\s+/).filter(Boolean))].sort();\n}\n\nfunction json(status: number, body: unknown): Response {\n return Response.json(body, {\n status,\n headers: { \"cache-control\": \"no-store\" },\n });\n}\n\nfunction oauthError(status: number, error: string, description: string): Response {\n return json(status, { error, error_description: description });\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\");\n}\n\nfunction formValue(form: URLSearchParams, name: string): string | undefined {\n const value = form.get(name);\n return value === null || value === \"\" ? undefined : value;\n}\n\nexport class McpFnMockOAuthAuthorizationServer {\n readonly issuer: URL;\n readonly clientId: string;\n readonly redirectUris: string[];\n readonly scopesSupported: string[];\n readonly clientMetadata: McpFnOAuthClientMetadataVariants;\n\n private readonly codes = new Map<string, AuthorizationCodeRecord>();\n private readonly accessTokens = new Map<string, AccessTokenRecord>();\n private readonly refreshTokens = new Map<string, RefreshTokenRecord>();\n private readonly callbacks: McpFnOAuthCallback[] = [];\n private readonly clock: () => number;\n private readonly accessTokenLifetimeSeconds: number;\n private readonly authorizationCodeLifetimeSeconds: number;\n private readonly refreshTokenLifetimeSeconds: number;\n\n constructor(options: McpFnMockOAuthServerOptions) {\n this.issuer = new URL(options.issuer);\n this.issuer.pathname = this.issuer.pathname.replace(/\\/?$/, \"/\");\n this.clientId = options.clientId ?? \"mcpfn-playwright\";\n this.redirectUris = options.redirectUris?.map((uri) => new URL(uri).toString()) ?? [\n new URL(\"callback\", this.issuer).toString(),\n ];\n this.scopesSupported = [...new Set(options.scopesSupported ?? [\"mcp:read\", \"mcp:write\"])].sort();\n this.clock = options.clock ?? Date.now;\n this.accessTokenLifetimeSeconds = options.accessTokenLifetimeSeconds ?? 300;\n this.authorizationCodeLifetimeSeconds = options.authorizationCodeLifetimeSeconds ?? 120;\n this.refreshTokenLifetimeSeconds = options.refreshTokenLifetimeSeconds ?? 3_600;\n this.clientMetadata = createOAuthClientMetadataVariants(\n this.issuer,\n this.redirectUris[0],\n );\n }\n\n get metadataUrl(): string {\n return new URL(\".well-known/oauth-authorization-server\", this.issuer).toString();\n }\n\n get authorizationEndpoint(): string {\n return new URL(\"authorize\", this.issuer).toString();\n }\n\n get tokenEndpoint(): string {\n return new URL(\"token\", this.issuer).toString();\n }\n\n get revocationEndpoint(): string {\n return new URL(\"revoke\", this.issuer).toString();\n }\n\n get callbackUrl(): string {\n return this.redirectUris[0];\n }\n\n clientMetadataUrl(\n variant: keyof McpFnOAuthClientMetadataVariants,\n ): string {\n return this.clientMetadata[variant].client_id;\n }\n\n authorizationUrl(input: {\n clientId?: string;\n redirectUri?: string;\n codeChallenge: string;\n state?: string;\n scopes?: string[];\n resource?: string;\n extraParameters?: Record<string, string>;\n }): string {\n const url = new URL(this.authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", input.clientId ?? this.clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUri ?? this.callbackUrl);\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\"scope\", (input.scopes ?? [\"mcp:read\"]).join(\" \"));\n if (input.state) url.searchParams.set(\"state\", input.state);\n if (input.resource) url.searchParams.set(\"resource\", input.resource);\n for (const [name, value] of Object.entries(input.extraParameters ?? {})) {\n url.searchParams.set(name, value);\n }\n return url.toString();\n }\n\n latestCallback(): McpFnOAuthCallback | undefined {\n return this.callbacks.at(-1);\n }\n\n expireAccessToken(token: string): void {\n const record = this.accessTokens.get(token);\n if (record) record.expiresAt = this.nowSeconds() - 1;\n }\n\n revokeToken(token: string): void {\n const access = this.accessTokens.get(token);\n if (access) access.revoked = true;\n const refresh = this.refreshTokens.get(token);\n if (refresh) refresh.revoked = true;\n }\n\n async verifyAccessToken(token: string): Promise<AuthInfo> {\n const record = this.accessTokens.get(token);\n if (!record || record.revoked || record.expiresAt <= this.nowSeconds()) {\n throw new Error(\"Invalid access token\");\n }\n return {\n token,\n clientId: record.clientId,\n scopes: record.scopes,\n expiresAt: record.expiresAt,\n ...(record.resource ? { resource: new URL(record.resource) } : {}),\n };\n }\n\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const path = url.pathname.replace(this.issuer.pathname.replace(/\\/$/, \"\"), \"\") || \"/\";\n if (request.method === \"GET\" && path === \"/.well-known/oauth-authorization-server\") {\n return json(200, {\n issuer: this.issuer.toString(),\n authorization_endpoint: this.authorizationEndpoint,\n token_endpoint: this.tokenEndpoint,\n revocation_endpoint: this.revocationEndpoint,\n response_types_supported: [\"code\"],\n grant_types_supported: [\"authorization_code\", \"refresh_token\"],\n code_challenge_methods_supported: [\"S256\"],\n token_endpoint_auth_methods_supported: [\"none\"],\n scopes_supported: this.scopesSupported,\n client_id_metadata_document_supported: true,\n });\n }\n\n if (request.method === \"GET\" && path.startsWith(\"/client-metadata/\")) {\n const metadata = Object.values(this.clientMetadata)\n .find((candidate) => new URL(candidate.client_id).pathname === url.pathname);\n return metadata ? json(200, metadata) : json(404, { error: \"not_found\" });\n }\n\n if (request.method === \"GET\" && path === \"/authorize\") {\n return this.authorize(url);\n }\n if (request.method === \"POST\" && path === \"/token\") {\n return this.token(await request.text());\n }\n if (request.method === \"POST\" && path === \"/revoke\") {\n return this.revoke(await request.text());\n }\n if (request.method === \"GET\" && path === \"/callback\") {\n const parameters = Object.fromEntries(url.searchParams);\n this.callbacks.push({ url: url.toString(), parameters });\n return new Response(\n `<!doctype html><html><body><main><h1>OAuth callback received</h1><pre>${escapeHtml(JSON.stringify(parameters, null, 2))}</pre></main></body></html>`,\n { headers: { \"content-type\": \"text/html; charset=utf-8\" } },\n );\n }\n return json(404, { error: \"not_found\" });\n }\n\n private nowSeconds(): number {\n return Math.floor(this.clock() / 1_000);\n }\n\n private resolveClient(clientId: string): { clientId: string; redirectUris: string[] } | undefined {\n if (clientId === this.clientId) {\n return { clientId, redirectUris: this.redirectUris };\n }\n const metadata = Object.values(this.clientMetadata)\n .find((candidate) => candidate.client_id === clientId);\n if (!metadata) return undefined;\n assertAuthorizationCodeClientMetadata(metadata, clientId);\n return { clientId, redirectUris: metadata.redirect_uris };\n }\n\n private authorize(url: URL): Response {\n const clientId = url.searchParams.get(\"client_id\") ?? \"\";\n const redirectUri = url.searchParams.get(\"redirect_uri\") ?? \"\";\n const client = this.resolveClient(clientId);\n if (!client) return oauthError(400, \"invalid_client\", \"Unknown OAuth client\");\n if (!client.redirectUris.includes(redirectUri)) {\n return oauthError(400, \"invalid_request\", \"redirect_uri is not registered\");\n }\n if (url.searchParams.get(\"response_type\") !== \"code\") {\n return this.authorizationRedirect(url, redirectUri, \"unsupported_response_type\");\n }\n const codeChallenge = url.searchParams.get(\"code_challenge\");\n if (!codeChallenge || url.searchParams.get(\"code_challenge_method\") !== \"S256\") {\n return this.authorizationRedirect(url, redirectUri, \"invalid_request\", \"PKCE S256 is required\");\n }\n const requestedScopes = uniqueScopes(url.searchParams.get(\"scope\"));\n if (requestedScopes.some((scope) => !this.scopesSupported.includes(scope))) {\n return this.authorizationRedirect(url, redirectUri, \"invalid_scope\");\n }\n const decision = url.searchParams.get(\"decision\");\n if (!decision) {\n const hidden = [...url.searchParams]\n .filter(([name]) => name !== \"decision\")\n .map(([name, value]) => `<input type=\"hidden\" name=\"${escapeHtml(name)}\" value=\"${escapeHtml(value)}\">`)\n .join(\"\");\n return new Response(\n `<!doctype html><html><body><main><h1>Authorize MCP client</h1><p>${escapeHtml(clientId)}</p><form method=\"get\" action=\"${escapeHtml(this.authorizationEndpoint)}\">${hidden}<button name=\"decision\" value=\"approve\">Approve</button><button name=\"decision\" value=\"deny\">Deny</button></form></main></body></html>`,\n { headers: { \"content-type\": \"text/html; charset=utf-8\", \"cache-control\": \"no-store\" } },\n );\n }\n if (decision === \"deny\") {\n return this.authorizationRedirect(url, redirectUri, \"access_denied\");\n }\n if (decision !== \"approve\") {\n return oauthError(400, \"invalid_request\", \"Unknown authorization decision\");\n }\n const code = `mcpfn_code_${randomUUID()}`;\n this.codes.set(code, {\n clientId,\n redirectUri,\n codeChallenge,\n scopes: requestedScopes,\n resource: url.searchParams.get(\"resource\") ?? undefined,\n expiresAt: this.nowSeconds() + this.authorizationCodeLifetimeSeconds,\n used: false,\n });\n const callback = new URL(redirectUri);\n callback.searchParams.set(\"code\", code);\n const state = url.searchParams.get(\"state\");\n if (state) callback.searchParams.set(\"state\", state);\n return Response.redirect(callback, 302);\n }\n\n private authorizationRedirect(\n requestUrl: URL,\n redirectUri: string,\n error: string,\n description?: string,\n ): Response {\n const callback = new URL(redirectUri);\n callback.searchParams.set(\"error\", error);\n if (description) callback.searchParams.set(\"error_description\", description);\n const state = requestUrl.searchParams.get(\"state\");\n if (state) callback.searchParams.set(\"state\", state);\n return Response.redirect(callback, 302);\n }\n\n private token(source: string): Response {\n const form = new URLSearchParams(source);\n const grantType = formValue(form, \"grant_type\");\n if (grantType === \"authorization_code\") return this.exchangeAuthorizationCode(form);\n if (grantType === \"refresh_token\") return this.exchangeRefreshToken(form);\n return oauthError(400, \"unsupported_grant_type\", \"The grant type is not supported\");\n }\n\n private exchangeAuthorizationCode(form: URLSearchParams): Response {\n const code = formValue(form, \"code\");\n const record = code ? this.codes.get(code) : undefined;\n if (!record || record.used || record.expiresAt <= this.nowSeconds()) {\n return oauthError(400, \"invalid_grant\", \"Authorization code is invalid or expired\");\n }\n if (\n formValue(form, \"client_id\") !== record.clientId ||\n formValue(form, \"redirect_uri\") !== record.redirectUri\n ) {\n return oauthError(400, \"invalid_grant\", \"Authorization code binding does not match\");\n }\n const verifier = formValue(form, \"code_verifier\");\n const challenge = verifier ? derivePkceS256Challenge(verifier) : undefined;\n if (challenge !== record.codeChallenge) {\n return oauthError(400, \"invalid_grant\", \"PKCE verification failed\");\n }\n record.used = true;\n return json(200, this.issueTokenSet(record));\n }\n\n private exchangeRefreshToken(form: URLSearchParams): Response {\n const refreshToken = formValue(form, \"refresh_token\");\n const record = refreshToken ? this.refreshTokens.get(refreshToken) : undefined;\n if (!record || record.revoked || record.expiresAt <= this.nowSeconds()) {\n return oauthError(400, \"invalid_grant\", \"Refresh token is invalid or expired\");\n }\n if (formValue(form, \"client_id\") !== record.clientId) {\n return oauthError(400, \"invalid_grant\", \"Refresh token client binding does not match\");\n }\n const requestedScopes = form.has(\"scope\")\n ? uniqueScopes(form.get(\"scope\"))\n : record.scopes;\n if (requestedScopes.some((scope) => !record.scopes.includes(scope))) {\n return oauthError(400, \"invalid_scope\", \"Refresh cannot increase granted scopes\");\n }\n record.revoked = true;\n return json(200, this.issueTokenSet({ ...record, scopes: requestedScopes }));\n }\n\n private issueTokenSet(\n input: Pick<AccessTokenRecord, \"clientId\" | \"scopes\" | \"resource\">,\n ): McpFnMockOAuthTokenSet {\n const accessToken = `mcpfn_access_${randomUUID()}`;\n const refreshToken = `mcpfn_refresh_${randomUUID()}`;\n this.accessTokens.set(accessToken, {\n ...input,\n expiresAt: this.nowSeconds() + this.accessTokenLifetimeSeconds,\n revoked: false,\n });\n this.refreshTokens.set(refreshToken, {\n ...input,\n expiresAt: this.nowSeconds() + this.refreshTokenLifetimeSeconds,\n revoked: false,\n });\n return {\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: this.accessTokenLifetimeSeconds,\n refresh_token: refreshToken,\n scope: input.scopes.join(\" \"),\n ...(input.resource ? { resource: input.resource } : {}),\n };\n }\n\n private revoke(source: string): Response {\n const token = new URLSearchParams(source).get(\"token\");\n if (token) this.revokeToken(token);\n return new Response(null, { status: 200 });\n }\n}\n\nexport interface McpFnStartedMockOAuthServer {\n origin: string;\n oauth: McpFnMockOAuthAuthorizationServer;\n close(): Promise<void>;\n}\n\nexport interface McpFnStartMockOAuthServerOptions\n extends Omit<McpFnMockOAuthServerOptions, \"issuer\"> {\n hostname?: string;\n port?: number;\n /** Optional same-origin route used to compose a protected MCP fixture. */\n handle?(\n request: Request,\n oauth: McpFnMockOAuthAuthorizationServer,\n ): Promise<Response | undefined> | Response | undefined;\n}\n\nasync function toRequest(req: IncomingMessage, origin: string): Promise<Request> {\n const chunks: Buffer[] = [];\n for await (const chunk of req) chunks.push(Buffer.from(chunk));\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers)) {\n if (Array.isArray(value)) {\n for (const entry of value) headers.append(name, entry);\n } else if (value !== undefined) {\n headers.set(name, value);\n }\n }\n const body = chunks.length ? Buffer.concat(chunks) : undefined;\n const init: RequestInit & { duplex?: \"half\" } = {\n method: req.method,\n headers,\n ...(body ? { body, duplex: \"half\" } : {}),\n };\n return new Request(new URL(req.url ?? \"/\", origin), init);\n}\n\nasync function writeResponse(res: ServerResponse, response: Response): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, name) => res.setHeader(name, value));\n res.end(Buffer.from(await response.arrayBuffer()));\n}\n\nexport async function startMockOAuthAuthorizationServer(\n options: McpFnStartMockOAuthServerOptions = {},\n): Promise<McpFnStartedMockOAuthServer> {\n let oauth: McpFnMockOAuthAuthorizationServer | undefined;\n let origin = \"\";\n const server = createServer(async (req, res) => {\n try {\n if (!oauth) throw new Error(\"Mock OAuth server is not initialized\");\n const request = await toRequest(req, origin);\n const custom = await options.handle?.(request.clone(), oauth);\n await writeResponse(res, custom ?? await oauth.handle(request));\n } catch {\n res.statusCode = 500;\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify({\n error: \"server_error\",\n error_description: \"Internal Server Error\",\n }));\n }\n });\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(options.port ?? 0, options.hostname ?? \"127.0.0.1\", () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n const address = server.address() as AddressInfo;\n origin = `http://${address.address.includes(\":\") ? `[${address.address}]` : address.address}:${address.port}`;\n oauth = new McpFnMockOAuthAuthorizationServer({ ...options, issuer: origin });\n return {\n origin,\n oauth,\n close: () => new Promise<void>((resolve, reject) => {\n server.close((error) => error ? reject(error) : resolve());\n }),\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/auth.ts","../src/auth-core.ts","../src/mock-oauth-server.ts"],"sourcesContent":["export * from \"./auth-core.js\";\nexport * from \"./mock-oauth-server.js\";\n","export type McpFnAuthCredentialKind = \"api-key\" | \"oauth\";\n\nexport interface McpFnAuthCredential {\n kind: McpFnAuthCredentialKind;\n headers: HeadersInit;\n label?: string;\n dispose?(): void | Promise<void>;\n}\n\nexport interface McpFnAuthCredentialRequest {\n kind: McpFnAuthCredentialKind;\n scopes?: string[];\n resource?: string;\n expiresInSeconds?: number;\n}\n\nexport interface McpFnAuthProviderCapabilities {\n expiration?: boolean;\n resourceBinding?: boolean;\n revocation?: boolean;\n scopes?: boolean;\n}\n\n/**\n * The application-owned seam. Skillplane only needs to map this interface to\n * its existing API-key or OAuth issuance and revocation helpers.\n */\nexport interface McpFnAuthProviderAdapter {\n capabilities?: McpFnAuthProviderCapabilities;\n issue(input: McpFnAuthCredentialRequest): Promise<McpFnAuthCredential>;\n revoke?(credential: McpFnAuthCredential): void | Promise<void>;\n}\n\nexport interface McpFnAuthProbeScenario {\n name:\n | \"missing-credential\"\n | \"invalid-credential\"\n | \"valid-credential\"\n | \"insufficient-scope\"\n | \"expired-credential\"\n | \"missing-resource\"\n | \"wrong-resource\"\n | \"revoked-credential\";\n kind: McpFnAuthCredentialKind;\n}\n\nexport interface McpFnAuthTarget {\n request(\n headers: Headers,\n scenario: McpFnAuthProbeScenario,\n ): Promise<Response>;\n}\n\nexport interface McpFnAuthRegressionOptions {\n kind: McpFnAuthCredentialKind;\n provider: McpFnAuthProviderAdapter;\n target: McpFnAuthTarget;\n requiredScopes?: string[];\n resource?: string;\n wrongResource?: string;\n invalidCredentialHeaders?: HeadersInit;\n /** OAuth failures are required to advertise protected-resource metadata. */\n protectedResourceMetadataUrl?: string;\n expectedAuthenticatedStatuses?: number[];\n verifyAuthenticatedResponse?(\n response: Response,\n credential: McpFnAuthCredential,\n ): void | Promise<void>;\n}\n\nexport interface McpFnAuthScenarioResult {\n name: McpFnAuthProbeScenario[\"name\"];\n status: \"passed\" | \"failed\";\n responseStatus?: number;\n error?: string;\n}\n\nexport interface McpFnBearerChallenge {\n scheme: string;\n parameters: Record<string, string>;\n}\n\nexport class McpFnAuthAssertionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"McpFnAuthAssertionError\";\n }\n}\n\nfunction expectedStatus(\n response: Response,\n expected: number | number[],\n name: string,\n): void {\n const statuses = Array.isArray(expected) ? expected : [expected];\n if (!statuses.includes(response.status)) {\n throw new McpFnAuthAssertionError(\n `${name} expected HTTP ${statuses.join(\" or \")}, received ${response.status}`,\n );\n }\n}\n\nexport function parseBearerChallenge(\n header: string | null,\n): McpFnBearerChallenge | undefined {\n if (!header) return undefined;\n const matches = [...header.matchAll(/(?:^|,)\\s*([A-Za-z][A-Za-z0-9_-]*)\\s+/g)];\n const selectedIndex = matches.findIndex((entry) => entry[1].toLowerCase() === \"bearer\");\n if (selectedIndex < 0) return undefined;\n const match = matches[selectedIndex];\n const start = (match.index ?? 0) + match[0].length;\n const end = matches[selectedIndex + 1]?.index ?? header.length;\n const parameters: Record<string, string> = {};\n const source = header.slice(start, end);\n const pattern = /([A-Za-z][A-Za-z0-9_-]*)\\s*=\\s*(?:\"((?:[^\"\\\\]|\\\\.)*)\"|([^,\\s]+))/g;\n for (const entry of source.matchAll(pattern)) {\n const quoted = entry[2]?.replace(/\\\\([\"\\\\])/g, \"$1\");\n parameters[entry[1]] = quoted ?? entry[3] ?? \"\";\n }\n return { scheme: match[1], parameters };\n}\n\nexport function assertBearerResourceChallenge(\n response: Response,\n expectedMetadataUrl?: string,\n): McpFnBearerChallenge {\n const challenge = parseBearerChallenge(response.headers.get(\"www-authenticate\"));\n if (!challenge || challenge.scheme.toLowerCase() !== \"bearer\") {\n throw new McpFnAuthAssertionError(\"Expected a Bearer WWW-Authenticate challenge\");\n }\n const metadataUrl = challenge.parameters.resource_metadata;\n if (!metadataUrl) {\n throw new McpFnAuthAssertionError(\n \"Bearer challenge is missing the resource_metadata parameter\",\n );\n }\n try {\n const parsed = new URL(metadataUrl);\n if (!parsed.protocol || !parsed.host) throw new Error(\"not absolute\");\n } catch {\n throw new McpFnAuthAssertionError(\n `Bearer challenge has an invalid resource_metadata URL: ${metadataUrl}`,\n );\n }\n if (expectedMetadataUrl && metadataUrl !== expectedMetadataUrl) {\n throw new McpFnAuthAssertionError(\n `Expected resource_metadata=${expectedMetadataUrl}, received ${metadataUrl}`,\n );\n }\n return challenge;\n}\n\nexport function bearerCredential(\n token: string,\n kind: McpFnAuthCredentialKind = \"oauth\",\n): McpFnAuthCredential {\n return {\n kind,\n headers: { authorization: `Bearer ${token}` },\n };\n}\n\nexport function apiKeyCredential(\n apiKey: string,\n options: { headerName?: string; scheme?: string } = {},\n): McpFnAuthCredential {\n const headerName = options.headerName ?? \"authorization\";\n const value = options.scheme === \"\"\n ? apiKey\n : `${options.scheme ?? \"Bearer\"} ${apiKey}`;\n return { kind: \"api-key\", headers: { [headerName]: value } };\n}\n\nfunction invalidHeaders(options: McpFnAuthRegressionOptions): Headers {\n return new Headers(options.invalidCredentialHeaders ?? {\n authorization: `Bearer mcpfn-invalid-${options.kind}`,\n });\n}\n\nasync function runOne(\n options: McpFnAuthRegressionOptions,\n scenario: McpFnAuthProbeScenario,\n prepare: () => Promise<McpFnAuthCredential | undefined>,\n expected: number | number[],\n): Promise<McpFnAuthScenarioResult> {\n let credential: McpFnAuthCredential | undefined;\n try {\n credential = await prepare();\n const headers = credential\n ? new Headers(credential.headers)\n : scenario.name === \"invalid-credential\"\n ? invalidHeaders(options)\n : new Headers();\n const response = await options.target.request(headers, scenario);\n expectedStatus(response, expected, scenario.name);\n if (\n options.kind === \"oauth\" &&\n scenario.name !== \"valid-credential\"\n ) {\n assertBearerResourceChallenge(\n response,\n options.protectedResourceMetadataUrl,\n );\n }\n if (scenario.name === \"valid-credential\" && credential) {\n await options.verifyAuthenticatedResponse?.(response.clone(), credential);\n }\n return {\n name: scenario.name,\n status: \"passed\",\n responseStatus: response.status,\n };\n } catch (error) {\n return {\n name: scenario.name,\n status: \"failed\",\n error: error instanceof Error ? error.message : String(error),\n };\n } finally {\n await credential?.dispose?.();\n }\n}\n\n/** Runs the common API-key or OAuth resource-server regression matrix. */\nexport async function runAuthRegressionSuite(\n options: McpFnAuthRegressionOptions,\n): Promise<McpFnAuthScenarioResult[]> {\n const issue = (overrides: Partial<McpFnAuthCredentialRequest> = {}) =>\n options.provider.issue({\n kind: options.kind,\n scopes: options.requiredScopes,\n resource: options.resource,\n ...overrides,\n });\n const results: McpFnAuthScenarioResult[] = [];\n results.push(await runOne(\n options,\n { name: \"missing-credential\", kind: options.kind },\n async () => undefined,\n 401,\n ));\n results.push(await runOne(\n options,\n { name: \"invalid-credential\", kind: options.kind },\n async () => undefined,\n 401,\n ));\n results.push(await runOne(\n options,\n { name: \"valid-credential\", kind: options.kind },\n () => issue(),\n options.expectedAuthenticatedStatuses ?? [200, 201, 202, 204],\n ));\n\n const capabilities = options.provider.capabilities ?? {};\n if (capabilities.scopes && options.requiredScopes?.length) {\n results.push(await runOne(\n options,\n { name: \"insufficient-scope\", kind: options.kind },\n () => issue({ scopes: [] }),\n 403,\n ));\n }\n if (capabilities.expiration) {\n results.push(await runOne(\n options,\n { name: \"expired-credential\", kind: options.kind },\n () => issue({ expiresInSeconds: -60 }),\n 401,\n ));\n }\n if (capabilities.resourceBinding && options.resource) {\n results.push(await runOne(\n options,\n { name: \"missing-resource\", kind: options.kind },\n () => issue({ resource: undefined }),\n 401,\n ));\n }\n if (capabilities.resourceBinding && options.wrongResource) {\n results.push(await runOne(\n options,\n { name: \"wrong-resource\", kind: options.kind },\n () => issue({ resource: options.wrongResource }),\n 401,\n ));\n }\n if (capabilities.revocation && options.provider.revoke) {\n results.push(await runOne(\n options,\n { name: \"revoked-credential\", kind: options.kind },\n async () => {\n const credential = await issue();\n await options.provider.revoke?.(credential);\n return credential;\n },\n 401,\n ));\n }\n return results;\n}\n\nexport async function assertAuthRegressionSuite(\n options: McpFnAuthRegressionOptions,\n): Promise<McpFnAuthScenarioResult[]> {\n const results = await runAuthRegressionSuite(options);\n const failures = results.filter((result) => result.status === \"failed\");\n if (failures.length) {\n throw new McpFnAuthAssertionError(\n failures.map((failure) => `${failure.name}: ${failure.error}`).join(\"\\n\"),\n );\n }\n return results;\n}\n\nexport interface McpFnFetchAuthTargetOptions {\n url: string | URL;\n fetch?: typeof globalThis.fetch;\n requestInit?: RequestInit | ((scenario: McpFnAuthProbeScenario) => RequestInit);\n}\n\n/** Creates a protected MCP initialize probe suitable for local or deployed endpoints. */\nexport function createFetchAuthTarget(\n options: McpFnFetchAuthTargetOptions,\n): McpFnAuthTarget {\n const fetchImplementation = options.fetch ?? globalThis.fetch;\n if (!fetchImplementation) {\n throw new Error(\"A fetch implementation is required\");\n }\n return {\n async request(authHeaders, scenario) {\n const configured = typeof options.requestInit === \"function\"\n ? options.requestInit(scenario)\n : options.requestInit ?? {};\n const headers = new Headers(configured.headers);\n headers.set(\"accept\", \"application/json, text/event-stream\");\n headers.set(\"content-type\", \"application/json\");\n authHeaders.forEach((value, name) => headers.set(name, value));\n return fetchImplementation(options.url, {\n method: \"POST\",\n body: JSON.stringify({\n jsonrpc: \"2.0\",\n id: `mcpfn-auth-${scenario.name}`,\n method: \"initialize\",\n params: {\n protocolVersion: \"2025-11-25\",\n capabilities: {},\n clientInfo: { name: \"mcpfn-auth-test\", version: \"1.0.0\" },\n },\n }),\n ...configured,\n headers,\n });\n },\n };\n}\n\nexport const MCPFN_OAUTH_EXTENSION_GRANTS = {\n jwtBearer: \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n deviceCode: \"urn:ietf:params:oauth:grant-type:device_code\",\n generic: \"urn:example:params:oauth:grant-type:extension\",\n} as const;\n\nexport interface McpFnOAuthClientMetadata extends Record<string, unknown> {\n client_id: string;\n client_name?: string;\n redirect_uris: string[];\n response_types: string[];\n grant_types: string[];\n token_endpoint_auth_method: string;\n}\n\nexport interface McpFnOAuthClientMetadataFixtureOptions {\n clientId: string;\n redirectUris: string[];\n clientName?: string;\n extraGrantTypes?: string[];\n extraMetadata?: Record<string, unknown>;\n}\n\nexport interface McpFnOAuthClientMetadataVariants {\n basic: McpFnOAuthClientMetadata;\n jwtBearerExtension: McpFnOAuthClientMetadata;\n deviceCodeExtension: McpFnOAuthClientMetadata;\n genericExtension: McpFnOAuthClientMetadata;\n}\n\nexport interface McpFnNamedOAuthHostFixture {\n id: \"chatgpt\" | \"claude\";\n name: string;\n redirectUris: string[];\n responseTypes: [\"code\"];\n grantTypes: string[];\n tokenEndpointAuthMethod: \"none\";\n}\n\n/** Host-shaped fixtures; applications still configure their registered production URLs. */\nexport const MCPFN_NAMED_OAUTH_HOST_FIXTURES = {\n chatgpt: {\n id: \"chatgpt\",\n name: \"ChatGPT connector\",\n redirectUris: [\"https://chatgpt.com/connector_platform_oauth_redirect\"],\n responseTypes: [\"code\"],\n grantTypes: [\"authorization_code\", \"refresh_token\"],\n tokenEndpointAuthMethod: \"none\",\n },\n claude: {\n id: \"claude\",\n name: \"Claude connector\",\n redirectUris: [\"https://claude.example.com/oauth/callback\"],\n responseTypes: [\"code\"],\n grantTypes: [\n \"authorization_code\",\n \"refresh_token\",\n MCPFN_OAUTH_EXTENSION_GRANTS.jwtBearer,\n ],\n tokenEndpointAuthMethod: \"none\",\n },\n} as const satisfies Record<string, McpFnNamedOAuthHostFixture>;\n\nexport function createOAuthClientMetadataFixture(\n options: McpFnOAuthClientMetadataFixtureOptions,\n): McpFnOAuthClientMetadata {\n return {\n ...options.extraMetadata,\n client_id: new URL(options.clientId).toString(),\n client_name: options.clientName ?? \"McpFn OAuth test client\",\n redirect_uris: [...new Set(options.redirectUris.map((uri) => new URL(uri).toString()))],\n response_types: [\"code\"],\n grant_types: [\n \"authorization_code\",\n \"refresh_token\",\n ...new Set(options.extraGrantTypes ?? []),\n ],\n token_endpoint_auth_method: \"none\",\n };\n}\n\nexport function createOAuthClientMetadataVariants(\n baseUrl: string | URL,\n redirectUri = new URL(\"/callback\", baseUrl).toString(),\n): McpFnOAuthClientMetadataVariants {\n const base = new URL(baseUrl);\n const fixture = (name: string, extraGrantTypes: string[] = []) =>\n createOAuthClientMetadataFixture({\n clientId: new URL(`/client-metadata/${name}`, base).toString(),\n redirectUris: [redirectUri],\n clientName: `McpFn ${name} client`,\n extraGrantTypes,\n });\n return {\n basic: fixture(\"basic\"),\n jwtBearerExtension: fixture(\"jwt-bearer-extension\", [MCPFN_OAUTH_EXTENSION_GRANTS.jwtBearer]),\n deviceCodeExtension: fixture(\"device-code-extension\", [MCPFN_OAUTH_EXTENSION_GRANTS.deviceCode]),\n genericExtension: fixture(\"generic-extension\", [MCPFN_OAUTH_EXTENSION_GRANTS.generic]),\n };\n}\n\n/**\n * Validates compatibility with authorization-code + PKCE without applying a\n * closed-world allowlist to unrelated grant types advertised by the client.\n */\nexport function assertAuthorizationCodeClientMetadata(\n metadata: McpFnOAuthClientMetadata,\n expectedClientId?: string,\n): McpFnOAuthClientMetadata {\n if (expectedClientId && metadata.client_id !== new URL(expectedClientId).toString()) {\n throw new McpFnAuthAssertionError(\"OAuth client metadata client_id does not match its document URL\");\n }\n if (!metadata.response_types.includes(\"code\")) {\n throw new McpFnAuthAssertionError(\"OAuth client metadata does not support response_type=code\");\n }\n if (!metadata.grant_types.includes(\"authorization_code\")) {\n throw new McpFnAuthAssertionError(\"OAuth client metadata does not support authorization_code\");\n }\n if (!metadata.redirect_uris.length) {\n throw new McpFnAuthAssertionError(\"OAuth client metadata must declare at least one redirect URI\");\n }\n return metadata;\n}\n","import { randomUUID } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\n\nimport type { AuthInfo } from \"@modelcontextprotocol/sdk/server/auth/types.js\";\nimport { derivePkceS256Challenge, generatePkcePair } from \"@superfunctions/oauth-core\";\n\nimport {\n assertAuthorizationCodeClientMetadata,\n createOAuthClientMetadataVariants,\n type McpFnOAuthClientMetadataVariants,\n} from \"./auth-core.js\";\n\ninterface AuthorizationCodeRecord {\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n scopes: string[];\n resource?: string;\n expiresAt: number;\n used: boolean;\n}\n\ninterface AccessTokenRecord {\n clientId: string;\n scopes: string[];\n resource?: string;\n expiresAt: number;\n revoked: boolean;\n}\n\ninterface RefreshTokenRecord {\n clientId: string;\n scopes: string[];\n resource?: string;\n expiresAt: number;\n revoked: boolean;\n}\n\nexport interface McpFnMockOAuthServerOptions {\n issuer: string | URL;\n clientId?: string;\n redirectUris?: string[];\n scopesSupported?: string[];\n accessTokenLifetimeSeconds?: number;\n authorizationCodeLifetimeSeconds?: number;\n refreshTokenLifetimeSeconds?: number;\n clock?: () => number;\n}\n\nexport interface McpFnMockOAuthTokenSet {\n access_token: string;\n token_type: \"Bearer\";\n expires_in: number;\n refresh_token: string;\n scope: string;\n resource?: string;\n}\n\nexport interface McpFnPkcePair {\n verifier: string;\n challenge: string;\n method: \"S256\";\n}\n\nexport interface McpFnOAuthCallback {\n url: string;\n parameters: Record<string, string>;\n}\n\nexport function createPkcePair(): McpFnPkcePair {\n const pair = generatePkcePair(48);\n return {\n verifier: pair.codeVerifier,\n challenge: pair.codeChallenge,\n method: pair.codeChallengeMethod,\n };\n}\n\nfunction uniqueScopes(value: string | null | undefined): string[] {\n return [...new Set((value ?? \"\").split(/\\s+/).filter(Boolean))].sort();\n}\n\nfunction json(status: number, body: unknown): Response {\n return Response.json(body, {\n status,\n headers: { \"cache-control\": \"no-store\", pragma: \"no-cache\" },\n });\n}\n\nfunction oauthError(status: number, error: string, description: string): Response {\n return json(status, { error, error_description: description });\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\");\n}\n\nfunction formValue(form: URLSearchParams, name: string): string | undefined {\n const value = form.get(name);\n return value === null || value === \"\" ? undefined : value;\n}\n\nexport class McpFnMockOAuthAuthorizationServer {\n readonly issuer: URL;\n readonly clientId: string;\n readonly redirectUris: string[];\n readonly scopesSupported: string[];\n readonly clientMetadata: McpFnOAuthClientMetadataVariants;\n\n private readonly codes = new Map<string, AuthorizationCodeRecord>();\n private readonly accessTokens = new Map<string, AccessTokenRecord>();\n private readonly refreshTokens = new Map<string, RefreshTokenRecord>();\n private readonly callbacks: McpFnOAuthCallback[] = [];\n private readonly clock: () => number;\n private readonly accessTokenLifetimeSeconds: number;\n private readonly authorizationCodeLifetimeSeconds: number;\n private readonly refreshTokenLifetimeSeconds: number;\n\n constructor(options: McpFnMockOAuthServerOptions) {\n this.issuer = new URL(options.issuer);\n this.issuer.pathname = this.issuer.pathname.replace(/\\/?$/, \"/\");\n this.clientId = options.clientId ?? \"mcpfn-playwright\";\n this.redirectUris = options.redirectUris?.map((uri) => new URL(uri).toString()) ?? [\n new URL(\"callback\", this.issuer).toString(),\n ];\n this.scopesSupported = [...new Set(options.scopesSupported ?? [\"mcp:read\", \"mcp:write\"])].sort();\n this.clock = options.clock ?? Date.now;\n this.accessTokenLifetimeSeconds = options.accessTokenLifetimeSeconds ?? 300;\n this.authorizationCodeLifetimeSeconds = options.authorizationCodeLifetimeSeconds ?? 120;\n this.refreshTokenLifetimeSeconds = options.refreshTokenLifetimeSeconds ?? 3_600;\n this.clientMetadata = createOAuthClientMetadataVariants(\n this.issuer,\n this.redirectUris[0],\n );\n }\n\n get metadataUrl(): string {\n return new URL(\".well-known/oauth-authorization-server\", this.issuer).toString();\n }\n\n get authorizationEndpoint(): string {\n return new URL(\"authorize\", this.issuer).toString();\n }\n\n get tokenEndpoint(): string {\n return new URL(\"token\", this.issuer).toString();\n }\n\n get revocationEndpoint(): string {\n return new URL(\"revoke\", this.issuer).toString();\n }\n\n get callbackUrl(): string {\n return this.redirectUris[0];\n }\n\n clientMetadataUrl(\n variant: keyof McpFnOAuthClientMetadataVariants,\n ): string {\n return this.clientMetadata[variant].client_id;\n }\n\n authorizationUrl(input: {\n clientId?: string;\n redirectUri?: string;\n codeChallenge: string;\n state?: string;\n scopes?: string[];\n resource?: string;\n extraParameters?: Record<string, string>;\n }): string {\n const url = new URL(this.authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", input.clientId ?? this.clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUri ?? this.callbackUrl);\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\"scope\", (input.scopes ?? [\"mcp:read\"]).join(\" \"));\n if (input.state) url.searchParams.set(\"state\", input.state);\n if (input.resource) url.searchParams.set(\"resource\", input.resource);\n for (const [name, value] of Object.entries(input.extraParameters ?? {})) {\n url.searchParams.set(name, value);\n }\n return url.toString();\n }\n\n latestCallback(): McpFnOAuthCallback | undefined {\n return this.callbacks.at(-1);\n }\n\n expireAccessToken(token: string): void {\n const record = this.accessTokens.get(token);\n if (record) record.expiresAt = this.nowSeconds() - 1;\n }\n\n revokeToken(token: string): void {\n const access = this.accessTokens.get(token);\n if (access) access.revoked = true;\n const refresh = this.refreshTokens.get(token);\n if (refresh) refresh.revoked = true;\n }\n\n async verifyAccessToken(token: string): Promise<AuthInfo> {\n const record = this.accessTokens.get(token);\n if (!record || record.revoked || record.expiresAt <= this.nowSeconds()) {\n throw new Error(\"Invalid access token\");\n }\n return {\n token,\n clientId: record.clientId,\n scopes: record.scopes,\n expiresAt: record.expiresAt,\n ...(record.resource ? { resource: new URL(record.resource) } : {}),\n };\n }\n\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const path = url.pathname.replace(this.issuer.pathname.replace(/\\/$/, \"\"), \"\") || \"/\";\n if (request.method === \"GET\" && path === \"/.well-known/oauth-authorization-server\") {\n return json(200, {\n issuer: this.issuer.toString(),\n authorization_endpoint: this.authorizationEndpoint,\n token_endpoint: this.tokenEndpoint,\n revocation_endpoint: this.revocationEndpoint,\n response_types_supported: [\"code\"],\n grant_types_supported: [\"authorization_code\", \"refresh_token\"],\n code_challenge_methods_supported: [\"S256\"],\n token_endpoint_auth_methods_supported: [\"none\"],\n scopes_supported: this.scopesSupported,\n client_id_metadata_document_supported: true,\n });\n }\n\n if (request.method === \"GET\" && path.startsWith(\"/client-metadata/\")) {\n const metadata = Object.values(this.clientMetadata)\n .find((candidate) => new URL(candidate.client_id).pathname === url.pathname);\n return metadata ? json(200, metadata) : json(404, { error: \"not_found\" });\n }\n\n if (request.method === \"GET\" && path === \"/authorize\") {\n return this.authorize(url);\n }\n if (request.method === \"POST\" && path === \"/token\") {\n return this.token(await request.text());\n }\n if (request.method === \"POST\" && path === \"/revoke\") {\n return this.revoke(await request.text());\n }\n if (request.method === \"GET\" && path === \"/callback\") {\n const parameters = Object.fromEntries(url.searchParams);\n this.callbacks.push({ url: url.toString(), parameters });\n return new Response(\n `<!doctype html><html><body><main><h1>OAuth callback received</h1><pre>${escapeHtml(JSON.stringify(parameters, null, 2))}</pre></main></body></html>`,\n { headers: { \"content-type\": \"text/html; charset=utf-8\" } },\n );\n }\n return json(404, { error: \"not_found\" });\n }\n\n private nowSeconds(): number {\n return Math.floor(this.clock() / 1_000);\n }\n\n private resolveClient(clientId: string): { clientId: string; redirectUris: string[] } | undefined {\n if (clientId === this.clientId) {\n return { clientId, redirectUris: this.redirectUris };\n }\n const metadata = Object.values(this.clientMetadata)\n .find((candidate) => candidate.client_id === clientId);\n if (!metadata) return undefined;\n assertAuthorizationCodeClientMetadata(metadata, clientId);\n return { clientId, redirectUris: metadata.redirect_uris };\n }\n\n private authorize(url: URL): Response {\n const clientId = url.searchParams.get(\"client_id\") ?? \"\";\n const redirectUri = url.searchParams.get(\"redirect_uri\") ?? \"\";\n const client = this.resolveClient(clientId);\n if (!client) return oauthError(400, \"invalid_client\", \"Unknown OAuth client\");\n if (!client.redirectUris.includes(redirectUri)) {\n return oauthError(400, \"invalid_request\", \"redirect_uri is not registered\");\n }\n if (url.searchParams.get(\"response_type\") !== \"code\") {\n return this.authorizationRedirect(url, redirectUri, \"unsupported_response_type\");\n }\n const codeChallenge = url.searchParams.get(\"code_challenge\");\n if (!codeChallenge || url.searchParams.get(\"code_challenge_method\") !== \"S256\") {\n return this.authorizationRedirect(url, redirectUri, \"invalid_request\", \"PKCE S256 is required\");\n }\n const requestedScopes = uniqueScopes(url.searchParams.get(\"scope\"));\n if (requestedScopes.some((scope) => !this.scopesSupported.includes(scope))) {\n return this.authorizationRedirect(url, redirectUri, \"invalid_scope\");\n }\n const decision = url.searchParams.get(\"decision\");\n if (!decision) {\n const hidden = [...url.searchParams]\n .filter(([name]) => name !== \"decision\")\n .map(([name, value]) => `<input type=\"hidden\" name=\"${escapeHtml(name)}\" value=\"${escapeHtml(value)}\">`)\n .join(\"\");\n return new Response(\n `<!doctype html><html><body><main><h1>Authorize MCP client</h1><p>${escapeHtml(clientId)}</p><form method=\"get\" action=\"${escapeHtml(this.authorizationEndpoint)}\">${hidden}<button name=\"decision\" value=\"approve\">Approve</button><button name=\"decision\" value=\"deny\">Deny</button></form></main></body></html>`,\n { headers: { \"content-type\": \"text/html; charset=utf-8\", \"cache-control\": \"no-store\" } },\n );\n }\n if (decision === \"deny\") {\n return this.authorizationRedirect(url, redirectUri, \"access_denied\");\n }\n if (decision !== \"approve\") {\n return oauthError(400, \"invalid_request\", \"Unknown authorization decision\");\n }\n const code = `mcpfn_code_${randomUUID()}`;\n this.codes.set(code, {\n clientId,\n redirectUri,\n codeChallenge,\n scopes: requestedScopes,\n resource: url.searchParams.get(\"resource\") ?? undefined,\n expiresAt: this.nowSeconds() + this.authorizationCodeLifetimeSeconds,\n used: false,\n });\n const callback = new URL(redirectUri);\n callback.searchParams.set(\"code\", code);\n const state = url.searchParams.get(\"state\");\n if (state) callback.searchParams.set(\"state\", state);\n return Response.redirect(callback, 302);\n }\n\n private authorizationRedirect(\n requestUrl: URL,\n redirectUri: string,\n error: string,\n description?: string,\n ): Response {\n const callback = new URL(redirectUri);\n callback.searchParams.set(\"error\", error);\n if (description) callback.searchParams.set(\"error_description\", description);\n const state = requestUrl.searchParams.get(\"state\");\n if (state) callback.searchParams.set(\"state\", state);\n return Response.redirect(callback, 302);\n }\n\n private token(source: string): Response {\n const form = new URLSearchParams(source);\n const grantType = formValue(form, \"grant_type\");\n if (grantType === \"authorization_code\") return this.exchangeAuthorizationCode(form);\n if (grantType === \"refresh_token\") return this.exchangeRefreshToken(form);\n return oauthError(400, \"unsupported_grant_type\", \"The grant type is not supported\");\n }\n\n private exchangeAuthorizationCode(form: URLSearchParams): Response {\n const code = formValue(form, \"code\");\n const record = code ? this.codes.get(code) : undefined;\n if (!record || record.used || record.expiresAt <= this.nowSeconds()) {\n return oauthError(400, \"invalid_grant\", \"Authorization code is invalid or expired\");\n }\n if (\n formValue(form, \"client_id\") !== record.clientId ||\n formValue(form, \"redirect_uri\") !== record.redirectUri\n ) {\n return oauthError(400, \"invalid_grant\", \"Authorization code binding does not match\");\n }\n const verifier = formValue(form, \"code_verifier\");\n const challenge = verifier ? derivePkceS256Challenge(verifier) : undefined;\n if (challenge !== record.codeChallenge) {\n return oauthError(400, \"invalid_grant\", \"PKCE verification failed\");\n }\n record.used = true;\n return json(200, this.issueTokenSet(record));\n }\n\n private exchangeRefreshToken(form: URLSearchParams): Response {\n const refreshToken = formValue(form, \"refresh_token\");\n const record = refreshToken ? this.refreshTokens.get(refreshToken) : undefined;\n if (!record || record.revoked || record.expiresAt <= this.nowSeconds()) {\n return oauthError(400, \"invalid_grant\", \"Refresh token is invalid or expired\");\n }\n if (formValue(form, \"client_id\") !== record.clientId) {\n return oauthError(400, \"invalid_grant\", \"Refresh token client binding does not match\");\n }\n const requestedScopes = form.has(\"scope\")\n ? uniqueScopes(form.get(\"scope\"))\n : record.scopes;\n if (requestedScopes.some((scope) => !record.scopes.includes(scope))) {\n return oauthError(400, \"invalid_scope\", \"Refresh cannot increase granted scopes\");\n }\n record.revoked = true;\n return json(200, this.issueTokenSet({ ...record, scopes: requestedScopes }));\n }\n\n private issueTokenSet(\n input: Pick<AccessTokenRecord, \"clientId\" | \"scopes\" | \"resource\">,\n ): McpFnMockOAuthTokenSet {\n const accessToken = `mcpfn_access_${randomUUID()}`;\n const refreshToken = `mcpfn_refresh_${randomUUID()}`;\n this.accessTokens.set(accessToken, {\n ...input,\n expiresAt: this.nowSeconds() + this.accessTokenLifetimeSeconds,\n revoked: false,\n });\n this.refreshTokens.set(refreshToken, {\n ...input,\n expiresAt: this.nowSeconds() + this.refreshTokenLifetimeSeconds,\n revoked: false,\n });\n return {\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: this.accessTokenLifetimeSeconds,\n refresh_token: refreshToken,\n scope: input.scopes.join(\" \"),\n ...(input.resource ? { resource: input.resource } : {}),\n };\n }\n\n private revoke(source: string): Response {\n const token = new URLSearchParams(source).get(\"token\");\n if (token) this.revokeToken(token);\n return new Response(null, { status: 200 });\n }\n}\n\nexport interface McpFnStartedMockOAuthServer {\n origin: string;\n oauth: McpFnMockOAuthAuthorizationServer;\n close(): Promise<void>;\n}\n\nexport interface McpFnStartMockOAuthServerOptions\n extends Omit<McpFnMockOAuthServerOptions, \"issuer\"> {\n hostname?: string;\n port?: number;\n /** Optional same-origin route used to compose a protected MCP fixture. */\n handle?(\n request: Request,\n oauth: McpFnMockOAuthAuthorizationServer,\n ): Promise<Response | undefined> | Response | undefined;\n}\n\nasync function toRequest(req: IncomingMessage, origin: string): Promise<Request> {\n const chunks: Buffer[] = [];\n for await (const chunk of req) chunks.push(Buffer.from(chunk));\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers)) {\n if (Array.isArray(value)) {\n for (const entry of value) headers.append(name, entry);\n } else if (value !== undefined) {\n headers.set(name, value);\n }\n }\n const body = chunks.length ? Buffer.concat(chunks) : undefined;\n const init: RequestInit & { duplex?: \"half\" } = {\n method: req.method,\n headers,\n ...(body ? { body, duplex: \"half\" } : {}),\n };\n return new Request(new URL(req.url ?? \"/\", origin), init);\n}\n\nasync function writeResponse(res: ServerResponse, response: Response): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, name) => res.setHeader(name, value));\n res.end(Buffer.from(await response.arrayBuffer()));\n}\n\nexport async function startMockOAuthAuthorizationServer(\n options: McpFnStartMockOAuthServerOptions = {},\n): Promise<McpFnStartedMockOAuthServer> {\n let oauth: McpFnMockOAuthAuthorizationServer | undefined;\n let origin = \"\";\n const server = createServer(async (req, res) => {\n try {\n if (!oauth) throw new Error(\"Mock OAuth server is not initialized\");\n const request = await toRequest(req, origin);\n const custom = await options.handle?.(request.clone(), oauth);\n await writeResponse(res, custom ?? await oauth.handle(request));\n } catch {\n res.statusCode = 500;\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify({\n error: \"server_error\",\n error_description: \"Internal Server Error\",\n }));\n }\n });\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(options.port ?? 0, options.hostname ?? \"127.0.0.1\", () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n const address = server.address() as AddressInfo;\n origin = `http://${address.address.includes(\":\") ? `[${address.address}]` : address.address}:${address.port}`;\n oauth = new McpFnMockOAuthAuthorizationServer({ ...options, issuer: origin });\n return {\n origin,\n oauth,\n close: () => new Promise<void>((resolve, reject) => {\n server.close((error) => error ? reject(error) : resolve());\n }),\n };\n}\n"],"mappings":"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","names":[]}
package/dist/auth.js CHANGED
@@ -307,7 +307,7 @@ function uniqueScopes(value) {
307
307
  function json(status, body) {
308
308
  return Response.json(body, {
309
309
  status,
310
- headers: { "cache-control": "no-store" }
310
+ headers: { "cache-control": "no-store", pragma: "no-cache" }
311
311
  });
312
312
  }
313
313
  function oauthError(status, error, description) {