44
55import pytest
66from dirty_equals import IsList
7+ from inline_snapshot import snapshot
78from mcp .types import TextContent
89from pydantic import BaseModel , Field , TypeAdapter
910from typing_extensions import TypedDict
@@ -1055,38 +1056,6 @@ def add(x: int, y: int = 10) -> int:
10551056 await client .call_tool ("new_add" , {"x" : 1 , "y" : 2 })
10561057
10571058
1058- def test_arg_transform_examples_in_schema (add_tool ):
1059- # Simple example
1060- new_tool = Tool .from_tool (
1061- add_tool ,
1062- transform_args = {
1063- "old_x" : ArgTransform (examples = [1 , 2 , 3 ]),
1064- },
1065- )
1066- prop = get_property (new_tool , "old_x" )
1067- assert prop ["examples" ] == [1 , 2 , 3 ]
1068-
1069- # Nested example (e.g., for array type)
1070- new_tool2 = Tool .from_tool (
1071- add_tool ,
1072- transform_args = {
1073- "old_x" : ArgTransform (examples = [["a" , "b" ], ["c" , "d" ]]),
1074- },
1075- )
1076- prop2 = get_property (new_tool2 , "old_x" )
1077- assert prop2 ["examples" ] == [["a" , "b" ], ["c" , "d" ]]
1078-
1079- # If not set, should not be present
1080- new_tool3 = Tool .from_tool (
1081- add_tool ,
1082- transform_args = {
1083- "old_x" : ArgTransform (),
1084- },
1085- )
1086- prop3 = get_property (new_tool3 , "old_x" )
1087- assert "examples" not in prop3
1088-
1089-
10901059class TestTransformToolOutputSchema :
10911060 """Test output schema handling in transformed tools."""
10921061
@@ -1504,10 +1473,40 @@ def test_tool_transform_config_removes_meta(sample_tool):
15041473 assert transformed .meta is None
15051474
15061475
1507- # Tests for $defs and _find_referenced_defs functionality
1508- class TestDefsAndReferences :
1476+ class TestInputSchema :
15091477 """Test schema definition handling and reference finding."""
15101478
1479+ def test_arg_transform_examples_in_schema (self , add_tool : Tool ):
1480+ # Simple example
1481+ new_tool = Tool .from_tool (
1482+ add_tool ,
1483+ transform_args = {
1484+ "old_x" : ArgTransform (examples = [1 , 2 , 3 ]),
1485+ },
1486+ )
1487+ prop = get_property (new_tool , "old_x" )
1488+ assert prop ["examples" ] == [1 , 2 , 3 ]
1489+
1490+ # Nested example (e.g., for array type)
1491+ new_tool2 = Tool .from_tool (
1492+ add_tool ,
1493+ transform_args = {
1494+ "old_x" : ArgTransform (examples = [["a" , "b" ], ["c" , "d" ]]),
1495+ },
1496+ )
1497+ prop2 = get_property (new_tool2 , "old_x" )
1498+ assert prop2 ["examples" ] == [["a" , "b" ], ["c" , "d" ]]
1499+
1500+ # If not set, should not be present
1501+ new_tool3 = Tool .from_tool (
1502+ add_tool ,
1503+ transform_args = {
1504+ "old_x" : ArgTransform (),
1505+ },
1506+ )
1507+ prop3 = get_property (new_tool3 , "old_x" )
1508+ assert "examples" not in prop3
1509+
15111510 def test_merge_schema_with_defs_precedence (self ):
15121511 """Test _merge_schema_with_precedence merges $defs correctly."""
15131512 base_schema = {
@@ -1528,22 +1527,27 @@ def test_merge_schema_with_defs_precedence(self):
15281527 },
15291528 }
15301529
1531- result = TransformedTool ._merge_schema_with_precedence (
1530+ transformed_tool_schema = TransformedTool ._merge_schema_with_precedence (
15321531 base_schema , override_schema
15331532 )
15341533
1535- # Should have both field1 and field2
1536- assert "field1" in result ["properties" ]
1537- assert "field2" in result ["properties" ]
1538-
1539- # Should only include referenced defs
1540- defs = result .get ("$defs" , {})
1541- assert "BaseType" in defs # Referenced by field1
1542- assert "OverrideType" in defs # Referenced by field2
1543-
1544- # SharedType should use override version, but only if referenced
1545- # Since it's not referenced by either field, it shouldn't be included
1546- assert "SharedType" not in defs
1534+ # SharedType should no longer be present on the schema
1535+ assert "SharedType" not in transformed_tool_schema ["$defs" ]
1536+
1537+ assert transformed_tool_schema == snapshot (
1538+ {
1539+ "type" : "object" ,
1540+ "properties" : {
1541+ "field1" : {"$ref" : "#/$defs/BaseType" },
1542+ "field2" : {"$ref" : "#/$defs/OverrideType" },
1543+ },
1544+ "required" : [],
1545+ "$defs" : {
1546+ "BaseType" : {"type" : "string" , "description" : "base" },
1547+ "OverrideType" : {"type" : "boolean" },
1548+ },
1549+ }
1550+ )
15471551
15481552 def test_transform_tool_with_complex_defs_pruning (self ):
15491553 """Test that tool transformation properly prunes unused $defs."""
@@ -1561,20 +1565,29 @@ def complex_tool(
15611565 return used_param .value
15621566
15631567 # Transform to hide unused_param
1564- transformed = Tool .from_tool (
1568+ transformed_tool : TransformedTool = Tool .from_tool (
15651569 complex_tool , transform_args = {"unused_param" : ArgTransform (hide = True )}
15661570 )
15671571
1568- # Only UsedType should be in $defs, not UnusedType
1569- defs = transformed .parameters .get ("$defs" , {})
1570- type_names = set (defs .keys ())
1571-
1572- # Should contain UsedType but not UnusedType
1573- used_type_found = any ("UsedType" in name for name in type_names )
1574- unused_type_found = any ("UnusedType" in name for name in type_names )
1575-
1576- assert used_type_found , f"UsedType not found in defs: { type_names } "
1577- assert not unused_type_found , f"UnusedType should not be in defs: { type_names } "
1572+ assert "UnusedType" not in transformed_tool .parameters ["$defs" ]
1573+
1574+ assert transformed_tool .parameters == snapshot (
1575+ {
1576+ "type" : "object" ,
1577+ "properties" : {
1578+ "used_param" : {"$ref" : "#/$defs/UsedType" , "title" : "Used Param" }
1579+ },
1580+ "required" : ["used_param" ],
1581+ "$defs" : {
1582+ "UsedType" : {
1583+ "properties" : {"value" : {"title" : "Value" , "type" : "string" }},
1584+ "required" : ["value" ],
1585+ "title" : "UsedType" ,
1586+ "type" : "object" ,
1587+ }
1588+ },
1589+ }
1590+ )
15781591
15791592 def test_transform_with_custom_function_preserves_needed_defs (self ):
15801593 """Test that custom transform functions preserve necessary $defs."""
@@ -1599,12 +1612,26 @@ async def transform_function(renamed_input: InputType):
15991612 transform_args = {"input_data" : ArgTransform (name = "renamed_input" )},
16001613 )
16011614
1602- # Both InputType and OutputType should be preserved in defs
1603- defs = transformed .parameters .get ("$defs" , {})
1604- type_names = set (defs .keys ())
1605-
1606- input_type_found = any ("InputType" in name for name in type_names )
1607- assert input_type_found , f"InputType not found in defs: { type_names } "
1615+ assert transformed .parameters == snapshot (
1616+ {
1617+ "type" : "object" ,
1618+ "properties" : {
1619+ "renamed_input" : {
1620+ "$ref" : "#/$defs/InputType" ,
1621+ "title" : "Input Data" ,
1622+ }
1623+ },
1624+ "required" : ["renamed_input" ],
1625+ "$defs" : {
1626+ "InputType" : {
1627+ "properties" : {"data" : {"title" : "Data" , "type" : "string" }},
1628+ "required" : ["data" ],
1629+ "title" : "InputType" ,
1630+ "type" : "object" ,
1631+ }
1632+ },
1633+ }
1634+ )
16081635
16091636 def test_chained_transforms_preserve_correct_defs (self ):
16101637 """Test that chained transformations preserve correct $defs."""
@@ -1628,20 +1655,55 @@ def base_tool(param_a: TypeA, param_b: TypeB, param_c: TypeC) -> str:
16281655 transform_args = {"param_c" : ArgTransform (hide = True , default = TypeC (c = True ))},
16291656 )
16301657
1658+ assert transform1 .parameters == snapshot (
1659+ {
1660+ "type" : "object" ,
1661+ "properties" : {
1662+ "param_a" : {"$ref" : "#/$defs/TypeA" , "title" : "Param A" },
1663+ "param_b" : {"$ref" : "#/$defs/TypeB" , "title" : "Param B" },
1664+ },
1665+ "required" : IsList ("param_b" , "param_a" , check_order = False ),
1666+ "$defs" : {
1667+ "TypeA" : {
1668+ "properties" : {"a" : {"title" : "A" , "type" : "string" }},
1669+ "required" : ["a" ],
1670+ "title" : "TypeA" ,
1671+ "type" : "object" ,
1672+ },
1673+ "TypeB" : {
1674+ "properties" : {"b" : {"title" : "B" , "type" : "integer" }},
1675+ "required" : ["b" ],
1676+ "title" : "TypeB" ,
1677+ "type" : "object" ,
1678+ },
1679+ },
1680+ }
1681+ )
1682+
1683+ assert "TypeA" in transform1 .parameters ["$defs" ]
1684+
16311685 # Second transform: hide param_b
16321686 transform2 = Tool .from_tool (
16331687 transform1 ,
16341688 transform_args = {"param_b" : ArgTransform (hide = True , default = TypeB (b = 42 ))},
16351689 )
16361690
1637- # Final schema should only have TypeA in $defs
1638- defs = transform2 .parameters .get ("$defs" , {})
1639- type_names = set (defs .keys ())
1640-
1641- type_a_found = any ("TypeA" in name for name in type_names )
1642- type_b_found = any ("TypeB" in name for name in type_names )
1643- type_c_found = any ("TypeC" in name for name in type_names )
1644-
1645- assert type_a_found , f"TypeA should be in defs: { type_names } "
1646- assert not type_b_found , f"TypeB should not be in defs: { type_names } "
1647- assert not type_c_found , f"TypeC should not be in defs: { type_names } "
1691+ assert "TypeB" not in transform2 .parameters ["$defs" ]
1692+
1693+ assert transform2 .parameters == snapshot (
1694+ {
1695+ "type" : "object" ,
1696+ "properties" : {
1697+ "param_a" : {"$ref" : "#/$defs/TypeA" , "title" : "Param A" }
1698+ },
1699+ "required" : ["param_a" ],
1700+ "$defs" : {
1701+ "TypeA" : {
1702+ "properties" : {"a" : {"title" : "A" , "type" : "string" }},
1703+ "required" : ["a" ],
1704+ "title" : "TypeA" ,
1705+ "type" : "object" ,
1706+ }
1707+ },
1708+ }
1709+ )
0 commit comments