@@ -46,13 +46,17 @@ const GEMINI_SCHEMA_COMPATIBILITY_DROP_KEYS = new Set([
4646 "definitions" ,
4747] )
4848
49- function sanitizeSchemaForGemini ( schema : unknown , defs ?: Record < string , unknown > ) : unknown {
49+ function sanitizeSchemaForGemini (
50+ schema : unknown ,
51+ defs ?: Record < string , unknown > ,
52+ activeRefs : Set < string > = new Set ( ) ,
53+ ) : unknown {
5054 if ( ! schema || typeof schema !== "object" ) {
5155 return schema
5256 }
5357
5458 if ( Array . isArray ( schema ) ) {
55- return schema . map ( ( item ) => sanitizeSchemaForGemini ( item , defs ) )
59+ return schema . map ( ( item ) => sanitizeSchemaForGemini ( item , defs , activeRefs ) )
5660 }
5761
5862 const source = schema as Record < string , unknown >
@@ -68,7 +72,19 @@ function sanitizeSchemaForGemini(schema: unknown, defs?: Record<string, unknown>
6872 if ( match ) {
6973 const resolved = resolvedDefs [ match [ 1 ] ]
7074 if ( resolved !== undefined ) {
71- return sanitizeSchemaForGemini ( resolved , resolvedDefs )
75+ // Recursive MCP schemas are valid JSON Schema but not something Gemini
76+ // can consume directly. Stop at the recursive edge so we still send a
77+ // finite, serializable schema instead of overflowing the stack.
78+ if ( activeRefs . has ( match [ 1 ] ) ) {
79+ return { }
80+ }
81+
82+ activeRefs . add ( match [ 1 ] )
83+ try {
84+ return sanitizeSchemaForGemini ( resolved , resolvedDefs , activeRefs )
85+ } finally {
86+ activeRefs . delete ( match [ 1 ] )
87+ }
7288 }
7389 }
7490 }
@@ -84,12 +100,12 @@ function sanitizeSchemaForGemini(schema: unknown, defs?: Record<string, unknown>
84100 : true
85101 } )
86102 nullable = nullable || variants . length < composition . length
87- Object . assign ( result , sanitizeSchemaForGemini ( variants [ 0 ] ?? { } , resolvedDefs ) )
103+ Object . assign ( result , sanitizeSchemaForGemini ( variants [ 0 ] ?? { } , resolvedDefs , activeRefs ) )
88104 }
89105
90106 if ( Array . isArray ( source . allOf ) ) {
91107 for ( const variant of source . allOf ) {
92- const sanitized = sanitizeSchemaForGemini ( variant , resolvedDefs )
108+ const sanitized = sanitizeSchemaForGemini ( variant , resolvedDefs , activeRefs )
93109 if ( sanitized && typeof sanitized === "object" && ! Array . isArray ( sanitized ) ) {
94110 const s = sanitized as Record < string , unknown >
95111 // Deep-merge properties so later allOf fragments don't overwrite
@@ -116,7 +132,7 @@ function sanitizeSchemaForGemini(schema: unknown, defs?: Record<string, unknown>
116132 }
117133
118134 if ( key === "properties" && value && typeof value === "object" && ! Array . isArray ( value ) ) {
119- const sanitizedProperties = sanitizeSchemaForGemini ( value , resolvedDefs )
135+ const sanitizedProperties = sanitizeSchemaForGemini ( value , resolvedDefs , activeRefs )
120136 if ( sanitizedProperties && typeof sanitizedProperties === "object" && ! Array . isArray ( sanitizedProperties ) ) {
121137 result . properties = {
122138 ...( result . properties as Record < string , unknown > | undefined ) ,
@@ -143,7 +159,7 @@ function sanitizeSchemaForGemini(schema: unknown, defs?: Record<string, unknown>
143159 continue
144160 }
145161
146- result [ key ] = sanitizeSchemaForGemini ( value , resolvedDefs )
162+ result [ key ] = sanitizeSchemaForGemini ( value , resolvedDefs , activeRefs )
147163 }
148164
149165 if ( nullable ) {
0 commit comments