992. Method-style for direct tool access: `agent.tool.tool_name(param1="value")` 
1010""" 
1111
12- import  asyncio 
1312import  json 
1413import  logging 
1514import  random 
1615import  warnings 
17- from  concurrent .futures  import  ThreadPoolExecutor 
1816from  typing  import  (
17+     TYPE_CHECKING ,
1918    Any ,
2019    AsyncGenerator ,
2120    AsyncIterator ,
3231from  pydantic  import  BaseModel 
3332
3433from  .. import  _identifier 
34+ from  .._async  import  run_async 
3535from  ..event_loop .event_loop  import  event_loop_cycle 
36+ 
37+ if  TYPE_CHECKING :
38+     from  ..experimental .tools  import  ToolProvider 
3639from  ..handlers .callback_handler  import  PrintingCallbackHandler , null_callback_handler 
3740from  ..hooks  import  (
3841    AfterInvocationEvent ,
@@ -167,12 +170,7 @@ async def acall() -> ToolResult:
167170
168171                    return  tool_results [0 ]
169172
170-                 def  tcall () ->  ToolResult :
171-                     return  asyncio .run (acall ())
172- 
173-                 with  ThreadPoolExecutor () as  executor :
174-                     future  =  executor .submit (tcall )
175-                     tool_result  =  future .result ()
173+                 tool_result  =  run_async (acall )
176174
177175                if  record_direct_tool_call  is  not None :
178176                    should_record_direct_tool_call  =  record_direct_tool_call 
@@ -215,7 +213,7 @@ def __init__(
215213        self ,
216214        model : Union [Model , str , None ] =  None ,
217215        messages : Optional [Messages ] =  None ,
218-         tools : Optional [list [Union [str , dict [str , str ], Any ]]] =  None ,
216+         tools : Optional [list [Union [str , dict [str , str ], "ToolProvider" ,  Any ]]] =  None ,
219217        system_prompt : Optional [str ] =  None ,
220218        structured_output_model : Optional [Type [BaseModel ]] =  None ,
221219        callback_handler : Optional [
@@ -248,6 +246,7 @@ def __init__(
248246                - File paths (e.g., "/path/to/tool.py") 
249247                - Imported Python modules (e.g., from strands_tools import current_time) 
250248                - Dictionaries with name/path keys (e.g., {"name": "tool_name", "path": "/path/to/tool.py"}) 
249+                 - ToolProvider instances for managed tool collections 
251250                - Functions decorated with `@strands.tool` decorator. 
252251
253252                If provided, only these tools will be available. If None, all tools will be available. 
@@ -423,17 +422,11 @@ def __call__(
423422                - state: The final state of the event loop 
424423                - structured_output: Parsed structured output when structured_output_model was specified 
425424        """ 
426- 
427-         def  execute () ->  AgentResult :
428-             return  asyncio .run (
429-                 self .invoke_async (
430-                     prompt , invocation_state = invocation_state , structured_output_model = structured_output_model , ** kwargs 
431-                 )
425+         return  run_async (
426+             lambda : self .invoke_async (
427+                 prompt , invocation_state = invocation_state , structured_output_model = structured_output_model , ** kwargs 
432428            )
433- 
434-         with  ThreadPoolExecutor () as  executor :
435-             future  =  executor .submit (execute )
436-             return  future .result ()
429+         )
437430
438431    async  def  invoke_async (
439432        self ,
@@ -506,12 +499,7 @@ def structured_output(self, output_model: Type[T], prompt: AgentInput = None) ->
506499            stacklevel = 2 ,
507500        )
508501
509-         def  execute () ->  T :
510-             return  asyncio .run (self .structured_output_async (output_model , prompt ))
511- 
512-         with  ThreadPoolExecutor () as  executor :
513-             future  =  executor .submit (execute )
514-             return  future .result ()
502+         return  run_async (lambda : self .structured_output_async (output_model , prompt ))
515503
516504    async  def  structured_output_async (self , output_model : Type [T ], prompt : AgentInput  =  None ) ->  T :
517505        """This method allows you to get structured output from the agent. 
@@ -529,6 +517,7 @@ async def structured_output_async(self, output_model: Type[T], prompt: AgentInpu
529517
530518        Raises: 
531519            ValueError: If no conversation history or prompt is provided. 
520+         - 
532521        """ 
533522        if  self ._interrupt_state .activated :
534523            raise  RuntimeError ("cannot call structured output during interrupt" )
@@ -583,6 +572,25 @@ async def structured_output_async(self, output_model: Type[T], prompt: AgentInpu
583572            finally :
584573                self .hooks .invoke_callbacks (AfterInvocationEvent (agent = self ))
585574
575+     def  cleanup (self ) ->  None :
576+         """Clean up resources used by the agent. 
577+ 
578+         This method cleans up all tool providers that require explicit cleanup, 
579+         such as MCP clients. It should be called when the agent is no longer needed 
580+         to ensure proper resource cleanup. 
581+ 
582+         Note: This method uses a "belt and braces" approach with automatic cleanup 
583+         through finalizers as a fallback, but explicit cleanup is recommended. 
584+         """ 
585+         self .tool_registry .cleanup ()
586+ 
587+     def  __del__ (self ) ->  None :
588+         """Clean up resources when agent is garbage collected.""" 
589+         # __del__ is called even when an exception is thrown in the constructor, 
590+         # so there is no guarantee tool_registry was set.. 
591+         if  hasattr (self , "tool_registry" ):
592+             self .tool_registry .cleanup ()
593+ 
586594    async  def  stream_async (
587595        self ,
588596        prompt : AgentInput  =  None ,
0 commit comments