@mcpfn/testing 0.0.1 → 0.0.3
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 +21 -4
- package/dist/auth.cjs +35 -10
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +30 -1
- package/dist/auth.d.ts +30 -1
- package/dist/auth.js +35 -11
- package/dist/auth.js.map +1 -1
- package/dist/index.cjs +956 -148
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +242 -26
- package/dist/index.d.ts +242 -26
- package/dist/index.js +954 -151
- package/dist/index.js.map +1 -1
- package/dist/playwright.cjs +33 -10
- package/dist/playwright.cjs.map +1 -1
- package/dist/playwright.js +34 -11
- package/dist/playwright.js.map +1 -1
- package/package.json +12 -13
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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
9
|
- resource, prompt, completion, subscription, and task client methods;
|
|
10
10
|
- reusable API-key and OAuth resource-server regression matrices;
|
|
@@ -12,11 +12,20 @@ It includes:
|
|
|
12
12
|
- extensible Client ID Metadata fixtures that include unrelated grant types;
|
|
13
13
|
- optional Playwright fixtures for real redirect and consent-page coverage;
|
|
14
14
|
- generic tools-only, full-protocol, and MCP Apps host profiles;
|
|
15
|
+
- named ChatGPT- and Claude-shaped OAuth metadata fixtures;
|
|
16
|
+
- one suite for in-memory, custom, stdio, and Streamable HTTP targets;
|
|
15
17
|
- structured/text response parity checks;
|
|
16
|
-
- declarative
|
|
18
|
+
- version 1 declarative scenarios for capabilities, tasks, events, and auth phases;
|
|
19
|
+
- per-scenario timeout/cancellation, side-effect and incomplete metadata;
|
|
20
|
+
- bounded, redacted scenario and target-suite reports;
|
|
17
21
|
- orchestration of the official `@modelcontextprotocol/conformance` runner.
|
|
18
22
|
|
|
19
|
-
Official conformance validates protocol behavior. McpFn scenarios validate product behavior. Production MCP servers should run both.
|
|
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.
|
|
24
|
+
|
|
25
|
+
Use `runMcpFnTargetSuite({ target, scenarios, manifest })` when a test should
|
|
26
|
+
exercise a subprocess or deployed target. It constructs the same session used
|
|
27
|
+
by applications, the inspector, and CLI. Scenario execution is serial and
|
|
28
|
+
capability calls are never retried implicitly.
|
|
20
29
|
|
|
21
30
|
```ts
|
|
22
31
|
import {
|
|
@@ -45,7 +54,15 @@ try {
|
|
|
45
54
|
}
|
|
46
55
|
```
|
|
47
56
|
|
|
48
|
-
Scenarios run serially so stateful workflows and idempotency checks remain
|
|
57
|
+
Scenarios run serially so stateful workflows and idempotency checks remain
|
|
58
|
+
deterministic. Legacy arrays are readable; portable artifacts use
|
|
59
|
+
`{ formatVersion: 1, kind: "mcpfn.scenarios", status, scenarios }`. A runner
|
|
60
|
+
rejects an artifact whose top-level status is `incomplete`, even when its
|
|
61
|
+
individual scenarios are complete, so incomplete evidence cannot produce a
|
|
62
|
+
passing report. A runner
|
|
63
|
+
timeout supplies an abort signal and also bounds adapters that do not cooperate
|
|
64
|
+
with cancellation. `assertStructuredTextParity` requires a JSON text block; do
|
|
65
|
+
not use it for intentionally human-readable text.
|
|
49
66
|
|
|
50
67
|
`checkHostCompatibility(manifest, profile)` returns `compatible`, `degraded`, or `incompatible`. Unsupported optional server surfaces are degraded; missing protocol overlap or required client-mediated features are incompatible. The built-in profiles are stable capability fixtures, not claims about the current behavior of named commercial hosts. Supply a custom profile for a captured host version.
|
|
51
68
|
|
package/dist/auth.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/auth.ts
|
|
21
21
|
var auth_exports = {};
|
|
22
22
|
__export(auth_exports, {
|
|
23
|
+
MCPFN_NAMED_OAUTH_HOST_FIXTURES: () => MCPFN_NAMED_OAUTH_HOST_FIXTURES,
|
|
23
24
|
MCPFN_OAUTH_EXTENSION_GRANTS: () => MCPFN_OAUTH_EXTENSION_GRANTS,
|
|
24
25
|
McpFnAuthAssertionError: () => McpFnAuthAssertionError,
|
|
25
26
|
McpFnMockOAuthAuthorizationServer: () => McpFnMockOAuthAuthorizationServer,
|
|
@@ -261,6 +262,28 @@ var MCPFN_OAUTH_EXTENSION_GRANTS = {
|
|
|
261
262
|
deviceCode: "urn:ietf:params:oauth:grant-type:device_code",
|
|
262
263
|
generic: "urn:example:params:oauth:grant-type:extension"
|
|
263
264
|
};
|
|
265
|
+
var MCPFN_NAMED_OAUTH_HOST_FIXTURES = {
|
|
266
|
+
chatgpt: {
|
|
267
|
+
id: "chatgpt",
|
|
268
|
+
name: "ChatGPT connector",
|
|
269
|
+
redirectUris: ["https://chatgpt.com/connector_platform_oauth_redirect"],
|
|
270
|
+
responseTypes: ["code"],
|
|
271
|
+
grantTypes: ["authorization_code", "refresh_token"],
|
|
272
|
+
tokenEndpointAuthMethod: "none"
|
|
273
|
+
},
|
|
274
|
+
claude: {
|
|
275
|
+
id: "claude",
|
|
276
|
+
name: "Claude connector",
|
|
277
|
+
redirectUris: ["https://claude.example.com/oauth/callback"],
|
|
278
|
+
responseTypes: ["code"],
|
|
279
|
+
grantTypes: [
|
|
280
|
+
"authorization_code",
|
|
281
|
+
"refresh_token",
|
|
282
|
+
MCPFN_OAUTH_EXTENSION_GRANTS.jwtBearer
|
|
283
|
+
],
|
|
284
|
+
tokenEndpointAuthMethod: "none"
|
|
285
|
+
}
|
|
286
|
+
};
|
|
264
287
|
function createOAuthClientMetadataFixture(options) {
|
|
265
288
|
return {
|
|
266
289
|
...options.extraMetadata,
|
|
@@ -310,15 +333,13 @@ function assertAuthorizationCodeClientMetadata(metadata, expectedClientId) {
|
|
|
310
333
|
// src/mock-oauth-server.ts
|
|
311
334
|
var import_node_crypto = require("crypto");
|
|
312
335
|
var import_node_http = require("http");
|
|
313
|
-
|
|
314
|
-
return value.toString("base64url");
|
|
315
|
-
}
|
|
336
|
+
var import_oauth_core = require("@superfunctions/oauth-core");
|
|
316
337
|
function createPkcePair() {
|
|
317
|
-
const
|
|
338
|
+
const pair = (0, import_oauth_core.generatePkcePair)(48);
|
|
318
339
|
return {
|
|
319
|
-
verifier,
|
|
320
|
-
challenge:
|
|
321
|
-
method:
|
|
340
|
+
verifier: pair.codeVerifier,
|
|
341
|
+
challenge: pair.codeChallenge,
|
|
342
|
+
method: pair.codeChallengeMethod
|
|
322
343
|
};
|
|
323
344
|
}
|
|
324
345
|
function uniqueScopes(value) {
|
|
@@ -443,7 +464,8 @@ var McpFnMockOAuthAuthorizationServer = class {
|
|
|
443
464
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
444
465
|
code_challenge_methods_supported: ["S256"],
|
|
445
466
|
token_endpoint_auth_methods_supported: ["none"],
|
|
446
|
-
scopes_supported: this.scopesSupported
|
|
467
|
+
scopes_supported: this.scopesSupported,
|
|
468
|
+
client_id_metadata_document_supported: true
|
|
447
469
|
});
|
|
448
470
|
}
|
|
449
471
|
if (request.method === "GET" && path.startsWith("/client-metadata/")) {
|
|
@@ -555,7 +577,7 @@ var McpFnMockOAuthAuthorizationServer = class {
|
|
|
555
577
|
return oauthError(400, "invalid_grant", "Authorization code binding does not match");
|
|
556
578
|
}
|
|
557
579
|
const verifier = formValue(form, "code_verifier");
|
|
558
|
-
const challenge = verifier ? (0,
|
|
580
|
+
const challenge = verifier ? (0, import_oauth_core.derivePkceS256Challenge)(verifier) : void 0;
|
|
559
581
|
if (challenge !== record.codeChallenge) {
|
|
560
582
|
return oauthError(400, "invalid_grant", "PKCE verification failed");
|
|
561
583
|
}
|
|
@@ -636,7 +658,9 @@ async function startMockOAuthAuthorizationServer(options = {}) {
|
|
|
636
658
|
const server = (0, import_node_http.createServer)(async (req, res) => {
|
|
637
659
|
try {
|
|
638
660
|
if (!oauth) throw new Error("Mock OAuth server is not initialized");
|
|
639
|
-
|
|
661
|
+
const request = await toRequest(req, origin);
|
|
662
|
+
const custom = await options.handle?.(request.clone(), oauth);
|
|
663
|
+
await writeResponse(res, custom ?? await oauth.handle(request));
|
|
640
664
|
} catch {
|
|
641
665
|
res.statusCode = 500;
|
|
642
666
|
res.setHeader("content-type", "application/json");
|
|
@@ -666,6 +690,7 @@ async function startMockOAuthAuthorizationServer(options = {}) {
|
|
|
666
690
|
}
|
|
667
691
|
// Annotate the CommonJS export names for ESM import in node:
|
|
668
692
|
0 && (module.exports = {
|
|
693
|
+
MCPFN_NAMED_OAUTH_HOST_FIXTURES,
|
|
669
694
|
MCPFN_OAUTH_EXTENSION_GRANTS,
|
|
670
695
|
McpFnAuthAssertionError,
|
|
671
696
|
McpFnMockOAuthAuthorizationServer,
|
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 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 { createHash, randomBytes, 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\";\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\nfunction base64Url(value: Buffer): string {\n return value.toString(\"base64url\");\n}\n\nexport function createPkcePair(): McpFnPkcePair {\n const verifier = base64Url(randomBytes(48));\n return {\n verifier,\n challenge: createHash(\"sha256\").update(verifier).digest(\"base64url\"),\n method: \"S256\",\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 });\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\n ? createHash(\"sha256\").update(verifier).digest(\"base64url\")\n : 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}\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 await writeResponse(res, await oauth.handle(await toRequest(req, origin)));\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\" },\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.d.cts
CHANGED
|
@@ -104,6 +104,33 @@ interface McpFnOAuthClientMetadataVariants {
|
|
|
104
104
|
deviceCodeExtension: McpFnOAuthClientMetadata;
|
|
105
105
|
genericExtension: McpFnOAuthClientMetadata;
|
|
106
106
|
}
|
|
107
|
+
interface McpFnNamedOAuthHostFixture {
|
|
108
|
+
id: "chatgpt" | "claude";
|
|
109
|
+
name: string;
|
|
110
|
+
redirectUris: string[];
|
|
111
|
+
responseTypes: ["code"];
|
|
112
|
+
grantTypes: string[];
|
|
113
|
+
tokenEndpointAuthMethod: "none";
|
|
114
|
+
}
|
|
115
|
+
/** Host-shaped fixtures; applications still configure their registered production URLs. */
|
|
116
|
+
declare const MCPFN_NAMED_OAUTH_HOST_FIXTURES: {
|
|
117
|
+
readonly chatgpt: {
|
|
118
|
+
readonly id: "chatgpt";
|
|
119
|
+
readonly name: "ChatGPT connector";
|
|
120
|
+
readonly redirectUris: ["https://chatgpt.com/connector_platform_oauth_redirect"];
|
|
121
|
+
readonly responseTypes: ["code"];
|
|
122
|
+
readonly grantTypes: ["authorization_code", "refresh_token"];
|
|
123
|
+
readonly tokenEndpointAuthMethod: "none";
|
|
124
|
+
};
|
|
125
|
+
readonly claude: {
|
|
126
|
+
readonly id: "claude";
|
|
127
|
+
readonly name: "Claude connector";
|
|
128
|
+
readonly redirectUris: ["https://claude.example.com/oauth/callback"];
|
|
129
|
+
readonly responseTypes: ["code"];
|
|
130
|
+
readonly grantTypes: ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:jwt-bearer"];
|
|
131
|
+
readonly tokenEndpointAuthMethod: "none";
|
|
132
|
+
};
|
|
133
|
+
};
|
|
107
134
|
declare function createOAuthClientMetadataFixture(options: McpFnOAuthClientMetadataFixtureOptions): McpFnOAuthClientMetadata;
|
|
108
135
|
declare function createOAuthClientMetadataVariants(baseUrl: string | URL, redirectUri?: string): McpFnOAuthClientMetadataVariants;
|
|
109
136
|
/**
|
|
@@ -193,7 +220,9 @@ interface McpFnStartedMockOAuthServer {
|
|
|
193
220
|
interface McpFnStartMockOAuthServerOptions extends Omit<McpFnMockOAuthServerOptions, "issuer"> {
|
|
194
221
|
hostname?: string;
|
|
195
222
|
port?: number;
|
|
223
|
+
/** Optional same-origin route used to compose a protected MCP fixture. */
|
|
224
|
+
handle?(request: Request, oauth: McpFnMockOAuthAuthorizationServer): Promise<Response | undefined> | Response | undefined;
|
|
196
225
|
}
|
|
197
226
|
declare function startMockOAuthAuthorizationServer(options?: McpFnStartMockOAuthServerOptions): Promise<McpFnStartedMockOAuthServer>;
|
|
198
227
|
|
|
199
|
-
export { MCPFN_OAUTH_EXTENSION_GRANTS, McpFnAuthAssertionError, type McpFnAuthCredential, type McpFnAuthCredentialKind, type McpFnAuthCredentialRequest, type McpFnAuthProbeScenario, type McpFnAuthProviderAdapter, type McpFnAuthProviderCapabilities, type McpFnAuthRegressionOptions, type McpFnAuthScenarioResult, type McpFnAuthTarget, type McpFnBearerChallenge, type McpFnFetchAuthTargetOptions, McpFnMockOAuthAuthorizationServer, type McpFnMockOAuthServerOptions, type McpFnMockOAuthTokenSet, type McpFnOAuthCallback, type McpFnOAuthClientMetadata, type McpFnOAuthClientMetadataFixtureOptions, type McpFnOAuthClientMetadataVariants, type McpFnPkcePair, type McpFnStartMockOAuthServerOptions, type McpFnStartedMockOAuthServer, apiKeyCredential, assertAuthRegressionSuite, assertAuthorizationCodeClientMetadata, assertBearerResourceChallenge, bearerCredential, createFetchAuthTarget, createOAuthClientMetadataFixture, createOAuthClientMetadataVariants, createPkcePair, parseBearerChallenge, runAuthRegressionSuite, startMockOAuthAuthorizationServer };
|
|
228
|
+
export { MCPFN_NAMED_OAUTH_HOST_FIXTURES, MCPFN_OAUTH_EXTENSION_GRANTS, McpFnAuthAssertionError, type McpFnAuthCredential, type McpFnAuthCredentialKind, type McpFnAuthCredentialRequest, type McpFnAuthProbeScenario, type McpFnAuthProviderAdapter, type McpFnAuthProviderCapabilities, type McpFnAuthRegressionOptions, type McpFnAuthScenarioResult, type McpFnAuthTarget, type McpFnBearerChallenge, type McpFnFetchAuthTargetOptions, McpFnMockOAuthAuthorizationServer, type McpFnMockOAuthServerOptions, type McpFnMockOAuthTokenSet, type McpFnNamedOAuthHostFixture, type McpFnOAuthCallback, type McpFnOAuthClientMetadata, type McpFnOAuthClientMetadataFixtureOptions, type McpFnOAuthClientMetadataVariants, type McpFnPkcePair, type McpFnStartMockOAuthServerOptions, type McpFnStartedMockOAuthServer, apiKeyCredential, assertAuthRegressionSuite, assertAuthorizationCodeClientMetadata, assertBearerResourceChallenge, bearerCredential, createFetchAuthTarget, createOAuthClientMetadataFixture, createOAuthClientMetadataVariants, createPkcePair, parseBearerChallenge, runAuthRegressionSuite, startMockOAuthAuthorizationServer };
|
package/dist/auth.d.ts
CHANGED
|
@@ -104,6 +104,33 @@ interface McpFnOAuthClientMetadataVariants {
|
|
|
104
104
|
deviceCodeExtension: McpFnOAuthClientMetadata;
|
|
105
105
|
genericExtension: McpFnOAuthClientMetadata;
|
|
106
106
|
}
|
|
107
|
+
interface McpFnNamedOAuthHostFixture {
|
|
108
|
+
id: "chatgpt" | "claude";
|
|
109
|
+
name: string;
|
|
110
|
+
redirectUris: string[];
|
|
111
|
+
responseTypes: ["code"];
|
|
112
|
+
grantTypes: string[];
|
|
113
|
+
tokenEndpointAuthMethod: "none";
|
|
114
|
+
}
|
|
115
|
+
/** Host-shaped fixtures; applications still configure their registered production URLs. */
|
|
116
|
+
declare const MCPFN_NAMED_OAUTH_HOST_FIXTURES: {
|
|
117
|
+
readonly chatgpt: {
|
|
118
|
+
readonly id: "chatgpt";
|
|
119
|
+
readonly name: "ChatGPT connector";
|
|
120
|
+
readonly redirectUris: ["https://chatgpt.com/connector_platform_oauth_redirect"];
|
|
121
|
+
readonly responseTypes: ["code"];
|
|
122
|
+
readonly grantTypes: ["authorization_code", "refresh_token"];
|
|
123
|
+
readonly tokenEndpointAuthMethod: "none";
|
|
124
|
+
};
|
|
125
|
+
readonly claude: {
|
|
126
|
+
readonly id: "claude";
|
|
127
|
+
readonly name: "Claude connector";
|
|
128
|
+
readonly redirectUris: ["https://claude.example.com/oauth/callback"];
|
|
129
|
+
readonly responseTypes: ["code"];
|
|
130
|
+
readonly grantTypes: ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:jwt-bearer"];
|
|
131
|
+
readonly tokenEndpointAuthMethod: "none";
|
|
132
|
+
};
|
|
133
|
+
};
|
|
107
134
|
declare function createOAuthClientMetadataFixture(options: McpFnOAuthClientMetadataFixtureOptions): McpFnOAuthClientMetadata;
|
|
108
135
|
declare function createOAuthClientMetadataVariants(baseUrl: string | URL, redirectUri?: string): McpFnOAuthClientMetadataVariants;
|
|
109
136
|
/**
|
|
@@ -193,7 +220,9 @@ interface McpFnStartedMockOAuthServer {
|
|
|
193
220
|
interface McpFnStartMockOAuthServerOptions extends Omit<McpFnMockOAuthServerOptions, "issuer"> {
|
|
194
221
|
hostname?: string;
|
|
195
222
|
port?: number;
|
|
223
|
+
/** Optional same-origin route used to compose a protected MCP fixture. */
|
|
224
|
+
handle?(request: Request, oauth: McpFnMockOAuthAuthorizationServer): Promise<Response | undefined> | Response | undefined;
|
|
196
225
|
}
|
|
197
226
|
declare function startMockOAuthAuthorizationServer(options?: McpFnStartMockOAuthServerOptions): Promise<McpFnStartedMockOAuthServer>;
|
|
198
227
|
|
|
199
|
-
export { MCPFN_OAUTH_EXTENSION_GRANTS, McpFnAuthAssertionError, type McpFnAuthCredential, type McpFnAuthCredentialKind, type McpFnAuthCredentialRequest, type McpFnAuthProbeScenario, type McpFnAuthProviderAdapter, type McpFnAuthProviderCapabilities, type McpFnAuthRegressionOptions, type McpFnAuthScenarioResult, type McpFnAuthTarget, type McpFnBearerChallenge, type McpFnFetchAuthTargetOptions, McpFnMockOAuthAuthorizationServer, type McpFnMockOAuthServerOptions, type McpFnMockOAuthTokenSet, type McpFnOAuthCallback, type McpFnOAuthClientMetadata, type McpFnOAuthClientMetadataFixtureOptions, type McpFnOAuthClientMetadataVariants, type McpFnPkcePair, type McpFnStartMockOAuthServerOptions, type McpFnStartedMockOAuthServer, apiKeyCredential, assertAuthRegressionSuite, assertAuthorizationCodeClientMetadata, assertBearerResourceChallenge, bearerCredential, createFetchAuthTarget, createOAuthClientMetadataFixture, createOAuthClientMetadataVariants, createPkcePair, parseBearerChallenge, runAuthRegressionSuite, startMockOAuthAuthorizationServer };
|
|
228
|
+
export { MCPFN_NAMED_OAUTH_HOST_FIXTURES, MCPFN_OAUTH_EXTENSION_GRANTS, McpFnAuthAssertionError, type McpFnAuthCredential, type McpFnAuthCredentialKind, type McpFnAuthCredentialRequest, type McpFnAuthProbeScenario, type McpFnAuthProviderAdapter, type McpFnAuthProviderCapabilities, type McpFnAuthRegressionOptions, type McpFnAuthScenarioResult, type McpFnAuthTarget, type McpFnBearerChallenge, type McpFnFetchAuthTargetOptions, McpFnMockOAuthAuthorizationServer, type McpFnMockOAuthServerOptions, type McpFnMockOAuthTokenSet, type McpFnNamedOAuthHostFixture, type McpFnOAuthCallback, type McpFnOAuthClientMetadata, type McpFnOAuthClientMetadataFixtureOptions, type McpFnOAuthClientMetadataVariants, type McpFnPkcePair, type McpFnStartMockOAuthServerOptions, type McpFnStartedMockOAuthServer, apiKeyCredential, assertAuthRegressionSuite, assertAuthorizationCodeClientMetadata, assertBearerResourceChallenge, bearerCredential, createFetchAuthTarget, createOAuthClientMetadataFixture, createOAuthClientMetadataVariants, createPkcePair, parseBearerChallenge, runAuthRegressionSuite, startMockOAuthAuthorizationServer };
|
package/dist/auth.js
CHANGED
|
@@ -221,6 +221,28 @@ var MCPFN_OAUTH_EXTENSION_GRANTS = {
|
|
|
221
221
|
deviceCode: "urn:ietf:params:oauth:grant-type:device_code",
|
|
222
222
|
generic: "urn:example:params:oauth:grant-type:extension"
|
|
223
223
|
};
|
|
224
|
+
var MCPFN_NAMED_OAUTH_HOST_FIXTURES = {
|
|
225
|
+
chatgpt: {
|
|
226
|
+
id: "chatgpt",
|
|
227
|
+
name: "ChatGPT connector",
|
|
228
|
+
redirectUris: ["https://chatgpt.com/connector_platform_oauth_redirect"],
|
|
229
|
+
responseTypes: ["code"],
|
|
230
|
+
grantTypes: ["authorization_code", "refresh_token"],
|
|
231
|
+
tokenEndpointAuthMethod: "none"
|
|
232
|
+
},
|
|
233
|
+
claude: {
|
|
234
|
+
id: "claude",
|
|
235
|
+
name: "Claude connector",
|
|
236
|
+
redirectUris: ["https://claude.example.com/oauth/callback"],
|
|
237
|
+
responseTypes: ["code"],
|
|
238
|
+
grantTypes: [
|
|
239
|
+
"authorization_code",
|
|
240
|
+
"refresh_token",
|
|
241
|
+
MCPFN_OAUTH_EXTENSION_GRANTS.jwtBearer
|
|
242
|
+
],
|
|
243
|
+
tokenEndpointAuthMethod: "none"
|
|
244
|
+
}
|
|
245
|
+
};
|
|
224
246
|
function createOAuthClientMetadataFixture(options) {
|
|
225
247
|
return {
|
|
226
248
|
...options.extraMetadata,
|
|
@@ -268,17 +290,15 @@ function assertAuthorizationCodeClientMetadata(metadata, expectedClientId) {
|
|
|
268
290
|
}
|
|
269
291
|
|
|
270
292
|
// src/mock-oauth-server.ts
|
|
271
|
-
import {
|
|
293
|
+
import { randomUUID } from "crypto";
|
|
272
294
|
import { createServer } from "http";
|
|
273
|
-
|
|
274
|
-
return value.toString("base64url");
|
|
275
|
-
}
|
|
295
|
+
import { derivePkceS256Challenge, generatePkcePair } from "@superfunctions/oauth-core";
|
|
276
296
|
function createPkcePair() {
|
|
277
|
-
const
|
|
297
|
+
const pair = generatePkcePair(48);
|
|
278
298
|
return {
|
|
279
|
-
verifier,
|
|
280
|
-
challenge:
|
|
281
|
-
method:
|
|
299
|
+
verifier: pair.codeVerifier,
|
|
300
|
+
challenge: pair.codeChallenge,
|
|
301
|
+
method: pair.codeChallengeMethod
|
|
282
302
|
};
|
|
283
303
|
}
|
|
284
304
|
function uniqueScopes(value) {
|
|
@@ -403,7 +423,8 @@ var McpFnMockOAuthAuthorizationServer = class {
|
|
|
403
423
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
404
424
|
code_challenge_methods_supported: ["S256"],
|
|
405
425
|
token_endpoint_auth_methods_supported: ["none"],
|
|
406
|
-
scopes_supported: this.scopesSupported
|
|
426
|
+
scopes_supported: this.scopesSupported,
|
|
427
|
+
client_id_metadata_document_supported: true
|
|
407
428
|
});
|
|
408
429
|
}
|
|
409
430
|
if (request.method === "GET" && path.startsWith("/client-metadata/")) {
|
|
@@ -515,7 +536,7 @@ var McpFnMockOAuthAuthorizationServer = class {
|
|
|
515
536
|
return oauthError(400, "invalid_grant", "Authorization code binding does not match");
|
|
516
537
|
}
|
|
517
538
|
const verifier = formValue(form, "code_verifier");
|
|
518
|
-
const challenge = verifier ?
|
|
539
|
+
const challenge = verifier ? derivePkceS256Challenge(verifier) : void 0;
|
|
519
540
|
if (challenge !== record.codeChallenge) {
|
|
520
541
|
return oauthError(400, "invalid_grant", "PKCE verification failed");
|
|
521
542
|
}
|
|
@@ -596,7 +617,9 @@ async function startMockOAuthAuthorizationServer(options = {}) {
|
|
|
596
617
|
const server = createServer(async (req, res) => {
|
|
597
618
|
try {
|
|
598
619
|
if (!oauth) throw new Error("Mock OAuth server is not initialized");
|
|
599
|
-
|
|
620
|
+
const request = await toRequest(req, origin);
|
|
621
|
+
const custom = await options.handle?.(request.clone(), oauth);
|
|
622
|
+
await writeResponse(res, custom ?? await oauth.handle(request));
|
|
600
623
|
} catch {
|
|
601
624
|
res.statusCode = 500;
|
|
602
625
|
res.setHeader("content-type", "application/json");
|
|
@@ -625,6 +648,7 @@ async function startMockOAuthAuthorizationServer(options = {}) {
|
|
|
625
648
|
};
|
|
626
649
|
}
|
|
627
650
|
export {
|
|
651
|
+
MCPFN_NAMED_OAUTH_HOST_FIXTURES,
|
|
628
652
|
MCPFN_OAUTH_EXTENSION_GRANTS,
|
|
629
653
|
McpFnAuthAssertionError,
|
|
630
654
|
McpFnMockOAuthAuthorizationServer,
|