Skip to content

Validate against the schema passed in, not the first registered under its $id - #2974

Merged
cliffhall merged 1 commit into
v2/mainfrom
v2/fix/2935-schema-id-reuse
Nov 12, 2026
Merged

cliffhall merged 1 commit into
v2/mainfrom
v2/fix/2935-schema-id-reuse

Conversation

@cliffhall

Copy link
Copy Markdown
Member

Closes #2935

Problem

LinearPatternJsonSchemaValidator.getValidator did engine.getSchema($id) ?? engine.compile(schema) whenever the schema had a string $id, so whatever was first registered under that $id won:

  • a tool's outputSchema changed after tools/list_changed but kept its $id → results checked against the old schema (false failures and false passes);
  • two tools declaring the same generic $id → the second tool's results checked against the first tool's schema.

Fix

core/mcp/linearPatternValidator.ts: a new private validatorFor(engine, schema).

  • No string $id → compile(schema), as before.
  • String $id → look it up; reuse the result only when its .schema is the very object passed in (so a repeated call with the same schema, and a $defs entry Ajv compiles lazily on first lookup by $id, are reused exactly as before). Otherwise removeSchema the registration and compile the new schema — removing first is what keeps Ajv's "schema with key or id already exists" from throwing.
  • Removal uses Ajv's normalized key (trailing # / #/ dropped, mirroring Ajv's normalizeId), because Ajv registers normalized but removeSchema takes the key verbatim — without that, "$id": "urn:x#" would throw on the second schema.
  • The lookup and the compile each run under their own tracked() budget, so a lookup that compiles a schema this call then discards does not charge the replacement's budget. tracked is now generic so it accepts getSchema's possibly-undefined result.

A validator already compiled against an old registration (e.g. an outer schema whose $defs registered the $id) keeps working: Ajv resolved its references when it compiled.

Tests

New "schemas sharing a $id" block in clients/web/src/test/core/mcp/linearPatternValidator.test.ts:

  • changed schema keeping its $id (both directions from the issue: "21C" accepted, 21 rejected with data/temp must be string);
  • two schemas with the same $id, alternating, each validated against itself with no throw;
  • a $id with a trailing empty fragment replaced without throwing;
  • a $id registered by an earlier schema's $defs replaced, while the earlier outer schema still validates against its own definition.

The first three cases from the issue fail on v2/main and pass with the change; the existing nested-$id budget test still passes. npm run local:gate is green.

Approach follows the prototype prompt shared on the issue (object-identity reuse, removeSchema then compile).

🤖 Generated with Claude Code

…2935)

getValidator reused engine.getSchema($id) unconditionally, so a changed
outputSchema that kept its $id, or a second tool declaring the same
$id, was validated against whatever was registered first. Reuse the
lookup only when it is the very schema object passed in; otherwise
remove the registration (by Ajv's normalized key) and compile the new
schema. Each Ajv entry point keeps a compile budget of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

There are no active review issues with the shared-ID schema validation change.

No open issues 🎉

What changed in this PR

Fixes #2935 in the shared MCP schema validator so schemas sharing a $id are validated against the supplied schema rather than the first registration.

Changes:

  • Reuses registered validators only when their schema object matches; otherwise removes the normalized $id registration and compiles the replacement.
  • Tracks lookup and compilation budgets separately.
  • Adds regression coverage for changed schemas, alternating schemas, trailing fragments, and IDs registered through $defs.
File Description
core/​mcp/​linearPatternValidator.ts Adds identity-aware validator reuse and normalized registration replacement while preserving compilation-budget tracking.
clients/​web/​src/​test/​core/​mcp/​linearPatternValidator.test.ts Tests shared-ID replacements and continued correctness of previously compiled validators.

Review effort: Balanced

@cliffhall

Copy link
Copy Markdown
Member Author

Copilot review closed: round 1 was clean (no inline comments, no suppressed findings), so the loop stopped on the first clean round.

@cliffhall
cliffhall merged commit b453851 into v2/main Nov 12, 2026
12 checks passed
@cliffhall
cliffhall deleted the v2/fix/2935-schema-id-reuse branch November 12, 2026 06:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

2 participants