@mcpdesc/validator 0.4.0 → 0.5.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,54 @@
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.5.0] - 2026-08-31
12
+
13
+ ### Added
14
+
15
+ - Added `resolveMcpDescriptionSpecification` for resolving an immutable validator selector from a document's `$schema` identity or checking an exact caller-supplied selector.
16
+ - Added each snapshot's recorded schema URI to `specificationProvenance`.
17
+ - 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.
18
+ - Added the immutable `0.8.0-rc.1` selector with the release candidate's canonical schema identity and published schema digest.
19
+
20
+ ## [0.4.0] - 2026-08-29
21
+
22
+ ### Added
23
+
24
+ - Added the immutable `0.8.0-draft.4` selector, embedded schema, semantic implementation, and frozen fixture corpus.
25
+ - Added Draft 4 schema-digest and snapshot metadata checks while preserving the Draft 1 through Draft 3 implementations.
26
+
27
+ ## [0.3.0] - 2026-08-27
28
+
29
+ ### Added
30
+
31
+ - Added the immutable `0.8.0-draft.3` selector, embedded schema, semantic implementation, and frozen fixture corpus.
32
+ - Added Draft 3 to the cumulative browser bundle, declarations, support metadata, and package-content checks.
33
+
34
+ ## [0.2.0] - 2026-08-27
35
+
36
+ ### Added
37
+
38
+ - Added the immutable `0.8.0-draft.2` selector as a sibling implementation without changing Draft 1 behavior.
39
+ - Added cumulative selector dispatch, protocol-version metadata, declarations, browser checks, and a frozen Draft 2 fixture corpus.
40
+
41
+ ## [0.1.0] - 2026-08-25
42
+
43
+ ### Added
44
+
45
+ - Published the initial reusable validator package for the immutable `0.8.0-draft.1` snapshot.
46
+ - Added synchronous structural and semantic validation for parsed JavaScript values with deterministic diagnostics and exact selector dispatch.
47
+ - Added ESM browser support, TypeScript declarations, embedded schema provenance, frozen fixtures, and package-content checks.
48
+
49
+ [Unreleased]: https://github.com/mcpdesc/mcpdesc-specification/compare/validator-v0.5.0...HEAD
50
+ [0.5.0]: https://github.com/mcpdesc/mcpdesc-specification/compare/17cc533e79b19ea2dbc1edcf06e30ba68a7d9b79...validator-v0.5.0
51
+ [0.4.0]: https://www.npmjs.com/package/@mcpdesc/validator/v/0.4.0
52
+ [0.3.0]: https://github.com/mcpdesc/mcpdesc-specification/releases/tag/validator-v0.3.0
53
+ [0.2.0]: https://www.npmjs.com/package/@mcpdesc/validator/v/0.2.0
54
+ [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.5.0` cumulatively supports these immutable snapshots and is prepared for npm `latest` after the RC.1 specification tag and package tarball are reviewed.
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,50 @@ 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
+ ### Strict CSP browsers
36
+
37
+ Browser applications that prohibit dynamic code generation can use the CSP-safe standalone entry:
38
+
39
+ ```js
40
+ import { validateMcpDescription } from '@mcpdesc/validator/standalone';
41
+ ```
42
+
43
+ It has 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 the shipped file contains no `eval` or `new Function`. This compatibility entry is larger and slower than the default AJV-based entry and is JavaScript, not WASM.
44
+
45
+ ## Snapshot resolution
46
+
47
+ `resolveMcpDescriptionSpecification` resolves an exact validator selector from
48
+ the document's `$schema` identity or checks a caller-supplied exact selector:
49
+
50
+ ```js
51
+ import { resolveMcpDescriptionSpecification } from '@mcpdesc/validator';
52
+
53
+ const resolution = resolveMcpDescriptionSpecification(parsedDocument);
54
+ if (resolution.status === 'resolved') {
55
+ console.log(resolution.specification, resolution.provenance);
56
+ } else {
57
+ console.error(resolution.diagnostics);
58
+ }
59
+ ```
60
+
61
+ Resolution is pure and performs no network retrieval. It does not validate the
62
+ document and never infers a draft snapshot from `mcpdesc: "0.8.0"` alone.
63
+ Draft 4's format-qualified schema URI resolves uniquely. Drafts 1 through 3
64
+ share the legacy `https://mcpdesc.org/schema/0.8.0.json` URI, so that URI is
65
+ ambiguous unless the caller supplies a consistent exact selector:
66
+
67
+ ```js
68
+ const resolution = resolveMcpDescriptionSpecification(parsedDocument, {
69
+ specification: '0.8.0-draft.3'
70
+ });
71
+ ```
72
+
73
+ Unresolved results distinguish missing, invalid, unknown, ambiguous, and
74
+ contradictory identity, as well as unsupported caller selectors. A supplied
75
+ selector can resolve a document with no `$schema`; when `$schema` is present it
76
+ must match the selected snapshot's recorded schema URI.
33
77
 
34
78
  ## Result
35
79
 
@@ -53,11 +97,11 @@ Structural paths start with AJV's instance path. A `required` error appends its
53
97
 
54
98
  ## Support metadata
55
99
 
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.
100
+ 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
101
 
58
102
  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
103
 
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.
104
+ 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. Both ESM entry points support Node.js 20 or later and browser bundlers.
61
105
 
62
106
  ## Snapshot lifecycle
63
107
 
@@ -65,7 +109,7 @@ For each approved specification snapshot, maintainers:
65
109
 
66
110
  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
111
  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.
112
+ 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
113
  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
114
 
71
115
  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.
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.5.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,18 @@
17
17
  ".": {
18
18
  "types": "./index.d.ts",
19
19
  "import": "./src/index.js"
20
+ },
21
+ "./standalone": {
22
+ "types": "./index.d.ts",
23
+ "import": "./standalone.js"
20
24
  }
21
25
  },
22
26
  "files": [
23
27
  "src/index.js",
24
28
  "src/snapshots",
29
+ "standalone.js",
25
30
  "index.d.ts",
31
+ "CHANGELOG.md",
26
32
  "README.md",
27
33
  "LICENSE",
28
34
  "NOTICE",
@@ -30,12 +36,14 @@
30
36
  "MODIFICATIONS.md"
31
37
  ],
32
38
  "scripts": {
33
- "test": "node --test test/*.test.js && npm run test:types && npm run test:browser && npm run test:package",
39
+ "build:standalone": "node scripts/build-standalone.mjs",
40
+ "test": "npm run build:standalone && node --test test/*.test.js && npm run test:types && npm run test:browser && npm run test:package",
34
41
  "test:browser": "node scripts/check-browser-bundle.mjs",
35
42
  "test:package": "node scripts/check-package.mjs",
36
43
  "test:types": "tsc --project test/types/tsconfig.json && node scripts/check-declarations.mjs"
37
44
  },
38
45
  "dependencies": {
46
+ "@cfworker/json-schema": "^4.1.1",
39
47
  "ajv": "^8.20.0",
40
48
  "ajv-formats": "^3.0.1",
41
49
  "uri-template-matcher": "^1.1.2"
@@ -45,4 +53,4 @@
45
53
  "typescript": "^5.9.0",
46
54
  "yaml": "^2.9.0"
47
55
  }
48
- }
56
+ }
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');