@mcpdesc/validator 0.4.0 → 0.6.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@mcpdesc/validator` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this package follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ Dates for published releases are the UTC publication dates recorded by npm.
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [0.6.0] - 2026-09-02
12
+
13
+ ### Added
14
+
15
+ - Added `@mcpdesc/validator/browser` as a descriptive strict-CSP alias for the existing `@mcpdesc/validator/standalone` implementation, with the same API and declarations.
16
+ - Added blocked-code-generation validation coverage, including document-provided schemas, plus esbuild, Vite, runtime-AJV exclusion, and packed-export checks.
17
+
18
+ ### Changed
19
+
20
+ - Documented the strict-CSP and runtime-compilation behavior of the default, browser, and standalone entries. The default and standalone exports remain unchanged, and no conditional browser export was added.
21
+
22
+ ### Bundle size
23
+
24
+ - Before this change, the generated standalone artifact was 1,730,897 bytes (195,998 bytes gzip). After adding the zero-source-file browser alias it remains 1,730,897 bytes (195,998 bytes gzip). Minified esbuild bundles of `/browser` and `/standalone` are both 1,752,417 bytes (0-byte delta); the minified Vite `/browser` bundle is 2,088,016 bytes in the same development environment.
25
+ - A metadata-only entry was evaluated but not added. The current public metadata is coupled to the cumulative snapshot registry, and splitting it would introduce another declaration and synchronization boundary without an approved consumer or measured package-level benefit.
26
+
27
+ ## [0.5.0] - 2026-08-31
28
+
29
+ ### Added
30
+
31
+ - Added `resolveMcpDescriptionSpecification` for resolving an immutable validator selector from a document's `$schema` identity or checking an exact caller-supplied selector.
32
+ - Added each snapshot's recorded schema URI to `specificationProvenance`.
33
+ - Added the `@mcpdesc/validator/standalone` entry for strict-CSP browsers. It preserves the synchronous public API without `eval`, `new Function`, Node.js built-ins, network schema retrieval, or WASM.
34
+ - Added the immutable `0.8.0-rc.1` selector with the release candidate's canonical schema identity and published schema digest.
35
+
36
+ ## [0.4.0] - 2026-08-29
37
+
38
+ ### Added
39
+
40
+ - Added the immutable `0.8.0-draft.4` selector, embedded schema, semantic implementation, and frozen fixture corpus.
41
+ - Added Draft 4 schema-digest and snapshot metadata checks while preserving the Draft 1 through Draft 3 implementations.
42
+
43
+ ## [0.3.0] - 2026-08-27
44
+
45
+ ### Added
46
+
47
+ - Added the immutable `0.8.0-draft.3` selector, embedded schema, semantic implementation, and frozen fixture corpus.
48
+ - Added Draft 3 to the cumulative browser bundle, declarations, support metadata, and package-content checks.
49
+
50
+ ## [0.2.0] - 2026-08-27
51
+
52
+ ### Added
53
+
54
+ - Added the immutable `0.8.0-draft.2` selector as a sibling implementation without changing Draft 1 behavior.
55
+ - Added cumulative selector dispatch, protocol-version metadata, declarations, browser checks, and a frozen Draft 2 fixture corpus.
56
+
57
+ ## [0.1.0] - 2026-08-25
58
+
59
+ ### Added
60
+
61
+ - Published the initial reusable validator package for the immutable `0.8.0-draft.1` snapshot.
62
+ - Added synchronous structural and semantic validation for parsed JavaScript values with deterministic diagnostics and exact selector dispatch.
63
+ - Added ESM browser support, TypeScript declarations, embedded schema provenance, frozen fixtures, and package-content checks.
64
+
65
+ [Unreleased]: https://github.com/mcpdesc/mcpdesc-specification/compare/validator-v0.6.0...HEAD
66
+ [0.6.0]: https://github.com/mcpdesc/mcpdesc-specification/compare/validator-v0.5.0...validator-v0.6.0
67
+ [0.5.0]: https://github.com/mcpdesc/mcpdesc-specification/compare/17cc533e79b19ea2dbc1edcf06e30ba68a7d9b79...validator-v0.5.0
68
+ [0.4.0]: https://www.npmjs.com/package/@mcpdesc/validator/v/0.4.0
69
+ [0.3.0]: https://github.com/mcpdesc/mcpdesc-specification/releases/tag/validator-v0.3.0
70
+ [0.2.0]: https://www.npmjs.com/package/@mcpdesc/validator/v/0.2.0
71
+ [0.1.0]: https://github.com/mcpdesc/mcpdesc-specification/releases/tag/validator-v0.1.0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Isomorphic structural and semantic validation for immutable MCP Description specification snapshots.
4
4
 
5
- Version `0.4.0` cumulatively supports these immutable snapshots and is intended for npm `latest` after the Draft 4 specification tag and package tarball are reviewed.
5
+ Version `0.6.0` cumulatively supports these immutable snapshots.
6
6
 
7
7
  | Selector | Tag | Embedded schema SHA-256 |
8
8
  |---|---|---|
@@ -10,6 +10,7 @@ Version `0.4.0` cumulatively supports these immutable snapshots and is intended
10
10
  | `0.8.0-draft.2` | `v0.8.0-draft.2` | `ab692c1a5a0f7e5f29be1940aa8c64a56d4620be0a19d00cf0a64680b7e517fa` |
11
11
  | `0.8.0-draft.3` | `v0.8.0-draft.3` | `8823c1f1946360b2a44d00920e2092e5e4acd139a1964befad4eb0bf3ce96002` |
12
12
  | `0.8.0-draft.4` | `v0.8.0-draft.4` | `93ed03f74059b5b3ce7509a96b59161bdab2c3cf7734397a9bec5a7588d0b03b` |
13
+ | `0.8.0-rc.1` | `v0.8.0-rc.1` | `936a0f24ade501fcabf3d6498c0440c445daa672a575573a35954cee49430ac4` |
13
14
 
14
15
  Repository changes alone do not publish the package or create specification or validator tags.
15
16
 
@@ -19,7 +20,7 @@ Repository changes alone do not publish the package or create specification or v
19
20
  import { validateMcpDescription } from '@mcpdesc/validator';
20
21
 
21
22
  const result = validateMcpDescription(parsedDocument, {
22
- specification: '0.8.0-draft.4'
23
+ specification: '0.8.0-rc.1'
23
24
  });
24
25
 
25
26
  if (!result.valid) {
@@ -29,7 +30,58 @@ if (!result.valid) {
29
30
 
30
31
  Callers provide an already parsed JavaScript value. JSON and YAML parsing are outside this package.
31
32
 
32
- The `options` argument and exact `specification` selector are required. The unqualified selector `0.8.0` is intentionally unsupported because draft iterations are immutable compatibility contracts.
33
+ The `options` argument and exact `specification` selector are required. The unqualified selector `0.8.0` is intentionally unsupported because draft and release-candidate iterations are immutable compatibility contracts.
34
+
35
+ ### Entry points
36
+
37
+ | Entry | Strict CSP without `unsafe-eval` | Fixed MCP Description schemas | Document-provided schemas |
38
+ |---|---|---|---|
39
+ | `@mcpdesc/validator` | No | Compiled by AJV at runtime | Compiled or interpreted at runtime |
40
+ | `@mcpdesc/validator/browser` | Yes | Precompiled during package development | Interpreted at runtime |
41
+ | `@mcpdesc/validator/standalone` | Yes | Precompiled during package development | Interpreted at runtime |
42
+
43
+ Browser applications that prohibit dynamic code generation should use the descriptive CSP-safe browser entry:
44
+
45
+ ```js
46
+ import { validateMcpDescription } from '@mcpdesc/validator/browser';
47
+ ```
48
+
49
+ The `/browser` entry is a public alias for the existing `/standalone` implementation. `/standalone` remains supported for backward compatibility. Both have the same synchronous API, selectors, diagnostics, and offline external-reference behavior as the default entry. Fixed MCP Description schemas and JSON Schema meta-schemas are precompiled during package development; document-provided Tool and Elicitation schemas use the package's interpreted JSON Schema dependency, so neither entry uses `eval` or `new Function` or requires `unsafe-eval`. These entries are larger and slower than the default AJV-based entry and are JavaScript, not WASM.
50
+
51
+ The default entry remains unchanged and uses AJV runtime compilation. Applications that need dynamic schema compilation can use it where their runtime policy permits dynamic code generation. Importing it may fail when a browser enforces strict CSP. The package intentionally does not select a different implementation through conditional exports; consumers choose the required behavior explicitly.
52
+
53
+ ## Snapshot resolution
54
+
55
+ `resolveMcpDescriptionSpecification` resolves an exact validator selector from
56
+ the document's `$schema` identity or checks a caller-supplied exact selector:
57
+
58
+ ```js
59
+ import { resolveMcpDescriptionSpecification } from '@mcpdesc/validator';
60
+
61
+ const resolution = resolveMcpDescriptionSpecification(parsedDocument);
62
+ if (resolution.status === 'resolved') {
63
+ console.log(resolution.specification, resolution.provenance);
64
+ } else {
65
+ console.error(resolution.diagnostics);
66
+ }
67
+ ```
68
+
69
+ Resolution is pure and performs no network retrieval. It does not validate the
70
+ document and never infers a draft snapshot from `mcpdesc: "0.8.0"` alone.
71
+ Draft 4's format-qualified schema URI resolves uniquely. Drafts 1 through 3
72
+ share the legacy `https://mcpdesc.org/schema/0.8.0.json` URI, so that URI is
73
+ ambiguous unless the caller supplies a consistent exact selector:
74
+
75
+ ```js
76
+ const resolution = resolveMcpDescriptionSpecification(parsedDocument, {
77
+ specification: '0.8.0-draft.3'
78
+ });
79
+ ```
80
+
81
+ Unresolved results distinguish missing, invalid, unknown, ambiguous, and
82
+ contradictory identity, as well as unsupported caller selectors. A supplied
83
+ selector can resolve a document with no `$schema`; when `$schema` is present it
84
+ must match the selected snapshot's recorded schema URI.
33
85
 
34
86
  ## Result
35
87
 
@@ -53,11 +105,11 @@ Structural paths start with AJV's instance path. A `required` error appends its
53
105
 
54
106
  ## Support metadata
55
107
 
56
- The package exports frozen `supportedSpecifications`, `supportedProtocolVersions`, and `specificationProvenance` values. Public validation dispatches through a registry keyed by exact specification selectors. The current repository selector set is `0.8.0-draft.1`, `0.8.0-draft.2`, `0.8.0-draft.3`, and `0.8.0-draft.4`; the protocol-version export is the deduplicated union supported by those snapshots.
108
+ The package exports frozen `supportedSpecifications`, `supportedProtocolVersions`, and `specificationProvenance` values. Provenance records include the snapshot tag, recorded schema URI, and embedded schema SHA-256 digest. Public validation dispatches through a registry keyed by exact specification selectors. The current repository selector set is `0.8.0-draft.1`, `0.8.0-draft.2`, `0.8.0-draft.3`, `0.8.0-draft.4`, and `0.8.0-rc.1`; the protocol-version export is the deduplicated union supported by those snapshots.
57
109
 
58
110
  npm package SemVer tracks implementation releases independently from specification snapshot identity. Adding a later snapshot is additive: it must use a sibling implementation and selector rather than changing an existing snapshot's schema, semantics, metadata, fixtures, or results.
59
111
 
60
- The runtime bundles its schema, performs no network fetches for external schema references, and imports no Node.js built-ins. Unresolved external Tool-schema references produce incomplete-validation warnings and are preserved. The same ESM entry point supports Node.js 20 or later and browser bundlers.
112
+ The runtime bundles its schema, performs no network fetches for external schema references, and imports no Node.js built-ins. Unresolved external Tool-schema references produce incomplete-validation warnings and are preserved. All three ESM entry points support Node.js 20 or later and browser bundlers.
61
113
 
62
114
  ## Snapshot lifecycle
63
115
 
@@ -65,7 +117,7 @@ For each approved specification snapshot, maintainers:
65
117
 
66
118
  1. Add a versioned implementation under `src/snapshots/<selector>/` with its exact selector, snapshot tag, embedded schema, schema SHA-256 digest, and semantic rules. Existing snapshot directories remain unchanged.
67
119
  2. Freeze the matching fixture corpus under `test/snapshots/<selector>/fixtures/`. Package tests must not read mutable `spec/draft/fixtures/` for an already published selector.
68
- 3. Add the exact selector to the runtime registry and update support metadata, TypeScript declarations, tests, and expected package contents. Unqualified versions, aliases, ranges, and not-yet-published selectors remain unsupported.
120
+ 3. Add the exact selector to the runtime registry and update support metadata, TypeScript declarations, tests, the package changelog, and expected package contents. Unqualified versions, aliases, ranges, and not-yet-published selectors remain unsupported.
69
121
  4. Run the package and repository validation suites. The schema digest, immutable metadata, fixture behavior, browser bundle, declarations, and tarball contents must all pass.
70
122
 
71
123
  The test snapshots are repository-only development assets and are excluded from the npm tarball. Runtime snapshot implementations and embedded schemas do ship so installed packages remain self-contained.
@@ -83,4 +135,4 @@ npm run test:browser --workspace @mcpdesc/validator
83
135
  npm run test:package --workspace @mcpdesc/validator
84
136
  ```
85
137
 
86
- The package test runs each immutable snapshot against its own frozen valid, invalid, and warning fixture corpus. YAML source fixtures are decoded by the test harness before validation; the public API continues to accept parsed JavaScript values only. The other checks compile the declarations, build all runtime snapshots for a browser target, and inspect `npm pack --dry-run --json` against the intended tarball contents, including all runtime snapshots and exclusion of test snapshots.
138
+ The package test runs each immutable snapshot against its own frozen valid, invalid, and warning fixture corpus. YAML source fixtures are decoded by the test harness before validation; the public API continues to accept parsed JavaScript values only. The other checks compile the declarations, bundle the public browser entry with esbuild and Vite, reject runtime AJV compiler inputs and dynamic code generation in those bundles, and inspect `npm pack --dry-run --json` against the intended tarball contents and declared export targets.
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type McpDescriptionSpecification = '0.8.0-draft.1' | '0.8.0-draft.2' | '0.8.0-draft.3' | '0.8.0-draft.4';
1
+ export type McpDescriptionSpecification = '0.8.0-draft.1' | '0.8.0-draft.2' | '0.8.0-draft.3' | '0.8.0-draft.4' | '0.8.0-rc.1';
2
2
 
3
3
  export type SupportedProtocolVersion =
4
4
  | '2024-11-05'
@@ -28,23 +28,53 @@ export interface ValidateMcpDescriptionOptions {
28
28
  export interface SpecificationProvenance {
29
29
  readonly '0.8.0-draft.1': {
30
30
  readonly snapshotTag: 'v0.8.0-draft.1';
31
+ readonly schemaUri: 'https://mcpdesc.org/schema/0.8.0.json';
31
32
  readonly schemaSha256: '4ceb6042c3fd31703199cd3db869ec5c35c17d2fe9ab7b2f5b96a2a3af0cebe4';
32
33
  };
33
34
  readonly '0.8.0-draft.2': {
34
35
  readonly snapshotTag: 'v0.8.0-draft.2';
36
+ readonly schemaUri: 'https://mcpdesc.org/schema/0.8.0.json';
35
37
  readonly schemaSha256: 'ab692c1a5a0f7e5f29be1940aa8c64a56d4620be0a19d00cf0a64680b7e517fa';
36
38
  };
37
39
  readonly '0.8.0-draft.3': {
38
40
  readonly snapshotTag: 'v0.8.0-draft.3';
41
+ readonly schemaUri: 'https://mcpdesc.org/schema/0.8.0.json';
39
42
  readonly schemaSha256: '8823c1f1946360b2a44d00920e2092e5e4acd139a1964befad4eb0bf3ce96002';
40
43
  };
41
44
  readonly '0.8.0-draft.4': {
42
45
  readonly snapshotTag: 'v0.8.0-draft.4';
46
+ readonly schemaUri: 'https://mcpdesc.org/schema/mcp-description/0.8.0-draft.4.json';
43
47
  readonly schemaSha256: '93ed03f74059b5b3ce7509a96b59161bdab2c3cf7734397a9bec5a7588d0b03b';
44
48
  };
49
+ readonly '0.8.0-rc.1': {
50
+ readonly snapshotTag: 'v0.8.0-rc.1';
51
+ readonly schemaUri: 'https://mcpdesc.org/schema/mcp-description/0.8.0-rc.1.json';
52
+ readonly schemaSha256: '936a0f24ade501fcabf3d6498c0440c445daa672a575573a35954cee49430ac4';
53
+ };
54
+ }
55
+
56
+ export interface ResolveMcpDescriptionSpecificationOptions {
57
+ readonly specification?: McpDescriptionSpecification;
58
+ }
59
+
60
+ export interface ResolvedMcpDescriptionSpecification {
61
+ readonly status: 'resolved';
62
+ readonly specification: McpDescriptionSpecification;
63
+ readonly schemaUri: string;
64
+ readonly provenance: SpecificationProvenance[McpDescriptionSpecification];
65
+ readonly diagnostics: McpDescriptionDiagnostic[];
45
66
  }
46
67
 
47
- export declare const supportedSpecifications: readonly ['0.8.0-draft.1', '0.8.0-draft.2', '0.8.0-draft.3', '0.8.0-draft.4'];
68
+ export interface UnresolvedMcpDescriptionSpecification {
69
+ readonly status: 'unresolved';
70
+ readonly diagnostics: McpDescriptionDiagnostic[];
71
+ }
72
+
73
+ export type McpDescriptionSpecificationResolution =
74
+ | ResolvedMcpDescriptionSpecification
75
+ | UnresolvedMcpDescriptionSpecification;
76
+
77
+ export declare const supportedSpecifications: readonly ['0.8.0-draft.1', '0.8.0-draft.2', '0.8.0-draft.3', '0.8.0-draft.4', '0.8.0-rc.1'];
48
78
 
49
79
  export declare const supportedProtocolVersions: readonly [
50
80
  '2024-11-05',
@@ -56,6 +86,11 @@ export declare const supportedProtocolVersions: readonly [
56
86
 
57
87
  export declare const specificationProvenance: Readonly<SpecificationProvenance>;
58
88
 
89
+ export declare function resolveMcpDescriptionSpecification(
90
+ document: unknown,
91
+ options?: ResolveMcpDescriptionSpecificationOptions
92
+ ): McpDescriptionSpecificationResolution;
93
+
59
94
  export declare function validateMcpDescription(
60
95
  document: unknown,
61
96
  options: ValidateMcpDescriptionOptions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcpdesc/validator",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Structural and semantic validation for MCP Description snapshots",
5
5
  "type": "module",
6
6
  "types": "./index.d.ts",
@@ -17,12 +17,22 @@
17
17
  ".": {
18
18
  "types": "./index.d.ts",
19
19
  "import": "./src/index.js"
20
+ },
21
+ "./browser": {
22
+ "types": "./index.d.ts",
23
+ "import": "./standalone.js"
24
+ },
25
+ "./standalone": {
26
+ "types": "./index.d.ts",
27
+ "import": "./standalone.js"
20
28
  }
21
29
  },
22
30
  "files": [
23
31
  "src/index.js",
24
32
  "src/snapshots",
33
+ "standalone.js",
25
34
  "index.d.ts",
35
+ "CHANGELOG.md",
26
36
  "README.md",
27
37
  "LICENSE",
28
38
  "NOTICE",
@@ -30,12 +40,14 @@
30
40
  "MODIFICATIONS.md"
31
41
  ],
32
42
  "scripts": {
33
- "test": "node --test test/*.test.js && npm run test:types && npm run test:browser && npm run test:package",
43
+ "build:standalone": "node scripts/build-standalone.mjs",
44
+ "test": "npm run build:standalone && node --test test/*.test.js && npm run test:types && npm run test:browser && npm run test:package",
34
45
  "test:browser": "node scripts/check-browser-bundle.mjs",
35
46
  "test:package": "node scripts/check-package.mjs",
36
47
  "test:types": "tsc --project test/types/tsconfig.json && node scripts/check-declarations.mjs"
37
48
  },
38
49
  "dependencies": {
50
+ "@cfworker/json-schema": "^4.1.1",
39
51
  "ajv": "^8.20.0",
40
52
  "ajv-formats": "^3.0.1",
41
53
  "uri-template-matcher": "^1.1.2"
@@ -43,6 +55,7 @@
43
55
  "devDependencies": {
44
56
  "esbuild": "^0.25.0",
45
57
  "typescript": "^5.9.0",
58
+ "vite": "^6.4.3",
46
59
  "yaml": "^2.9.0"
47
60
  }
48
- }
61
+ }
package/src/index.js CHANGED
@@ -2,12 +2,22 @@ import * as draft1 from './snapshots/0.8.0-draft.1/index.js';
2
2
  import * as draft2 from './snapshots/0.8.0-draft.2/index.js';
3
3
  import * as draft3 from './snapshots/0.8.0-draft.3/index.js';
4
4
  import * as draft4 from './snapshots/0.8.0-draft.4/index.js';
5
+ import * as rc1 from './snapshots/0.8.0-rc.1/index.js';
5
6
 
6
7
  const snapshots = Object.freeze({
7
8
  [draft1.specification]: draft1,
8
9
  [draft2.specification]: draft2,
9
10
  [draft3.specification]: draft3,
10
- [draft4.specification]: draft4
11
+ [draft4.specification]: draft4,
12
+ [rc1.specification]: rc1
13
+ });
14
+
15
+ const schemaUris = Object.freeze({
16
+ '0.8.0-draft.1': 'https://mcpdesc.org/schema/0.8.0.json',
17
+ '0.8.0-draft.2': 'https://mcpdesc.org/schema/0.8.0.json',
18
+ '0.8.0-draft.3': 'https://mcpdesc.org/schema/0.8.0.json',
19
+ '0.8.0-draft.4': 'https://mcpdesc.org/schema/mcp-description/0.8.0-draft.4.json',
20
+ '0.8.0-rc.1': 'https://mcpdesc.org/schema/mcp-description/0.8.0-rc.1.json'
11
21
  });
12
22
 
13
23
  export const supportedSpecifications = Object.freeze(Object.keys(snapshots));
@@ -18,10 +28,111 @@ export const supportedProtocolVersions = Object.freeze([
18
28
  export const specificationProvenance = Object.freeze(Object.fromEntries(
19
29
  Object.entries(snapshots).map(([selector, snapshot]) => [selector, Object.freeze({
20
30
  snapshotTag: snapshot.snapshotTag,
31
+ schemaUri: schemaUris[selector],
21
32
  schemaSha256: snapshot.schemaSha256
22
33
  })])
23
34
  ));
24
35
 
36
+ const specificationsBySchemaUri = new Map();
37
+ for (const snapshot of Object.values(snapshots)) {
38
+ const schemaUri = schemaUris[snapshot.specification];
39
+ const selectors = specificationsBySchemaUri.get(schemaUri) ?? [];
40
+ selectors.push(snapshot.specification);
41
+ specificationsBySchemaUri.set(schemaUri, selectors);
42
+ }
43
+
44
+ function resolutionDiagnostic(code, message, path = []) {
45
+ return { code, severity: 'error', message, path };
46
+ }
47
+
48
+ function resolvedSpecification(specification, diagnostics = []) {
49
+ const snapshot = snapshots[specification];
50
+ return {
51
+ status: 'resolved',
52
+ specification,
53
+ schemaUri: schemaUris[specification],
54
+ provenance: specificationProvenance[specification],
55
+ diagnostics
56
+ };
57
+ }
58
+
59
+ export function resolveMcpDescriptionSpecification(document, options = {}) {
60
+ const override = options?.specification;
61
+ if (override !== undefined && (typeof override !== 'string' || !Object.hasOwn(snapshots, override))) {
62
+ return {
63
+ status: 'unresolved',
64
+ diagnostics: [resolutionDiagnostic(
65
+ 'unsupported-specification',
66
+ `Unsupported MCP Description specification: ${String(override)}`
67
+ )]
68
+ };
69
+ }
70
+
71
+ const declaredSchema = document !== null
72
+ && typeof document === 'object'
73
+ && !Array.isArray(document)
74
+ && Object.hasOwn(document, '$schema')
75
+ ? document.$schema
76
+ : undefined;
77
+
78
+ if (declaredSchema === undefined) {
79
+ if (override !== undefined) return resolvedSpecification(override);
80
+ return {
81
+ status: 'unresolved',
82
+ diagnostics: [resolutionDiagnostic(
83
+ 'missing-snapshot-identity',
84
+ 'An exact specification selector or recognized $schema identity is required',
85
+ ['$schema']
86
+ )]
87
+ };
88
+ }
89
+
90
+ if (typeof declaredSchema !== 'string') {
91
+ return {
92
+ status: 'unresolved',
93
+ diagnostics: [resolutionDiagnostic(
94
+ 'invalid-schema-identity',
95
+ '$schema must be a string to resolve an MCP Description specification',
96
+ ['$schema']
97
+ )]
98
+ };
99
+ }
100
+
101
+ const candidates = specificationsBySchemaUri.get(declaredSchema) ?? [];
102
+ if (override !== undefined) {
103
+ if (schemaUris[override] === declaredSchema) return resolvedSpecification(override);
104
+ return {
105
+ status: 'unresolved',
106
+ diagnostics: [resolutionDiagnostic(
107
+ 'contradictory-snapshot-identity',
108
+ `$schema ${JSON.stringify(declaredSchema)} does not identify specification ${JSON.stringify(override)}`,
109
+ ['$schema']
110
+ )]
111
+ };
112
+ }
113
+
114
+ if (candidates.length === 1) return resolvedSpecification(candidates[0]);
115
+ if (candidates.length > 1) {
116
+ return {
117
+ status: 'unresolved',
118
+ diagnostics: [resolutionDiagnostic(
119
+ 'ambiguous-schema-identity',
120
+ `$schema ${JSON.stringify(declaredSchema)} is shared by ${candidates.join(', ')} and does not select one immutable snapshot`,
121
+ ['$schema']
122
+ )]
123
+ };
124
+ }
125
+
126
+ return {
127
+ status: 'unresolved',
128
+ diagnostics: [resolutionDiagnostic(
129
+ 'unknown-schema-identity',
130
+ `Unknown MCP Description $schema identity: ${JSON.stringify(declaredSchema)}`,
131
+ ['$schema']
132
+ )]
133
+ };
134
+ }
135
+
25
136
  export function validateMcpDescription(document, options) {
26
137
  if (!options || typeof options !== 'object' || Array.isArray(options) || !Object.hasOwn(options, 'specification')) {
27
138
  throw new TypeError('options.specification is required');