From ead6fb50460a194dcfe8b4348ad3f3e8a2b9a218 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 3 Jul 2025 19:05:02 +0100 Subject: [PATCH 01/14] Add type compatibility test between SDK and spec types --- .gitignore | 3 + package.json | 3 +- src/spec.types.test.ts | 657 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 662 insertions(+), 1 deletion(-) create mode 100644 src/spec.types.test.ts diff --git a/.gitignore b/.gitignore index 6c4bf1a6..b39dd94e 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,9 @@ web_modules/ # Output of 'npm pack' *.tgz +# Output of 'npm run fetch:spec-types' +src/spec.types.ts + # Yarn Integrity file .yarn-integrity diff --git a/package.json b/package.json index 15b7753b..bbca197b 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "dist" ], "scripts": { + "fetch:spec-types": "test -f src/spec.types.ts || curl -o src/spec.types.ts https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/heads/main/schema/draft/schema.ts", "build": "npm run build:esm && npm run build:cjs", "build:esm": "mkdir -p dist/esm && echo '{\"type\": \"module\"}' > dist/esm/package.json && tsc -p tsconfig.prod.json", "build:esm:w": "npm run build:esm -- -w", @@ -43,7 +44,7 @@ "examples:simple-server:w": "tsx --watch src/examples/server/simpleStreamableHttp.ts --oauth", "prepack": "npm run build:esm && npm run build:cjs", "lint": "eslint src/", - "test": "jest", + "test": "npm run fetch:spec-types && jest", "start": "npm run server", "server": "tsx watch --clear-screen=false src/cli.ts server", "client": "tsx src/cli.ts client" diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts new file mode 100644 index 00000000..b6c88689 --- /dev/null +++ b/src/spec.types.test.ts @@ -0,0 +1,657 @@ +// import ts from 'typescript'; + +import * as SDKTypes from "./types.js"; +import * as SpecTypes from "./spec.types.js"; + +// Deep version that recursively removes index signatures (caused by ZodObject.passthrough()) and turns unknowns into `object | undefined` +type DeepKnownKeys = T extends object + ? T extends Array + ? Array> + : T extends Function + ? T + : { + [K in keyof T as string extends K ? never : number extends K ? never : K]: DeepKnownKeys; + } + : unknown extends T + ? (object | undefined) + : T; + +function checkCancelledNotification( + sdk: SDKTypes.CancelledNotification, + spec: SpecTypes.CancelledNotification +) { + sdk = spec; + spec = sdk; +} +function checkBaseMetadata( + sdk: SDKTypes.BaseMetadata, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkImplementation( + sdk: SDKTypes.Implementation, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkProgressNotification( + sdk: SDKTypes.ProgressNotification, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} + +function checkSubscribeRequest( + sdk: SDKTypes.SubscribeRequest, + spec: SpecTypes.SubscribeRequest +) { + sdk = spec; + spec = sdk; +} +function checkUnsubscribeRequest( + sdk: SDKTypes.UnsubscribeRequest, + spec: SpecTypes.UnsubscribeRequest +) { + sdk = spec; + spec = sdk; +} +function checkPaginatedRequest( + sdk: SDKTypes.PaginatedRequest, + spec: SpecTypes.PaginatedRequest +) { + sdk = spec; + spec = sdk; +} +function checkPaginatedResult( + sdk: SDKTypes.PaginatedResult, + spec: SpecTypes.PaginatedResult +) { + sdk = spec; + spec = sdk; +} +function checkListRootsRequest( + sdk: SDKTypes.ListRootsRequest, + spec: SpecTypes.ListRootsRequest +) { + sdk = spec; + spec = sdk; +} +function checkListRootsResult( + sdk: SDKTypes.ListRootsResult, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkRoot(sdk: SDKTypes.Root, spec: DeepKnownKeys) { + sdk = spec; + spec = sdk; +} +function checkElicitRequest(sdk: SDKTypes.ElicitRequest, spec: DeepKnownKeys) { + sdk = spec; + spec = sdk; +} +function checkElicitResult(sdk: SDKTypes.ElicitResult, spec: DeepKnownKeys) { + sdk = spec; + spec = sdk; +} +function checkCompleteRequest(sdk: SDKTypes.CompleteRequest, spec: DeepKnownKeys) { + sdk = spec; + spec = sdk; +} +function checkCompleteResult(sdk: SDKTypes.CompleteResult, spec: SpecTypes.CompleteResult) { + sdk = spec; + spec = sdk; +} +function checkProgressToken( + sdk: SDKTypes.ProgressToken, + spec: SpecTypes.ProgressToken +) { + sdk = spec; + spec = sdk; +} +function checkCursor(sdk: SDKTypes.Cursor, spec: SpecTypes.Cursor) { + sdk = spec; + spec = sdk; +} +function checkRequest( + sdk: SDKTypes.Request, + spec: SpecTypes.Request +) { + sdk = spec; + spec = sdk; +} +function checkResult( + sdk: SDKTypes.Result, + spec: SpecTypes.Result +) { + sdk = spec; + spec = sdk; +} +function checkRequestId( + sdk: SDKTypes.RequestId, + spec: SpecTypes.RequestId +) { + sdk = spec; + spec = sdk; +} +function checkJSONRPCRequest( + sdk: SDKTypes.JSONRPCRequest, + spec: SpecTypes.JSONRPCRequest +) { + sdk = spec; + spec = sdk; +} +function checkJSONRPCNotification( + sdk: SDKTypes.JSONRPCNotification, + spec: SpecTypes.JSONRPCNotification +) { + sdk = spec; + spec = sdk; +} +function checkJSONRPCResponse( + sdk: SDKTypes.JSONRPCResponse, + spec: SpecTypes.JSONRPCResponse +) { + sdk = spec; + spec = sdk; +} +function checkEmptyResult( + sdk: SDKTypes.EmptyResult, + spec: SpecTypes.EmptyResult +) { + sdk = spec; + spec = sdk; +} +function checkNotification( + sdk: SDKTypes.Notification, + spec: SpecTypes.Notification +) { + sdk = spec; + spec = sdk; +} +function checkClientResult( + sdk: SDKTypes.ClientResult, + spec: SpecTypes.ClientResult +) { + sdk = spec; + spec = sdk; +} +function checkClientNotification( + sdk: SDKTypes.ClientNotification, + spec: SpecTypes.ClientNotification +) { + sdk = spec; + spec = sdk; +} +function checkServerResult( + sdk: SDKTypes.ServerResult, + spec: SpecTypes.ServerResult +) { + sdk = spec; + spec = sdk; +} +function checkResourceTemplateReference( + sdk: SDKTypes.ResourceTemplateReference, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkPromptReference( + sdk: SDKTypes.PromptReference, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkResourceReference( + sdk: SDKTypes.ResourceReference, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkToolAnnotations( + sdk: SDKTypes.ToolAnnotations, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkTool( + sdk: SDKTypes.Tool, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkListToolsRequest( + sdk: SDKTypes.ListToolsRequest, + spec: SpecTypes.ListToolsRequest +) { + sdk = spec; + spec = sdk; +} +function checkListToolsResult( + sdk: SDKTypes.ListToolsResult, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkCallToolResult( + sdk: SDKTypes.CallToolResult, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkCallToolRequest( + sdk: SDKTypes.CallToolRequest, + spec: SpecTypes.CallToolRequest +) { + sdk = spec; + spec = sdk; +} +function checkToolListChangedNotification( + sdk: SDKTypes.ToolListChangedNotification, + spec: SpecTypes.ToolListChangedNotification +) { + sdk = spec; + spec = sdk; +} +function checkResourceListChangedNotification( + sdk: SDKTypes.ResourceListChangedNotification, + spec: SpecTypes.ResourceListChangedNotification +) { + sdk = spec; + spec = sdk; +} +function checkPromptListChangedNotification( + sdk: SDKTypes.PromptListChangedNotification, + spec: SpecTypes.PromptListChangedNotification +) { + sdk = spec; + spec = sdk; +} +function checkRootsListChangedNotification( + sdk: SDKTypes.RootsListChangedNotification, + spec: SpecTypes.RootsListChangedNotification +) { + sdk = spec; + spec = sdk; +} +function checkResourceUpdatedNotification( + sdk: SDKTypes.ResourceUpdatedNotification, + spec: SpecTypes.ResourceUpdatedNotification +) { + sdk = spec; + spec = sdk; +} +function checkSamplingMessage( + sdk: SDKTypes.SamplingMessage, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkCreateMessageResult( + sdk: SDKTypes.CreateMessageResult, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkSetLevelRequest( + sdk: SDKTypes.SetLevelRequest, + spec: SpecTypes.SetLevelRequest +) { + sdk = spec; + spec = sdk; +} +function checkPingRequest( + sdk: SDKTypes.PingRequest, + spec: SpecTypes.PingRequest +) { + sdk = spec; + spec = sdk; +} +function checkInitializedNotification( + sdk: SDKTypes.InitializedNotification, + spec: SpecTypes.InitializedNotification +) { + sdk = spec; + spec = sdk; +} +function checkListResourcesRequest( + sdk: SDKTypes.ListResourcesRequest, + spec: SpecTypes.ListResourcesRequest +) { + sdk = spec; + spec = sdk; +} +function checkListResourcesResult( + sdk: SDKTypes.ListResourcesResult, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkListResourceTemplatesRequest( + sdk: SDKTypes.ListResourceTemplatesRequest, + spec: SpecTypes.ListResourceTemplatesRequest +) { + sdk = spec; + spec = sdk; +} +function checkListResourceTemplatesResult( + sdk: SDKTypes.ListResourceTemplatesResult, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkReadResourceRequest( + sdk: SDKTypes.ReadResourceRequest, + spec: SpecTypes.ReadResourceRequest +) { + sdk = spec; + spec = sdk; +} +function checkReadResourceResult( + sdk: SDKTypes.ReadResourceResult, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkResourceContents( + sdk: SDKTypes.ResourceContents, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkTextResourceContents( + sdk: SDKTypes.TextResourceContents, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkBlobResourceContents( + sdk: SDKTypes.BlobResourceContents, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkResource( + sdk: SDKTypes.Resource, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkResourceTemplate( + sdk: SDKTypes.ResourceTemplate, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkPromptArgument( + sdk: SDKTypes.PromptArgument, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkPrompt( + sdk: SDKTypes.Prompt, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkListPromptsRequest( + sdk: SDKTypes.ListPromptsRequest, + spec: SpecTypes.ListPromptsRequest +) { + sdk = spec; + spec = sdk; +} +function checkListPromptsResult( + sdk: SDKTypes.ListPromptsResult, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkGetPromptRequest( + sdk: SDKTypes.GetPromptRequest, + spec: SpecTypes.GetPromptRequest +) { + sdk = spec; + spec = sdk; +} +function checkTextContent( + sdk: SDKTypes.TextContent, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkImageContent( + sdk: SDKTypes.ImageContent, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkAudioContent( + sdk: SDKTypes.AudioContent, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkEmbeddedResource( + sdk: SDKTypes.EmbeddedResource, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkResourceLink( + sdk: SDKTypes.ResourceLink, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkContentBlock( + sdk: SDKTypes.ContentBlock, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkPromptMessage( + sdk: SDKTypes.PromptMessage, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkGetPromptResult( + sdk: SDKTypes.GetPromptResult, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkBooleanSchema( + sdk: SDKTypes.BooleanSchema, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkStringSchema( + sdk: SDKTypes.StringSchema, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkNumberSchema( + sdk: SDKTypes.NumberSchema, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkEnumSchema( + sdk: SDKTypes.EnumSchema, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkPrimitiveSchemaDefinition( + sdk: SDKTypes.PrimitiveSchemaDefinition, + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkCreateMessageRequest( + sdk: DeepKnownKeys, // TODO(quirk): some {} type + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkInitializeRequest( + sdk: DeepKnownKeys, // TODO(quirk): some {} type + spec: SpecTypes.InitializeRequest +) { + sdk = spec; + spec = sdk; +} +function checkInitializeResult( + sdk: DeepKnownKeys, // TODO(quirk): some {} type + spec: SpecTypes.InitializeResult +) { + sdk = spec; + spec = sdk; +} +function checkClientCapabilities( + sdk: DeepKnownKeys, // TODO(quirk): {} + spec: SpecTypes.ClientCapabilities +) { + sdk = spec; + spec = sdk; +} +function checkServerCapabilities( + sdk: DeepKnownKeys, // TODO(quirk): {} + spec: SpecTypes.ServerCapabilities +) { + sdk = spec; + spec = sdk; +} +function checkJSONRPCError( + sdk: DeepKnownKeys, // TODO(quirk): error.data + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkJSONRPCMessage( + sdk: DeepKnownKeys, // TODO(quirk): error.data + spec: DeepKnownKeys +) { + sdk = spec; + spec = sdk; +} +function checkClientRequest( + sdk: DeepKnownKeys, // TODO(quirk): capabilities.logging is {} + spec: SpecTypes.ClientRequest +) { + sdk = spec; + spec = sdk; +} +function checkServerRequest( + sdk: DeepKnownKeys, // TODO(quirk): some {} typ + spec: SpecTypes.ServerRequest +) { + sdk = spec; + spec = sdk; +} +function checkLoggingMessageNotification( + sdk: SDKTypes.LoggingMessageNotification, + spec: SpecTypes.LoggingMessageNotification +) { + sdk = spec; + // spec = sdk; // TODO(bug): data is optional +} +function checkServerNotification( + sdk: SDKTypes.ServerNotification, + spec: SpecTypes.ServerNotification +) { + sdk = spec; + // spec = sdk; // TODO(bug): data is optional +} + +// TODO(bug): missing type in SDK +// function checkModelHint( +// sdk: SDKTypes.ModelHint, +// spec: DeepKnownKeys +// ) { +// sdk = spec; +// spec = sdk; +// } + +// TODO(bug): missing type in SDK +// function checkModelPreferences( +// sdk: SDKTypes.ModelPreferences, +// spec: DeepKnownKeys +// ) { +// sdk = spec; +// spec = sdk; +// } + +// TODO(bug): missing type in SDK +// function checkAnnotations( +// sdk: SDKTypes.Annotations, +// spec: DeepKnownKeys +// ) { +// sdk = spec; +// spec = sdk; +// } + +const SPEC_TYPES_FILE = 'src/spec.types.ts'; +const THIS_SOURCE_FILE = 'src/spec.types.test.ts'; + +describe('Spec Types', () => { + const specTypesContent = require('fs').readFileSync(SPEC_TYPES_FILE, 'utf-8'); + const typeNames = [...specTypesContent.matchAll(/export\s+interface\s+(\w+)\b/g)].map(m => m[1]); + const testContent = require('fs').readFileSync(THIS_SOURCE_FILE, 'utf-8'); + + it('should define some expected types', () => { + expect(typeNames).toContain('JSONRPCNotification'); + expect(typeNames).toContain('ElicitResult'); + }); + + for (const typeName of typeNames) { + it(`${typeName} should have a compatibility test`, () => { + expect(testContent).toContain(`function check${typeName}(`); + }); + } +}); \ No newline at end of file From d1a30ab2d2a41bcc10b67f401c1ffaaf0c3ffaaf Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 3 Jul 2025 19:29:30 +0100 Subject: [PATCH 02/14] disable lints in spec.types.test.ts --- src/spec.types.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index b6c88689..bdfd953b 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -1,9 +1,13 @@ -// import ts from 'typescript'; - import * as SDKTypes from "./types.js"; import * as SpecTypes from "./spec.types.js"; +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-unsafe-function-type */ +/* eslint-disable @typescript-eslint/no-require-imports */ + // Deep version that recursively removes index signatures (caused by ZodObject.passthrough()) and turns unknowns into `object | undefined` +// TODO: make string index mapping tighter +// TODO: split into multiple transformations (e.g. RemovePassthrough) and only use the ones needed for each type. type DeepKnownKeys = T extends object ? T extends Array ? Array> From e0fc39066d3d9b2173e59120df4afe32c776faac Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 3 Jul 2025 19:37:50 +0100 Subject: [PATCH 03/14] rename + switch position of RemovePassthrough --- src/spec.types.test.ts | 242 ++++++++++++++++++++++------------------- 1 file changed, 130 insertions(+), 112 deletions(-) diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index bdfd953b..515079bf 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -7,14 +7,14 @@ import * as SpecTypes from "./spec.types.js"; // Deep version that recursively removes index signatures (caused by ZodObject.passthrough()) and turns unknowns into `object | undefined` // TODO: make string index mapping tighter -// TODO: split into multiple transformations (e.g. RemovePassthrough) and only use the ones needed for each type. -type DeepKnownKeys = T extends object +// TODO: split into multiple transformations if needed +type RemovePassthrough = T extends object ? T extends Array - ? Array> + ? Array> : T extends Function ? T : { - [K in keyof T as string extends K ? never : number extends K ? never : K]: DeepKnownKeys; + [K in keyof T as string extends K ? never : number extends K ? never : K]: RemovePassthrough; } : unknown extends T ? (object | undefined) @@ -28,22 +28,22 @@ function checkCancelledNotification( spec = sdk; } function checkBaseMetadata( - sdk: SDKTypes.BaseMetadata, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.BaseMetadata ) { sdk = spec; spec = sdk; } function checkImplementation( - sdk: SDKTypes.Implementation, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.Implementation ) { sdk = spec; spec = sdk; } function checkProgressNotification( - sdk: SDKTypes.ProgressNotification, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ProgressNotification ) { sdk = spec; spec = sdk; @@ -85,29 +85,44 @@ function checkListRootsRequest( spec = sdk; } function checkListRootsResult( - sdk: SDKTypes.ListRootsResult, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ListRootsResult ) { sdk = spec; spec = sdk; } -function checkRoot(sdk: SDKTypes.Root, spec: DeepKnownKeys) { +function checkRoot( + sdk: RemovePassthrough, + spec: SpecTypes.Root +) { sdk = spec; spec = sdk; } -function checkElicitRequest(sdk: SDKTypes.ElicitRequest, spec: DeepKnownKeys) { +function checkElicitRequest( + sdk: RemovePassthrough, + spec: SpecTypes.ElicitRequest +) { sdk = spec; spec = sdk; } -function checkElicitResult(sdk: SDKTypes.ElicitResult, spec: DeepKnownKeys) { +function checkElicitResult( + sdk: RemovePassthrough, + spec: SpecTypes.ElicitResult +) { sdk = spec; spec = sdk; } -function checkCompleteRequest(sdk: SDKTypes.CompleteRequest, spec: DeepKnownKeys) { +function checkCompleteRequest( + sdk: RemovePassthrough, + spec: SpecTypes.CompleteRequest +) { sdk = spec; spec = sdk; } -function checkCompleteResult(sdk: SDKTypes.CompleteResult, spec: SpecTypes.CompleteResult) { +function checkCompleteResult( + sdk: SDKTypes.CompleteResult, + spec: SpecTypes.CompleteResult +) { sdk = spec; spec = sdk; } @@ -118,7 +133,10 @@ function checkProgressToken( sdk = spec; spec = sdk; } -function checkCursor(sdk: SDKTypes.Cursor, spec: SpecTypes.Cursor) { +function checkCursor( + sdk: SDKTypes.Cursor, + spec: SpecTypes.Cursor +) { sdk = spec; spec = sdk; } @@ -200,36 +218,36 @@ function checkServerResult( spec = sdk; } function checkResourceTemplateReference( - sdk: SDKTypes.ResourceTemplateReference, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ResourceTemplateReference ) { sdk = spec; spec = sdk; } function checkPromptReference( - sdk: SDKTypes.PromptReference, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.PromptReference ) { sdk = spec; spec = sdk; } function checkResourceReference( - sdk: SDKTypes.ResourceReference, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ResourceTemplateReference ) { sdk = spec; spec = sdk; } function checkToolAnnotations( - sdk: SDKTypes.ToolAnnotations, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ToolAnnotations ) { sdk = spec; spec = sdk; } function checkTool( - sdk: SDKTypes.Tool, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.Tool ) { sdk = spec; spec = sdk; @@ -242,15 +260,15 @@ function checkListToolsRequest( spec = sdk; } function checkListToolsResult( - sdk: SDKTypes.ListToolsResult, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ListToolsResult ) { sdk = spec; spec = sdk; } function checkCallToolResult( - sdk: SDKTypes.CallToolResult, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.CallToolResult ) { sdk = spec; spec = sdk; @@ -298,15 +316,15 @@ function checkResourceUpdatedNotification( spec = sdk; } function checkSamplingMessage( - sdk: SDKTypes.SamplingMessage, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.SamplingMessage ) { sdk = spec; spec = sdk; } function checkCreateMessageResult( - sdk: SDKTypes.CreateMessageResult, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.CreateMessageResult ) { sdk = spec; spec = sdk; @@ -340,8 +358,8 @@ function checkListResourcesRequest( spec = sdk; } function checkListResourcesResult( - sdk: SDKTypes.ListResourcesResult, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ListResourcesResult ) { sdk = spec; spec = sdk; @@ -354,8 +372,8 @@ function checkListResourceTemplatesRequest( spec = sdk; } function checkListResourceTemplatesResult( - sdk: SDKTypes.ListResourceTemplatesResult, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ListResourceTemplatesResult ) { sdk = spec; spec = sdk; @@ -368,57 +386,57 @@ function checkReadResourceRequest( spec = sdk; } function checkReadResourceResult( - sdk: SDKTypes.ReadResourceResult, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ReadResourceResult ) { sdk = spec; spec = sdk; } function checkResourceContents( - sdk: SDKTypes.ResourceContents, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ResourceContents ) { sdk = spec; spec = sdk; } function checkTextResourceContents( - sdk: SDKTypes.TextResourceContents, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.TextResourceContents ) { sdk = spec; spec = sdk; } function checkBlobResourceContents( - sdk: SDKTypes.BlobResourceContents, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.BlobResourceContents ) { sdk = spec; spec = sdk; } function checkResource( - sdk: SDKTypes.Resource, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.Resource ) { sdk = spec; spec = sdk; } function checkResourceTemplate( - sdk: SDKTypes.ResourceTemplate, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ResourceTemplate ) { sdk = spec; spec = sdk; } function checkPromptArgument( - sdk: SDKTypes.PromptArgument, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.PromptArgument ) { sdk = spec; spec = sdk; } function checkPrompt( - sdk: SDKTypes.Prompt, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.Prompt ) { sdk = spec; spec = sdk; @@ -431,8 +449,8 @@ function checkListPromptsRequest( spec = sdk; } function checkListPromptsResult( - sdk: SDKTypes.ListPromptsResult, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ListPromptsResult ) { sdk = spec; spec = sdk; @@ -445,154 +463,154 @@ function checkGetPromptRequest( spec = sdk; } function checkTextContent( - sdk: SDKTypes.TextContent, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.TextContent ) { sdk = spec; spec = sdk; } function checkImageContent( - sdk: SDKTypes.ImageContent, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ImageContent ) { sdk = spec; spec = sdk; } function checkAudioContent( - sdk: SDKTypes.AudioContent, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.AudioContent ) { sdk = spec; spec = sdk; } function checkEmbeddedResource( - sdk: SDKTypes.EmbeddedResource, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.EmbeddedResource ) { sdk = spec; spec = sdk; } function checkResourceLink( - sdk: SDKTypes.ResourceLink, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ResourceLink ) { sdk = spec; spec = sdk; } function checkContentBlock( - sdk: SDKTypes.ContentBlock, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.ContentBlock ) { sdk = spec; spec = sdk; } function checkPromptMessage( - sdk: SDKTypes.PromptMessage, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.PromptMessage ) { sdk = spec; spec = sdk; } function checkGetPromptResult( - sdk: SDKTypes.GetPromptResult, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.GetPromptResult ) { sdk = spec; spec = sdk; } function checkBooleanSchema( - sdk: SDKTypes.BooleanSchema, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.BooleanSchema ) { sdk = spec; spec = sdk; } function checkStringSchema( - sdk: SDKTypes.StringSchema, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.StringSchema ) { sdk = spec; spec = sdk; } function checkNumberSchema( - sdk: SDKTypes.NumberSchema, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.NumberSchema ) { sdk = spec; spec = sdk; } function checkEnumSchema( - sdk: SDKTypes.EnumSchema, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.EnumSchema ) { sdk = spec; spec = sdk; } function checkPrimitiveSchemaDefinition( - sdk: SDKTypes.PrimitiveSchemaDefinition, - spec: DeepKnownKeys + sdk: RemovePassthrough, + spec: SpecTypes.PrimitiveSchemaDefinition +) { + sdk = spec; + spec = sdk; +} +function checkJSONRPCError( + sdk: SDKTypes.JSONRPCError, + spec: SpecTypes.JSONRPCError +) { + sdk = spec; + spec = sdk; +} +function checkJSONRPCMessage( + sdk: SDKTypes.JSONRPCMessage, + spec: SpecTypes.JSONRPCMessage ) { sdk = spec; spec = sdk; } function checkCreateMessageRequest( - sdk: DeepKnownKeys, // TODO(quirk): some {} type - spec: DeepKnownKeys + sdk: RemovePassthrough, // TODO(quirk): some {} typ>e + spec: SpecTypes.CreateMessageRequest ) { sdk = spec; spec = sdk; } function checkInitializeRequest( - sdk: DeepKnownKeys, // TODO(quirk): some {} type + sdk: RemovePassthrough, // TODO(quirk): some {} type spec: SpecTypes.InitializeRequest ) { sdk = spec; spec = sdk; } function checkInitializeResult( - sdk: DeepKnownKeys, // TODO(quirk): some {} type + sdk: RemovePassthrough, // TODO(quirk): some {} type spec: SpecTypes.InitializeResult ) { sdk = spec; spec = sdk; } function checkClientCapabilities( - sdk: DeepKnownKeys, // TODO(quirk): {} + sdk: RemovePassthrough, // TODO(quirk): {} spec: SpecTypes.ClientCapabilities ) { sdk = spec; spec = sdk; } function checkServerCapabilities( - sdk: DeepKnownKeys, // TODO(quirk): {} + sdk: RemovePassthrough, // TODO(quirk): {} spec: SpecTypes.ServerCapabilities ) { sdk = spec; spec = sdk; } -function checkJSONRPCError( - sdk: DeepKnownKeys, // TODO(quirk): error.data - spec: DeepKnownKeys -) { - sdk = spec; - spec = sdk; -} -function checkJSONRPCMessage( - sdk: DeepKnownKeys, // TODO(quirk): error.data - spec: DeepKnownKeys -) { - sdk = spec; - spec = sdk; -} function checkClientRequest( - sdk: DeepKnownKeys, // TODO(quirk): capabilities.logging is {} + sdk: RemovePassthrough, // TODO(quirk): capabilities.logging is {} spec: SpecTypes.ClientRequest ) { sdk = spec; spec = sdk; } function checkServerRequest( - sdk: DeepKnownKeys, // TODO(quirk): some {} typ + sdk: RemovePassthrough, // TODO(quirk): some {} typ spec: SpecTypes.ServerRequest ) { sdk = spec; @@ -615,8 +633,8 @@ function checkServerNotification( // TODO(bug): missing type in SDK // function checkModelHint( -// sdk: SDKTypes.ModelHint, -// spec: DeepKnownKeys +// RemovePassthrough< sdk: SDKTypes.ModelHint>, +// spec: SpecTypes.ModelHint // ) { // sdk = spec; // spec = sdk; @@ -624,8 +642,8 @@ function checkServerNotification( // TODO(bug): missing type in SDK // function checkModelPreferences( -// sdk: SDKTypes.ModelPreferences, -// spec: DeepKnownKeys +// RemovePassthrough< sdk: SDKTypes.ModelPreferences>, +// spec: SpecTypes.ModelPreferences // ) { // sdk = spec; // spec = sdk; @@ -633,8 +651,8 @@ function checkServerNotification( // TODO(bug): missing type in SDK // function checkAnnotations( -// sdk: SDKTypes.Annotations, -// spec: DeepKnownKeys +// RemovePassthrough< sdk: SDKTypes.Annotations>, +// spec: SpecTypes.Annotations // ) { // sdk = spec; // spec = sdk; From b3c5d27c701f98f468576f70cfa155f09fe69ef3 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 3 Jul 2025 19:38:58 +0100 Subject: [PATCH 04/14] Update spec.types.test.ts --- src/spec.types.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index 515079bf..a36aec1a 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -42,7 +42,7 @@ function checkImplementation( spec = sdk; } function checkProgressNotification( - sdk: RemovePassthrough, + sdk: SDKTypes.ProgressNotification, spec: SpecTypes.ProgressNotification ) { sdk = spec; From d57d35e3480746f9cf872e9754a21d03abe9c19b Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 3 Jul 2025 19:41:51 +0100 Subject: [PATCH 05/14] Update spec.types.test.ts --- src/spec.types.test.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index a36aec1a..b8b89fc6 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -12,12 +12,8 @@ type RemovePassthrough = T extends object ? T extends Array ? Array> : T extends Function - ? T - : { - [K in keyof T as string extends K ? never : number extends K ? never : K]: RemovePassthrough; - } - : unknown extends T - ? (object | undefined) + ? T + : {[K in keyof T as string extends K ? never : K]: RemovePassthrough} : T; function checkCancelledNotification( From e3888069f9bbb53d29ac85d3592096b4cf4be532 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 3 Jul 2025 20:40:43 +0100 Subject: [PATCH 06/14] add MakeUnknownsNotOptional to fix optionality of Zod unknown() fields --- package.json | 2 +- src/spec.types.test.ts | 71 ++++++++++++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index bbca197b..79da5193 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "dist" ], "scripts": { - "fetch:spec-types": "test -f src/spec.types.ts || curl -o src/spec.types.ts https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/heads/main/schema/draft/schema.ts", + "fetch:spec-types": "curl -o src/spec.types.ts https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/heads/main/schema/draft/schema.ts", "build": "npm run build:esm && npm run build:cjs", "build:esm": "mkdir -p dist/esm && echo '{\"type\": \"module\"}' > dist/esm/package.json && tsc -p tsconfig.prod.json", "build:esm:w": "npm run build:esm -- -w", diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index b8b89fc6..8914fcba 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -1,3 +1,10 @@ +/** + * This contains: + * - Static type checks to verify the Spec's types are compatible with the SDK's types + * (mutually assignable, w/ slight affordances to get rid of ZodObject.passthrough() index signatures, etc) + * - Runtime checks to verify all Spec types have a static check + * (a few don't have SDK types, see TODOs in this file) + */ import * as SDKTypes from "./types.js"; import * as SpecTypes from "./spec.types.js"; @@ -5,16 +12,40 @@ import * as SpecTypes from "./spec.types.js"; /* eslint-disable @typescript-eslint/no-unsafe-function-type */ /* eslint-disable @typescript-eslint/no-require-imports */ -// Deep version that recursively removes index signatures (caused by ZodObject.passthrough()) and turns unknowns into `object | undefined` -// TODO: make string index mapping tighter -// TODO: split into multiple transformations if needed +// Removes index signatures added by ZodObject.passthrough(). type RemovePassthrough = T extends object ? T extends Array ? Array> : T extends Function ? T : {[K in keyof T as string extends K ? never : K]: RemovePassthrough} - : T; + : T; + +type IsUnknown = [unknown] extends [T] ? [T] extends [unknown] ? true : false : false; + +// Turns {x?: unknown} into {x: unknown} but keeps {_meta?: unknown} unchanged (and leaves other optional properties unchanged, e.g. {x?: string}). +// This works around an apparent quirk of ZodObject.unknown() (makes fields optional) +type MakeUnknownsNotOptional = + IsUnknown extends true + ? unknown + : (T extends object + ? (T extends Array + ? Array> + : (T extends Function + ? T + : Pick & { + // Start with empty object to avoid duplicates + // Make unknown properties required (except _meta) + [K in keyof T as '_meta' extends K ? never : IsUnknown extends true ? K : never]-?: unknown; + } & + Pick extends true ? never : K + }[keyof T]> & { + // Recurse on the picked properties + [K in keyof Pick extends true ? never : K}[keyof T]>]: MakeUnknownsNotOptional + })) + : T); function checkCancelledNotification( sdk: SDKTypes.CancelledNotification, @@ -36,7 +67,7 @@ function checkImplementation( ) { sdk = spec; spec = sdk; -} +} function checkProgressNotification( sdk: SDKTypes.ProgressNotification, spec: SpecTypes.ProgressNotification @@ -564,70 +595,70 @@ function checkJSONRPCMessage( spec = sdk; } function checkCreateMessageRequest( - sdk: RemovePassthrough, // TODO(quirk): some {} typ>e + sdk: RemovePassthrough, spec: SpecTypes.CreateMessageRequest ) { sdk = spec; spec = sdk; } function checkInitializeRequest( - sdk: RemovePassthrough, // TODO(quirk): some {} type + sdk: RemovePassthrough, spec: SpecTypes.InitializeRequest ) { sdk = spec; spec = sdk; } function checkInitializeResult( - sdk: RemovePassthrough, // TODO(quirk): some {} type + sdk: RemovePassthrough, spec: SpecTypes.InitializeResult ) { sdk = spec; spec = sdk; } function checkClientCapabilities( - sdk: RemovePassthrough, // TODO(quirk): {} + sdk: RemovePassthrough, spec: SpecTypes.ClientCapabilities ) { sdk = spec; spec = sdk; } function checkServerCapabilities( - sdk: RemovePassthrough, // TODO(quirk): {} + sdk: RemovePassthrough, spec: SpecTypes.ServerCapabilities ) { sdk = spec; spec = sdk; } function checkClientRequest( - sdk: RemovePassthrough, // TODO(quirk): capabilities.logging is {} + sdk: RemovePassthrough, spec: SpecTypes.ClientRequest ) { sdk = spec; spec = sdk; } function checkServerRequest( - sdk: RemovePassthrough, // TODO(quirk): some {} typ + sdk: RemovePassthrough, spec: SpecTypes.ServerRequest ) { sdk = spec; spec = sdk; } function checkLoggingMessageNotification( - sdk: SDKTypes.LoggingMessageNotification, + sdk: MakeUnknownsNotOptional, spec: SpecTypes.LoggingMessageNotification ) { sdk = spec; - // spec = sdk; // TODO(bug): data is optional + spec = sdk; } function checkServerNotification( - sdk: SDKTypes.ServerNotification, + sdk: MakeUnknownsNotOptional, spec: SpecTypes.ServerNotification ) { sdk = spec; - // spec = sdk; // TODO(bug): data is optional + spec = sdk; } -// TODO(bug): missing type in SDK +// TODO(bug): missing type in SDK. This dead code is checked by the test suite below. // function checkModelHint( // RemovePassthrough< sdk: SDKTypes.ModelHint>, // spec: SpecTypes.ModelHint @@ -636,7 +667,7 @@ function checkServerNotification( // spec = sdk; // } -// TODO(bug): missing type in SDK +// TODO(bug): missing type in SDK. This dead code is checked by the test suite below. // function checkModelPreferences( // RemovePassthrough< sdk: SDKTypes.ModelPreferences>, // spec: SpecTypes.ModelPreferences @@ -645,7 +676,7 @@ function checkServerNotification( // spec = sdk; // } -// TODO(bug): missing type in SDK +// TODO(bug): missing type in SDK. This dead code is checked by the test suite below. // function checkAnnotations( // RemovePassthrough< sdk: SDKTypes.Annotations>, // spec: SpecTypes.Annotations @@ -661,7 +692,7 @@ describe('Spec Types', () => { const specTypesContent = require('fs').readFileSync(SPEC_TYPES_FILE, 'utf-8'); const typeNames = [...specTypesContent.matchAll(/export\s+interface\s+(\w+)\b/g)].map(m => m[1]); const testContent = require('fs').readFileSync(THIS_SOURCE_FILE, 'utf-8'); - + it('should define some expected types', () => { expect(typeNames).toContain('JSONRPCNotification'); expect(typeNames).toContain('ElicitResult'); From 0cd88db1f7577d0bc6a1dec8cdbcf91f37b9264a Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 3 Jul 2025 20:43:45 +0100 Subject: [PATCH 07/14] Update spec.types.test.ts --- src/spec.types.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index 8914fcba..7ae1f2b8 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -2,8 +2,8 @@ * This contains: * - Static type checks to verify the Spec's types are compatible with the SDK's types * (mutually assignable, w/ slight affordances to get rid of ZodObject.passthrough() index signatures, etc) - * - Runtime checks to verify all Spec types have a static check - * (a few don't have SDK types, see TODOs in this file) + * - Runtime checks to verify each Spec type has a static check + * (note: a few don't have SDK types, see TODOs in this file) */ import * as SDKTypes from "./types.js"; import * as SpecTypes from "./spec.types.js"; @@ -685,6 +685,7 @@ function checkServerNotification( // spec = sdk; // } +// This file is .gitignore'd, and fetched by `npm run fetch:spec-types` (called by `npm run test`) const SPEC_TYPES_FILE = 'src/spec.types.ts'; const THIS_SOURCE_FILE = 'src/spec.types.test.ts'; From a19175a26e7f929239766522ef27b2c48e9334b5 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Thu, 3 Jul 2025 20:44:58 +0100 Subject: [PATCH 08/14] Update spec.types.test.ts --- src/spec.types.test.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index 7ae1f2b8..be8891ca 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -7,6 +7,7 @@ */ import * as SDKTypes from "./types.js"; import * as SpecTypes from "./spec.types.js"; +import fs from "node:fs"; /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unsafe-function-type */ @@ -690,18 +691,18 @@ const SPEC_TYPES_FILE = 'src/spec.types.ts'; const THIS_SOURCE_FILE = 'src/spec.types.test.ts'; describe('Spec Types', () => { - const specTypesContent = require('fs').readFileSync(SPEC_TYPES_FILE, 'utf-8'); - const typeNames = [...specTypesContent.matchAll(/export\s+interface\s+(\w+)\b/g)].map(m => m[1]); - const testContent = require('fs').readFileSync(THIS_SOURCE_FILE, 'utf-8'); + const specTypesContent = fs.readFileSync(SPEC_TYPES_FILE, 'utf-8'); + const typeNames = [...specTypesContent.matchAll(/export\s+interface\s+(\w+)\b/g)].map(m => m[1]); + const testContent = fs.readFileSync(THIS_SOURCE_FILE, 'utf-8'); - it('should define some expected types', () => { - expect(typeNames).toContain('JSONRPCNotification'); - expect(typeNames).toContain('ElicitResult'); - }); + it('should define some expected types', () => { + expect(typeNames).toContain('JSONRPCNotification'); + expect(typeNames).toContain('ElicitResult'); + }); - for (const typeName of typeNames) { - it(`${typeName} should have a compatibility test`, () => { - expect(testContent).toContain(`function check${typeName}(`); - }); - } + for (const typeName of typeNames) { + it(`${typeName} should have a compatibility test`, () => { + expect(testContent).toContain(`function check${typeName}(`); + }); + } }); \ No newline at end of file From 2fc1581dfb0dbf99d6ad7ce0aae3f70ee302dc12 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Fri, 4 Jul 2025 10:28:25 +0100 Subject: [PATCH 09/14] widen type extraction regex, introduce + test MISSING_SDK_TYPES, check LoggingLevel --- src/spec.types.test.ts | 74 ++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index be8891ca..105ab906 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -658,51 +658,55 @@ function checkServerNotification( sdk = spec; spec = sdk; } +function checkLoggingLevel( + sdk: SDKTypes.LoggingLevel, + spec: SpecTypes.LoggingLevel +) { + sdk = spec; + spec = sdk; +} -// TODO(bug): missing type in SDK. This dead code is checked by the test suite below. -// function checkModelHint( -// RemovePassthrough< sdk: SDKTypes.ModelHint>, -// spec: SpecTypes.ModelHint -// ) { -// sdk = spec; -// spec = sdk; -// } +// This file is .gitignore'd, and fetched by `npm run fetch:spec-types` (called by `npm run test`) +const SPEC_TYPES_FILE = 'src/spec.types.ts'; +const SDK_TYPES_FILE = 'src/types.ts'; -// TODO(bug): missing type in SDK. This dead code is checked by the test suite below. -// function checkModelPreferences( -// RemovePassthrough< sdk: SDKTypes.ModelPreferences>, -// spec: SpecTypes.ModelPreferences -// ) { -// sdk = spec; -// spec = sdk; -// } +const MISSING_SDK_TYPES = [ + // These are inlined in the SDK: + 'Role', -// TODO(bug): missing type in SDK. This dead code is checked by the test suite below. -// function checkAnnotations( -// RemovePassthrough< sdk: SDKTypes.Annotations>, -// spec: SpecTypes.Annotations -// ) { -// sdk = spec; -// spec = sdk; -// } + // These aren't supported by the SDK yet: + // TODO: Add definitions to the SDK + 'ModelHint', + 'ModelPreferences', + 'Annotations', +] -// This file is .gitignore'd, and fetched by `npm run fetch:spec-types` (called by `npm run test`) -const SPEC_TYPES_FILE = 'src/spec.types.ts'; -const THIS_SOURCE_FILE = 'src/spec.types.test.ts'; +function extractExportedTypes(source: string): string[] { + return [...source.matchAll(/export\s+(?:interface|class|type)\s+(\w+)\b/g)].map(m => m[1]); +} describe('Spec Types', () => { - const specTypesContent = fs.readFileSync(SPEC_TYPES_FILE, 'utf-8'); - const typeNames = [...specTypesContent.matchAll(/export\s+interface\s+(\w+)\b/g)].map(m => m[1]); - const testContent = fs.readFileSync(THIS_SOURCE_FILE, 'utf-8'); + const specTypes = extractExportedTypes(fs.readFileSync(SPEC_TYPES_FILE, 'utf-8')); + const sdkTypes = extractExportedTypes(fs.readFileSync(SDK_TYPES_FILE, 'utf-8')); + const testSource = fs.readFileSync(__filename, 'utf-8'); it('should define some expected types', () => { - expect(typeNames).toContain('JSONRPCNotification'); - expect(typeNames).toContain('ElicitResult'); + expect(specTypes).toContain('JSONRPCNotification'); + expect(specTypes).toContain('ElicitResult'); + }); + + it('should have up to date list of missing sdk types', () => { + for (const typeName of MISSING_SDK_TYPES) { + expect(sdkTypes).not.toContain(typeName); + } }); - for (const typeName of typeNames) { - it(`${typeName} should have a compatibility test`, () => { - expect(testContent).toContain(`function check${typeName}(`); + for (const type of specTypes) { + if (MISSING_SDK_TYPES.includes(type)) { + continue; // Skip missing SDK types + } + it(`${type} should have a compatibility test`, () => { + expect(testSource).toContain(`function check${type}(`); }); } }); \ No newline at end of file From 3ae5522193c88ffa3fe265da8c68260da321d9d5 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Fri, 4 Jul 2025 10:30:36 +0100 Subject: [PATCH 10/14] Update spec.types.test.ts --- src/spec.types.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index 105ab906..ffeffeed 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -3,7 +3,7 @@ * - Static type checks to verify the Spec's types are compatible with the SDK's types * (mutually assignable, w/ slight affordances to get rid of ZodObject.passthrough() index signatures, etc) * - Runtime checks to verify each Spec type has a static check - * (note: a few don't have SDK types, see TODOs in this file) + * (note: a few don't have SDK types, see MISSING_SDK_TYPES below) */ import * as SDKTypes from "./types.js"; import * as SpecTypes from "./spec.types.js"; From b6e00f9a492d84a9b6915e998b6309e61c9aa0cd Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Fri, 4 Jul 2025 10:33:14 +0100 Subject: [PATCH 11/14] Update spec.types.test.ts --- src/spec.types.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index ffeffeed..eefc80b5 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -676,9 +676,9 @@ const MISSING_SDK_TYPES = [ // These aren't supported by the SDK yet: // TODO: Add definitions to the SDK + 'Annotations', 'ModelHint', 'ModelPreferences', - 'Annotations', ] function extractExportedTypes(source: string): string[] { @@ -693,6 +693,7 @@ describe('Spec Types', () => { it('should define some expected types', () => { expect(specTypes).toContain('JSONRPCNotification'); expect(specTypes).toContain('ElicitResult'); + expect(specTypes).toHaveLength(91); }); it('should have up to date list of missing sdk types', () => { @@ -709,4 +710,4 @@ describe('Spec Types', () => { expect(testSource).toContain(`function check${type}(`); }); } -}); \ No newline at end of file +}); From a7e4289f3eccfdc3f62a221073228da5c0150bf4 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Fri, 4 Jul 2025 10:34:50 +0100 Subject: [PATCH 12/14] Update spec.types.test.ts --- src/spec.types.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index eefc80b5..87514cb0 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -11,7 +11,6 @@ import fs from "node:fs"; /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unsafe-function-type */ -/* eslint-disable @typescript-eslint/no-require-imports */ // Removes index signatures added by ZodObject.passthrough(). type RemovePassthrough = T extends object From 98a04e6e38a8762c66e3f0c7d997d9656427ac77 Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Mon, 7 Jul 2025 13:22:15 +0100 Subject: [PATCH 13/14] moved src/spec.types.ts to . --- .gitignore | 2 +- package.json | 2 +- src/spec.types.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index b39dd94e..694735b6 100644 --- a/.gitignore +++ b/.gitignore @@ -70,7 +70,7 @@ web_modules/ *.tgz # Output of 'npm run fetch:spec-types' -src/spec.types.ts +spec.types.ts # Yarn Integrity file .yarn-integrity diff --git a/package.json b/package.json index b42b1ea2..bf21340f 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "dist" ], "scripts": { - "fetch:spec-types": "curl -o src/spec.types.ts https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/heads/main/schema/draft/schema.ts", + "fetch:spec-types": "curl -o spec.types.ts https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/refs/heads/main/schema/draft/schema.ts", "build": "npm run build:esm && npm run build:cjs", "build:esm": "mkdir -p dist/esm && echo '{\"type\": \"module\"}' > dist/esm/package.json && tsc -p tsconfig.prod.json", "build:esm:w": "npm run build:esm -- -w", diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index 87514cb0..796aab1c 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -6,7 +6,7 @@ * (note: a few don't have SDK types, see MISSING_SDK_TYPES below) */ import * as SDKTypes from "./types.js"; -import * as SpecTypes from "./spec.types.js"; +import * as SpecTypes from "../spec.types.js"; import fs from "node:fs"; /* eslint-disable @typescript-eslint/no-unused-vars */ @@ -666,7 +666,7 @@ function checkLoggingLevel( } // This file is .gitignore'd, and fetched by `npm run fetch:spec-types` (called by `npm run test`) -const SPEC_TYPES_FILE = 'src/spec.types.ts'; +const SPEC_TYPES_FILE = 'spec.types.ts'; const SDK_TYPES_FILE = 'src/types.ts'; const MISSING_SDK_TYPES = [ From 96a4f714e56be1ab5ba7fd985a3e6c5a0414ce0f Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Mon, 7 Jul 2025 13:22:41 +0100 Subject: [PATCH 14/14] removed checkResourceReference (sdk's ResourceReference is deprecated / aliased to ResourceTemplateReference) --- src/spec.types.test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/spec.types.test.ts b/src/spec.types.test.ts index 796aab1c..09cd6c2d 100644 --- a/src/spec.types.test.ts +++ b/src/spec.types.test.ts @@ -258,13 +258,6 @@ function checkPromptReference( sdk = spec; spec = sdk; } -function checkResourceReference( - sdk: RemovePassthrough, - spec: SpecTypes.ResourceTemplateReference -) { - sdk = spec; - spec = sdk; -} function checkToolAnnotations( sdk: RemovePassthrough, spec: SpecTypes.ToolAnnotations