@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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  It includes:
6
6
 
7
- - an official MCP client fixture;
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 semantic scenarios;
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 deterministic. `assertStructuredTextParity` requires a JSON text block; do not use it for intentionally human-readable text.
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
- function base64Url(value) {
314
- return value.toString("base64url");
315
- }
336
+ var import_oauth_core = require("@superfunctions/oauth-core");
316
337
  function createPkcePair() {
317
- const verifier = base64Url((0, import_node_crypto.randomBytes)(48));
338
+ const pair = (0, import_oauth_core.generatePkcePair)(48);
318
339
  return {
319
- verifier,
320
- challenge: (0, import_node_crypto.createHash)("sha256").update(verifier).digest("base64url"),
321
- method: "S256"
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, import_node_crypto.createHash)("sha256").update(verifier).digest("base64url") : void 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
- await writeResponse(res, await oauth.handle(await toRequest(req, origin)));
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(\"&\", \"&amp;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\");\n}\n\nfunction formValue(form: URLSearchParams, name: string): string | undefined {\n const value = form.get(name);\n return value === null || value === \"\" ? undefined : value;\n}\n\nexport class McpFnMockOAuthAuthorizationServer {\n readonly issuer: URL;\n readonly clientId: string;\n readonly redirectUris: string[];\n readonly scopesSupported: string[];\n readonly clientMetadata: McpFnOAuthClientMetadataVariants;\n\n private readonly codes = new Map<string, AuthorizationCodeRecord>();\n private readonly accessTokens = new Map<string, AccessTokenRecord>();\n private readonly refreshTokens = new Map<string, RefreshTokenRecord>();\n private readonly callbacks: McpFnOAuthCallback[] = [];\n private readonly clock: () => number;\n private readonly accessTokenLifetimeSeconds: number;\n private readonly authorizationCodeLifetimeSeconds: number;\n private readonly refreshTokenLifetimeSeconds: number;\n\n constructor(options: McpFnMockOAuthServerOptions) {\n this.issuer = new URL(options.issuer);\n this.issuer.pathname = this.issuer.pathname.replace(/\\/?$/, \"/\");\n this.clientId = options.clientId ?? \"mcpfn-playwright\";\n this.redirectUris = options.redirectUris?.map((uri) => new URL(uri).toString()) ?? [\n new URL(\"callback\", this.issuer).toString(),\n ];\n this.scopesSupported = [...new Set(options.scopesSupported ?? [\"mcp:read\", \"mcp:write\"])].sort();\n this.clock = options.clock ?? Date.now;\n this.accessTokenLifetimeSeconds = options.accessTokenLifetimeSeconds ?? 300;\n this.authorizationCodeLifetimeSeconds = options.authorizationCodeLifetimeSeconds ?? 120;\n this.refreshTokenLifetimeSeconds = options.refreshTokenLifetimeSeconds ?? 3_600;\n this.clientMetadata = createOAuthClientMetadataVariants(\n this.issuer,\n this.redirectUris[0],\n );\n }\n\n get metadataUrl(): string {\n return new URL(\".well-known/oauth-authorization-server\", this.issuer).toString();\n }\n\n get authorizationEndpoint(): string {\n return new URL(\"authorize\", this.issuer).toString();\n }\n\n get tokenEndpoint(): string {\n return new URL(\"token\", this.issuer).toString();\n }\n\n get revocationEndpoint(): string {\n return new URL(\"revoke\", this.issuer).toString();\n }\n\n get callbackUrl(): string {\n return this.redirectUris[0];\n }\n\n clientMetadataUrl(\n variant: keyof McpFnOAuthClientMetadataVariants,\n ): string {\n return this.clientMetadata[variant].client_id;\n }\n\n authorizationUrl(input: {\n clientId?: string;\n redirectUri?: string;\n codeChallenge: string;\n state?: string;\n scopes?: string[];\n resource?: string;\n extraParameters?: Record<string, string>;\n }): string {\n const url = new URL(this.authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", input.clientId ?? this.clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUri ?? this.callbackUrl);\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\"scope\", (input.scopes ?? [\"mcp:read\"]).join(\" \"));\n if (input.state) url.searchParams.set(\"state\", input.state);\n if (input.resource) url.searchParams.set(\"resource\", input.resource);\n for (const [name, value] of Object.entries(input.extraParameters ?? {})) {\n url.searchParams.set(name, value);\n }\n return url.toString();\n }\n\n latestCallback(): McpFnOAuthCallback | undefined {\n return this.callbacks.at(-1);\n }\n\n expireAccessToken(token: string): void {\n const record = this.accessTokens.get(token);\n if (record) record.expiresAt = this.nowSeconds() - 1;\n }\n\n revokeToken(token: string): void {\n const access = this.accessTokens.get(token);\n if (access) access.revoked = true;\n const refresh = this.refreshTokens.get(token);\n if (refresh) refresh.revoked = true;\n }\n\n async verifyAccessToken(token: string): Promise<AuthInfo> {\n const record = this.accessTokens.get(token);\n if (!record || record.revoked || record.expiresAt <= this.nowSeconds()) {\n throw new Error(\"Invalid access token\");\n }\n return {\n token,\n clientId: record.clientId,\n scopes: record.scopes,\n expiresAt: record.expiresAt,\n ...(record.resource ? { resource: new URL(record.resource) } : {}),\n };\n }\n\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const path = url.pathname.replace(this.issuer.pathname.replace(/\\/$/, \"\"), \"\") || \"/\";\n if (request.method === \"GET\" && path === \"/.well-known/oauth-authorization-server\") {\n return json(200, {\n issuer: this.issuer.toString(),\n authorization_endpoint: this.authorizationEndpoint,\n token_endpoint: this.tokenEndpoint,\n revocation_endpoint: this.revocationEndpoint,\n response_types_supported: [\"code\"],\n grant_types_supported: [\"authorization_code\", \"refresh_token\"],\n code_challenge_methods_supported: [\"S256\"],\n token_endpoint_auth_methods_supported: [\"none\"],\n scopes_supported: this.scopesSupported,\n });\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(\"&\", \"&amp;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\");\n}\n\nfunction formValue(form: URLSearchParams, name: string): string | undefined {\n const value = form.get(name);\n return value === null || value === \"\" ? undefined : value;\n}\n\nexport class McpFnMockOAuthAuthorizationServer {\n readonly issuer: URL;\n readonly clientId: string;\n readonly redirectUris: string[];\n readonly scopesSupported: string[];\n readonly clientMetadata: McpFnOAuthClientMetadataVariants;\n\n private readonly codes = new Map<string, AuthorizationCodeRecord>();\n private readonly accessTokens = new Map<string, AccessTokenRecord>();\n private readonly refreshTokens = new Map<string, RefreshTokenRecord>();\n private readonly callbacks: McpFnOAuthCallback[] = [];\n private readonly clock: () => number;\n private readonly accessTokenLifetimeSeconds: number;\n private readonly authorizationCodeLifetimeSeconds: number;\n private readonly refreshTokenLifetimeSeconds: number;\n\n constructor(options: McpFnMockOAuthServerOptions) {\n this.issuer = new URL(options.issuer);\n this.issuer.pathname = this.issuer.pathname.replace(/\\/?$/, \"/\");\n this.clientId = options.clientId ?? \"mcpfn-playwright\";\n this.redirectUris = options.redirectUris?.map((uri) => new URL(uri).toString()) ?? [\n new URL(\"callback\", this.issuer).toString(),\n ];\n this.scopesSupported = [...new Set(options.scopesSupported ?? [\"mcp:read\", \"mcp:write\"])].sort();\n this.clock = options.clock ?? Date.now;\n this.accessTokenLifetimeSeconds = options.accessTokenLifetimeSeconds ?? 300;\n this.authorizationCodeLifetimeSeconds = options.authorizationCodeLifetimeSeconds ?? 120;\n this.refreshTokenLifetimeSeconds = options.refreshTokenLifetimeSeconds ?? 3_600;\n this.clientMetadata = createOAuthClientMetadataVariants(\n this.issuer,\n this.redirectUris[0],\n );\n }\n\n get metadataUrl(): string {\n return new URL(\".well-known/oauth-authorization-server\", this.issuer).toString();\n }\n\n get authorizationEndpoint(): string {\n return new URL(\"authorize\", this.issuer).toString();\n }\n\n get tokenEndpoint(): string {\n return new URL(\"token\", this.issuer).toString();\n }\n\n get revocationEndpoint(): string {\n return new URL(\"revoke\", this.issuer).toString();\n }\n\n get callbackUrl(): string {\n return this.redirectUris[0];\n }\n\n clientMetadataUrl(\n variant: keyof McpFnOAuthClientMetadataVariants,\n ): string {\n return this.clientMetadata[variant].client_id;\n }\n\n authorizationUrl(input: {\n clientId?: string;\n redirectUri?: string;\n codeChallenge: string;\n state?: string;\n scopes?: string[];\n resource?: string;\n extraParameters?: Record<string, string>;\n }): string {\n const url = new URL(this.authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", input.clientId ?? this.clientId);\n url.searchParams.set(\"redirect_uri\", input.redirectUri ?? this.callbackUrl);\n url.searchParams.set(\"code_challenge\", input.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\"scope\", (input.scopes ?? [\"mcp:read\"]).join(\" \"));\n if (input.state) url.searchParams.set(\"state\", input.state);\n if (input.resource) url.searchParams.set(\"resource\", input.resource);\n for (const [name, value] of Object.entries(input.extraParameters ?? {})) {\n url.searchParams.set(name, value);\n }\n return url.toString();\n }\n\n latestCallback(): McpFnOAuthCallback | undefined {\n return this.callbacks.at(-1);\n }\n\n expireAccessToken(token: string): void {\n const record = this.accessTokens.get(token);\n if (record) record.expiresAt = this.nowSeconds() - 1;\n }\n\n revokeToken(token: string): void {\n const access = this.accessTokens.get(token);\n if (access) access.revoked = true;\n const refresh = this.refreshTokens.get(token);\n if (refresh) refresh.revoked = true;\n }\n\n async verifyAccessToken(token: string): Promise<AuthInfo> {\n const record = this.accessTokens.get(token);\n if (!record || record.revoked || record.expiresAt <= this.nowSeconds()) {\n throw new Error(\"Invalid access token\");\n }\n return {\n token,\n clientId: record.clientId,\n scopes: record.scopes,\n expiresAt: record.expiresAt,\n ...(record.resource ? { resource: new URL(record.resource) } : {}),\n };\n }\n\n async handle(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const path = url.pathname.replace(this.issuer.pathname.replace(/\\/$/, \"\"), \"\") || \"/\";\n if (request.method === \"GET\" && path === \"/.well-known/oauth-authorization-server\") {\n return json(200, {\n issuer: this.issuer.toString(),\n authorization_endpoint: this.authorizationEndpoint,\n token_endpoint: this.tokenEndpoint,\n revocation_endpoint: this.revocationEndpoint,\n response_types_supported: [\"code\"],\n grant_types_supported: [\"authorization_code\", \"refresh_token\"],\n code_challenge_methods_supported: [\"S256\"],\n token_endpoint_auth_methods_supported: [\"none\"],\n scopes_supported: this.scopesSupported,\n client_id_metadata_document_supported: true,\n });\n }\n\n if (request.method === \"GET\" && path.startsWith(\"/client-metadata/\")) {\n const metadata = Object.values(this.clientMetadata)\n .find((candidate) => new URL(candidate.client_id).pathname === url.pathname);\n return metadata ? json(200, metadata) : json(404, { error: \"not_found\" });\n }\n\n if (request.method === \"GET\" && path === \"/authorize\") {\n return this.authorize(url);\n }\n if (request.method === \"POST\" && path === \"/token\") {\n return this.token(await request.text());\n }\n if (request.method === \"POST\" && path === \"/revoke\") {\n return this.revoke(await request.text());\n }\n if (request.method === \"GET\" && path === \"/callback\") {\n const parameters = Object.fromEntries(url.searchParams);\n this.callbacks.push({ url: url.toString(), parameters });\n return new Response(\n `<!doctype html><html><body><main><h1>OAuth callback received</h1><pre>${escapeHtml(JSON.stringify(parameters, null, 2))}</pre></main></body></html>`,\n { headers: { \"content-type\": \"text/html; charset=utf-8\" } },\n );\n }\n return json(404, { error: \"not_found\" });\n }\n\n private nowSeconds(): number {\n return Math.floor(this.clock() / 1_000);\n }\n\n private resolveClient(clientId: string): { clientId: string; redirectUris: string[] } | undefined {\n if (clientId === this.clientId) {\n return { clientId, redirectUris: this.redirectUris };\n }\n const metadata = Object.values(this.clientMetadata)\n .find((candidate) => candidate.client_id === clientId);\n if (!metadata) return undefined;\n assertAuthorizationCodeClientMetadata(metadata, clientId);\n return { clientId, redirectUris: metadata.redirect_uris };\n }\n\n private authorize(url: URL): Response {\n const clientId = url.searchParams.get(\"client_id\") ?? \"\";\n const redirectUri = url.searchParams.get(\"redirect_uri\") ?? \"\";\n const client = this.resolveClient(clientId);\n if (!client) return oauthError(400, \"invalid_client\", \"Unknown OAuth client\");\n if (!client.redirectUris.includes(redirectUri)) {\n return oauthError(400, \"invalid_request\", \"redirect_uri is not registered\");\n }\n if (url.searchParams.get(\"response_type\") !== \"code\") {\n return this.authorizationRedirect(url, redirectUri, \"unsupported_response_type\");\n }\n const codeChallenge = url.searchParams.get(\"code_challenge\");\n if (!codeChallenge || url.searchParams.get(\"code_challenge_method\") !== \"S256\") {\n return this.authorizationRedirect(url, redirectUri, \"invalid_request\", \"PKCE S256 is required\");\n }\n const requestedScopes = uniqueScopes(url.searchParams.get(\"scope\"));\n if (requestedScopes.some((scope) => !this.scopesSupported.includes(scope))) {\n return this.authorizationRedirect(url, redirectUri, \"invalid_scope\");\n }\n const decision = url.searchParams.get(\"decision\");\n if (!decision) {\n const hidden = [...url.searchParams]\n .filter(([name]) => name !== \"decision\")\n .map(([name, value]) => `<input type=\"hidden\" name=\"${escapeHtml(name)}\" value=\"${escapeHtml(value)}\">`)\n .join(\"\");\n return new Response(\n `<!doctype html><html><body><main><h1>Authorize MCP client</h1><p>${escapeHtml(clientId)}</p><form method=\"get\" action=\"${escapeHtml(this.authorizationEndpoint)}\">${hidden}<button name=\"decision\" value=\"approve\">Approve</button><button name=\"decision\" value=\"deny\">Deny</button></form></main></body></html>`,\n { headers: { \"content-type\": \"text/html; charset=utf-8\", \"cache-control\": \"no-store\" } },\n );\n }\n if (decision === \"deny\") {\n return this.authorizationRedirect(url, redirectUri, \"access_denied\");\n }\n if (decision !== \"approve\") {\n return oauthError(400, \"invalid_request\", \"Unknown authorization decision\");\n }\n const code = `mcpfn_code_${randomUUID()}`;\n this.codes.set(code, {\n clientId,\n redirectUri,\n codeChallenge,\n scopes: requestedScopes,\n resource: url.searchParams.get(\"resource\") ?? undefined,\n expiresAt: this.nowSeconds() + this.authorizationCodeLifetimeSeconds,\n used: false,\n });\n const callback = new URL(redirectUri);\n callback.searchParams.set(\"code\", code);\n const state = url.searchParams.get(\"state\");\n if (state) callback.searchParams.set(\"state\", state);\n return Response.redirect(callback, 302);\n }\n\n private authorizationRedirect(\n requestUrl: URL,\n redirectUri: string,\n error: string,\n description?: string,\n ): Response {\n const callback = new URL(redirectUri);\n callback.searchParams.set(\"error\", error);\n if (description) callback.searchParams.set(\"error_description\", description);\n const state = requestUrl.searchParams.get(\"state\");\n if (state) callback.searchParams.set(\"state\", state);\n return Response.redirect(callback, 302);\n }\n\n private token(source: string): Response {\n const form = new URLSearchParams(source);\n const grantType = formValue(form, \"grant_type\");\n if (grantType === \"authorization_code\") return this.exchangeAuthorizationCode(form);\n if (grantType === \"refresh_token\") return this.exchangeRefreshToken(form);\n return oauthError(400, \"unsupported_grant_type\", \"The grant type is not supported\");\n }\n\n private exchangeAuthorizationCode(form: URLSearchParams): Response {\n const code = formValue(form, \"code\");\n const record = code ? this.codes.get(code) : undefined;\n if (!record || record.used || record.expiresAt <= this.nowSeconds()) {\n return oauthError(400, \"invalid_grant\", \"Authorization code is invalid or expired\");\n }\n if (\n formValue(form, \"client_id\") !== record.clientId ||\n formValue(form, \"redirect_uri\") !== record.redirectUri\n ) {\n return oauthError(400, \"invalid_grant\", \"Authorization code binding does not match\");\n }\n const verifier = formValue(form, \"code_verifier\");\n const challenge = verifier ? derivePkceS256Challenge(verifier) : undefined;\n if (challenge !== record.codeChallenge) {\n return oauthError(400, \"invalid_grant\", \"PKCE verification failed\");\n }\n record.used = true;\n return json(200, this.issueTokenSet(record));\n }\n\n private exchangeRefreshToken(form: URLSearchParams): Response {\n const refreshToken = formValue(form, \"refresh_token\");\n const record = refreshToken ? this.refreshTokens.get(refreshToken) : undefined;\n if (!record || record.revoked || record.expiresAt <= this.nowSeconds()) {\n return oauthError(400, \"invalid_grant\", \"Refresh token is invalid or expired\");\n }\n if (formValue(form, \"client_id\") !== record.clientId) {\n return oauthError(400, \"invalid_grant\", \"Refresh token client binding does not match\");\n }\n const requestedScopes = form.has(\"scope\")\n ? uniqueScopes(form.get(\"scope\"))\n : record.scopes;\n if (requestedScopes.some((scope) => !record.scopes.includes(scope))) {\n return oauthError(400, \"invalid_scope\", \"Refresh cannot increase granted scopes\");\n }\n record.revoked = true;\n return json(200, this.issueTokenSet({ ...record, scopes: requestedScopes }));\n }\n\n private issueTokenSet(\n input: Pick<AccessTokenRecord, \"clientId\" | \"scopes\" | \"resource\">,\n ): McpFnMockOAuthTokenSet {\n const accessToken = `mcpfn_access_${randomUUID()}`;\n const refreshToken = `mcpfn_refresh_${randomUUID()}`;\n this.accessTokens.set(accessToken, {\n ...input,\n expiresAt: this.nowSeconds() + this.accessTokenLifetimeSeconds,\n revoked: false,\n });\n this.refreshTokens.set(refreshToken, {\n ...input,\n expiresAt: this.nowSeconds() + this.refreshTokenLifetimeSeconds,\n revoked: false,\n });\n return {\n access_token: accessToken,\n token_type: \"Bearer\",\n expires_in: this.accessTokenLifetimeSeconds,\n refresh_token: refreshToken,\n scope: input.scopes.join(\" \"),\n ...(input.resource ? { resource: input.resource } : {}),\n };\n }\n\n private revoke(source: string): Response {\n const token = new URLSearchParams(source).get(\"token\");\n if (token) this.revokeToken(token);\n return new Response(null, { status: 200 });\n }\n}\n\nexport interface McpFnStartedMockOAuthServer {\n origin: string;\n oauth: McpFnMockOAuthAuthorizationServer;\n close(): Promise<void>;\n}\n\nexport interface McpFnStartMockOAuthServerOptions\n extends Omit<McpFnMockOAuthServerOptions, \"issuer\"> {\n hostname?: string;\n port?: number;\n /** Optional same-origin route used to compose a protected MCP fixture. */\n handle?(\n request: Request,\n oauth: McpFnMockOAuthAuthorizationServer,\n ): Promise<Response | undefined> | Response | undefined;\n}\n\nasync function toRequest(req: IncomingMessage, origin: string): Promise<Request> {\n const chunks: Buffer[] = [];\n for await (const chunk of req) chunks.push(Buffer.from(chunk));\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers)) {\n if (Array.isArray(value)) {\n for (const entry of value) headers.append(name, entry);\n } else if (value !== undefined) {\n headers.set(name, value);\n }\n }\n const body = chunks.length ? Buffer.concat(chunks) : undefined;\n const init: RequestInit & { duplex?: \"half\" } = {\n method: req.method,\n headers,\n ...(body ? { body, duplex: \"half\" } : {}),\n };\n return new Request(new URL(req.url ?? \"/\", origin), init);\n}\n\nasync function writeResponse(res: ServerResponse, response: Response): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, name) => res.setHeader(name, value));\n res.end(Buffer.from(await response.arrayBuffer()));\n}\n\nexport async function startMockOAuthAuthorizationServer(\n options: McpFnStartMockOAuthServerOptions = {},\n): Promise<McpFnStartedMockOAuthServer> {\n let oauth: McpFnMockOAuthAuthorizationServer | undefined;\n let origin = \"\";\n const server = createServer(async (req, res) => {\n try {\n if (!oauth) throw new Error(\"Mock OAuth server is not initialized\");\n const request = await toRequest(req, origin);\n const custom = await options.handle?.(request.clone(), oauth);\n await writeResponse(res, custom ?? await oauth.handle(request));\n } catch {\n res.statusCode = 500;\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify({\n error: \"server_error\",\n error_description: \"Internal Server Error\",\n }));\n }\n });\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(options.port ?? 0, options.hostname ?? \"127.0.0.1\", () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n const address = server.address() as AddressInfo;\n origin = `http://${address.address.includes(\":\") ? `[${address.address}]` : address.address}:${address.port}`;\n oauth = new McpFnMockOAuthAuthorizationServer({ ...options, issuer: origin });\n return {\n origin,\n oauth,\n close: () => new Promise<void>((resolve, reject) => {\n server.close((error) => error ? reject(error) : resolve());\n }),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACkFO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EACjD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEA,SAAS,eACP,UACA,UACA,MACM;AACN,QAAM,WAAW,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAQ;AAC/D,MAAI,CAAC,SAAS,SAAS,SAAS,MAAM,GAAG;AACvC,UAAM,IAAI;AAAA,MACR,GAAG,IAAI,kBAAkB,SAAS,KAAK,MAAM,CAAC,cAAc,SAAS,MAAM;AAAA,IAC7E;AAAA,EACF;AACF;AAEO,SAAS,qBACd,QACkC;AAClC,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,CAAC,GAAG,OAAO,SAAS,wCAAwC,CAAC;AAC7E,QAAM,gBAAgB,QAAQ,UAAU,CAAC,UAAU,MAAM,CAAC,EAAE,YAAY,MAAM,QAAQ;AACtF,MAAI,gBAAgB,EAAG,QAAO;AAC9B,QAAM,QAAQ,QAAQ,aAAa;AACnC,QAAM,SAAS,MAAM,SAAS,KAAK,MAAM,CAAC,EAAE;AAC5C,QAAM,MAAM,QAAQ,gBAAgB,CAAC,GAAG,SAAS,OAAO;AACxD,QAAM,aAAqC,CAAC;AAC5C,QAAM,SAAS,OAAO,MAAM,OAAO,GAAG;AACtC,QAAM,UAAU;AAChB,aAAW,SAAS,OAAO,SAAS,OAAO,GAAG;AAC5C,UAAM,SAAS,MAAM,CAAC,GAAG,QAAQ,cAAc,IAAI;AACnD,eAAW,MAAM,CAAC,CAAC,IAAI,UAAU,MAAM,CAAC,KAAK;AAAA,EAC/C;AACA,SAAO,EAAE,QAAQ,MAAM,CAAC,GAAG,WAAW;AACxC;AAEO,SAAS,8BACd,UACA,qBACsB;AACtB,QAAM,YAAY,qBAAqB,SAAS,QAAQ,IAAI,kBAAkB,CAAC;AAC/E,MAAI,CAAC,aAAa,UAAU,OAAO,YAAY,MAAM,UAAU;AAC7D,UAAM,IAAI,wBAAwB,8CAA8C;AAAA,EAClF;AACA,QAAM,cAAc,UAAU,WAAW;AACzC,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,WAAW;AAClC,QAAI,CAAC,OAAO,YAAY,CAAC,OAAO,KAAM,OAAM,IAAI,MAAM,cAAc;AAAA,EACtE,QAAQ;AACN,UAAM,IAAI;AAAA,MACR,0DAA0D,WAAW;AAAA,IACvE;AAAA,EACF;AACA,MAAI,uBAAuB,gBAAgB,qBAAqB;AAC9D,UAAM,IAAI;AAAA,MACR,8BAA8B,mBAAmB,cAAc,WAAW;AAAA,IAC5E;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,iBACd,OACA,OAAgC,SACX;AACrB,SAAO;AAAA,IACL;AAAA,IACA,SAAS,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,EAC9C;AACF;AAEO,SAAS,iBACd,QACA,UAAoD,CAAC,GAChC;AACrB,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,QAAQ,QAAQ,WAAW,KAC7B,SACA,GAAG,QAAQ,UAAU,QAAQ,IAAI,MAAM;AAC3C,SAAO,EAAE,MAAM,WAAW,SAAS,EAAE,CAAC,UAAU,GAAG,MAAM,EAAE;AAC7D;AAEA,SAAS,eAAe,SAA8C;AACpE,SAAO,IAAI,QAAQ,QAAQ,4BAA4B;AAAA,IACrD,eAAe,wBAAwB,QAAQ,IAAI;AAAA,EACrD,CAAC;AACH;AAEA,eAAe,OACb,SACA,UACA,SACA,UACkC;AAClC,MAAI;AACJ,MAAI;AACF,iBAAa,MAAM,QAAQ;AAC3B,UAAM,UAAU,aACZ,IAAI,QAAQ,WAAW,OAAO,IAC9B,SAAS,SAAS,uBAChB,eAAe,OAAO,IACtB,IAAI,QAAQ;AAClB,UAAM,WAAW,MAAM,QAAQ,OAAO,QAAQ,SAAS,QAAQ;AAC/D,mBAAe,UAAU,UAAU,SAAS,IAAI;AAChD,QACE,QAAQ,SAAS,WACjB,SAAS,SAAS,oBAClB;AACA;AAAA,QACE;AAAA,QACA,QAAQ;AAAA,MACV;AAAA,IACF;AACA,QAAI,SAAS,SAAS,sBAAsB,YAAY;AACtD,YAAM,QAAQ,8BAA8B,SAAS,MAAM,GAAG,UAAU;AAAA,IAC1E;AACA,WAAO;AAAA,MACL,MAAM,SAAS;AAAA,MACf,QAAQ;AAAA,MACR,gBAAgB,SAAS;AAAA,IAC3B;AAAA,EACF,SAAS,OAAO;AACd,WAAO;AAAA,MACL,MAAM,SAAS;AAAA,MACf,QAAQ;AAAA,MACR,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAC9D;AAAA,EACF,UAAE;AACA,UAAM,YAAY,UAAU;AAAA,EAC9B;AACF;AAGA,eAAsB,uBACpB,SACoC;AACpC,QAAM,QAAQ,CAAC,YAAiD,CAAC,MAC/D,QAAQ,SAAS,MAAM;AAAA,IACrB,MAAM,QAAQ;AAAA,IACd,QAAQ,QAAQ;AAAA,IAChB,UAAU,QAAQ;AAAA,IAClB,GAAG;AAAA,EACL,CAAC;AACH,QAAM,UAAqC,CAAC;AAC5C,UAAQ,KAAK,MAAM;AAAA,IACjB;AAAA,IACA,EAAE,MAAM,sBAAsB,MAAM,QAAQ,KAAK;AAAA,IACjD,YAAY;AAAA,IACZ;AAAA,EACF,CAAC;AACD,UAAQ,KAAK,MAAM;AAAA,IACjB;AAAA,IACA,EAAE,MAAM,sBAAsB,MAAM,QAAQ,KAAK;AAAA,IACjD,YAAY;AAAA,IACZ;AAAA,EACF,CAAC;AACD,UAAQ,KAAK,MAAM;AAAA,IACjB;AAAA,IACA,EAAE,MAAM,oBAAoB,MAAM,QAAQ,KAAK;AAAA,IAC/C,MAAM,MAAM;AAAA,IACZ,QAAQ,iCAAiC,CAAC,KAAK,KAAK,KAAK,GAAG;AAAA,EAC9D,CAAC;AAED,QAAM,eAAe,QAAQ,SAAS,gBAAgB,CAAC;AACvD,MAAI,aAAa,UAAU,QAAQ,gBAAgB,QAAQ;AACzD,YAAQ,KAAK,MAAM;AAAA,MACjB;AAAA,MACA,EAAE,MAAM,sBAAsB,MAAM,QAAQ,KAAK;AAAA,MACjD,MAAM,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACH;AACA,MAAI,aAAa,YAAY;AAC3B,YAAQ,KAAK,MAAM;AAAA,MACjB;AAAA,MACA,EAAE,MAAM,sBAAsB,MAAM,QAAQ,KAAK;AAAA,MACjD,MAAM,MAAM,EAAE,kBAAkB,IAAI,CAAC;AAAA,MACrC;AAAA,IACF,CAAC;AAAA,EACH;AACA,MAAI,aAAa,mBAAmB,QAAQ,UAAU;AACpD,YAAQ,KAAK,MAAM;AAAA,MACjB;AAAA,MACA,EAAE,MAAM,oBAAoB,MAAM,QAAQ,KAAK;AAAA,MAC/C,MAAM,MAAM,EAAE,UAAU,OAAU,CAAC;AAAA,MACnC;AAAA,IACF,CAAC;AAAA,EACH;AACA,MAAI,aAAa,mBAAmB,QAAQ,eAAe;AACzD,YAAQ,KAAK,MAAM;AAAA,MACjB;AAAA,MACA,EAAE,MAAM,kBAAkB,MAAM,QAAQ,KAAK;AAAA,MAC7C,MAAM,MAAM,EAAE,UAAU,QAAQ,cAAc,CAAC;AAAA,MAC/C;AAAA,IACF,CAAC;AAAA,EACH;AACA,MAAI,aAAa,cAAc,QAAQ,SAAS,QAAQ;AACtD,YAAQ,KAAK,MAAM;AAAA,MACjB;AAAA,MACA,EAAE,MAAM,sBAAsB,MAAM,QAAQ,KAAK;AAAA,MACjD,YAAY;AACV,cAAM,aAAa,MAAM,MAAM;AAC/B,cAAM,QAAQ,SAAS,SAAS,UAAU;AAC1C,eAAO;AAAA,MACT;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,eAAsB,0BACpB,SACoC;AACpC,QAAM,UAAU,MAAM,uBAAuB,OAAO;AACpD,QAAM,WAAW,QAAQ,OAAO,CAAC,WAAW,OAAO,WAAW,QAAQ;AACtE,MAAI,SAAS,QAAQ;AACnB,UAAM,IAAI;AAAA,MACR,SAAS,IAAI,CAAC,YAAY,GAAG,QAAQ,IAAI,KAAK,QAAQ,KAAK,EAAE,EAAE,KAAK,IAAI;AAAA,IAC1E;AAAA,EACF;AACA,SAAO;AACT;AASO,SAAS,sBACd,SACiB;AACjB,QAAM,sBAAsB,QAAQ,SAAS,WAAW;AACxD,MAAI,CAAC,qBAAqB;AACxB,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,SAAO;AAAA,IACL,MAAM,QAAQ,aAAa,UAAU;AACnC,YAAM,aAAa,OAAO,QAAQ,gBAAgB,aAC9C,QAAQ,YAAY,QAAQ,IAC5B,QAAQ,eAAe,CAAC;AAC5B,YAAM,UAAU,IAAI,QAAQ,WAAW,OAAO;AAC9C,cAAQ,IAAI,UAAU,qCAAqC;AAC3D,cAAQ,IAAI,gBAAgB,kBAAkB;AAC9C,kBAAY,QAAQ,CAAC,OAAO,SAAS,QAAQ,IAAI,MAAM,KAAK,CAAC;AAC7D,aAAO,oBAAoB,QAAQ,KAAK;AAAA,QACtC,QAAQ;AAAA,QACR,MAAM,KAAK,UAAU;AAAA,UACnB,SAAS;AAAA,UACT,IAAI,cAAc,SAAS,IAAI;AAAA,UAC/B,QAAQ;AAAA,UACR,QAAQ;AAAA,YACN,iBAAiB;AAAA,YACjB,cAAc,CAAC;AAAA,YACf,YAAY,EAAE,MAAM,mBAAmB,SAAS,QAAQ;AAAA,UAC1D;AAAA,QACF,CAAC;AAAA,QACD,GAAG;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEO,IAAM,+BAA+B;AAAA,EAC1C,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,SAAS;AACX;AAoCO,IAAM,kCAAkC;AAAA,EAC7C,SAAS;AAAA,IACP,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,cAAc,CAAC,uDAAuD;AAAA,IACtE,eAAe,CAAC,MAAM;AAAA,IACtB,YAAY,CAAC,sBAAsB,eAAe;AAAA,IAClD,yBAAyB;AAAA,EAC3B;AAAA,EACA,QAAQ;AAAA,IACN,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,cAAc,CAAC,2CAA2C;AAAA,IAC1D,eAAe,CAAC,MAAM;AAAA,IACtB,YAAY;AAAA,MACV;AAAA,MACA;AAAA,MACA,6BAA6B;AAAA,IAC/B;AAAA,IACA,yBAAyB;AAAA,EAC3B;AACF;AAEO,SAAS,iCACd,SAC0B;AAC1B,SAAO;AAAA,IACL,GAAG,QAAQ;AAAA,IACX,WAAW,IAAI,IAAI,QAAQ,QAAQ,EAAE,SAAS;AAAA,IAC9C,aAAa,QAAQ,cAAc;AAAA,IACnC,eAAe,CAAC,GAAG,IAAI,IAAI,QAAQ,aAAa,IAAI,CAAC,QAAQ,IAAI,IAAI,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;AAAA,IACtF,gBAAgB,CAAC,MAAM;AAAA,IACvB,aAAa;AAAA,MACX;AAAA,MACA;AAAA,MACA,GAAG,IAAI,IAAI,QAAQ,mBAAmB,CAAC,CAAC;AAAA,IAC1C;AAAA,IACA,4BAA4B;AAAA,EAC9B;AACF;AAEO,SAAS,kCACd,SACA,cAAc,IAAI,IAAI,aAAa,OAAO,EAAE,SAAS,GACnB;AAClC,QAAM,OAAO,IAAI,IAAI,OAAO;AAC5B,QAAM,UAAU,CAAC,MAAc,kBAA4B,CAAC,MAC1D,iCAAiC;AAAA,IAC/B,UAAU,IAAI,IAAI,oBAAoB,IAAI,IAAI,IAAI,EAAE,SAAS;AAAA,IAC7D,cAAc,CAAC,WAAW;AAAA,IAC1B,YAAY,SAAS,IAAI;AAAA,IACzB;AAAA,EACF,CAAC;AACH,SAAO;AAAA,IACL,OAAO,QAAQ,OAAO;AAAA,IACtB,oBAAoB,QAAQ,wBAAwB,CAAC,6BAA6B,SAAS,CAAC;AAAA,IAC5F,qBAAqB,QAAQ,yBAAyB,CAAC,6BAA6B,UAAU,CAAC;AAAA,IAC/F,kBAAkB,QAAQ,qBAAqB,CAAC,6BAA6B,OAAO,CAAC;AAAA,EACvF;AACF;AAMO,SAAS,sCACd,UACA,kBAC0B;AAC1B,MAAI,oBAAoB,SAAS,cAAc,IAAI,IAAI,gBAAgB,EAAE,SAAS,GAAG;AACnF,UAAM,IAAI,wBAAwB,iEAAiE;AAAA,EACrG;AACA,MAAI,CAAC,SAAS,eAAe,SAAS,MAAM,GAAG;AAC7C,UAAM,IAAI,wBAAwB,2DAA2D;AAAA,EAC/F;AACA,MAAI,CAAC,SAAS,YAAY,SAAS,oBAAoB,GAAG;AACxD,UAAM,IAAI,wBAAwB,2DAA2D;AAAA,EAC/F;AACA,MAAI,CAAC,SAAS,cAAc,QAAQ;AAClC,UAAM,IAAI,wBAAwB,8DAA8D;AAAA,EAClG;AACA,SAAO;AACT;;;AC/dA,yBAA2B;AAC3B,uBAAwE;AAIxE,wBAA0D;AAiEnD,SAAS,iBAAgC;AAC9C,QAAM,WAAO,oCAAiB,EAAE;AAChC,SAAO;AAAA,IACL,UAAU,KAAK;AAAA,IACf,WAAW,KAAK;AAAA,IAChB,QAAQ,KAAK;AAAA,EACf;AACF;AAEA,SAAS,aAAa,OAA4C;AAChE,SAAO,CAAC,GAAG,IAAI,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE,OAAO,OAAO,CAAC,CAAC,EAAE,KAAK;AACvE;AAEA,SAAS,KAAK,QAAgB,MAAyB;AACrD,SAAO,SAAS,KAAK,MAAM;AAAA,IACzB;AAAA,IACA,SAAS,EAAE,iBAAiB,WAAW;AAAA,EACzC,CAAC;AACH;AAEA,SAAS,WAAW,QAAgB,OAAe,aAA+B;AAChF,SAAO,KAAK,QAAQ,EAAE,OAAO,mBAAmB,YAAY,CAAC;AAC/D;AAEA,SAAS,WAAW,OAAuB;AACzC,SAAO,MACJ,WAAW,KAAK,OAAO,EACvB,WAAW,KAAK,QAAQ,EACxB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM;AAC3B;AAEA,SAAS,UAAU,MAAuB,MAAkC;AAC1E,QAAM,QAAQ,KAAK,IAAI,IAAI;AAC3B,SAAO,UAAU,QAAQ,UAAU,KAAK,SAAY;AACtD;AAEO,IAAM,oCAAN,MAAwC;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEQ,QAAQ,oBAAI,IAAqC;AAAA,EACjD,eAAe,oBAAI,IAA+B;AAAA,EAClD,gBAAgB,oBAAI,IAAgC;AAAA,EACpD,YAAkC,CAAC;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,SAAsC;AAChD,SAAK,SAAS,IAAI,IAAI,QAAQ,MAAM;AACpC,SAAK,OAAO,WAAW,KAAK,OAAO,SAAS,QAAQ,QAAQ,GAAG;AAC/D,SAAK,WAAW,QAAQ,YAAY;AACpC,SAAK,eAAe,QAAQ,cAAc,IAAI,CAAC,QAAQ,IAAI,IAAI,GAAG,EAAE,SAAS,CAAC,KAAK;AAAA,MACjF,IAAI,IAAI,YAAY,KAAK,MAAM,EAAE,SAAS;AAAA,IAC5C;AACA,SAAK,kBAAkB,CAAC,GAAG,IAAI,IAAI,QAAQ,mBAAmB,CAAC,YAAY,WAAW,CAAC,CAAC,EAAE,KAAK;AAC/F,SAAK,QAAQ,QAAQ,SAAS,KAAK;AACnC,SAAK,6BAA6B,QAAQ,8BAA8B;AACxE,SAAK,mCAAmC,QAAQ,oCAAoC;AACpF,SAAK,8BAA8B,QAAQ,+BAA+B;AAC1E,SAAK,iBAAiB;AAAA,MACpB,KAAK;AAAA,MACL,KAAK,aAAa,CAAC;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,IAAI,cAAsB;AACxB,WAAO,IAAI,IAAI,0CAA0C,KAAK,MAAM,EAAE,SAAS;AAAA,EACjF;AAAA,EAEA,IAAI,wBAAgC;AAClC,WAAO,IAAI,IAAI,aAAa,KAAK,MAAM,EAAE,SAAS;AAAA,EACpD;AAAA,EAEA,IAAI,gBAAwB;AAC1B,WAAO,IAAI,IAAI,SAAS,KAAK,MAAM,EAAE,SAAS;AAAA,EAChD;AAAA,EAEA,IAAI,qBAA6B;AAC/B,WAAO,IAAI,IAAI,UAAU,KAAK,MAAM,EAAE,SAAS;AAAA,EACjD;AAAA,EAEA,IAAI,cAAsB;AACxB,WAAO,KAAK,aAAa,CAAC;AAAA,EAC5B;AAAA,EAEA,kBACE,SACQ;AACR,WAAO,KAAK,eAAe,OAAO,EAAE;AAAA,EACtC;AAAA,EAEA,iBAAiB,OAQN;AACT,UAAM,MAAM,IAAI,IAAI,KAAK,qBAAqB;AAC9C,QAAI,aAAa,IAAI,iBAAiB,MAAM;AAC5C,QAAI,aAAa,IAAI,aAAa,MAAM,YAAY,KAAK,QAAQ;AACjE,QAAI,aAAa,IAAI,gBAAgB,MAAM,eAAe,KAAK,WAAW;AAC1E,QAAI,aAAa,IAAI,kBAAkB,MAAM,aAAa;AAC1D,QAAI,aAAa,IAAI,yBAAyB,MAAM;AACpD,QAAI,aAAa,IAAI,UAAU,MAAM,UAAU,CAAC,UAAU,GAAG,KAAK,GAAG,CAAC;AACtE,QAAI,MAAM,MAAO,KAAI,aAAa,IAAI,SAAS,MAAM,KAAK;AAC1D,QAAI,MAAM,SAAU,KAAI,aAAa,IAAI,YAAY,MAAM,QAAQ;AACnE,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAM,mBAAmB,CAAC,CAAC,GAAG;AACvE,UAAI,aAAa,IAAI,MAAM,KAAK;AAAA,IAClC;AACA,WAAO,IAAI,SAAS;AAAA,EACtB;AAAA,EAEA,iBAAiD;AAC/C,WAAO,KAAK,UAAU,GAAG,EAAE;AAAA,EAC7B;AAAA,EAEA,kBAAkB,OAAqB;AACrC,UAAM,SAAS,KAAK,aAAa,IAAI,KAAK;AAC1C,QAAI,OAAQ,QAAO,YAAY,KAAK,WAAW,IAAI;AAAA,EACrD;AAAA,EAEA,YAAY,OAAqB;AAC/B,UAAM,SAAS,KAAK,aAAa,IAAI,KAAK;AAC1C,QAAI,OAAQ,QAAO,UAAU;AAC7B,UAAM,UAAU,KAAK,cAAc,IAAI,KAAK;AAC5C,QAAI,QAAS,SAAQ,UAAU;AAAA,EACjC;AAAA,EAEA,MAAM,kBAAkB,OAAkC;AACxD,UAAM,SAAS,KAAK,aAAa,IAAI,KAAK;AAC1C,QAAI,CAAC,UAAU,OAAO,WAAW,OAAO,aAAa,KAAK,WAAW,GAAG;AACtE,YAAM,IAAI,MAAM,sBAAsB;AAAA,IACxC;AACA,WAAO;AAAA,MACL;AAAA,MACA,UAAU,OAAO;AAAA,MACjB,QAAQ,OAAO;AAAA,MACf,WAAW,OAAO;AAAA,MAClB,GAAI,OAAO,WAAW,EAAE,UAAU,IAAI,IAAI,OAAO,QAAQ,EAAE,IAAI,CAAC;AAAA,IAClE;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,SAAqC;AAChD,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,OAAO,IAAI,SAAS,QAAQ,KAAK,OAAO,SAAS,QAAQ,OAAO,EAAE,GAAG,EAAE,KAAK;AAClF,QAAI,QAAQ,WAAW,SAAS,SAAS,2CAA2C;AAClF,aAAO,KAAK,KAAK;AAAA,QACf,QAAQ,KAAK,OAAO,SAAS;AAAA,QAC7B,wBAAwB,KAAK;AAAA,QAC7B,gBAAgB,KAAK;AAAA,QACrB,qBAAqB,KAAK;AAAA,QAC1B,0BAA0B,CAAC,MAAM;AAAA,QACjC,uBAAuB,CAAC,sBAAsB,eAAe;AAAA,QAC7D,kCAAkC,CAAC,MAAM;AAAA,QACzC,uCAAuC,CAAC,MAAM;AAAA,QAC9C,kBAAkB,KAAK;AAAA,QACvB,uCAAuC;AAAA,MACzC,CAAC;AAAA,IACH;AAEA,QAAI,QAAQ,WAAW,SAAS,KAAK,WAAW,mBAAmB,GAAG;AACpE,YAAM,WAAW,OAAO,OAAO,KAAK,cAAc,EAC/C,KAAK,CAAC,cAAc,IAAI,IAAI,UAAU,SAAS,EAAE,aAAa,IAAI,QAAQ;AAC7E,aAAO,WAAW,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,IAC1E;AAEA,QAAI,QAAQ,WAAW,SAAS,SAAS,cAAc;AACrD,aAAO,KAAK,UAAU,GAAG;AAAA,IAC3B;AACA,QAAI,QAAQ,WAAW,UAAU,SAAS,UAAU;AAClD,aAAO,KAAK,MAAM,MAAM,QAAQ,KAAK,CAAC;AAAA,IACxC;AACA,QAAI,QAAQ,WAAW,UAAU,SAAS,WAAW;AACnD,aAAO,KAAK,OAAO,MAAM,QAAQ,KAAK,CAAC;AAAA,IACzC;AACA,QAAI,QAAQ,WAAW,SAAS,SAAS,aAAa;AACpD,YAAM,aAAa,OAAO,YAAY,IAAI,YAAY;AACtD,WAAK,UAAU,KAAK,EAAE,KAAK,IAAI,SAAS,GAAG,WAAW,CAAC;AACvD,aAAO,IAAI;AAAA,QACT,yEAAyE,WAAW,KAAK,UAAU,YAAY,MAAM,CAAC,CAAC,CAAC;AAAA,QACxH,EAAE,SAAS,EAAE,gBAAgB,2BAA2B,EAAE;AAAA,MAC5D;AAAA,IACF;AACA,WAAO,KAAK,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,EACzC;AAAA,EAEQ,aAAqB;AAC3B,WAAO,KAAK,MAAM,KAAK,MAAM,IAAI,GAAK;AAAA,EACxC;AAAA,EAEQ,cAAc,UAA4E;AAChG,QAAI,aAAa,KAAK,UAAU;AAC9B,aAAO,EAAE,UAAU,cAAc,KAAK,aAAa;AAAA,IACrD;AACA,UAAM,WAAW,OAAO,OAAO,KAAK,cAAc,EAC/C,KAAK,CAAC,cAAc,UAAU,cAAc,QAAQ;AACvD,QAAI,CAAC,SAAU,QAAO;AACtB,0CAAsC,UAAU,QAAQ;AACxD,WAAO,EAAE,UAAU,cAAc,SAAS,cAAc;AAAA,EAC1D;AAAA,EAEQ,UAAU,KAAoB;AACpC,UAAM,WAAW,IAAI,aAAa,IAAI,WAAW,KAAK;AACtD,UAAM,cAAc,IAAI,aAAa,IAAI,cAAc,KAAK;AAC5D,UAAM,SAAS,KAAK,cAAc,QAAQ;AAC1C,QAAI,CAAC,OAAQ,QAAO,WAAW,KAAK,kBAAkB,sBAAsB;AAC5E,QAAI,CAAC,OAAO,aAAa,SAAS,WAAW,GAAG;AAC9C,aAAO,WAAW,KAAK,mBAAmB,gCAAgC;AAAA,IAC5E;AACA,QAAI,IAAI,aAAa,IAAI,eAAe,MAAM,QAAQ;AACpD,aAAO,KAAK,sBAAsB,KAAK,aAAa,2BAA2B;AAAA,IACjF;AACA,UAAM,gBAAgB,IAAI,aAAa,IAAI,gBAAgB;AAC3D,QAAI,CAAC,iBAAiB,IAAI,aAAa,IAAI,uBAAuB,MAAM,QAAQ;AAC9E,aAAO,KAAK,sBAAsB,KAAK,aAAa,mBAAmB,uBAAuB;AAAA,IAChG;AACA,UAAM,kBAAkB,aAAa,IAAI,aAAa,IAAI,OAAO,CAAC;AAClE,QAAI,gBAAgB,KAAK,CAAC,UAAU,CAAC,KAAK,gBAAgB,SAAS,KAAK,CAAC,GAAG;AAC1E,aAAO,KAAK,sBAAsB,KAAK,aAAa,eAAe;AAAA,IACrE;AACA,UAAM,WAAW,IAAI,aAAa,IAAI,UAAU;AAChD,QAAI,CAAC,UAAU;AACb,YAAM,SAAS,CAAC,GAAG,IAAI,YAAY,EAChC,OAAO,CAAC,CAAC,IAAI,MAAM,SAAS,UAAU,EACtC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,8BAA8B,WAAW,IAAI,CAAC,YAAY,WAAW,KAAK,CAAC,IAAI,EACtG,KAAK,EAAE;AACV,aAAO,IAAI;AAAA,QACT,oEAAoE,WAAW,QAAQ,CAAC,kCAAkC,WAAW,KAAK,qBAAqB,CAAC,KAAK,MAAM;AAAA,QAC3K,EAAE,SAAS,EAAE,gBAAgB,4BAA4B,iBAAiB,WAAW,EAAE;AAAA,MACzF;AAAA,IACF;AACA,QAAI,aAAa,QAAQ;AACvB,aAAO,KAAK,sBAAsB,KAAK,aAAa,eAAe;AAAA,IACrE;AACA,QAAI,aAAa,WAAW;AAC1B,aAAO,WAAW,KAAK,mBAAmB,gCAAgC;AAAA,IAC5E;AACA,UAAM,OAAO,kBAAc,+BAAW,CAAC;AACvC,SAAK,MAAM,IAAI,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,UAAU,IAAI,aAAa,IAAI,UAAU,KAAK;AAAA,MAC9C,WAAW,KAAK,WAAW,IAAI,KAAK;AAAA,MACpC,MAAM;AAAA,IACR,CAAC;AACD,UAAM,WAAW,IAAI,IAAI,WAAW;AACpC,aAAS,aAAa,IAAI,QAAQ,IAAI;AACtC,UAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAC1C,QAAI,MAAO,UAAS,aAAa,IAAI,SAAS,KAAK;AACnD,WAAO,SAAS,SAAS,UAAU,GAAG;AAAA,EACxC;AAAA,EAEQ,sBACN,YACA,aACA,OACA,aACU;AACV,UAAM,WAAW,IAAI,IAAI,WAAW;AACpC,aAAS,aAAa,IAAI,SAAS,KAAK;AACxC,QAAI,YAAa,UAAS,aAAa,IAAI,qBAAqB,WAAW;AAC3E,UAAM,QAAQ,WAAW,aAAa,IAAI,OAAO;AACjD,QAAI,MAAO,UAAS,aAAa,IAAI,SAAS,KAAK;AACnD,WAAO,SAAS,SAAS,UAAU,GAAG;AAAA,EACxC;AAAA,EAEQ,MAAM,QAA0B;AACtC,UAAM,OAAO,IAAI,gBAAgB,MAAM;AACvC,UAAM,YAAY,UAAU,MAAM,YAAY;AAC9C,QAAI,cAAc,qBAAsB,QAAO,KAAK,0BAA0B,IAAI;AAClF,QAAI,cAAc,gBAAiB,QAAO,KAAK,qBAAqB,IAAI;AACxE,WAAO,WAAW,KAAK,0BAA0B,iCAAiC;AAAA,EACpF;AAAA,EAEQ,0BAA0B,MAAiC;AACjE,UAAM,OAAO,UAAU,MAAM,MAAM;AACnC,UAAM,SAAS,OAAO,KAAK,MAAM,IAAI,IAAI,IAAI;AAC7C,QAAI,CAAC,UAAU,OAAO,QAAQ,OAAO,aAAa,KAAK,WAAW,GAAG;AACnE,aAAO,WAAW,KAAK,iBAAiB,0CAA0C;AAAA,IACpF;AACA,QACE,UAAU,MAAM,WAAW,MAAM,OAAO,YACxC,UAAU,MAAM,cAAc,MAAM,OAAO,aAC3C;AACA,aAAO,WAAW,KAAK,iBAAiB,2CAA2C;AAAA,IACrF;AACA,UAAM,WAAW,UAAU,MAAM,eAAe;AAChD,UAAM,YAAY,eAAW,2CAAwB,QAAQ,IAAI;AACjE,QAAI,cAAc,OAAO,eAAe;AACtC,aAAO,WAAW,KAAK,iBAAiB,0BAA0B;AAAA,IACpE;AACA,WAAO,OAAO;AACd,WAAO,KAAK,KAAK,KAAK,cAAc,MAAM,CAAC;AAAA,EAC7C;AAAA,EAEQ,qBAAqB,MAAiC;AAC5D,UAAM,eAAe,UAAU,MAAM,eAAe;AACpD,UAAM,SAAS,eAAe,KAAK,cAAc,IAAI,YAAY,IAAI;AACrE,QAAI,CAAC,UAAU,OAAO,WAAW,OAAO,aAAa,KAAK,WAAW,GAAG;AACtE,aAAO,WAAW,KAAK,iBAAiB,qCAAqC;AAAA,IAC/E;AACA,QAAI,UAAU,MAAM,WAAW,MAAM,OAAO,UAAU;AACpD,aAAO,WAAW,KAAK,iBAAiB,6CAA6C;AAAA,IACvF;AACA,UAAM,kBAAkB,KAAK,IAAI,OAAO,IACpC,aAAa,KAAK,IAAI,OAAO,CAAC,IAC9B,OAAO;AACX,QAAI,gBAAgB,KAAK,CAAC,UAAU,CAAC,OAAO,OAAO,SAAS,KAAK,CAAC,GAAG;AACnE,aAAO,WAAW,KAAK,iBAAiB,wCAAwC;AAAA,IAClF;AACA,WAAO,UAAU;AACjB,WAAO,KAAK,KAAK,KAAK,cAAc,EAAE,GAAG,QAAQ,QAAQ,gBAAgB,CAAC,CAAC;AAAA,EAC7E;AAAA,EAEQ,cACN,OACwB;AACxB,UAAM,cAAc,oBAAgB,+BAAW,CAAC;AAChD,UAAM,eAAe,qBAAiB,+BAAW,CAAC;AAClD,SAAK,aAAa,IAAI,aAAa;AAAA,MACjC,GAAG;AAAA,MACH,WAAW,KAAK,WAAW,IAAI,KAAK;AAAA,MACpC,SAAS;AAAA,IACX,CAAC;AACD,SAAK,cAAc,IAAI,cAAc;AAAA,MACnC,GAAG;AAAA,MACH,WAAW,KAAK,WAAW,IAAI,KAAK;AAAA,MACpC,SAAS;AAAA,IACX,CAAC;AACD,WAAO;AAAA,MACL,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,YAAY,KAAK;AAAA,MACjB,eAAe;AAAA,MACf,OAAO,MAAM,OAAO,KAAK,GAAG;AAAA,MAC5B,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,IACvD;AAAA,EACF;AAAA,EAEQ,OAAO,QAA0B;AACvC,UAAM,QAAQ,IAAI,gBAAgB,MAAM,EAAE,IAAI,OAAO;AACrD,QAAI,MAAO,MAAK,YAAY,KAAK;AACjC,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3C;AACF;AAmBA,eAAe,UAAU,KAAsB,QAAkC;AAC/E,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,IAAK,QAAO,KAAK,OAAO,KAAK,KAAK,CAAC;AAC7D,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACvD,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,iBAAW,SAAS,MAAO,SAAQ,OAAO,MAAM,KAAK;AAAA,IACvD,WAAW,UAAU,QAAW;AAC9B,cAAQ,IAAI,MAAM,KAAK;AAAA,IACzB;AAAA,EACF;AACA,QAAM,OAAO,OAAO,SAAS,OAAO,OAAO,MAAM,IAAI;AACrD,QAAM,OAA0C;AAAA,IAC9C,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA,GAAI,OAAO,EAAE,MAAM,QAAQ,OAAO,IAAI,CAAC;AAAA,EACzC;AACA,SAAO,IAAI,QAAQ,IAAI,IAAI,IAAI,OAAO,KAAK,MAAM,GAAG,IAAI;AAC1D;AAEA,eAAe,cAAc,KAAqB,UAAmC;AACnF,MAAI,aAAa,SAAS;AAC1B,WAAS,QAAQ,QAAQ,CAAC,OAAO,SAAS,IAAI,UAAU,MAAM,KAAK,CAAC;AACpE,MAAI,IAAI,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC,CAAC;AACnD;AAEA,eAAsB,kCACpB,UAA4C,CAAC,GACP;AACtC,MAAI;AACJ,MAAI,SAAS;AACb,QAAM,aAAS,+BAAa,OAAO,KAAK,QAAQ;AAC9C,QAAI;AACF,UAAI,CAAC,MAAO,OAAM,IAAI,MAAM,sCAAsC;AAClE,YAAM,UAAU,MAAM,UAAU,KAAK,MAAM;AAC3C,YAAM,SAAS,MAAM,QAAQ,SAAS,QAAQ,MAAM,GAAG,KAAK;AAC5D,YAAM,cAAc,KAAK,UAAU,MAAM,MAAM,OAAO,OAAO,CAAC;AAAA,IAChE,QAAQ;AACN,UAAI,aAAa;AACjB,UAAI,UAAU,gBAAgB,kBAAkB;AAChD,UAAI,IAAI,KAAK,UAAU;AAAA,QACrB,OAAO;AAAA,QACP,mBAAmB;AAAA,MACrB,CAAC,CAAC;AAAA,IACJ;AAAA,EACF,CAAC;AACD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,QAAQ,QAAQ,GAAG,QAAQ,YAAY,aAAa,MAAM;AACtE,aAAO,IAAI,SAAS,MAAM;AAC1B,cAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACD,QAAM,UAAU,OAAO,QAAQ;AAC/B,WAAS,UAAU,QAAQ,QAAQ,SAAS,GAAG,IAAI,IAAI,QAAQ,OAAO,MAAM,QAAQ,OAAO,IAAI,QAAQ,IAAI;AAC3G,UAAQ,IAAI,kCAAkC,EAAE,GAAG,SAAS,QAAQ,OAAO,CAAC;AAC5E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,MAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAClD,aAAO,MAAM,CAAC,UAAU,QAAQ,OAAO,KAAK,IAAI,QAAQ,CAAC;AAAA,IAC3D,CAAC;AAAA,EACH;AACF;","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 { createHash, randomBytes, randomUUID } from "crypto";
293
+ import { randomUUID } from "crypto";
272
294
  import { createServer } from "http";
273
- function base64Url(value) {
274
- return value.toString("base64url");
275
- }
295
+ import { derivePkceS256Challenge, generatePkcePair } from "@superfunctions/oauth-core";
276
296
  function createPkcePair() {
277
- const verifier = base64Url(randomBytes(48));
297
+ const pair = generatePkcePair(48);
278
298
  return {
279
- verifier,
280
- challenge: createHash("sha256").update(verifier).digest("base64url"),
281
- method: "S256"
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 ? createHash("sha256").update(verifier).digest("base64url") : void 0;
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
- await writeResponse(res, await oauth.handle(await toRequest(req, origin)));
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,