@@ -1485,4 +1485,334 @@ describe('convertJsonSchemaToZod', () => {
1485
1485
expect ( resolvedKeywords . additionalProperties ) . toBe ( false ) ;
1486
1486
} ) ;
1487
1487
} ) ;
1488
+
1489
+ describe ( 'Bare object schema handling for dynamic properties' , ( ) => {
1490
+ it ( 'should handle object type without explicit properties but expecting dynamic field definitions' , ( ) => {
1491
+ // This simulates the Kintone add_fields tool schema
1492
+ const schema : JsonSchemaType = {
1493
+ type : 'object' ,
1494
+ properties : {
1495
+ app_id : {
1496
+ type : 'number' ,
1497
+ description : 'アプリID' ,
1498
+ } ,
1499
+ properties : {
1500
+ type : 'object' ,
1501
+ description : 'フィールドの設定(各フィールドには code, type, label の指定が必須)' ,
1502
+ } ,
1503
+ } ,
1504
+ required : [ 'app_id' , 'properties' ] ,
1505
+ } ;
1506
+
1507
+ const zodSchema = convertWithResolvedRefs ( schema ) ;
1508
+
1509
+ // Test case 1: Basic field definition
1510
+ const testData1 = {
1511
+ app_id : 810 ,
1512
+ properties : {
1513
+ minutes_id : {
1514
+ code : 'minutes_id' ,
1515
+ type : 'SINGLE_LINE_TEXT' ,
1516
+ label : 'minutes_id' ,
1517
+ } ,
1518
+ } ,
1519
+ } ;
1520
+
1521
+ // WITH THE FIX: Bare object schemas now act as passthrough
1522
+ const result = zodSchema ?. parse ( testData1 ) ;
1523
+ expect ( result ) . toEqual ( testData1 ) ; // Properties pass through!
1524
+ } ) ;
1525
+
1526
+ it ( 'should work when properties field has additionalProperties true' , ( ) => {
1527
+ const schema : JsonSchemaType = {
1528
+ type : 'object' ,
1529
+ properties : {
1530
+ app_id : {
1531
+ type : 'number' ,
1532
+ description : 'アプリID' ,
1533
+ } ,
1534
+ properties : {
1535
+ type : 'object' ,
1536
+ description : 'フィールドの設定(各フィールドには code, type, label の指定が必須)' ,
1537
+ additionalProperties : true ,
1538
+ } ,
1539
+ } ,
1540
+ required : [ 'app_id' , 'properties' ] ,
1541
+ } ;
1542
+
1543
+ const zodSchema = convertWithResolvedRefs ( schema ) ;
1544
+
1545
+ const testData = {
1546
+ app_id : 810 ,
1547
+ properties : {
1548
+ minutes_id : {
1549
+ code : 'minutes_id' ,
1550
+ type : 'SINGLE_LINE_TEXT' ,
1551
+ label : 'minutes_id' ,
1552
+ } ,
1553
+ } ,
1554
+ } ;
1555
+
1556
+ const result = zodSchema ?. parse ( testData ) ;
1557
+ expect ( result ) . toEqual ( testData ) ;
1558
+ expect ( result ?. properties ?. minutes_id ) . toBeDefined ( ) ;
1559
+ } ) ;
1560
+
1561
+ it ( 'should work with proper field type definitions in additionalProperties' , ( ) => {
1562
+ const schema : JsonSchemaType = {
1563
+ type : 'object' ,
1564
+ properties : {
1565
+ app_id : {
1566
+ type : 'number' ,
1567
+ description : 'アプリID' ,
1568
+ } ,
1569
+ properties : {
1570
+ type : 'object' ,
1571
+ description : 'フィールドの設定(各フィールドには code, type, label の指定が必須)' ,
1572
+ additionalProperties : {
1573
+ type : 'object' ,
1574
+ properties : {
1575
+ type : { type : 'string' } ,
1576
+ code : { type : 'string' } ,
1577
+ label : { type : 'string' } ,
1578
+ required : { type : 'boolean' } ,
1579
+ options : {
1580
+ type : 'object' ,
1581
+ additionalProperties : {
1582
+ type : 'object' ,
1583
+ properties : {
1584
+ label : { type : 'string' } ,
1585
+ index : { type : 'string' } ,
1586
+ } ,
1587
+ } ,
1588
+ } ,
1589
+ } ,
1590
+ required : [ 'type' , 'code' , 'label' ] ,
1591
+ } ,
1592
+ } ,
1593
+ } ,
1594
+ required : [ 'app_id' , 'properties' ] ,
1595
+ } ;
1596
+
1597
+ const zodSchema = convertWithResolvedRefs ( schema ) ;
1598
+
1599
+ // Test case 1: Simple text field
1600
+ const testData1 = {
1601
+ app_id : 810 ,
1602
+ properties : {
1603
+ minutes_id : {
1604
+ code : 'minutes_id' ,
1605
+ type : 'SINGLE_LINE_TEXT' ,
1606
+ label : 'minutes_id' ,
1607
+ required : false ,
1608
+ } ,
1609
+ } ,
1610
+ } ;
1611
+
1612
+ const result1 = zodSchema ?. parse ( testData1 ) ;
1613
+ expect ( result1 ) . toEqual ( testData1 ) ;
1614
+
1615
+ // Test case 2: Dropdown field with options
1616
+ const testData2 = {
1617
+ app_id : 820 ,
1618
+ properties : {
1619
+ status : {
1620
+ type : 'DROP_DOWN' ,
1621
+ code : 'status' ,
1622
+ label : 'Status' ,
1623
+ options : {
1624
+ 'Not Started' : {
1625
+ label : 'Not Started' ,
1626
+ index : '0' ,
1627
+ } ,
1628
+ 'In Progress' : {
1629
+ label : 'In Progress' ,
1630
+ index : '1' ,
1631
+ } ,
1632
+ } ,
1633
+ } ,
1634
+ } ,
1635
+ } ;
1636
+
1637
+ const result2 = zodSchema ?. parse ( testData2 ) ;
1638
+ expect ( result2 ) . toEqual ( testData2 ) ;
1639
+
1640
+ // Test case 3: Multiple fields
1641
+ const testData3 = {
1642
+ app_id : 123 ,
1643
+ properties : {
1644
+ number_field : {
1645
+ type : 'NUMBER' ,
1646
+ code : 'number_field' ,
1647
+ label : '数値フィールド' ,
1648
+ } ,
1649
+ text_field : {
1650
+ type : 'SINGLE_LINE_TEXT' ,
1651
+ code : 'text_field' ,
1652
+ label : 'テキストフィールド' ,
1653
+ } ,
1654
+ } ,
1655
+ } ;
1656
+
1657
+ const result3 = zodSchema ?. parse ( testData3 ) ;
1658
+ expect ( result3 ) . toEqual ( testData3 ) ;
1659
+ } ) ;
1660
+
1661
+ it ( 'should handle the actual reported failing case' , ( ) => {
1662
+ // This is the exact schema that's failing for the user
1663
+ const schema : JsonSchemaType = {
1664
+ type : 'object' ,
1665
+ properties : {
1666
+ app_id : {
1667
+ type : 'number' ,
1668
+ description : 'アプリID' ,
1669
+ } ,
1670
+ properties : {
1671
+ type : 'object' ,
1672
+ description : 'フィールドの設定(各フィールドには code, type, label の指定が必須)' ,
1673
+ } ,
1674
+ } ,
1675
+ required : [ 'app_id' , 'properties' ] ,
1676
+ } ;
1677
+
1678
+ const zodSchema = convertWithResolvedRefs ( schema ) ;
1679
+
1680
+ // The exact data the user is trying to send
1681
+ const userData = {
1682
+ app_id : 810 ,
1683
+ properties : {
1684
+ minutes_id : {
1685
+ code : 'minutes_id' ,
1686
+ type : 'SINGLE_LINE_TEXT' ,
1687
+ label : 'minutes_id' ,
1688
+ required : false ,
1689
+ } ,
1690
+ } ,
1691
+ } ;
1692
+
1693
+ // WITH THE FIX: The properties now pass through correctly!
1694
+ const result = zodSchema ?. parse ( userData ) ;
1695
+ expect ( result ) . toEqual ( userData ) ;
1696
+
1697
+ // This fixes the error "properties requires at least one field definition"
1698
+ // The MCP server now receives the full properties object
1699
+ } ) ;
1700
+
1701
+ it ( 'should demonstrate fix by treating bare object type as passthrough' , ( ) => {
1702
+ // Test what happens if we modify the conversion to treat bare object types
1703
+ // without properties as passthrough schemas
1704
+ const schema : JsonSchemaType = {
1705
+ type : 'object' ,
1706
+ properties : {
1707
+ app_id : {
1708
+ type : 'number' ,
1709
+ description : 'アプリID' ,
1710
+ } ,
1711
+ properties : {
1712
+ type : 'object' ,
1713
+ description : 'フィールドの設定(各フィールドには code, type, label の指定が必須)' ,
1714
+ } ,
1715
+ } ,
1716
+ required : [ 'app_id' , 'properties' ] ,
1717
+ } ;
1718
+
1719
+ // For now, we'll simulate the fix by adding additionalProperties
1720
+ const fixedSchema : JsonSchemaType = {
1721
+ ...schema ,
1722
+ properties : {
1723
+ ...schema . properties ,
1724
+ properties : {
1725
+ ...( schema . properties ! . properties as JsonSchemaType ) ,
1726
+ additionalProperties : true ,
1727
+ } ,
1728
+ } ,
1729
+ } ;
1730
+
1731
+ const zodSchema = convertWithResolvedRefs ( fixedSchema ) ;
1732
+
1733
+ const userData = {
1734
+ app_id : 810 ,
1735
+ properties : {
1736
+ minutes_id : {
1737
+ code : 'minutes_id' ,
1738
+ type : 'SINGLE_LINE_TEXT' ,
1739
+ label : 'minutes_id' ,
1740
+ required : false ,
1741
+ } ,
1742
+ } ,
1743
+ } ;
1744
+
1745
+ const result = zodSchema ?. parse ( userData ) ;
1746
+ expect ( result ) . toEqual ( userData ) ;
1747
+ } ) ;
1748
+
1749
+ it ( 'should NOT treat object schemas with $ref or complex properties as bare objects' , ( ) => {
1750
+ // This test ensures our fix doesn't affect schemas with $ref or other complex structures
1751
+ const schemaWithRef = {
1752
+ type : 'object' as const ,
1753
+ properties : {
1754
+ data : {
1755
+ type : 'object' as const ,
1756
+ // This has anyOf with $ref - should NOT be treated as a bare object
1757
+ anyOf : [ { $ref : '#/$defs/dataSchema' } , { type : 'null' as const } ] ,
1758
+ } ,
1759
+ } ,
1760
+ $defs : {
1761
+ dataSchema : {
1762
+ type : 'object' as const ,
1763
+ additionalProperties : {
1764
+ type : 'string' as const ,
1765
+ } ,
1766
+ } ,
1767
+ } ,
1768
+ } ;
1769
+
1770
+ // Convert without resolving refs
1771
+ const zodSchema = convertJsonSchemaToZod ( schemaWithRef as any , {
1772
+ transformOneOfAnyOf : true ,
1773
+ } ) ;
1774
+
1775
+ const testData = {
1776
+ data : {
1777
+ field1 : 'value1' ,
1778
+ field2 : 'value2' ,
1779
+ } ,
1780
+ } ;
1781
+
1782
+ // Without ref resolution, the data field should be stripped/empty
1783
+ const result = zodSchema ?. parse ( testData ) ;
1784
+ expect ( result ?. data ) . toEqual ( { } ) ;
1785
+ } ) ;
1786
+
1787
+ it ( 'should NOT treat object schemas with oneOf/anyOf as bare objects' , ( ) => {
1788
+ // Ensure schemas with oneOf/anyOf are not treated as bare objects
1789
+ const schemaWithOneOf = {
1790
+ type : 'object' as const ,
1791
+ properties : {
1792
+ config : {
1793
+ type : 'object' as const ,
1794
+ // Empty properties but has oneOf - should NOT be passthrough
1795
+ oneOf : [
1796
+ { properties : { type : { const : 'A' } } } ,
1797
+ { properties : { type : { const : 'B' } } } ,
1798
+ ] ,
1799
+ } as any ,
1800
+ } ,
1801
+ } ;
1802
+
1803
+ const zodSchema = convertWithResolvedRefs ( schemaWithOneOf as any , {
1804
+ transformOneOfAnyOf : true ,
1805
+ } ) ;
1806
+
1807
+ const testData = {
1808
+ config : {
1809
+ randomField : 'should not pass through' ,
1810
+ } ,
1811
+ } ;
1812
+
1813
+ // The random field should be stripped because this isn't a bare object
1814
+ const result = zodSchema ?. parse ( testData ) ;
1815
+ expect ( result ?. config ) . toEqual ( { } ) ;
1816
+ } ) ;
1817
+ } ) ;
1488
1818
} ) ;
0 commit comments