@@ -251,6 +251,264 @@ describe("Simplified Tool Adapter Tests", () => {
251251 expect ( tools [ 1 ] . name ) . toBe ( "tool2" ) ;
252252 } ) ;
253253
254+ test ( "should handle JSON schemas with $defs references (Pydantic v2 style)" , async ( ) => {
255+ // This schema is similar to what Pydantic v2 generates with nested models
256+ const pydanticV2Schema = {
257+ type : "object" as const ,
258+ properties : {
259+ items : {
260+ type : "array" ,
261+ items : {
262+ $ref : "#/$defs/DataItem" ,
263+ } ,
264+ description : "List of items" ,
265+ } ,
266+ metadata : {
267+ $ref : "#/$defs/Metadata" ,
268+ description : "Response metadata" ,
269+ } ,
270+ } ,
271+ required : [ "items" , "metadata" ] ,
272+ $defs : {
273+ DataItem : {
274+ type : "object" ,
275+ properties : {
276+ id : { type : "string" , description : "Item ID" } ,
277+ name : { type : "string" , description : "Item name" } ,
278+ value : { type : "number" , description : "Item value" } ,
279+ } ,
280+ required : [ "id" , "name" , "value" ] ,
281+ } ,
282+ Metadata : {
283+ type : "object" ,
284+ properties : {
285+ total_count : { type : "integer" , description : "Total count" } ,
286+ timestamp : { type : "string" , description : "Timestamp" } ,
287+ } ,
288+ required : [ "total_count" , "timestamp" ] ,
289+ } ,
290+ } ,
291+ } ;
292+
293+ mockClient . listTools . mockReturnValueOnce (
294+ Promise . resolve ( {
295+ tools : [
296+ {
297+ name : "query_data" ,
298+ description : "Query tool that returns nested response" ,
299+ inputSchema : pydanticV2Schema ,
300+ } ,
301+ ] ,
302+ } )
303+ ) ;
304+
305+ mockClient . callTool . mockImplementation ( ( params ) => {
306+ const args = params . arguments as {
307+ items : Array < { id : string ; name : string ; value : number } > ;
308+ metadata : { total_count : number ; timestamp : string } ;
309+ } ;
310+ return Promise . resolve ( {
311+ content : [
312+ {
313+ type : "text" ,
314+ text : `Received ${ args . items . length } items with total_count=${ args . metadata . total_count } ` ,
315+ } ,
316+ ] ,
317+ } ) ;
318+ } ) ;
319+
320+ // Load tools - this should not throw even though schema has $defs
321+ const tools = await loadMcpTools (
322+ "mockServer(should handle $defs)" ,
323+ mockClient as Client
324+ ) ;
325+
326+ expect ( tools . length ) . toBe ( 1 ) ;
327+ expect ( tools [ 0 ] . name ) . toBe ( "query_data" ) ;
328+
329+ // Invoke the tool with valid input matching the dereferenced schema
330+ const result = await tools [ 0 ] . invoke ( {
331+ items : [ { id : "1" , name : "Test" , value : 100.0 } ] ,
332+ metadata : { total_count : 1 , timestamp : "2024-01-01" } ,
333+ } ) ;
334+
335+ expect ( result ) . toBe ( "Received 1 items with total_count=1" ) ;
336+ expect ( mockClient . callTool ) . toHaveBeenCalledWith ( {
337+ name : "query_data" ,
338+ arguments : {
339+ items : [ { id : "1" , name : "Test" , value : 100.0 } ] ,
340+ metadata : { total_count : 1 , timestamp : "2024-01-01" } ,
341+ } ,
342+ } ) ;
343+ } ) ;
344+
345+ test ( "should handle JSON schemas with definitions (older JSON Schema style)" , async ( ) => {
346+ // Some tools use 'definitions' instead of '$defs'
347+ const schemaWithDefinitions = {
348+ type : "object" as const ,
349+ properties : {
350+ user : {
351+ $ref : "#/definitions/User" ,
352+ } ,
353+ } ,
354+ required : [ "user" ] ,
355+ definitions : {
356+ User : {
357+ type : "object" ,
358+ properties : {
359+ name : { type : "string" } ,
360+ email : { type : "string" } ,
361+ } ,
362+ required : [ "name" , "email" ] ,
363+ } ,
364+ } ,
365+ } ;
366+
367+ mockClient . listTools . mockReturnValueOnce (
368+ Promise . resolve ( {
369+ tools : [
370+ {
371+ name : "create_user" ,
372+ description : "Create a user" ,
373+ inputSchema : schemaWithDefinitions ,
374+ } ,
375+ ] ,
376+ } )
377+ ) ;
378+
379+ mockClient . callTool . mockImplementation ( ( params ) => {
380+ const args = params . arguments as {
381+ user : { name : string ; email : string } ;
382+ } ;
383+ return Promise . resolve ( {
384+ content : [
385+ {
386+ type : "text" ,
387+ text : `Created user: ${ args . user . name } ` ,
388+ } ,
389+ ] ,
390+ } ) ;
391+ } ) ;
392+
393+ const tools = await loadMcpTools (
394+ "mockServer(should handle definitions)" ,
395+ mockClient as Client
396+ ) ;
397+
398+ expect ( tools . length ) . toBe ( 1 ) ;
399+
400+ const result = await tools [ 0 ] . invoke ( {
401+ user : { name : "John" , email : "john@example.com" } ,
402+ } ) ;
403+
404+ expect ( result ) . toBe ( "Created user: John" ) ;
405+ } ) ;
406+
407+ test ( "should handle deeply nested $ref references" , async ( ) => {
408+ const deeplyNestedSchema = {
409+ type : "object" as const ,
410+ properties : {
411+ order : {
412+ $ref : "#/$defs/Order" ,
413+ } ,
414+ } ,
415+ required : [ "order" ] ,
416+ $defs : {
417+ Order : {
418+ type : "object" ,
419+ properties : {
420+ id : { type : "string" } ,
421+ customer : {
422+ $ref : "#/$defs/Customer" ,
423+ } ,
424+ items : {
425+ type : "array" ,
426+ items : {
427+ $ref : "#/$defs/OrderItem" ,
428+ } ,
429+ } ,
430+ } ,
431+ required : [ "id" , "customer" , "items" ] ,
432+ } ,
433+ Customer : {
434+ type : "object" ,
435+ properties : {
436+ name : { type : "string" } ,
437+ address : {
438+ $ref : "#/$defs/Address" ,
439+ } ,
440+ } ,
441+ required : [ "name" , "address" ] ,
442+ } ,
443+ Address : {
444+ type : "object" ,
445+ properties : {
446+ street : { type : "string" } ,
447+ city : { type : "string" } ,
448+ } ,
449+ required : [ "street" , "city" ] ,
450+ } ,
451+ OrderItem : {
452+ type : "object" ,
453+ properties : {
454+ product : { type : "string" } ,
455+ quantity : { type : "integer" } ,
456+ } ,
457+ required : [ "product" , "quantity" ] ,
458+ } ,
459+ } ,
460+ } ;
461+
462+ mockClient . listTools . mockReturnValueOnce (
463+ Promise . resolve ( {
464+ tools : [
465+ {
466+ name : "create_order" ,
467+ description : "Create an order" ,
468+ inputSchema : deeplyNestedSchema ,
469+ } ,
470+ ] ,
471+ } )
472+ ) ;
473+
474+ mockClient . callTool . mockImplementation ( ( ) => {
475+ return Promise . resolve ( {
476+ content : [
477+ {
478+ type : "text" ,
479+ text : "Order created successfully" ,
480+ } ,
481+ ] ,
482+ } ) ;
483+ } ) ;
484+
485+ const tools = await loadMcpTools (
486+ "mockServer(should handle deeply nested refs)" ,
487+ mockClient as Client
488+ ) ;
489+
490+ expect ( tools . length ) . toBe ( 1 ) ;
491+
492+ const result = await tools [ 0 ] . invoke ( {
493+ order : {
494+ id : "order-123" ,
495+ customer : {
496+ name : "Jane Doe" ,
497+ address : {
498+ street : "123 Main St" ,
499+ city : "Springfield" ,
500+ } ,
501+ } ,
502+ items : [
503+ { product : "Widget" , quantity : 2 } ,
504+ { product : "Gadget" , quantity : 1 } ,
505+ ] ,
506+ } ,
507+ } ) ;
508+
509+ expect ( result ) . toBe ( "Order created successfully" ) ;
510+ } ) ;
511+
254512 test ( "should load tools with specified response format" , async ( ) => {
255513 // Set up mock response with input schema
256514 mockClient . listTools . mockReturnValueOnce (
0 commit comments