@mcpdesc/validator 0.7.0 → 0.8.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 +28 -1
- package/README.md +45 -16
- package/index.d.ts +22 -1
- package/package.json +3 -1
- package/src/index.js +12 -0
- package/src/snapshots/0.8.0-rc.1/semantic.js +22 -10
- package/standalone.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
and this package follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
Dates for published releases are the UTC publication dates recorded by npm.
|
|
8
8
|
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
## [0.8.0] - 2026-09-03
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added RC.1-only `resolveMcpDescriptionComponentReferences` to the default,
|
|
16
|
+
browser, and standalone entries, returning the existing resolved clone,
|
|
17
|
+
diagnostics, and substitution count plus deterministic terminal-target
|
|
18
|
+
provenance.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Recorded the maintainer-approved additive provenance correction to the RC.1
|
|
23
|
+
snapshot implementation. Its schema, conformance results, diagnostics, and
|
|
24
|
+
frozen fixtures remain unchanged.
|
|
25
|
+
|
|
26
|
+
## [0.7.1] - 2026-09-02
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Added an explicit package-specific homepage and expanded the README with the
|
|
31
|
+
validator's purpose, boundaries, result model, installation, and newest-first
|
|
32
|
+
snapshot support.
|
|
33
|
+
|
|
9
34
|
## [0.7.0] - 2026-09-02
|
|
10
35
|
|
|
11
36
|
### Changed
|
|
@@ -69,7 +94,9 @@ Dates for published releases are the UTC publication dates recorded by npm.
|
|
|
69
94
|
- Added synchronous structural and semantic validation for parsed JavaScript values with deterministic diagnostics and exact selector dispatch.
|
|
70
95
|
- Added ESM browser support, TypeScript declarations, embedded schema provenance, frozen fixtures, and package-content checks.
|
|
71
96
|
|
|
72
|
-
[Unreleased]: https://github.com/mcpdesc/core/compare/validator-v0.
|
|
97
|
+
[Unreleased]: https://github.com/mcpdesc/core/compare/validator-v0.8.0...HEAD
|
|
98
|
+
[0.8.0]: https://github.com/mcpdesc/core/compare/validator-v0.7.1...validator-v0.8.0
|
|
99
|
+
[0.7.1]: https://github.com/mcpdesc/core/compare/validator-v0.7.0...validator-v0.7.1
|
|
73
100
|
[0.7.0]: https://github.com/mcpdesc/core/releases/tag/validator-v0.7.0
|
|
74
101
|
[0.6.0]: https://github.com/mcpdesc/mcpdesc-specification/compare/validator-v0.5.0...validator-v0.6.0
|
|
75
102
|
[0.5.0]: https://github.com/mcpdesc/mcpdesc-specification/compare/17cc533e79b19ea2dbc1edcf06e30ba68a7d9b79...validator-v0.5.0
|
package/README.md
CHANGED
|
@@ -1,36 +1,60 @@
|
|
|
1
1
|
# @mcpdesc/validator
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Validate parsed MCP Description documents against exact, immutable specification
|
|
4
|
+
snapshots. The package:
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
- checks document structure against the snapshot's embedded JSON Schema;
|
|
7
|
+
- applies semantic rules that JSON Schema alone cannot express;
|
|
8
|
+
- returns deterministic error and warning diagnostics with document paths; and
|
|
9
|
+
- runs synchronously and offline in Node.js 20+ and browser bundles.
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
| `0.8.0-draft.3` | `v0.8.0-draft.3` | `8823c1f1946360b2a44d00920e2092e5e4acd139a1964befad4eb0bf3ce96002` |
|
|
14
|
-
| `0.8.0-draft.4` | `v0.8.0-draft.4` | `93ed03f74059b5b3ce7509a96b59161bdab2c3cf7734397a9bec5a7588d0b03b` |
|
|
15
|
-
| `0.8.0-rc.1` | `v0.8.0-rc.1` | `936a0f24ade501fcabf3d6498c0440c445daa672a575573a35954cee49430ac4` |
|
|
11
|
+
Use it when accepting, generating, migrating, or transforming MCP Description
|
|
12
|
+
documents and you need to know whether the result conforms to a specific
|
|
13
|
+
published draft or release candidate. It validates MCP Description documents,
|
|
14
|
+
not live MCP servers or MCP protocol messages.
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
## Install
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
```bash
|
|
19
|
+
npm install @mcpdesc/validator
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
20
23
|
|
|
21
24
|
```js
|
|
22
25
|
import { validateMcpDescription } from '@mcpdesc/validator';
|
|
23
26
|
|
|
24
27
|
const result = validateMcpDescription(parsedDocument, {
|
|
25
|
-
specification: '0.8.0-rc.1'
|
|
28
|
+
specification: '0.8.0-rc.1',
|
|
26
29
|
});
|
|
27
30
|
|
|
31
|
+
for (const diagnostic of result.diagnostics) {
|
|
32
|
+
console.log(diagnostic.severity, diagnostic.path, diagnostic.message);
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
if (!result.valid) {
|
|
29
|
-
|
|
36
|
+
// At least one error diagnostic was returned.
|
|
30
37
|
}
|
|
31
38
|
```
|
|
32
39
|
|
|
33
|
-
Callers provide an already parsed JavaScript value. JSON and YAML parsing
|
|
40
|
+
Callers provide an already parsed JavaScript value. JSON and YAML parsing, file
|
|
41
|
+
access, network access, and live-server inspection are outside this package.
|
|
42
|
+
The exact `specification` selector is required so validation never changes when
|
|
43
|
+
a later draft is published.
|
|
44
|
+
|
|
45
|
+
## Supported snapshots
|
|
46
|
+
|
|
47
|
+
Version `0.8.0` supports these immutable snapshots, newest first:
|
|
48
|
+
|
|
49
|
+
| Selector | Tag | Embedded schema SHA-256 |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `0.8.0-rc.1` | `v0.8.0-rc.1` | `936a0f24ade501fcabf3d6498c0440c445daa672a575573a35954cee49430ac4` |
|
|
52
|
+
| `0.8.0-draft.4` | `v0.8.0-draft.4` | `93ed03f74059b5b3ce7509a96b59161bdab2c3cf7734397a9bec5a7588d0b03b` |
|
|
53
|
+
| `0.8.0-draft.3` | `v0.8.0-draft.3` | `8823c1f1946360b2a44d00920e2092e5e4acd139a1964befad4eb0bf3ce96002` |
|
|
54
|
+
| `0.8.0-draft.2` | `v0.8.0-draft.2` | `ab692c1a5a0f7e5f29be1940aa8c64a56d4620be0a19d00cf0a64680b7e517fa` |
|
|
55
|
+
| `0.8.0-draft.1` | `v0.8.0-draft.1` | `4ceb6042c3fd31703199cd3db869ec5c35c17d2fe9ab7b2f5b96a2a3af0cebe4` |
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
34
58
|
|
|
35
59
|
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.
|
|
36
60
|
|
|
@@ -111,6 +135,11 @@ The package exports frozen `supportedSpecifications`, `supportedProtocolVersions
|
|
|
111
135
|
|
|
112
136
|
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.
|
|
113
137
|
|
|
138
|
+
The RC.1 component resolver has one maintainer-approved additive tooling
|
|
139
|
+
correction: successful substitutions also report authored and terminal target
|
|
140
|
+
paths. The correction does not change the RC.1 schema, validation results,
|
|
141
|
+
diagnostics, or fixtures and is recorded in the integrity manifest.
|
|
142
|
+
|
|
114
143
|
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.
|
|
115
144
|
|
|
116
145
|
## Snapshot lifecycle
|
package/index.d.ts
CHANGED
|
@@ -25,6 +25,22 @@ export interface ValidateMcpDescriptionOptions {
|
|
|
25
25
|
specification: McpDescriptionSpecification;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export interface McpDescriptionComponentReferenceProvenance {
|
|
29
|
+
readonly referencePath: Array<string | number>;
|
|
30
|
+
readonly targetPath: Array<string | number>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface McpDescriptionComponentResolutionResult {
|
|
34
|
+
readonly document: unknown;
|
|
35
|
+
readonly diagnostics: McpDescriptionDiagnostic[];
|
|
36
|
+
readonly substitutions: number;
|
|
37
|
+
readonly provenance: McpDescriptionComponentReferenceProvenance[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ResolveMcpDescriptionComponentReferencesOptions {
|
|
41
|
+
readonly specification: '0.8.0-rc.1';
|
|
42
|
+
}
|
|
43
|
+
|
|
28
44
|
export interface SpecificationProvenance {
|
|
29
45
|
readonly '0.8.0-draft.1': {
|
|
30
46
|
readonly snapshotTag: 'v0.8.0-draft.1';
|
|
@@ -94,4 +110,9 @@ export declare function resolveMcpDescriptionSpecification(
|
|
|
94
110
|
export declare function validateMcpDescription(
|
|
95
111
|
document: unknown,
|
|
96
112
|
options: ValidateMcpDescriptionOptions
|
|
97
|
-
): McpDescriptionValidationResult;
|
|
113
|
+
): McpDescriptionValidationResult;
|
|
114
|
+
|
|
115
|
+
export declare function resolveMcpDescriptionComponentReferences(
|
|
116
|
+
document: unknown,
|
|
117
|
+
options: ResolveMcpDescriptionComponentReferencesOptions
|
|
118
|
+
): McpDescriptionComponentResolutionResult;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcpdesc/validator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Structural and semantic validation for MCP Description snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
"url": "git+https://github.com/mcpdesc/core.git",
|
|
11
11
|
"directory": "packages/validator"
|
|
12
12
|
},
|
|
13
|
+
"homepage": "https://github.com/mcpdesc/core/tree/main/packages/validator#readme",
|
|
14
|
+
"bugs": "https://github.com/mcpdesc/core/issues",
|
|
13
15
|
"engines": {
|
|
14
16
|
"node": ">=20"
|
|
15
17
|
},
|
package/src/index.js
CHANGED
|
@@ -3,6 +3,7 @@ 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
5
|
import * as rc1 from './snapshots/0.8.0-rc.1/index.js';
|
|
6
|
+
import { resolveComponentReferences as resolveRc1ComponentReferences } from './snapshots/0.8.0-rc.1/semantic.js';
|
|
6
7
|
|
|
7
8
|
const snapshots = Object.freeze({
|
|
8
9
|
[draft1.specification]: draft1,
|
|
@@ -142,4 +143,15 @@ export function validateMcpDescription(document, options) {
|
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
return snapshots[options.specification].validate(document);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function resolveMcpDescriptionComponentReferences(document, options) {
|
|
149
|
+
if (!options || typeof options !== 'object' || Array.isArray(options) || !Object.hasOwn(options, 'specification')) {
|
|
150
|
+
throw new TypeError('options.specification is required');
|
|
151
|
+
}
|
|
152
|
+
if (options.specification !== '0.8.0-rc.1') {
|
|
153
|
+
throw new RangeError(`Component reference resolution does not support specification: ${String(options.specification)}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return resolveRc1ComponentReferences(document);
|
|
145
157
|
}
|
|
@@ -61,11 +61,12 @@ function componentDiagnostic(code, rel, message, path) {
|
|
|
61
61
|
export function resolveComponentReferences(document, rel = 'document') {
|
|
62
62
|
const resolved = structuredClone(document);
|
|
63
63
|
const diagnostics = [];
|
|
64
|
+
const provenance = [];
|
|
64
65
|
let substitutions = 0;
|
|
65
66
|
|
|
66
67
|
function resolve(reference, expectedNamespace, path, stack = []) {
|
|
67
68
|
const match = /^#\/components\/([^/]+)\/([^/]+)$/.exec(reference?.$componentRef ?? '');
|
|
68
|
-
if (!match) return reference;
|
|
69
|
+
if (!match) return { value: reference };
|
|
69
70
|
const [, namespace, name] = match;
|
|
70
71
|
if (namespace !== expectedNamespace) {
|
|
71
72
|
diagnostics.push(componentDiagnostic(
|
|
@@ -74,7 +75,7 @@ export function resolveComponentReferences(document, rel = 'document') {
|
|
|
74
75
|
`must target #/components/${expectedNamespace}, not #/components/${namespace}`,
|
|
75
76
|
path
|
|
76
77
|
));
|
|
77
|
-
return reference;
|
|
78
|
+
return { value: reference };
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
const key = `${namespace}/${name}`;
|
|
@@ -85,7 +86,7 @@ export function resolveComponentReferences(document, rel = 'document') {
|
|
|
85
86
|
`forms a cycle through ${[...stack, key].join(' -> ')}`,
|
|
86
87
|
path
|
|
87
88
|
));
|
|
88
|
-
return reference;
|
|
89
|
+
return { value: reference };
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
const target = document?.components?.[namespace]?.[name];
|
|
@@ -96,18 +97,29 @@ export function resolveComponentReferences(document, rel = 'document') {
|
|
|
96
97
|
`targets missing component ${JSON.stringify(reference.$componentRef)}`,
|
|
97
98
|
path
|
|
98
99
|
));
|
|
99
|
-
return reference;
|
|
100
|
+
return { value: reference };
|
|
100
101
|
}
|
|
101
102
|
substitutions += 1;
|
|
102
103
|
return isReferenceObject(target)
|
|
103
104
|
? resolve(target, expectedNamespace, path, [...stack, key])
|
|
104
|
-
:
|
|
105
|
+
: {
|
|
106
|
+
value: structuredClone(target),
|
|
107
|
+
targetPath: ['components', namespace, name]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function substitute(reference, expectedNamespace, path, stack = []) {
|
|
112
|
+
const result = resolve(reference, expectedNamespace, path, stack);
|
|
113
|
+
if (result.targetPath) {
|
|
114
|
+
provenance.push({ referencePath: path, targetPath: result.targetPath });
|
|
115
|
+
}
|
|
116
|
+
return result.value;
|
|
105
117
|
}
|
|
106
118
|
|
|
107
119
|
for (const namespace of componentNamespaces) {
|
|
108
120
|
for (const [name, value] of Object.entries(document?.components?.[namespace] ?? {})) {
|
|
109
121
|
if (isReferenceObject(value)) {
|
|
110
|
-
resolved.components[namespace][name] =
|
|
122
|
+
resolved.components[namespace][name] = substitute(value, namespace, ['components', namespace, name], [`${namespace}/${name}`]);
|
|
111
123
|
}
|
|
112
124
|
}
|
|
113
125
|
}
|
|
@@ -123,7 +135,7 @@ export function resolveComponentReferences(document, rel = 'document') {
|
|
|
123
135
|
if (collection === 'tools') {
|
|
124
136
|
for (const field of ['inputSchema', 'outputSchema']) {
|
|
125
137
|
if (isReferenceObject(declaration[field])) {
|
|
126
|
-
resolvedDeclaration[field] =
|
|
138
|
+
resolvedDeclaration[field] = substitute(declaration[field], 'schemas', [collection, declarationIndex, field]);
|
|
127
139
|
}
|
|
128
140
|
}
|
|
129
141
|
}
|
|
@@ -137,7 +149,7 @@ export function resolveComponentReferences(document, rel = 'document') {
|
|
|
137
149
|
: 'promptExamples';
|
|
138
150
|
for (const [name, example] of Object.entries(declaration.examples ?? {})) {
|
|
139
151
|
if (isReferenceObject(example)) {
|
|
140
|
-
resolvedDeclaration.examples[name] =
|
|
152
|
+
resolvedDeclaration.examples[name] = substitute(
|
|
141
153
|
example,
|
|
142
154
|
exampleNamespace,
|
|
143
155
|
[collection, declarationIndex, 'examples', name]
|
|
@@ -151,7 +163,7 @@ export function resolveComponentReferences(document, rel = 'document') {
|
|
|
151
163
|
for (const [declarationIndex, declaration] of (document?.[collection] ?? []).entries()) {
|
|
152
164
|
for (const [elicitationIndex, elicitation] of (declaration.elicitations ?? []).entries()) {
|
|
153
165
|
if (isReferenceObject(elicitation.requestedSchema)) {
|
|
154
|
-
resolved[collection][declarationIndex].elicitations[elicitationIndex].requestedSchema =
|
|
166
|
+
resolved[collection][declarationIndex].elicitations[elicitationIndex].requestedSchema = substitute(
|
|
155
167
|
elicitation.requestedSchema,
|
|
156
168
|
'schemas',
|
|
157
169
|
[collection, declarationIndex, 'elicitations', elicitationIndex, 'requestedSchema']
|
|
@@ -161,7 +173,7 @@ export function resolveComponentReferences(document, rel = 'document') {
|
|
|
161
173
|
}
|
|
162
174
|
}
|
|
163
175
|
|
|
164
|
-
return { document: resolved, diagnostics, substitutions };
|
|
176
|
+
return { document: resolved, diagnostics, substitutions, provenance };
|
|
165
177
|
}
|
|
166
178
|
|
|
167
179
|
function structuralPath(document, error) {
|