test: add compile-time key-parity assertions for spec type checks#1652
Conversation
Add 151 compile-time key-parity assertions to spec.types.test.ts that verify SDK and spec types have exactly the same set of named property keys. This catches missing .optional() parameters that the existing mutual-assignability checks cannot detect due to TypeScript structural subtyping. Introduces three utility types: - KnownKeys<T>: strips index signatures, keeps named keys only - AssertExactKeys<A, B>: resolves to true on match, descriptive error type on mismatch - Assert<T extends true>: constraint that forces compile error Coverage: 151 of 174 sdkTypeChecks entries. 23 exclusions documented (15 union types, 8 primitive aliases). Discovered 7 genuine SDK/spec key mismatches, documented with @ts-expect-error annotations as a living drift inventory. Closes modelcontextprotocol#1298
|
@modelcontextprotocol/client
@modelcontextprotocol/server
@modelcontextprotocol/express
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
Fix CI lint failure by applying Prettier formatting to the key-parity assertions test file.
|
@claude review |
There was a problem hiding this comment.
Thanks for this.
One ask before merging: could you add a completeness guard that ties the _K_ assertions back to sdkTypeChecks? Something like a parallel runtime array:
const keyParityChecked = ['RequestParams', 'NotificationParams', /* ... */] as const;that the existing should have comprehensive compatibility tests check also validates against (minus the 23 documented exclusions). Otherwise if someone adds a new entry to sdkTypeChecks but forgets the _K_ line, nothing catches the gap.
Happy to merge once that's in.
Ensures every sdkTypeChecks entry (minus excluded union/primitive types) has a corresponding _K_ compile-time assertion. The checked set is derived at test time by parsing _K_ type aliases from the file itself, so adding a new _K_ assertion automatically satisfies the guard.
|
Added the completeness guard requested in review. What: A new runtime test ( How it works: Instead of maintaining a hardcoded list of checked types, it reads the test file itself at test time and extracts
Verified:
|
|
@claude review |
Summary
spec.types.test.tsthat catch missing/extra.optional()fields between SDK and spec typesKnownKeys<T>,AssertExactKeys<A,B>,Assert<T>) that produce descriptive compile errors on key-set mismatches@ts-expect-errorannotations (serving as a living drift inventory)sdkTypeChecksentries; 23 exclusions documented (union types and primitive aliases)Motivation
Closes #1298.
The existing mutual-assignability checks (
sdk = spec; spec = sdk) pass even when one type has optional keys that the other lacks, because TypeScript structural subtyping treats optional properties as compatible in both directions. This means a Zod schema missing.optional()on a field — or having.optional()when the spec requires the field — goes undetected.The new key-parity layer compares the exact set of named property keys between each SDK/spec type pair at compile time, with zero runtime cost.
Test plan
pnpm typecheck:allpasses (key-parity assertions validated at compile time)pnpm test:all— 734 tests pass (440 core + 37 server + 257 client)@ts-expect-errorannotations verified as necessary.optional()from a schema correctly triggers a compile error from the new assertions