The sep-2243 scenario check IDs drifted from the requirement-traceability
yaml in #259 — the code emitted one ID per test case while the yaml
declares one per normative requirement, so the IDs no longer matched.
Rename the emitted IDs so a check ID maps to a single MUST/SHOULD and is
emitted once per case (the per-case detail moves to name/description),
matching the repo's existing 'same id, vary status/message' convention:
- mcp-method-header-* / mcp-name-header-* -> client-includes-standard-headers
- reject-invalid-tool-* / keep-valid-tool -> client-reject-invalid-tool
- server-accepts-{lower,upper}case-name -> header-name-case-insensitive
- server reject status checks (mismatch/missing/case x method/name)
-> server-reject-invalid-headers
- their error-code variants -> server-reject-error-code
Merge the yaml's server-reject-mismatch + server-reject-status into one
server-reject-invalid-headers MUST (HTTP 400 on a header-validation
failure); keep server-reject-error-code as the SHOULD. Base64/custom-header
rejection checks keep their own param-validation IDs (out of scope here).
Gates and the whitespace-acceptance check keep their scenario-matching
names. No behavior change — only check IDs, descriptions, and the yaml.
* Add prepare script for git dependency installs
* Add conformance tests for SEP-2243 HTTP Standardization
* Add traceability file for SEP-2243
* Fix prettier formatting in http-standard-headers.ts
* Improve SEP-2243 conformance test coverage
Close gaps in HTTP header conformance scenarios:
Client standard headers (http-standard-headers.ts):
- Enforce all expected methods in checks, failing any that were not observed
- Track Mcp-Name header per-method separately from Mcp-Method
- Advertise resources and prompts capabilities so clients exercise those endpoints
- Add a prompt entry for prompts/get testing
Client custom headers (http-custom-headers.ts):
- Add Base64 encoding checks for non-ASCII, whitespace, and control-char values
- Add null/omitted parameter test (second tool call with null value)
- Add client-keeps-valid-tool check verifying clients still call valid tools
after filtering out invalid ones
Server header validation (server/http-standard-headers.ts):
- Replace fetch-based sendRawRequest with http.request to preserve exact
header casing on the wire (fetch/Headers lowercases names)
- Compute defaultArgs and defaultHeaders from tool schema so test requests
satisfy all required parameters while varying only the param under test
Traceability (sep-2243.yaml):
- Add 7 new spec-to-check mappings (nonascii, whitespace, controlchar,
keeps-valid-tool, literal-missing-base64-prefix/suffix, no-mirror-unannotated)
- Fix 2 incorrect existing mappings
* Fill SEP-2243 conformance test coverage gaps
Add missing test cases identified by comparing the branch against the
SEP-2243 spec's Conformance Test Cases section:
- Mcp-Method on notifications: add notifications/initialized to
expectedMethods so clients are checked for header on notifications
- Boolean true: add debug=true param to complement verbose=false
- Leading-space-only and trailing-space-only: separate checks for
Base64 encoding when only one edge has whitespace
- Internal spaces only: verify plain ASCII (no Base64) for values
like 'us west 1' that have spaces only in the middle
- CRLF string: add line1\r\nline2 test (complements existing \n test)
- Leading tab: add \tindented test for tab-triggered Base64 encoding
- Server rejects invalid Mcp-Param chars: mark as excluded in YAML
since HTTP itself prevents these characters in headers
Register all new checks in sep-2243.yaml.
* Remove case-insensitive =?base64? prefix test
The spec states header values are case-sensitive (RFC 9110). The
=?base64? prefix is part of the header value, not the header name,
so it should be matched case-sensitively. This was identified as a
bug in the conformance tests per feedback on SEP-2243.
Changes:
- Remove server-accepts-case-insensitive-base64 check from server
validation scenario
- Change base64 prefix regex from case-insensitive (/i) to
case-sensitive in client header validation
- Remove corresponding entry from sep-2243.yaml
* Remove nested x-mcp-header rejection test
The spec constrains x-mcp-header to primitive types but does not
restrict it to top-level properties only. A nested string property
with x-mcp-header is valid per the spec. This was confirmed by
mikekistler in SEP-2243 PR discussion.
Removes invalid_nested_header from the invalid tool definitions
list and its corresponding sep-2243.yaml entry.
* Use numeric comparison for number header values
Compare number header values numerically instead of as strings to
accommodate cross-SDK floating point representation differences
(e.g., '42' vs '42.0'). For integers, exact numeric match is
required. For decimals, a tolerance of 1e-9 is allowed.
See SEP-2243 discussion on number precision.
* client/http-standard-headers: SKIPPED (not FAILURE) for unexercised methods; make getChecks() idempotent
A client that never calls prompts/list isn't violating SEP-2243 — the spec
sentence is 'The client MUST include the standard MCP request headers on each
POST request', and a request that was never sent can't fail it. The Mcp-Method
requirement is already proven by initialize/tools/list etc., so there's nothing
unique to prompts/list. SKIPPED keeps the gap visible without a false red.
Separately: getChecks() was pushing into this.checks on every call. The runner
may call it more than once (e.g. progress + final report); that produced
duplicate rows. Build a fresh array per call instead.
* client/http-standard-headers: de-dup guard in checkMcpNameHeader
checkMcpMethodHeader already has 'if (this.methodHeaderChecks.has(method)) return'.
checkMcpNameHeader was missing the equivalent. The test server advertises two
tools and two resources; a client that calls both produces two
'client-mcp-name-header-tools-call' / '...-resources-read' rows.
* client/http-standard-headers: replace -> /\//g for slug generation
String.replace with a string arg only replaces the first occurrence.
'notifications/initialized' is fine (one slash) but a method like
'notifications/resources/updated' would yield 'notifications-resources/updated'
in the check id. (replaceAll isn't in this project's TS lib target.)
* client/http-custom-headers: base64 regex (.+) -> (.*) so empty string round-trips
The spec doesn't forbid base64-encoding values that don't strictly need it.
A client that always wraps would send '=?base64??=' for empty_val: ''. The
'.+' wouldn't match, so the harness would compare '=?base64??=' === ''
literally and FAIL a compliant client. '(.*)' lets the empty payload decode
to '' and match.
* client/http-custom-headers: emit SUCCESS for no-mirror-unannotated
This check only pushed on FAILURE; a correct client produced no row at all,
so the pass was invisible in reports and couldn't be counted toward coverage.
Same-slug SUCCESS/FAILURE pair is the repo convention.
* client/http-custom-headers: drop redundant optional-present check
checkParamHeader('Verbose', ...) already pushes a FAILURE when the header is
missing. The explicit 'optional-present' block re-tests the same condition
under a second check id, doubling the row.
* server/http-standard-headers: defaultArgs must use real number/boolean, not strings
defaultArgs feeds the JSON-RPC body. With Record<string, string> + '0'/'false',
a server that validates inputSchema rejects on type mismatch — which is also a
400. Every header-rejection check then false-PASSes (server rejected for the
wrong reason), and server-accepts-valid-base64 false-FAILs a compliant server
because it never gets past schema validation.
Also handle 'integer' (JSON Schema distinguishes it from 'number').
* server/http-standard-headers: createAcceptanceCheck must also assert no JSON-RPC error in body
A server can return HTTP 200 with {error: {code: -32001, ...}} in the body.
Status-only acceptance lets that through as a pass.
* use DRAFT_PROTOCOL_VERSION constant instead of 'DRAFT-2026-v1' literal
The literal will rot when the draft cycle rolls over. types.ts already exports
DRAFT_PROTOCOL_VERSION for this.
* sep-2243.yaml: spec-first rewrite + move to src/seps/
Regenerated from the SEP-2243 spec diff (transports.mdx + tools.mdx) rather
than from the scenario implementations: 21 check rows + 4 excluded vs the
previous 46 + 2. Differences:
- one check id per spec sentence (test variants go in 'details', not new ids)
- 'text:' quotes the spec sentence verbatim instead of paraphrasing
- '-reject-status' (MUST 400) split from '-reject-error-code' (SHOULD -32001
for standard headers, MUST for custom)
- rows with no spec backing dropped (whitespace, base64-padding/chars, prefix/
suffix-literal, control-char-name)
- two more SHOULD excludes (log-warning, no-sensitive-params)
- check ids use sep-2243-<slug> convention
- check: key first, excludes grouped at bottom (matches sep-2164)
Moved to src/seps/ to match #272 layout (this branch predates it; will
reconcile cleanly on rebase).
* add negative test for HttpStandardHeadersScenario
Self-contained vitest that POSTs initialize with and without Mcp-Method,
asserts FAILURE/SUCCESS on the pinned check id, and proves getChecks() is
idempotent. No example client/server file needed for this one because the
scenario *is* the server — the test crafts the bad request directly.
This is the negative-test half of AGENTS.md §Testing your scenario; the
everything-client passing-example half is still TODO.
* server/http-standard-headers: severity fixes for whitespace + malformed-base64 tests
These five tests assert behavior the SEP-2243 text doesn't pin down. Kept
because they're useful consistency signals across SDKs, but adjusted so they
don't FAIL spec-compliant servers:
- server-accepts-whitespace-header-value: this IS a MUST, but per RFC 9110
§5.5 (field parsing MUST exclude OWS), not SEP-2243. Kept as FAILURE,
specReference now cites RFC 9110.
- server-rejects-invalid-base64-padding / -chars: downgraded to INFO.
SEP-2243 says only 'MUST decode them accordingly' without picking RFC 4648
strict vs lenient. Node's Buffer.from() accepts unpadded/dirty input and
decodes to a matching value (server accepts); .NET's Convert.FromBase64String
throws (server rejects). Either is currently compliant. WARNING would force
Tier-1 SDKs into non-default strict decoding (#245); INFO records the
behavior without prescribing it, so cross-SDK divergence is visible
without tier impact.
- server-literal-missing-base64-prefix / -suffix: unchanged. The wrapper
syntax is '=?base64?{x}?=' complete; treating partial wrappers as literal
is the natural reading.
Plumbing: createRejectionCheck gains an optional failSeverity param;
testBase64Case gains a 'reject-info' mode. (This is also the hook for the
larger 400-MUST / -32001-SHOULD split that still needs doing.)
* server/http-standard-headers: split rejection check into status (MUST) + error-code (SHOULD/MUST)
SEP-2243 §Server Validation: 'servers MUST return HTTP status 400 ... and
SHOULD include a JSON-RPC error response using ... -32001'. So a server that
returns 400 with -32600 (or no error body) is compliant for standard headers.
The single conflated check FAILed it.
For custom headers (§Server Behavior for Custom Headers) -32001 IS a MUST,
so both halves stay FAILURE there.
createRejectionCheck → createRejectionChecks returning two rows: '<id>' for
the 400 status and '<id>-error-code' for -32001. testCase (standard) sets
errorCodeSeverity=WARNING; testBase64Case/testMissingCustomHeader (custom)
keep FAILURE; the two malformed-base64 INFO probes set both halves to INFO.
* rename check ids to sep-2243-* prefix; kebab-case throughout
Aligns with the sep-2243.yaml traceability ids and the repo convention
(sep-2164-*, sep-2207-*). Variant suffixes are kept (per-method, per-tool)
so per-case visibility isn't lost; the YAML's row id is a prefix of the
emitted ids, which is fine — extra ids not in the YAML are allowed.
Also fixes the mixed hyphen/underscore in reject-invalid-tool ids
(toolName has underscores; replace to hyphens).
* client: extract BaseHttpScenario to client/http-base.ts; both client scenarios extend it
http-standard-headers.ts had its own copy of the start/stop/handleRequest
boilerplate (~100 lines) that http-custom-headers.ts already abstracted as
BaseHttpScenario. Moved the abstract class to a shared file; both scenario
files now import it. HttpStandardHeadersScenario implements handlePost()
instead of handleRequest(); its handleInitialize() uses the base
sendInitialize() with the resources/prompts capability flags it needs.
Net: 465 → 361 lines for http-standard-headers.ts; the third inline copy of
the same HTTP-server scaffold is gone.
* use req.setEncoding('utf8') instead of per-chunk Buffer.toString()
Per-chunk .toString() corrupts a multi-byte UTF-8 char that straddles a TCP
chunk boundary (e.g. 0xC3 | 0xBC decodes to two replacement chars instead of
'ü'). The custom-headers scenario sends '日本語'/'naïve' in the body, so this
is reachable in principle. setEncoding('utf8') makes 'data' emit strings with
boundary handling done by Node's StringDecoder.
Fixed in both places: BaseHttpScenario.handleRequest (request body) and
sendRawRequest (response body).
* fix: migrate SEP-2243 scenarios from specVersions to source (introducedIn/removedIn)
The introducedIn/removedIn refactor (#265) replaced the specVersions array
with a source property on all scenario interfaces. The SEP-2243 scenarios
still used the old specVersions field, causing matchesSpecVersion() to
receive undefined and crash in spec-version.test.ts.
Replace specVersions with source = { introducedIn: DRAFT_PROTOCOL_VERSION }
in BaseHttpScenario and the two server ClientScenario classes. Remove the
now-unused SpecVersion imports.
* server/http-standard-headers: malformed-base64 padding/chars back to FAILURE
Per discussion on #259: the SEP-2243 conformance-test-case table is the
approved source of truth and lists these as reject cases, so they're
FAILURE-level even though the spec body itself only says 'MUST decode them
accordingly'. SDKs whose stdlib base64 is lenient (Node, browsers) will need
to validate before decoding; if that's burdensome we'll revisit.
Removes the now-unused 'reject-info' mode and statusSeverity/INFO plumbing
introduced for these two probes.
---------
Co-authored-by: Tarek Mahmoud Sayed <tarekms@microsoft.com>
Co-authored-by: Tarek Mahmoud Sayed <tarekms@ntdev.microsoft.com>
Co-authored-by: Tarek Mahmoud Sayed <10833894+tarekgh@users.noreply.github.com>
Co-authored-by: Paul Carleton <paulc@anthropic.com>
* refactor: replace specVersions list with introducedIn/removedIn range tagging
Replaces per-scenario `specVersions: SpecVersion[]` arrays with `introducedIn`
and optional `removedIn` range fields on all three scenario interfaces. Extension
scenarios get an orthogonal `extension?: boolean` flag instead of the former
`'extension'` tag in the versions list.
matchesSpecVersion() is rewritten as a range comparison
(introducedIn <= target && (!removedIn || target < removedIn)), with draft
sorting after all dated versions. getScenarioSpecVersions() reconstructs the
effective version list from the range for backward-compat with tier-check.
Closes#256.
* refactor: nest spec-version range under `source: ScenarioSource` union
The flat `introducedIn`/`removedIn`/`extension?` shape forced extension
scenarios to set a placeholder `introducedIn` that was never read. Nesting
the version-range fields inside a `source` property typed as a union lets
the compiler enforce introducedIn XOR extensionId — no runtime invariant
test needed.
type ScenarioSource =
| { introducedIn: SpecVersion; removedIn?: SpecVersion }
| { extensionId: ExtensionId };
A class can't `implements` a union type directly, so the union lives inside
a property and `Scenario` stays a plain interface — `class implements
Scenario` keeps working everywhere. `EXTENSION_IDS` is a const array
mirroring the `DATED_SPEC_VERSIONS` pattern.
Also renames the cross-app-access scenario to enterprise-managed-auth to
match the extension's canonical name: file, class, slug, context schema
literal, and the everything-client runner.
* refactor: use canonical SEP-2133 extension identifiers
EXTENSION_IDS now holds the wire identifiers from the spec's extension docs
(client-matrix.mdx, docs/extensions/auth/*.mdx) so extensionId values can be
cross-referenced against capabilities.extensions keys directly:
io.modelcontextprotocol/oauth-client-credentials
io.modelcontextprotocol/enterprise-managed-authorization
The previous commit used a shortened 'enterprise-managed-auth' which doesn't
match the canonical '-authorization'. Re-renames the scenario file/class/slug
and downstream references to align.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: support "draft" as a first-class spec version target
- Add LATEST_SPEC_VERSION and DATED_SPEC_VERSIONS constants so the next
spec release is a one-line change in types.ts.
- Accept "draft" as a valid protocolVersion in the initialize check and
mock-server response.
- --spec-version draft now selects latest-dated scenarios plus draft-tagged
ones, so SEP authors can run the full suite against an SDK tracking the
in-progress spec without retagging core scenarios.
- Forward --spec-version to the client process via
MCP_CONFORMANCE_SPEC_VERSION so SDK examples can pick the matching
protocolVersion.
Closes#253
* review: collapse SpecVersion to wire strings; dedup NEGOTIABLE_PROTOCOL_VERSIONS; split out 'extension'
Addresses review feedback from @felixweinberger and @mikekistler:
- SpecVersion is now always a wire protocolVersion string
(DatedSpecVersion | typeof DRAFT_PROTOCOL_VERSION). The separate
'draft' tag literal is gone from the type system; 'draft' survives
only as a CLI input alias in resolveSpecVersion. Removes the
tag-vs-wire confusion and the specVersionToProtocolVersion mapping.
- NEGOTIABLE_PROTOCOL_VERSIONS in types.ts is the single source for
what the mock server accepts on initialize (was duplicated in
checks/client.ts and scenarios/client/initialize.ts).
- 'extension' moved out of SpecVersion into a separate ScenarioSpecTag
type. --spec-version extension is no longer valid; extension scenarios
remain reachable via --suite extensions.
- Draft-tagged scenarios now use the DRAFT_PROTOCOL_VERSION constant
so a draft revision bump is a one-line change in types.ts.
* feat: add sep-2207 client checks
* Update description for the offline access test
Co-authored-by: Paul Carleton <paulcarletonjr@gmail.com>
---------
Co-authored-by: Paul Carleton <paulcarletonjr@gmail.com>
* feat: add specVersions classification to conformance scenarios
Each scenario declares which spec versions it applies to as a list.
Scenarios that carry forward (e.g. initialize) list all applicable versions
['2025-06-18', '2025-11-25']. Scenarios removed from newer specs (e.g.
backcompat auth) only list their original version ['2025-03-26'].
- specVersions list on Scenario and ClientScenario interfaces
- --spec-version CLI filter uses simple .includes()
- Tier-check conformance matrix (Server / Client: Core / Client: Auth)
with per-version columns and unique All* count
- 7 unit tests for specVersions helpers
- Updated tier-audit skill docs with matrix format
* fix: fix console output template for tier audit skill
The template had an orphan table header (Check | Value | T2 | T1)
with no rows above the conformance matrix, causing an empty table
to render. The scorecard rows below the matrix also lacked their
own header.
Fix: two self-contained tables with clear labels — 'Conformance:'
for the per-version matrix, 'Scorecard:' for the check rows.
* fix: align skill console template with tier-check script output
Add asterisk footnote ('unique scenarios — a scenario may apply to
multiple spec versions') and rename 'Scorecard' to 'Repository Health'
to match the labels used by the tier-check CLI output.
* fix: add specVersions to CrossAppAccessCompleteFlowScenario
Added in 83c446d on main after this branch diverged.
The SDK's `requireBearerAuth` middleware only converts `InvalidTokenError`
instances to HTTP 401 responses. Generic `Error` instances fall through
as HTTP 500, which prevents clients from detecting authentication failures
and initiating the OAuth refresh/re-auth flow.
This was discovered while building token refresh conformance scenarios —
the mock server was returning 500 for expired/invalid tokens instead of
the expected 401.
Co-authored-by: JD Maturen <70791+jdmaturen@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat: add conformance tests for SEP-990
* Resolving review changes: Removed redundant tests, updated audience params
* fix: unused serverUrl parameter in runCrossAppAccessTokenExchange
* chore: apply prettier formatting
* fix: address PR review comments for SEP-990 conformance tests
- Delete unused separate token-exchange and jwt-bearer scenarios,
keeping only the complete e2e flow (review comment)
- Add missing required token exchange params per SEP-990 spec:
requested_token_type, audience, resource (review comment)
- Use ctx.idp_client_id for token exchange client_id instead of
AS client_id (review comment)
- Client discovers resource and auth server via PRM metadata
instead of receiving auth_server_url via context (review comment)
- Server IdP handler verifies all required token exchange params
with detailed error messages (review comment)
- Add resource, client_id, jti claims to ID-JAG per SEP-990 spec
- Verify ID-JAG typ header (oauth-id-jag+jwt) in JWT bearer handler
- Remove auth_server_url from context schema
* feat: add client auth and ID-JAG validation to XAA conformance test
Server-side (AS) now verifies:
- client_secret_basic authentication on JWT bearer grant
- ID-JAG typ header is oauth-id-jag+jwt
- ID-JAG client_id claim matches the authenticating client (Section 5.1)
- ID-JAG resource claim matches the MCP server resource identifier
- Client credentials provided via context (client_secret)
Server-side (IdP) now:
- Sets ID-JAG client_id to the MCP Client's AS client_id (not the
IdP client_id), per Section 6.1
Example client now:
- Authenticates to AS via client_secret_basic (Authorization: Basic)
instead of sending client_id in body
- Checks AS metadata grant_types_supported includes jwt-bearer
before attempting the flow
* fix: share MockTokenVerifier and remove unadvertised auth method
- Add shared MockTokenVerifier between AS and MCP server so the MCP
server only accepts tokens actually issued by the auth server,
matching the pattern used by all other auth scenarios
- Remove private_key_jwt from tokenEndpointAuthMethodsSupported since
the handler only implements client_secret_basic
---------
Co-authored-by: Paul Carleton <paulc@anthropic.com>
The auth test server helper and no-dns-rebinding-protection example
reused a single Server instance across requests, calling connect()
on each new transport. SDK v1.26.0's security fix (GHSA-345p-7cg4-v4c7)
now throws "Already connected to a transport" in this case.
Fix by wrapping Server creation in a factory function called per
request, matching the pattern already used by tools_call.ts and
everything-server.ts.
Also updates no-dns-rebinding-protection.ts to use registerTool()
instead of the deprecated tool() API.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: bump SDK to 1.26.0, add session management to tools_call scenario
SDK 1.26.0 made Protocol.connect() throw if already connected to a
transport. The tools_call scenario was calling server.connect(transport)
on every request with the same Server instance, which now throws.
Fixed by adding proper session management: each initialize request
creates a new Server + Transport pair, and subsequent requests are
routed to the correct transport via the mcp-session-id header.
* fix: return 404 for invalid session IDs in tools_call POST handler
The else branch was returning 400 for both missing-session and
invalid/stale-session cases. The MCP spec requires 404 for invalid
session IDs and 400 only for non-initialization requests without
any session ID.
* refactor: keep tools_call stateless, create fresh server per request
Instead of adding session management, simply create a new Server
instance per request. This preserves the original stateless design
while fixing the SDK 1.26.0 Protocol.connect() restriction.
Move auth/2025-03-26-oauth-metadata-backcompat and
auth/2025-03-26-oauth-endpoint-fallback out of the required
authScenariosList into a new backcompatScenariosList.
These test backward compatibility with the old 2025-03-26 auth spec
(no PRM, OAuth metadata at server root) which is not part of the
current spec requirements.
- Add new 'backcompat' client suite
- Back-compat scenarios remain in the 'all' suite
- Removed from 'core' and 'auth' suites
- Add separate test describe block for back-compat scenarios
Closes#126
* Add allowClientError to Scenario interface for expected client failures
* Apply suggestion from @pcarleton
Co-authored-by: Paul Carleton <paulcarletonjr@gmail.com>
---------
Co-authored-by: Paul Carleton <paulcarletonjr@gmail.com>
* feat: add resource parameter validation tests (RFC 8707)
Adds conformance tests for OAuth Resource Indicators (RFC 8707) implementation:
1. Resource parameter checks added to token-endpoint-auth-basic scenario:
- resource-parameter-in-authorization: Verify resource in auth request
- resource-parameter-in-token: Verify resource in token request
- resource-parameter-valid-uri: Verify valid canonical URI
- resource-parameter-consistency: Verify consistency between requests
2. New auth/resource-mismatch scenario:
- Tests that client rejects when PRM resource doesn't match server URL
- Server returns mismatched resource in PRM
- Test passes if client does NOT proceed with authorization
Also adds spec references for RFC 8707 and MCP resource parameter spec.
Closes#33
* fix: make resource consistency check a FAILURE and remove dead code
- Change resource parameter consistency from WARNING to FAILURE
- Remove unreachable protocol check in validateCanonicalUri
(URL constructor already validates scheme presence)
* feat: add PKCE conformance tests
Adds conformance checks for PKCE requirements from the MCP auth spec:
- pkce-code-challenge-sent: Verifies client sends code_challenge in auth request
- pkce-s256-method-used: Verifies client uses S256 code challenge method
- pkce-code-verifier-sent: Verifies client sends code_verifier in token request
- pkce-verifier-matches-challenge: Validates BASE64URL(SHA256(verifier)) = challenge
Also adds auth/pkce-no-s256-support scenario that tests clients correctly
refuse when S256 is not in code_challenge_methods_supported.
All PKCE checks are added to the createAuthServer helper, so they automatically
apply to all existing auth scenarios.
Fixes#77
* feat: add bad client example that skips PKCE
Adds auth-test-no-pkce.ts which implements a custom OAuth flow that
deliberately skips PKCE (no code_challenge in auth request, no
code_verifier in token request).
Also adds negative test to verify the conformance suite correctly
detects this non-compliant behavior.
* chore: skip pkce-no-s256-support scenario for now
* chore: fix formatting
* refactor: remove pkce-no-s256-support scenario and always validate PKCE
- Remove pkce-no-s256-support scenario (was skipped anyway)
- Make pkce-verifier-matches-challenge fail if either code_challenge
or code_verifier is missing, rather than only running when both present
- Update negative test to expect the new failure
* docs: add SDK integration guide and fix spec references
- Add SDK_INTEGRATION.md with guide for SDK maintainers on integrating conformance tests
- Link to guide from README.md
- Update spec references to use 2025-11-25 instead of draft URLs
* fix: prettier formatting and correct action version to v0.1.10
* feat: add pre-registration conformance test
Add conformance test for OAuth pre-registration flow where the server
does NOT support Dynamic Client Registration (DCR) and clients must
use pre-configured static credentials.
This addresses issue #34 (Client Registration Methods) - specifically
the pre-registration section that was previously not covered.
Changes:
- Add disableDynamicRegistration option to createAuthServer helper
- Add auth/pre-registration scenario with pre-registered client creds
- Add MCP_PREREGISTRATION spec reference
- Add context schema for pre-registration credentials
- Add withOAuthRetryWithProvider helper for pre-configured providers
- Add negative test client (auth-test-attempts-dcr.ts) that ignores
pre-registered credentials and fails
The scenario verifies that when a server does not advertise
registration_endpoint in its OAuth metadata, compliant clients
use pre-registered credentials passed via context instead of
attempting DCR.
Closes#34
* fix: add redirect_uris to saveClientInformation call
* chore: remove unused WithOAuthRetryOptions interface
* feat: add core and extensions suites for SDK tiering
Add new suites to support SDK tiering requirements:
Client suites:
- core (19 scenarios): Tier 1 requirements (base + auth)
- extensions (2 scenarios): Optional protocol extensions (client credentials)
- all (21 scenarios): Everything (core + extensions)
- auth (15 scenarios): Auth scenarios only (excludes extensions)
Server suites:
- core: Alias for 'active' (tier 1 requirements)
This allows SDK developers to run --suite core for tier 1 certification
while keeping extension tests available but not blocking.
* feat: promote elicitation tests from pending to active
Move 3 elicitation scenarios to active suite:
- elicitation-sep1330-enums
- tools-call-elicitation
- elicitation-sep1034-defaults
These tests are now passing in the Python SDK and ready for
SDK tiering requirements.
Remaining pending tests (2):
- json-schema-2020-12 (blocked on SDK PR #1135)
- server-sse-polling (blocked on SDK PR #1129)
* Add client credentials support to everything-client
- Add ConformanceContextSchema with discriminated union for auth contexts
- Add runClientCredentialsJwt and runClientCredentialsBasic handlers
- Update runner to include scenario name in context for discriminated union parsing
- Update test helpers to set env vars for inline client runners
- Fix JWT audience validation to match SDK behavior (no trailing slash)
- Remove client credentials scenarios from skip list
* Accept both trailing slash forms for JWT audience per RFC 3986
Per RFC 3986, URLs with and without trailing slash are equivalent.
The MCP spec recommends (SHOULD) the form without trailing slash, but
since it's not a MUST, the conformance test should accept both forms
for interoperability with clients like Pydantic that normalize URLs.
* Remove .vscode from tracking and add to gitignore
* Address review feedback
- Remove unused issuerUrl variable and comment
- Simplify audience array to exactly 2 values with clearer naming
- Add comment explaining strip-then-add-back logic
- Rename ConformanceContextSchema to ClientConformanceContextSchema
* Remove dist from tracking and add to gitignore
* Fix .gitignore formatting
* Add getHandler to everything-client for cleaner test imports
Instead of importing individual handlers, tests can now import getHandler
and look up handlers by scenario name.
* Use getHandler exclusively, remove goodClient import
- Add CIMD support to runAuthClient in everything-client
- Add auth fallback to getHandler for unregistered auth/* scenarios
- Remove goodClient import from test file
* Remove runAuthClient fallback, register all auth scenarios explicitly
* [auth] Add metadata suite for running metadata discovery tests
Adds a new 'metadata' suite that runs just the auth/metadata-* scenarios
for faster iteration when testing metadata discovery specifically.
Usage: node dist/index.mjs client --suite metadata --command "..."
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Don't require lefthook to be installed
Set assert_lefthook_installed to false so the hooks gracefully skip
if lefthook is not installed on the system.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add token endpoint auth method conformance tests
Add three new scenarios to test that clients correctly use the
appropriate authentication method based on server metadata:
- auth/token-endpoint-auth-basic: Tests client_secret_basic (HTTP Basic)
- auth/token-endpoint-auth-post: Tests client_secret_post
- auth/token-endpoint-auth-none: Tests public client (no auth)
Each scenario configures the server to only support one auth method
and verifies the client uses the correct method in token requests.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Refactor token endpoint auth tests to use shared createAuthServer
Extend createAuthServer helper with:
- tokenEndpointAuthMethodsSupported option for metadata
- onTokenRequest callback now receives full Request object
- onRegistrationRequest callback for custom client credentials
This eliminates the duplicate auth server implementation in
token-endpoint-auth.ts and reduces code by ~140 lines.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix onTokenRequest callback to use new interface
The createAuthServer onTokenRequest callback now passes an object with
authorizationHeader, body, timestamp etc. instead of (req, timestamp).
Also added missing return value for TokenRequestResult.
* Update @modelcontextprotocol/sdk to 1.25.1
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
- Update client initialization check to accept both 2025-06-18 and 2025-11-25
- Server echoes back client's version if valid (proper version negotiation)
- Fixes compatibility with SDK 1.25.1+ which sends 2025-11-25
🦉 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: validate scope checks ran and report overall test status
- Add FAILURE checks to scope scenarios when authorization flow doesn't
complete (ScopeFromWwwAuthenticateScenario, ScopeFromScopesSupportedScenario,
ScopeOmittedWhenUndefinedScenario)
- Report client timeout and exit errors in test results
- Add OVERALL: PASSED/FAILED summary at end of test output
- Exit with code 1 on any failure (check failures, client timeout, or exit error)
Previously, if the OAuth authorization flow didn't complete, the scope
checks were never triggered and the test would incorrectly report success.
Now these scenarios emit a FAILURE if the expected scope check wasn't recorded.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Count warnings as failures in overall test result
Warnings now contribute to the overall failure status, causing the test
to exit with code 1 when any warnings are present. Also displays warning
checks in the output summary, similar to how failures are displayed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Count warnings as failures in suite runner
Update the suite summary to show ✗ for scenarios with warnings,
and exit with code 1 if any warnings are present across the suite.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Add client credentials conformance scenarios with dynamic keypair generation
Implements SEP-1046 client credentials conformance tests:
- auth/client-credentials-jwt: Tests private_key_jwt authentication
- auth/client-credentials-basic: Tests client_secret_basic authentication
Key changes:
- Generate EC P-256 keypair dynamically at test start (no hardcoded keys)
- Pass credentials to client via MCP_CONFORMANCE_CONTEXT environment variable
- Add context field to ScenarioUrls interface for scenario-specific data
- Update client runner to pass context as env var to spawned client process
The MCP_CONFORMANCE_CONTEXT env var contains a JSON object with:
- client_id: The expected client identifier
- private_key_pem: PEM-encoded private key (for JWT scenarios)
- client_secret: Client secret (for basic auth scenarios)
- signing_algorithm: JWT signing algorithm (defaults to ES256)
* Fix client-credentials scenarios for conformance testing
- Generate EC keypair with extractable: true so private key can be
exported and passed to clients via MCP_CONFORMANCE_CONTEXT
- Fix client_secret_basic scenario to use authorizationHeader param
instead of non-existent headers object
* Fix jose type import - use CryptoKey instead of KeyLike
* Add OAuth 2.1 section 4.2 spec reference to client credentials checks
Add OAUTH_2_1_CLIENT_CREDENTIALS spec reference pointing to OAuth 2.1
draft section 4.2 (Client Credentials Grant) and include it in all
client_credentials conformance checks for both JWT and basic auth flows.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add JWT iss claim verification for client credentials
Per RFC 7523, verify that the JWT issuer (iss) claim matches the
expected client_id, in addition to the existing sub claim check.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* lefthook: move deps installs to optional local change
* add test scenario for infinite retry
and negative test
* Add broken client for retry limit negative test and fix lefthook formatting
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix www-authenticate in scope scenario
* add suites and shift pending to reflect current pending
* lint
---------
Co-authored-by: Claude <noreply@anthropic.com>
* add cimd test and negative test
* Improve auth-test-no-cimd.ts documentation for clarity
Address review feedback: expanded comments to better explain what CIMD is,
why this client is non-compliant, and what behavior it's testing.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove .js extensions from relative imports
tsx doesn't require .js extensions for TypeScript imports.
Keeps .js for @modelcontextprotocol/sdk imports as those are external packages.
Also improves auth-test-no-cimd.ts documentation for clarity.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Skip auth/basic-cimd test until SDK support lands
Waiting on typescript-sdk PR #1127 for CIMD support.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix trailing whitespace in auth-test-no-cimd.ts
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* [auth] Add metadata suite for running metadata discovery tests
Adds a new 'metadata' suite that runs just the auth/metadata-* scenarios
for faster iteration when testing metadata discovery specifically.
Usage: node dist/index.mjs client --suite metadata --command "..."
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Don't require lefthook to be installed
Set assert_lefthook_installed to false so the hooks gracefully skip
if lefthook is not installed on the system.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* wip scope change
* rm useless bool
* fix-me: package changes for linking
* negative test
* negative test for multiple scopes
* fix displays of warnings
* wip step-up
* negative test for step up auth
* move warning into main request flow
* clean up more checks
* cleanup step-up scenario
* cleanup and simplify
* cleanup middleware
* inline negative tests
* inline the other test
* refactor logging and inlining for standalone execution too
* skip pending tests
* clean up constructor calls for server lifecycle
* add back compat test
* fix dup logging, rm prm
* avoid fallbacks
* better error from example client
* add final endpoint method
* add test for OASM priority order
* s/prepack/prepare/ for npx
* try switching to .js
* make tsdown version happy default
* fix lock
* fix prmPath issue
* back to main package-lock
* fix bin path
* Add comprehensive auth scenario test suite
- Add index.test.ts to test all auth scenarios with auth-test.ts client
- Upgrade @modelcontextprotocol/sdk from 1.20.1 to 1.22.0
- All 5 auth scenarios now pass including auth/basic-metadata-var2
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* consolidate tests into index.test.ts
* rename metadata
* fix comments
* rework auth scenario listing
* add 3rd variation of metadata
* fix some type issues introduced by version bump
* add reusable spec references
* more refs
* fix up spec references
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Add SEP-1330 conformance test for elicitation enum schemas
Adds server-side conformance test that validates servers properly request
elicitation with SEP-1330 enum schema improvements:
- Untitled single-select enum (type: string, enum: [...])
- Titled single-select enum (oneOf with const/title)
- Legacy titled enum (enumNames for backward compatibility)
- Untitled multi-select enum (type: array, items.enum)
- Titled multi-select enum (items.anyOf with const/title)
Test expects server to implement `test_elicitation_sep1330_enums` tool.
* Add reference server implementation for SEP-1330 conformance test
Adds test_elicitation_sep1330_enums tool to everything-server that
requests elicitation with all 5 enum schema variants. Also updates
SERVER_REQUIREMENTS.md with full specification.
* Fix prettier formatting
* only test active tests (#24)
* refactor maps to pull names from the test
* bring names back
* separate active from pending
* only test active
---------
Co-authored-by: Paul Carleton <paulcarletonjr@gmail.com>
* feat: add client-side SEP-1034 conformance test for elicitation defaults
Adds a conformance test that validates client SDKs properly apply
default values for omitted fields in elicitation responses per SEP-1034.
The test spins up a server that sends elicitation requests with defaults,
and validates that the client returns content with those defaults applied.
* feat: use elicitInput() for SEP-1034 default validation
Updates client-side test to validate server-side default application
using elicitInput() helper, which applies defaults via validation layer.
This aligns with the TypeScript SDK's SEP-1034 implementation approach.
The test now validates:
- Server uses elicitInput() with schemas containing defaults
- Client returns empty/partial content
- Server automatically applies defaults during validation
- Defaults are correctly merged for all primitive types
* refactor: use server.request() instead of elicitInput() for client-side defaults test
Ensures conformance test validates true client-side default application
rather than server-side validation behavior.
* feat: add applyDefaults capability flag for opt-in default application
Enables conformance test to opt into client-side elicitation defaults
by setting the applyDefaults capability flag.
* readme for auth tests
* wip prm test
* add example helper for local oauth w/o browser
* working through dangling timeout issue
* add better test function
* pretty printing
* add mcp method
* some more logging tweaks
* refactor logger into 1 class
* use common request logger
* fix base scenario
* some renaming
* refactor into helpers
* refactor to support second test
* rm readme
* add comment about www-authenticate
* spec for servers
* initial version of everything server and tests - tets are very simple
* some clean up
* lint etc.
* fix package lock agian
---------
Co-authored-by: Paul Carleton <paulc@anthropic.com>