Fix wire-validation attribution bugs and extend the schema erratum to 2025-06-18 (#421)
Follow-ups from the #399 review: - Stateless mock: validate error replies with a placeholder id so the JSON-RPC-mandated id:null is not recorded as a harness violation, and skip inbound validation when express left the body unparsed (wrong or missing Content-Type) — both were false positives blamed on the wrong party. Same unparsed-body guard in the stateful mock. - Stateful mock: validate client traffic against the protocol version the client declares via MCP-Protocol-Version, falling back to 2025-03-26 when the header is absent (it only exists from 2025-06-18, whose spec says to assume 2025-03-26), instead of always using the run version. - sdk-client: validate batches once as a whole array instead of also per element, which double-recorded every violation. - Make specVersion required on connectToServer/connectStateful/ createServerStateful; add isSpecVersion to types.ts. - Extend the NumberSchema erratum to 2025-06-18 (same generator bug on minimum/maximum, modelcontextprotocol#3139) and add a tripwire test that forces erratum deletion once the schemas are re-vendored. - Document the wire-schema-valid / wire-schema-harness-error check IDs in the README.
This commit is contained in:
parent
89c2c0452e
commit
5cc567c399
2 changed files with 201 additions and 39 deletions
|
|
@ -1,4 +1,6 @@
|
|||
import { createServer, type Server } from 'http';
|
||||
import rawSchema2025_06_18 from '../spec-types/2025-06-18.schema.json';
|
||||
import rawSchema2025_11_25 from '../spec-types/2025-11-25.schema.json';
|
||||
import { DRAFT_PROTOCOL_VERSION } from '../types';
|
||||
import { sendStatelessRequest } from '../connection/stateless';
|
||||
import {
|
||||
|
|
@ -506,4 +508,31 @@ describe('schema errata', () => {
|
|||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('accepts fractional NumberSchema bounds at 2025-06-18 (same generator bug, no default there)', () => {
|
||||
expect(
|
||||
wireSchemaErrors(
|
||||
'2025-06-18',
|
||||
elicitWithScore({ type: 'number', minimum: 0.5, maximum: 99.5 })
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('tripwire: delete applySchemaErrata once the vendored schemas are fixed (modelcontextprotocol#3139)', () => {
|
||||
const numberSchema = (raw: Record<string, unknown>, defsKey: string) =>
|
||||
(
|
||||
raw[defsKey] as Record<
|
||||
string,
|
||||
{ properties: Record<string, { type: string }> }
|
||||
>
|
||||
).NumberSchema.properties;
|
||||
expect(
|
||||
numberSchema(rawSchema2025_11_25, '$defs').default.type,
|
||||
'the re-vendored 2025-11-25 schema no longer needs the erratum — delete its applySchemaErrata branch'
|
||||
).toBe('integer');
|
||||
expect(
|
||||
numberSchema(rawSchema2025_06_18, 'definitions').minimum.type,
|
||||
'the re-vendored 2025-06-18 schema no longer needs the erratum — delete its applySchemaErrata branch'
|
||||
).toBe('integer');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -75,18 +75,19 @@ interface CompiledSpec {
|
|||
|
||||
const compiledSpecs = new Map<SpecVersion, CompiledSpec>();
|
||||
|
||||
/** Patch known bugs in released (frozen) generated spec schemas at load time
|
||||
* so validation matches the schema.ts source of truth. Each erratum cites the
|
||||
* upstream fix; delete it once the dated schema.json is corrected upstream
|
||||
* and re-vendored. */
|
||||
/** Patch known generator bugs in released spec schema.json files at load time
|
||||
* so validation matches the schema.ts source of truth; delete each branch (a
|
||||
* test trips) once the dated schema is fixed upstream and re-vendored. */
|
||||
function applySchemaErrata(
|
||||
specVersion: SpecVersion,
|
||||
schema: Record<string, unknown>
|
||||
): Record<string, unknown> {
|
||||
if (specVersion !== '2025-11-25') return schema;
|
||||
// 2025-11-25 generates NumberSchema minimum/maximum/default as `integer`,
|
||||
// contradicting its own schema.ts (`default?: number`). Fixed for draft in
|
||||
// modelcontextprotocol#2710, which left the released schema.json as-is.
|
||||
if (specVersion !== '2025-11-25' && specVersion !== '2025-06-18') {
|
||||
return schema;
|
||||
}
|
||||
// NumberSchema minimum/maximum (plus default at 2025-11-25) are generated
|
||||
// as `integer`, contradicting schema.ts (`number`). Fixed for draft in
|
||||
// modelcontextprotocol#2710; dated fixes proposed in modelcontextprotocol#3139.
|
||||
const patched = structuredClone(schema);
|
||||
const defs = (patched.$defs ?? patched.definitions) as Record<
|
||||
string,
|
||||
|
|
|
|||
Loading…
Reference in a new issue