@@ -170,11 +170,14 @@ function preProcessTypes(content: string): string {
170170 // (types.ts will define these locally with proper schema unions)
171171 inlineJSONRPCResponse ( sourceFile ) ;
172172
173- // Transform 6: Convert JSDoc comments to @description tags for .describe() generation
173+ // Transform 6: Inject SDK-specific extensions to spec types
174+ injectSdkExtensions ( sourceFile ) ;
175+
176+ // Transform 7: Convert JSDoc comments to @description tags for .describe() generation
174177 // (Must run before injectDerivedCapabilityTypes so inline types get @description)
175178 convertJsDocToDescription ( sourceFile ) ;
176179
177- // Transform 7 : Add derived capability types (extracts from parent interfaces)
180+ // Transform 8 : Add derived capability types (extracts from parent interfaces)
178181 injectDerivedCapabilityTypes ( sourceFile ) ;
179182
180183 return sourceFile . getFullText ( ) ;
@@ -475,6 +478,34 @@ function inlineJSONRPCResponse(sourceFile: SourceFile): void {
475478 }
476479}
477480
481+ /**
482+ * Inject SDK-specific extensions to spec types.
483+ * These are fields/types that the SDK adds beyond what the spec defines.
484+ */
485+ function injectSdkExtensions ( sourceFile : SourceFile ) : void {
486+ // Add applyDefaults to ClientCapabilities.elicitation.form
487+ // The SDK allows clients to request that servers apply schema defaults to elicitation responses
488+ const clientCaps = sourceFile . getInterface ( 'ClientCapabilities' ) ;
489+ if ( clientCaps ) {
490+ const elicitationProp = clientCaps . getProperty ( 'elicitation' ) ;
491+ if ( elicitationProp ) {
492+ const typeNode = elicitationProp . getTypeNode ( ) ;
493+ if ( typeNode ) {
494+ const originalType = typeNode . getText ( ) ;
495+ // Replace { form?: object; url?: object } with extended form type
496+ if ( originalType . includes ( 'form?: object' ) ) {
497+ const newType = originalType . replace (
498+ 'form?: object' ,
499+ 'form?: { applyDefaults?: boolean; [key: string]: unknown }'
500+ ) ;
501+ typeNode . replaceWithText ( newType ) ;
502+ console . log ( ' ✓ Added applyDefaults to ClientCapabilities.elicitation.form' ) ;
503+ }
504+ }
505+ }
506+ }
507+ }
508+
478509/**
479510 * Add derived capability types by extracting nested properties from parent interfaces.
480511 * This creates concrete interface definitions that ts-to-zod can generate schemas for.
@@ -1086,16 +1117,65 @@ function addElicitationPreprocess(sourceFile: SourceFile): void {
10861117 let text = initializer . getText ( ) ;
10871118
10881119 // Find the elicitation field and wrap with preprocess
1089- // Pattern: elicitation: z.object({ form: ..., url: ... }).optional().describe(...)
1090- // We need to capture everything up to and including the object's closing paren, then handle the trailing .optional().describe() separately
1091- const elicitationPattern = / e l i c i t a t i o n : \s * ( z \s * \. \s * o b j e c t \( \s * \{ [ ^ } ] * f o r m : [ ^ } ] * u r l : [ ^ } ] * \} \s * \) ) \s * \. o p t i o n a l \( \) ( \s * \. d e s c r i b e \( [ ^ ) ] * \) ) ? / ;
1092-
1093- const match = text . match ( elicitationPattern ) ;
1094- if ( match ) {
1095- const innerSchema = match [ 1 ] ; // z.object({...}) without .optional()
1096- const describeCall = match [ 2 ] || '' ; // .describe(...) if present
1097- const replacement = `elicitation: z.preprocess(
1098- (value) => {
1120+ // Handle both z.object and z.looseObject patterns
1121+ // The inner schema structure may be complex (nested objects, etc.), so we use brace counting
1122+ const elicitationStart = text . indexOf ( 'elicitation:' ) ;
1123+ if ( elicitationStart === - 1 ) return ;
1124+
1125+ // Find the schema after 'elicitation:'
1126+ const afterElicitation = text . substring ( elicitationStart + 'elicitation:' . length ) ;
1127+
1128+ // Find where z.object or z.looseObject starts
1129+ const objectMatch = afterElicitation . match ( / ^ \s * ( z \s * \. \s * (?: l o o s e O b j e c t | o b j e c t ) \s * \( ) / ) ;
1130+ if ( ! objectMatch ) return ;
1131+
1132+ const schemaStart = elicitationStart + 'elicitation:' . length + objectMatch . index ! ;
1133+
1134+ // Count braces/parens to find the end of the schema (before .optional())
1135+ let depth = 0 ;
1136+ let inString = false ;
1137+ let stringChar = '' ;
1138+ let schemaEnd = schemaStart ;
1139+ const startPos = schemaStart ;
1140+
1141+ for ( let i = startPos ; i < text . length ; i ++ ) {
1142+ const char = text [ i ] ;
1143+ const prevChar = i > 0 ? text [ i - 1 ] : '' ;
1144+
1145+ if ( inString ) {
1146+ if ( char === stringChar && prevChar !== '\\' ) {
1147+ inString = false ;
1148+ }
1149+ } else {
1150+ if ( char === '"' || char === "'" ) {
1151+ inString = true ;
1152+ stringChar = char ;
1153+ } else if ( char === '(' || char === '{' || char === '[' ) {
1154+ depth ++ ;
1155+ } else if ( char === ')' || char === '}' || char === ']' ) {
1156+ depth -- ;
1157+ if ( depth === 0 ) {
1158+ schemaEnd = i + 1 ;
1159+ break ;
1160+ }
1161+ }
1162+ }
1163+ }
1164+
1165+ // Extract the inner schema (z.looseObject({...}) or z.object({...}))
1166+ const innerSchema = text . substring ( schemaStart , schemaEnd ) . trim ( ) ;
1167+
1168+ // Find what follows the schema (.optional().describe(...) etc)
1169+ const afterSchema = text . substring ( schemaEnd ) ;
1170+ const optionalMatch = afterSchema . match ( / ^ \s * \. o p t i o n a l \( \) ( \s * \. d e s c r i b e \( [ ^ ) ] * \) ) ? / ) ;
1171+ if ( ! optionalMatch ) return ;
1172+
1173+ const describeCall = optionalMatch [ 1 ] || '' ;
1174+ const fullMatchEnd = schemaEnd + optionalMatch [ 0 ] . length ;
1175+
1176+ // Build the replacement with preprocess
1177+ const replacement = `elicitation: z.preprocess(
1178+ value => {
10991179 if (value && typeof value === 'object' && !Array.isArray(value)) {
11001180 if (Object.keys(value as Record<string, unknown>).length === 0) {
11011181 return { form: {} };
@@ -1106,10 +1186,9 @@ function addElicitationPreprocess(sourceFile: SourceFile): void {
11061186 ${ innerSchema } ${ describeCall }
11071187 ).optional()` ;
11081188
1109- text = text . replace ( elicitationPattern , replacement ) ;
1110- varDecl . setInitializer ( text ) ;
1111- console . log ( ' ✓ Added z.preprocess to ClientCapabilitiesSchema.elicitation' ) ;
1112- }
1189+ text = text . substring ( 0 , elicitationStart ) + replacement + text . substring ( fullMatchEnd ) ;
1190+ varDecl . setInitializer ( text ) ;
1191+ console . log ( ' ✓ Added z.preprocess to ClientCapabilitiesSchema.elicitation' ) ;
11131192}
11141193
11151194// =============================================================================
0 commit comments