11"""
22FastMCP Complex inputs Example
33
4- Demonstrates validation via pydantic with complex models.
4+ Demonstrates validation via pydantic with complex models,
5+ and structured output for returning validated data.
56"""
67
8+ from datetime import datetime
79from typing import Annotated
810
911from pydantic import BaseModel , Field
1618class ShrimpTank (BaseModel ):
1719 class Shrimp (BaseModel ):
1820 name : Annotated [str , Field (max_length = 10 )]
21+ color : str = "red"
22+ age_days : int = 0
1923
2024 shrimp : list [Shrimp ]
25+ temperature : float = Field (default = 24.0 , ge = 20.0 , le = 28.0 )
26+ ph_level : float = Field (default = 7.0 , ge = 6.5 , le = 8.0 )
2127
2228
2329@mcp .tool ()
@@ -28,3 +34,140 @@ def name_shrimp(
2834) -> list [str ]:
2935 """List all shrimp names in the tank"""
3036 return [shrimp .name for shrimp in tank .shrimp ] + extra_names
37+
38+
39+ # Structured output example - returns a validated tank analysis
40+ class TankAnalysis (BaseModel ):
41+ """Analysis of shrimp tank conditions"""
42+ total_shrimp : int
43+ temperature_status : str # "optimal", "too_cold", "too_hot"
44+ ph_status : str # "optimal", "too_acidic", "too_basic"
45+ shrimp_by_color : dict [str , int ]
46+ oldest_shrimp : str | None
47+ recommendations : list [str ]
48+
49+
50+ @mcp .tool (structured_output = True )
51+ def analyze_tank (tank : ShrimpTank ) -> TankAnalysis :
52+ """Analyze tank conditions and provide recommendations"""
53+ # Temperature analysis
54+ if tank .temperature < 22 :
55+ temp_status = "too_cold"
56+ elif tank .temperature > 26 :
57+ temp_status = "too_hot"
58+ else :
59+ temp_status = "optimal"
60+
61+ # pH analysis
62+ if tank .ph_level < 6.8 :
63+ ph_status = "too_acidic"
64+ elif tank .ph_level > 7.5 :
65+ ph_status = "too_basic"
66+ else :
67+ ph_status = "optimal"
68+
69+ # Count shrimp by color
70+ color_counts : dict [str , int ] = {}
71+ for shrimp in tank .shrimp :
72+ color_counts [shrimp .color ] = color_counts .get (shrimp .color , 0 ) + 1
73+
74+ # Find oldest shrimp
75+ oldest = None
76+ if tank .shrimp :
77+ oldest_shrimp_obj = max (tank .shrimp , key = lambda s : s .age_days )
78+ oldest = oldest_shrimp_obj .name
79+
80+ # Generate recommendations
81+ recommendations = []
82+ if temp_status == "too_cold" :
83+ recommendations .append ("Increase water temperature to 22-26°C" )
84+ elif temp_status == "too_hot" :
85+ recommendations .append ("Decrease water temperature to 22-26°C" )
86+
87+ if ph_status == "too_acidic" :
88+ recommendations .append ("Add crushed coral or baking soda to raise pH" )
89+ elif ph_status == "too_basic" :
90+ recommendations .append ("Add Indian almond leaves or driftwood to lower pH" )
91+
92+ if len (tank .shrimp ) > 20 :
93+ recommendations .append ("Consider dividing colony to prevent overcrowding" )
94+
95+ if not recommendations :
96+ recommendations .append ("Tank conditions are optimal!" )
97+
98+ return TankAnalysis (
99+ total_shrimp = len (tank .shrimp ),
100+ temperature_status = temp_status ,
101+ ph_status = ph_status ,
102+ shrimp_by_color = color_counts ,
103+ oldest_shrimp = oldest ,
104+ recommendations = recommendations
105+ )
106+
107+
108+ # Another structured output example - breeding recommendations
109+ @mcp .tool (structured_output = True )
110+ def get_breeding_pairs (tank : ShrimpTank ) -> dict [str , list [str ]]:
111+ """Suggest breeding pairs by color
112+
113+ Returns a dictionary mapping colors to lists of shrimp names
114+ that could be bred together.
115+ """
116+ pairs_by_color : dict [str , list [str ]] = {}
117+
118+ for shrimp in tank .shrimp :
119+ if shrimp .age_days >= 60 : # Mature enough to breed
120+ if shrimp .color not in pairs_by_color :
121+ pairs_by_color [shrimp .color ] = []
122+ pairs_by_color [shrimp .color ].append (shrimp .name )
123+
124+ # Only return colors with at least 2 shrimp
125+ return {
126+ color : names
127+ for color , names in pairs_by_color .items ()
128+ if len (names ) >= 2
129+ }
130+
131+
132+ if __name__ == "__main__" :
133+ # For testing the tools
134+ import asyncio
135+
136+ async def test ():
137+ # Create a test tank
138+ tank = ShrimpTank (
139+ shrimp = [
140+ ShrimpTank .Shrimp (name = "Rex" , color = "red" , age_days = 90 ),
141+ ShrimpTank .Shrimp (name = "Blue" , color = "blue" , age_days = 45 ),
142+ ShrimpTank .Shrimp (name = "Crimson" , color = "red" , age_days = 120 ),
143+ ShrimpTank .Shrimp (name = "Azure" , color = "blue" , age_days = 80 ),
144+ ShrimpTank .Shrimp (name = "Jade" , color = "green" , age_days = 30 ),
145+ ShrimpTank .Shrimp (name = "Ruby" , color = "red" , age_days = 75 ),
146+ ],
147+ temperature = 23.5 ,
148+ ph_level = 7.2
149+ )
150+
151+ # Test name_shrimp (non-structured output)
152+ names = name_shrimp (tank , ["Bonus1" , "Bonus2" ])
153+ print ("Shrimp names:" , names )
154+
155+ # Test analyze_tank (structured output)
156+ print ("\n Tank Analysis:" )
157+ analysis = analyze_tank (tank )
158+ print (analysis .model_dump_json (indent = 2 ))
159+
160+ # Test get_breeding_pairs (structured output returning dict)
161+ print ("\n Breeding Pairs:" )
162+ pairs = get_breeding_pairs (tank )
163+ print (f"Mature shrimp by color: { pairs } " )
164+
165+ # Show the tools that would be exposed
166+ print ("\n Available tools:" )
167+ tools = await mcp .list_tools ()
168+ for tool in tools :
169+ print (f"- { tool .name } : { tool .description } " )
170+ if tool .outputSchema :
171+ print (f" Output schema: { tool .outputSchema .get ('title' , tool .outputSchema .get ('type' , 'structured' ))} " )
172+
173+ asyncio .run (test ())
0 commit comments