Skip to main content
The OAuth Conformance SDK classes let you run the same flows the CLI oauth conformance command runs, but programmatically — for custom reporters, test frameworks, recording/replay via custom fetchFn, or Playwright-driven consent via openUrl.
For CLI usage (one-liners, CI recipes, troubleshooting), see CLI: OAuth Conformance. This page covers the TypeScript SDK only.

Import

import { OAuthConformanceTest, OAuthConformanceSuite } from "@mcpjam/sdk";

Single flow

const test = new OAuthConformanceTest({
  serverUrl: "https://your-server.com/mcp",
  protocolVersion: "2025-11-25",
  registrationStrategy: "dcr",
  auth: { mode: "headless" },
  verification: { listTools: true },
});

const result = await test.run();

console.log(result.passed);   // true
console.log(result.summary);  // "OAuth conformance passed for ..."
console.log(result.steps);    // Step-by-step results with HTTP traces

OAuthConformanceConfig

PropertyTypeRequiredDefaultDescription
serverUrlstringYesMCP server URL
protocolVersionstringYes2025-03-26, 2025-06-18, or 2025-11-25
registrationStrategystringYescimd, dcr, or preregistered
authOAuthConformanceAuthConfigNo{ mode: "interactive" }Auth mode config
clientOAuthConformanceClientConfigNo{}Client credentials config
verificationOAuthVerificationConfigNo{}Post-auth verification config
scopesstringNoSpace-separated scope string
customHeadersRecord<string, string>NoExtra HTTP headers
redirectUrlstringNoAuto-generatedOAuth redirect URL
fetchFntypeof fetchNofetchCustom fetch for recording/replay
stepTimeoutnumberNo30000Per-step timeout in ms
oauthConformanceChecksbooleanNofalseRun 6 additional negative checks post-flow

OAuthConformanceAuthConfig

type OAuthConformanceAuthConfig =
  | { mode: "interactive"; openUrl?: (url: string) => Promise<void> }
  | { mode: "headless" }
  | {
      mode: "client_credentials";
      clientId: string;
      clientSecret: string;
    };
  • interactive.openUrl — override how the consent URL is opened. Default launches the system browser. Use for Playwright, custom launchers, or logging the URL.

OAuthConformanceClientConfig

PropertyTypeDescription
preregistered.clientIdstringRequired when registrationStrategy === "preregistered"
preregistered.clientSecretstringRequired for preregistered + client_credentials
dynamicRegistrationPartial<OAuthDynamicRegistrationMetadata>Override the default DCR body (e.g. client_name, logo_uri, scope)
clientIdMetadataUrlstringCIMD metadata URL. Defaults to https://www.mcpjam.com/.well-known/oauth/client-metadata.json

OAuthVerificationConfig

PropertyTypeDefaultDescription
listToolsbooleanfalseConnect and call tools/list after auth
callTool{ name, params? }Also call the named tool (enables listTools)
timeoutnumber30000Verification timeout in ms

SDK-only features

These are available in the SDK but not exposed as CLI flags.

Custom fetch (fetchFn)

Swap in a recording, mocking, or proxying fetch implementation:
import { OAuthConformanceTest } from "@mcpjam/sdk";

const requests: Request[] = [];

const test = new OAuthConformanceTest({
  serverUrl: "https://your-server.com/mcp",
  protocolVersion: "2025-11-25",
  registrationStrategy: "dcr",
  fetchFn: async (url, init) => {
    requests.push(new Request(url, init));
    return fetch(url, init);
  },
});

const result = await test.run();
console.log(`Captured ${requests.length} HTTP requests`);

DCR metadata overrides (dynamicRegistration)

Customize the client metadata sent during Dynamic Client Registration:
const test = new OAuthConformanceTest({
  serverUrl: "https://your-server.com/mcp",
  protocolVersion: "2025-11-25",
  registrationStrategy: "dcr",
  client: {
    dynamicRegistration: {
      client_name: "My Custom Test Client",
      logo_uri: "https://example.com/logo.png",
      scope: "read:tools write:tools",
    },
  },
});

Negative conformance checks (oauthConformanceChecks)

When enabled, six extra checks run after a successful flow:
  1. DCR redirect URI policy — attempts dynamic client registration with a non-loopback http:// redirect URI; expects rejection under the MCP authorization profile.
  2. Invalid client — sends a token request with an unknown client_id; expects rejection.
  3. Invalid redirect at the authorization endpoint — sends an authorization request with a mismatched redirect_uri and looks for rejection before the server redirects back to it. This step may be skipped if the server defers validation behind user interaction.
  4. Invalid token — sends an authenticated MCP initialize request with an obviously invalid bearer token; expects HTTP 401 from the MCP server.
  5. Invalid redirect at the token endpoint — sends a token request with a mismatched redirect_uri to look for redirect exact-match enforcement. This step may be skipped if the request is rejected for another reason before redirect validation is demonstrated.
  6. Token format — validates the token response includes access_token, token_type, and expiration metadata.
const test = new OAuthConformanceTest({
  serverUrl: "https://your-server.com/mcp",
  protocolVersion: "2025-11-25",
  registrationStrategy: "dcr",
  oauthConformanceChecks: true,
});

const result = await test.run();
// result.steps will include oauth_dcr_http_redirect_uri, oauth_invalid_client,
// oauth_invalid_authorize_redirect, oauth_invalid_token,
// oauth_invalid_redirect, and oauth_token_format
oauthConformanceChecks is also available via the CLI’s --conformance-checks flag.

Suite

const suite = new OAuthConformanceSuite({
  name: "My Server",
  serverUrl: "https://your-server.com/mcp",
  defaults: {
    auth: { mode: "headless" },
    verification: { listTools: true },
  },
  flows: [
    { protocolVersion: "2025-11-25", registrationStrategy: "cimd" },
    { protocolVersion: "2025-11-25", registrationStrategy: "dcr" },
  ],
});

const result = await suite.run();

console.log(result.passed);   // true
console.log(result.summary);  // "All 2 flows passed for ..."
for (const flow of result.results) {
  console.log(`${flow.passed ? "PASS" : "FAIL"} ${flow.label}`);
}

Result types

ConformanceResult

PropertyTypeDescription
passedbooleanWhether the flow completed successfully
protocolVersionstringProtocol version tested
registrationStrategystringRegistration strategy tested
serverUrlstringServer URL tested
stepsStepResult[]Step-by-step results
summarystringHuman-readable summary
durationMsnumberTotal duration
verificationVerificationResultPost-auth verification results (if enabled)

StepResult

PropertyTypeDescription
stepstringStep identifier (e.g. "discovery", "token_request", "verify_list_tools")
titlestringHuman-readable step name
summarystringShort result summary
status"passed" | "failed" | "skipped"Step outcome
durationMsnumberStep duration
logsInfoLogEntry[]Structured log entries
httpHttpHistoryEntryPrimary HTTP exchange (if any)
httpAttemptsHttpHistoryEntry[]All HTTP traces including retries
error{ message, details? }Error info (if failed)
teachableMomentsstring[]Educational hints for this step

VerificationResult

PropertyTypeDescription
listTools.passedbooleanWhether tools/list succeeded
listTools.toolCountnumberNumber of tools returned
listTools.durationMsnumberDuration
listTools.errorstringError message if failed
callTool.passedbooleanWhether the tool call succeeded
callTool.toolNamestringName of the tool called
callTool.durationMsnumberDuration
callTool.errorstringError message if failed

OAuthConformanceSuiteResult

PropertyTypeDescription
namestringSuite name
serverUrlstringShared server URL
passedbooleantrue iff every flow passed
resultsArray<ConformanceResult & { label }>Per-flow results
summarystringHuman-readable suite summary
durationMsnumberTotal suite duration