refactor: range-based spec version tagging (introducedIn/removedIn) (#265)

* 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>
This commit is contained in:
Paul Carleton 2026-05-14 16:12:32 +01:00 • committed by GitHub
commit acc70de0f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -2,7 +2,7 @@
* Tools test scenarios for MCP servers
*/
import { ClientScenario, ConformanceCheck, SpecVersion } from '../../types';
import { ClientScenario, ConformanceCheck } from '../../types';
import { connectToServer, NotificationCollector } from './client-helper';
import {
CallToolResultSchema,
@ -90,7 +90,7 @@ export function buildToolsNameFormatCheck(
export class ToolsListScenario implements ClientScenario {
name = 'tools-list';
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];
readonly source = { introducedIn: '2025-06-18' } as const;
description = `Test listing available tools.
**Server Implementation Requirements:**
@ -177,7 +177,7 @@ export class ToolsListScenario implements ClientScenario {
export class ToolsCallSimpleTextScenario implements ClientScenario {
name = 'tools-call-simple-text';
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];
readonly source = { introducedIn: '2025-06-18' } as const;
description = `Test calling a tool that returns simple text.
**Server Implementation Requirements:**
@ -262,7 +262,7 @@ Implement tool \`test_simple_text\` with no arguments that returns:
export class ToolsCallImageScenario implements ClientScenario {
name = 'tools-call-image';
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];
readonly source = { introducedIn: '2025-06-18' } as const;
description = `Test calling a tool that returns image content.
**Server Implementation Requirements:**
@ -350,7 +350,7 @@ Implement tool \`test_image_content\` with no arguments that returns:
export class ToolsCallMultipleContentTypesScenario implements ClientScenario {
name = 'tools-call-mixed-content';
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];
readonly source = { introducedIn: '2025-06-18' } as const;
description = `Test tool returning multiple content types.
**Server Implementation Requirements:**
@ -451,7 +451,7 @@ Implement tool \`test_multiple_content_types\` with no arguments that returns:
export class ToolsCallWithLoggingScenario implements ClientScenario {
name = 'tools-call-with-logging';
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];
readonly source = { introducedIn: '2025-06-18' } as const;
description = `Test tool that sends log messages during execution.
**Server Implementation Requirements:**
@ -540,7 +540,7 @@ Implement tool \`test_tool_with_logging\` with no arguments.
export class ToolsCallErrorScenario implements ClientScenario {
name = 'tools-call-error';
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];
readonly source = { introducedIn: '2025-06-18' } as const;
description = `Test tool error reporting.
**Server Implementation Requirements:**
@ -625,7 +625,7 @@ Implement tool \`test_error_handling\` with no arguments.
export class ToolsCallWithProgressScenario implements ClientScenario {
name = 'tools-call-with-progress';
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];
readonly source = { introducedIn: '2025-06-18' } as const;
description = `Test tool that reports progress notifications.
**Server Implementation Requirements:**
@ -745,7 +745,7 @@ If no progress token provided, just execute with delays.
export class ToolsCallSamplingScenario implements ClientScenario {
name = 'tools-call-sampling';
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];
readonly source = { introducedIn: '2025-06-18' } as const;
description = `Test tool that requests LLM sampling from client.
**Server Implementation Requirements:**
@ -873,7 +873,7 @@ Implement tool \`test_sampling\` with argument:
export class ToolsCallElicitationScenario implements ClientScenario {
name = 'tools-call-elicitation';
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];
readonly source = { introducedIn: '2025-06-18' } as const;
description = `Test tool that requests user input (elicitation) from client.
**Server Implementation Requirements:**
@ -1004,7 +1004,7 @@ Implement tool \`test_elicitation\` with argument:
export class ToolsCallAudioScenario implements ClientScenario {
name = 'tools-call-audio';
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];
readonly source = { introducedIn: '2025-06-18' } as const;
description = `Test calling a tool that returns audio content.
**Server Implementation Requirements:**
@ -1099,7 +1099,7 @@ Implement tool \`test_audio_content\` with no arguments that returns:
export class ToolsCallEmbeddedResourceScenario implements ClientScenario {
name = 'tools-call-embedded-resource';
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];
readonly source = { introducedIn: '2025-06-18' } as const;
description = `Test calling a tool that returns embedded resource content.
**Server Implementation Requirements:**