@mcpfn/testing 0.0.4 → 0.0.5
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 +178 -2
- package/dist/auth.cjs +1 -1
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/index.cjs +3016 -158
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +426 -89
- package/dist/index.d.ts +426 -89
- package/dist/index.js +2993 -155
- package/dist/index.js.map +1 -1
- package/dist/playwright.cjs +1 -1
- package/dist/playwright.cjs.map +1 -1
- package/dist/playwright.js +1 -1
- package/dist/playwright.js.map +1 -1
- package/package.json +6 -5
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,
|
|
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(\"&\", \"&\")\n .replaceAll('\"', \""\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\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(\"&\", \"&\")\n .replaceAll('\"', \""\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\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) {
|