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
| Property | Type | Required | Default | Description |
|---|
serverUrl | string | Yes | | MCP server URL |
protocolVersion | string | Yes | | 2025-03-26, 2025-06-18, or 2025-11-25 |
registrationStrategy | string | Yes | | cimd, dcr, or preregistered |
auth | OAuthConformanceAuthConfig | No | { mode: "interactive" } | Auth mode config |
client | OAuthConformanceClientConfig | No | {} | Client credentials config |
verification | OAuthVerificationConfig | No | {} | Post-auth verification config |
scopes | string | No | | Space-separated scope string |
customHeaders | Record<string, string> | No | | Extra HTTP headers |
redirectUrl | string | No | Auto-generated | OAuth redirect URL |
fetchFn | typeof fetch | No | fetch | Custom fetch for recording/replay |
stepTimeout | number | No | 30000 | Per-step timeout in ms |
oauthConformanceChecks | boolean | No | false | Run 6 additional negative checks post-flow |
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.
| Property | Type | Description |
|---|
preregistered.clientId | string | Required when registrationStrategy === "preregistered" |
preregistered.clientSecret | string | Required for preregistered + client_credentials |
dynamicRegistration | Partial<OAuthDynamicRegistrationMetadata> | Override the default DCR body (e.g. client_name, logo_uri, scope) |
clientIdMetadataUrl | string | CIMD metadata URL. Defaults to https://www.mcpjam.com/.well-known/oauth/client-metadata.json |
OAuthVerificationConfig
| Property | Type | Default | Description |
|---|
listTools | boolean | false | Connect and call tools/list after auth |
callTool | { name, params? } | | Also call the named tool (enables listTools) |
timeout | number | 30000 | Verification 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`);
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",
},
},
});
When enabled, six extra checks run after a successful flow:
- DCR redirect URI policy — attempts dynamic client registration with a non-loopback
http:// redirect URI; expects rejection under the MCP authorization profile.
- Invalid client — sends a token request with an unknown
client_id; expects rejection.
- 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.
- Invalid token — sends an authenticated MCP initialize request with an obviously invalid bearer token; expects HTTP 401 from the MCP server.
- 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.
- 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
| Property | Type | Description |
|---|
passed | boolean | Whether the flow completed successfully |
protocolVersion | string | Protocol version tested |
registrationStrategy | string | Registration strategy tested |
serverUrl | string | Server URL tested |
steps | StepResult[] | Step-by-step results |
summary | string | Human-readable summary |
durationMs | number | Total duration |
verification | VerificationResult | Post-auth verification results (if enabled) |
StepResult
| Property | Type | Description |
|---|
step | string | Step identifier (e.g. "discovery", "token_request", "verify_list_tools") |
title | string | Human-readable step name |
summary | string | Short result summary |
status | "passed" | "failed" | "skipped" | Step outcome |
durationMs | number | Step duration |
logs | InfoLogEntry[] | Structured log entries |
http | HttpHistoryEntry | Primary HTTP exchange (if any) |
httpAttempts | HttpHistoryEntry[] | All HTTP traces including retries |
error | { message, details? } | Error info (if failed) |
teachableMoments | string[] | Educational hints for this step |
VerificationResult
| Property | Type | Description |
|---|
listTools.passed | boolean | Whether tools/list succeeded |
listTools.toolCount | number | Number of tools returned |
listTools.durationMs | number | Duration |
listTools.error | string | Error message if failed |
callTool.passed | boolean | Whether the tool call succeeded |
callTool.toolName | string | Name of the tool called |
callTool.durationMs | number | Duration |
callTool.error | string | Error message if failed |
| Property | Type | Description |
|---|
name | string | Suite name |
serverUrl | string | Shared server URL |
passed | boolean | true iff every flow passed |
results | Array<ConformanceResult & { label }> | Per-flow results |
summary | string | Human-readable suite summary |
durationMs | number | Total suite duration |