@@ -171,47 +171,39 @@ function preProcessTypes(content: string): string {
171171}
172172
173173/**
174- * Interfaces that should have their standalone index signatures removed.
175- * These are extensible types in the spec, but index signatures break TypeScript union narrowing.
176- * The schemas use .passthrough() for runtime extensibility, so types don't need index sigs.
177- */
178- const REMOVE_INDEX_SIGNATURE_INTERFACES = [
179- 'Result' ,
180- 'RequestParams' ,
181- 'NotificationParams' ,
182- // GetTaskPayloadResult intentionally keeps index signature - it's the payload container
183- ] ;
184-
185- /**
186- * Remove standalone index signatures from interfaces to enable TypeScript union narrowing.
174+ * Remove standalone index signatures from interface bodies in sdk.types.ts.
187175 *
188- * The MCP spec defines extensible types like `Result = { _meta?: {...}; [key: string]: unknown }`.
189- * Index signatures break TypeScript's ability to narrow unions (can't use `'id' in obj`).
176+ * The MCP spec uses index signatures for extensibility, but they break TypeScript union narrowing.
177+ * We only remove STANDALONE index signatures from interface bodies, NOT:
178+ * - Intersection patterns in property types (needed for params extensibility)
179+ * - Index signatures inside nested objects like _meta
190180 *
191- * We remove them from the generated types because:
192- * - Schemas use .passthrough() for runtime extensibility (accepts extra properties)
193- * - Types without index signatures allow proper TypeScript narrowing
194- * - The _meta field still has `{ [key: string]: unknown }` for its own extensibility
181+ * Example of what we remove:
182+ * interface Result {
183+ * _meta?: {...};
184+ * [key: string]: unknown; // <-- This is removed
185+ * }
186+ *
187+ * Example of what we keep:
188+ * interface Request {
189+ * params?: RequestParams & { [key: string]: any }; // <-- Kept (allows extra params)
190+ * }
195191 */
196- function removeStandaloneIndexSignatures ( content : string ) : string {
197- const project = new Project ( { useInMemoryFileSystem : true } ) ;
198- const sourceFile = project . createSourceFile ( 'types.ts' , content ) ;
199-
192+ function removeIndexSignaturesFromTypes ( content : string ) : string {
200193 console . log ( ' 🔧 Cleaning up index signatures for type exports...' ) ;
201194
202- for ( const ifaceName of REMOVE_INDEX_SIGNATURE_INTERFACES ) {
203- const iface = sourceFile . getInterface ( ifaceName ) ;
204- if ( ! iface ) continue ;
195+ let result = content ;
196+ let count = 0 ;
205197
206- // Find and remove index signatures from the interface body
207- const indexSigs = iface . getIndexSignatures ( ) ;
208- for ( const sig of indexSigs ) {
209- sig . remove ( ) ;
210- console . log ( ` ✓ Removed index signature from ${ ifaceName } ` ) ;
211- }
212- }
198+ // Only remove standalone index signatures from interface bodies
199+ // These are lines that ONLY contain an index signature (with optional leading whitespace)
200+ // Pattern matches: ` [key: string]: unknown;\n`
201+ const standalonePattern = / ^ ( \s * ) \[ k e y : \s * s t r i n g \] : \s * u n k n o w n ; \s * \n / gm;
202+ result = result . replace ( standalonePattern , ( ) => { count ++ ; return '' ; } ) ;
213203
214- return sourceFile . getFullText ( ) ;
204+ console . log ( ` ✓ Removed ${ count } standalone index signatures` ) ;
205+
206+ return result ;
215207}
216208
217209/**
@@ -987,8 +979,9 @@ async function main() {
987979 const rawSourceText = readFileSync ( SPEC_TYPES_FILE , 'utf-8' ) ;
988980 const sdkTypesContent = preProcessTypes ( rawSourceText ) ;
989981
990- // Clean up types for SDK export - remove index signatures that break union narrowing
991- const cleanedTypesContent = removeStandaloneIndexSignatures ( sdkTypesContent ) ;
982+ // Clean up types for SDK export - remove ALL index signature patterns
983+ // This enables TypeScript union narrowing while schemas handle runtime extensibility
984+ const cleanedTypesContent = removeIndexSignaturesFromTypes ( sdkTypesContent ) ;
992985
993986 // Write pre-processed types to sdk.types.ts
994987 const sdkTypesWithHeader = `/**
@@ -1000,10 +993,11 @@ async function main() {
1000993 * Transformations applied:
1001994 * - \`extends JSONRPCRequest\` → \`extends Request\`
1002995 * - \`extends JSONRPCNotification\` → \`extends Notification\`
1003- * - Standalone index signatures removed (enables TypeScript union narrowing)
996+ * - All index signature patterns removed (enables TypeScript union narrowing)
1004997 *
1005- * This allows SDK types to omit jsonrpc/id fields, which are
1006- * handled at the transport layer.
998+ * Note: Schemas use .passthrough() for runtime extensibility, so types
999+ * don't need index signatures. This separation allows clean types for
1000+ * TypeScript while maintaining runtime flexibility.
10071001 */
10081002${ cleanedTypesContent . replace ( / ^ \/ \* \* [ \s \S ] * ?\* \/ \n / , '' ) } `;
10091003 writeFileSync ( SDK_TYPES_FILE , sdkTypesWithHeader , 'utf-8' ) ;
@@ -1100,6 +1094,7 @@ function postProcessTests(content: string): string {
11001094 'CallToolResult' ,
11011095 'GetPromptResult' ,
11021096 'CreateMessageResult' ,
1097+ 'GetTaskPayloadResult' , // Has explicit Record<string, unknown> extension
11031098 // Request/Notification based schemas also use passthrough
11041099 'Request' ,
11051100 'Notification' ,
0 commit comments