agno 2.0.7__py3-none-any.whl → 2.0.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. agno/agent/agent.py +83 -51
  2. agno/db/base.py +14 -0
  3. agno/db/dynamo/dynamo.py +107 -27
  4. agno/db/firestore/firestore.py +109 -33
  5. agno/db/gcs_json/gcs_json_db.py +100 -20
  6. agno/db/in_memory/in_memory_db.py +95 -20
  7. agno/db/json/json_db.py +101 -21
  8. agno/db/migrations/v1_to_v2.py +322 -47
  9. agno/db/mongo/mongo.py +251 -26
  10. agno/db/mysql/mysql.py +307 -6
  11. agno/db/postgres/postgres.py +279 -33
  12. agno/db/redis/redis.py +99 -22
  13. agno/db/singlestore/singlestore.py +319 -38
  14. agno/db/sqlite/sqlite.py +339 -23
  15. agno/knowledge/embedder/sentence_transformer.py +3 -3
  16. agno/knowledge/knowledge.py +152 -31
  17. agno/knowledge/types.py +8 -0
  18. agno/models/anthropic/claude.py +0 -20
  19. agno/models/cometapi/__init__.py +5 -0
  20. agno/models/cometapi/cometapi.py +57 -0
  21. agno/models/google/gemini.py +4 -8
  22. agno/models/huggingface/huggingface.py +2 -1
  23. agno/models/ollama/chat.py +52 -3
  24. agno/models/openai/chat.py +9 -7
  25. agno/models/openai/responses.py +21 -17
  26. agno/os/interfaces/agui/agui.py +2 -2
  27. agno/os/interfaces/agui/utils.py +81 -18
  28. agno/os/interfaces/base.py +2 -0
  29. agno/os/interfaces/slack/router.py +50 -10
  30. agno/os/interfaces/slack/slack.py +6 -4
  31. agno/os/interfaces/whatsapp/router.py +7 -4
  32. agno/os/interfaces/whatsapp/whatsapp.py +2 -2
  33. agno/os/router.py +18 -0
  34. agno/os/utils.py +10 -2
  35. agno/reasoning/azure_ai_foundry.py +2 -2
  36. agno/reasoning/deepseek.py +2 -2
  37. agno/reasoning/default.py +3 -1
  38. agno/reasoning/groq.py +2 -2
  39. agno/reasoning/ollama.py +2 -2
  40. agno/reasoning/openai.py +2 -2
  41. agno/run/base.py +15 -2
  42. agno/session/agent.py +8 -5
  43. agno/session/team.py +14 -10
  44. agno/team/team.py +218 -111
  45. agno/tools/function.py +43 -4
  46. agno/tools/mcp.py +60 -37
  47. agno/tools/mcp_toolbox.py +284 -0
  48. agno/tools/scrapegraph.py +58 -31
  49. agno/tools/whatsapp.py +1 -1
  50. agno/utils/gemini.py +147 -19
  51. agno/utils/models/claude.py +9 -0
  52. agno/utils/print_response/agent.py +18 -2
  53. agno/utils/print_response/team.py +22 -6
  54. agno/utils/reasoning.py +22 -1
  55. agno/utils/string.py +9 -0
  56. agno/vectordb/base.py +2 -2
  57. agno/vectordb/langchaindb/langchaindb.py +5 -7
  58. agno/vectordb/llamaindex/llamaindexdb.py +25 -6
  59. agno/workflow/workflow.py +30 -15
  60. {agno-2.0.7.dist-info → agno-2.0.9.dist-info}/METADATA +4 -1
  61. {agno-2.0.7.dist-info → agno-2.0.9.dist-info}/RECORD +64 -61
  62. {agno-2.0.7.dist-info → agno-2.0.9.dist-info}/WHEEL +0 -0
  63. {agno-2.0.7.dist-info → agno-2.0.9.dist-info}/licenses/LICENSE +0 -0
  64. {agno-2.0.7.dist-info → agno-2.0.9.dist-info}/top_level.txt +0 -0
agno/tools/function.py CHANGED
@@ -124,6 +124,8 @@ class Function(BaseModel):
124
124
  _team: Optional[Any] = None
125
125
  # The session state that the function is associated with
126
126
  _session_state: Optional[Dict[str, Any]] = None
127
+ # The dependencies that the function is associated with
128
+ _dependencies: Optional[Dict[str, Any]] = None
127
129
 
128
130
  # Media context that the function is associated with
129
131
  _images: Optional[Sequence[Image]] = None
@@ -165,6 +167,8 @@ class Function(BaseModel):
165
167
  del type_hints["audios"]
166
168
  if "files" in sig.parameters and "files" in type_hints:
167
169
  del type_hints["files"]
170
+ if "dependencies" in sig.parameters and "dependencies" in type_hints:
171
+ del type_hints["dependencies"]
168
172
  # log_info(f"Type hints for {function_name}: {type_hints}")
169
173
 
170
174
  # Filter out return type and only process parameters
@@ -172,7 +176,8 @@ class Function(BaseModel):
172
176
  name: type_hints.get(name)
173
177
  for name in sig.parameters
174
178
  if name != "return"
175
- and name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
179
+ and name
180
+ not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files", "dependencies"]
176
181
  }
177
182
 
178
183
  # Parse docstring for parameters
@@ -201,7 +206,18 @@ class Function(BaseModel):
201
206
  parameters["required"] = [
202
207
  name
203
208
  for name in parameters["properties"]
204
- if name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
209
+ if name
210
+ not in [
211
+ "agent",
212
+ "team",
213
+ "session_state",
214
+ "self",
215
+ "images",
216
+ "videos",
217
+ "audios",
218
+ "files",
219
+ "dependencies",
220
+ ]
205
221
  ]
206
222
  else:
207
223
  # Mark a field as required if it has no default value (this would include optional fields)
@@ -209,7 +225,18 @@ class Function(BaseModel):
209
225
  name
210
226
  for name, param in sig.parameters.items()
211
227
  if param.default == param.empty
212
- and name not in ["agent", "team", "session_state", "self", "images", "videos", "audios", "files"]
228
+ and name
229
+ not in [
230
+ "agent",
231
+ "team",
232
+ "session_state",
233
+ "self",
234
+ "images",
235
+ "videos",
236
+ "audios",
237
+ "files",
238
+ "dependencies",
239
+ ]
213
240
  ]
214
241
 
215
242
  # log_debug(f"JSON schema for {function_name}: {parameters}")
@@ -268,6 +295,8 @@ class Function(BaseModel):
268
295
  del type_hints["audios"]
269
296
  if "files" in sig.parameters and "files" in type_hints:
270
297
  del type_hints["files"]
298
+ if "dependencies" in sig.parameters and "dependencies" in type_hints:
299
+ del type_hints["dependencies"]
271
300
  # log_info(f"Type hints for {self.name}: {type_hints}")
272
301
 
273
302
  # Filter out return type and only process parameters
@@ -281,6 +310,7 @@ class Function(BaseModel):
281
310
  "videos",
282
311
  "audios",
283
312
  "files",
313
+ "dependencies",
284
314
  ]
285
315
  if self.requires_user_input and self.user_input_fields:
286
316
  if len(self.user_input_fields) == 0:
@@ -396,7 +426,8 @@ class Function(BaseModel):
396
426
  self.parameters["required"] = [
397
427
  name
398
428
  for name in self.parameters["properties"]
399
- if name not in ["agent", "team", "session_state", "images", "videos", "audios", "files", "self"]
429
+ if name
430
+ not in ["agent", "team", "session_state", "images", "videos", "audios", "files", "self", "dependencies"]
400
431
  ]
401
432
 
402
433
  def _get_cache_key(self, entrypoint_args: Dict[str, Any], call_args: Optional[Dict[str, Any]] = None) -> str:
@@ -419,6 +450,8 @@ class Function(BaseModel):
419
450
  del copy_entrypoint_args["audios"]
420
451
  if "files" in copy_entrypoint_args:
421
452
  del copy_entrypoint_args["files"]
453
+ if "dependencies" in copy_entrypoint_args:
454
+ del copy_entrypoint_args["dependencies"]
422
455
  args_str = str(copy_entrypoint_args)
423
456
 
424
457
  kwargs_str = str(sorted((call_args or {}).items()))
@@ -599,6 +632,9 @@ class FunctionCall(BaseModel):
599
632
  # Check if the entrypoint has an session_state argument
600
633
  if "session_state" in signature(self.function.entrypoint).parameters: # type: ignore
601
634
  entrypoint_args["session_state"] = self.function._session_state
635
+ # Check if the entrypoint has an dependencies argument
636
+ if "dependencies" in signature(self.function.entrypoint).parameters: # type: ignore
637
+ entrypoint_args["dependencies"] = self.function._dependencies
602
638
  # Check if the entrypoint has an fc argument
603
639
  if "fc" in signature(self.function.entrypoint).parameters: # type: ignore
604
640
  entrypoint_args["fc"] = self
@@ -629,6 +665,9 @@ class FunctionCall(BaseModel):
629
665
  # Check if the hook has an session_state argument
630
666
  if "session_state" in signature(hook).parameters:
631
667
  hook_args["session_state"] = self.function._session_state
668
+ # Check if the hook has an dependencies argument
669
+ if "dependencies" in signature(hook).parameters:
670
+ hook_args["dependencies"] = self.function._dependencies
632
671
 
633
672
  if "name" in signature(hook).parameters:
634
673
  hook_args["name"] = name
agno/tools/mcp.py CHANGED
@@ -8,7 +8,7 @@ from typing import Any, Dict, List, Literal, Optional, Union
8
8
 
9
9
  from agno.tools import Toolkit
10
10
  from agno.tools.function import Function
11
- from agno.utils.log import log_debug, log_info, log_warning, logger
11
+ from agno.utils.log import log_debug, log_error, log_info, log_warning
12
12
  from agno.utils.mcp import get_entrypoint_for_tool
13
13
 
14
14
  try:
@@ -338,12 +338,13 @@ class MCPTools(Toolkit):
338
338
  self.functions[f.name] = f
339
339
  log_debug(f"Function: {f.name} registered with {self.name}")
340
340
  except Exception as e:
341
- logger.error(f"Failed to register tool {tool.name}: {e}")
341
+ log_error(f"Failed to register tool {tool.name}: {e}")
342
342
 
343
343
  log_debug(f"{self.name} initialized with {len(filtered_tools)} tools")
344
344
  self._initialized = True
345
+
345
346
  except Exception as e:
346
- logger.error(f"Failed to get MCP tools: {e}")
347
+ log_error(f"Failed to get MCP tools: {e}")
347
348
  raise
348
349
 
349
350
 
@@ -372,6 +373,7 @@ class MultiMCPTools(Toolkit):
372
373
  client=None,
373
374
  include_tools: Optional[list[str]] = None,
374
375
  exclude_tools: Optional[list[str]] = None,
376
+ allow_partial_failure: bool = False,
375
377
  **kwargs,
376
378
  ):
377
379
  """
@@ -387,6 +389,7 @@ class MultiMCPTools(Toolkit):
387
389
  timeout_seconds: Timeout in seconds for managing timeouts for Client Session if Agent or Tool doesn't respond.
388
390
  include_tools: Optional list of tool names to include (if None, includes all).
389
391
  exclude_tools: Optional list of tool names to exclude (if None, excludes none).
392
+ allow_partial_failure: If True, allows toolkit to initialize even if some MCP servers fail to connect. If False, any failure will raise an exception.
390
393
  """
391
394
  super().__init__(name="MultiMCPTools", **kwargs)
392
395
 
@@ -445,12 +448,16 @@ class MultiMCPTools(Toolkit):
445
448
  self.server_params_list.append(StreamableHTTPClientParams(url=url))
446
449
 
447
450
  self._async_exit_stack = AsyncExitStack()
451
+ self._successful_connections = 0
452
+
448
453
  self._initialized = False
449
454
  self._connection_task = None
450
455
  self._active_contexts: list[Any] = []
451
456
  self._used_as_context_manager = False
452
457
 
453
458
  self._client = client
459
+ self._initialized = False
460
+ self.allow_partial_failure = allow_partial_failure
454
461
 
455
462
  def cleanup():
456
463
  """Cancel active connections"""
@@ -511,39 +518,53 @@ class MultiMCPTools(Toolkit):
511
518
  if self._initialized:
512
519
  return
513
520
 
521
+ server_connection_errors = []
522
+
514
523
  for server_params in self.server_params_list:
515
- # Handle stdio connections
516
- if isinstance(server_params, StdioServerParameters):
517
- stdio_transport = await self._async_exit_stack.enter_async_context(stdio_client(server_params))
518
- self._active_contexts.append(stdio_transport)
519
- read, write = stdio_transport
520
- session = await self._async_exit_stack.enter_async_context(
521
- ClientSession(read, write, read_timeout_seconds=timedelta(seconds=self.timeout_seconds))
522
- )
523
- self._active_contexts.append(session)
524
- await self.initialize(session)
525
- # Handle SSE connections
526
- elif isinstance(server_params, SSEClientParams):
527
- client_connection = await self._async_exit_stack.enter_async_context(
528
- sse_client(**asdict(server_params))
529
- )
530
- self._active_contexts.append(client_connection)
531
- read, write = client_connection
532
- session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
533
- self._active_contexts.append(session)
534
- await self.initialize(session)
535
- # Handle Streamable HTTP connections
536
- elif isinstance(server_params, StreamableHTTPClientParams):
537
- client_connection = await self._async_exit_stack.enter_async_context(
538
- streamablehttp_client(**asdict(server_params))
539
- )
540
- self._active_contexts.append(client_connection)
541
- read, write = client_connection[0:2]
542
- session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
543
- self._active_contexts.append(session)
544
- await self.initialize(session)
524
+ try:
525
+ # Handle stdio connections
526
+ if isinstance(server_params, StdioServerParameters):
527
+ stdio_transport = await self._async_exit_stack.enter_async_context(stdio_client(server_params))
528
+ read, write = stdio_transport
529
+ session = await self._async_exit_stack.enter_async_context(
530
+ ClientSession(read, write, read_timeout_seconds=timedelta(seconds=self.timeout_seconds))
531
+ )
532
+ await self.initialize(session)
533
+ self._successful_connections += 1
545
534
 
546
- self._initialized = True
535
+ # Handle SSE connections
536
+ elif isinstance(server_params, SSEClientParams):
537
+ client_connection = await self._async_exit_stack.enter_async_context(
538
+ sse_client(**asdict(server_params))
539
+ )
540
+ read, write = client_connection
541
+ session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
542
+ await self.initialize(session)
543
+ self._successful_connections += 1
544
+
545
+ # Handle Streamable HTTP connections
546
+ elif isinstance(server_params, StreamableHTTPClientParams):
547
+ client_connection = await self._async_exit_stack.enter_async_context(
548
+ streamablehttp_client(**asdict(server_params))
549
+ )
550
+ read, write = client_connection[0:2]
551
+ session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
552
+ await self.initialize(session)
553
+ self._successful_connections += 1
554
+
555
+ except Exception as e:
556
+ if not self.allow_partial_failure:
557
+ raise ValueError(f"MCP connection failed: {e}")
558
+
559
+ log_error(f"Failed to initialize MCP server with params {server_params}: {e}")
560
+ server_connection_errors.append(str(e))
561
+ continue
562
+
563
+ if self._successful_connections == 0 and server_connection_errors:
564
+ raise ValueError(f"All MCP connections failed: {server_connection_errors}")
565
+
566
+ if not self._initialized and self._successful_connections > 0:
567
+ self._initialized = True
547
568
 
548
569
  async def close(self) -> None:
549
570
  """Close the MCP connections and clean up resources"""
@@ -563,6 +584,8 @@ class MultiMCPTools(Toolkit):
563
584
  ):
564
585
  """Exit the async context manager."""
565
586
  await self._async_exit_stack.aclose()
587
+ self._initialized = False
588
+ self._successful_connections = 0
566
589
 
567
590
  async def initialize(self, session: ClientSession) -> None:
568
591
  """Initialize the MCP toolkit by getting available tools from the MCP server"""
@@ -602,10 +625,10 @@ class MultiMCPTools(Toolkit):
602
625
  self.functions[f.name] = f
603
626
  log_debug(f"Function: {f.name} registered with {self.name}")
604
627
  except Exception as e:
605
- logger.error(f"Failed to register tool {tool.name}: {e}")
628
+ log_error(f"Failed to register tool {tool.name}: {e}")
606
629
 
607
- log_debug(f"{self.name} initialized with {len(filtered_tools)} tools")
630
+ log_debug(f"{self.name} initialized with {len(filtered_tools)} tools from one MCP server")
608
631
  self._initialized = True
609
632
  except Exception as e:
610
- logger.error(f"Failed to get MCP tools: {e}")
633
+ log_error(f"Failed to get MCP tools: {e}")
611
634
  raise
@@ -0,0 +1,284 @@
1
+ from typing import Any, Callable, Dict, List, Literal, Optional, Union
2
+ from warnings import warn
3
+
4
+ from agno.tools.function import Function
5
+ from agno.tools.mcp import MCPTools
6
+ from agno.utils.log import logger
7
+
8
+ try:
9
+ from toolbox_core import ToolboxClient # type: ignore
10
+ except ImportError:
11
+ raise ImportError("`toolbox_core` not installed. Please install using `pip install -U toolbox-core`.")
12
+
13
+
14
+ class MCPToolsMeta(type):
15
+ """Metaclass for MCPTools to ensure proper initialization with AgentOS"""
16
+
17
+ @property
18
+ def __name__(cls):
19
+ return "MCPTools"
20
+
21
+
22
+ class MCPToolbox(MCPTools, metaclass=MCPToolsMeta):
23
+ """
24
+ A toolkit that combines MCPTools server connectivity with MCP Toolbox for Databases client (toolbox-core).
25
+
26
+ MCPToolbox connects to an MCP Toolbox server and registers all available tools, then uses
27
+ toolbox-core to filter those tools by toolset or tool name. This enables agents to
28
+ receive only the specific tools they need while maintaining full MCP execution capabilities.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ url: str,
34
+ toolsets: Optional[List[str]] = None,
35
+ tool_name: Optional[str] = None,
36
+ headers: Optional[Dict[str, Any]] = None,
37
+ transport: Literal["stdio", "sse", "streamable-http"] = "streamable-http",
38
+ **kwargs,
39
+ ):
40
+ """Initialize MCPToolbox with filtering capabilities.
41
+
42
+ Args:
43
+ url (str): Base URL for the toolbox service.
44
+ toolsets (Optional[List[str]], optional): List of toolset names to filter tools by. Defaults to None.
45
+ tool_name (Optional[str], optional): Single tool name to load. Defaults to None.
46
+ headers (Optional[Dict[str, Any]], optional): Headers for toolbox-core client requests. Defaults to None.
47
+ transport (Literal["stdio", "sse", "streamable-http"], optional): MCP transport protocol. Defaults to "streamable-http".
48
+
49
+ """
50
+
51
+ # Ensure the URL ends in "/mcp" as expected
52
+ if not url.endswith("/mcp"):
53
+ url = url + "/mcp"
54
+
55
+ super().__init__(url=url, transport=transport, **kwargs)
56
+
57
+ self.name = "toolbox_client"
58
+ self.toolbox_url = url
59
+ self.toolsets = toolsets
60
+ self.tool_name = tool_name
61
+ self.headers = headers
62
+ self._core_client_initialized = False
63
+
64
+ # Validate that only one of toolsets or tool_name is provided
65
+ filter_params = [toolsets, tool_name]
66
+ non_none_params = [p for p in filter_params if p is not None]
67
+ if len(non_none_params) > 1:
68
+ raise ValueError("Only one of toolsets or tool_name can be specified")
69
+
70
+ async def connect(self):
71
+ """Initialize MCPToolbox instance and connect to the MCP server."""
72
+ # First, connect to MCP server and load all available tools
73
+ await super().connect()
74
+
75
+ if self._core_client_initialized:
76
+ return
77
+
78
+ # Then, connect to the ToolboxClient and filter tools based on toolsets or tool_name
79
+ await self._connect_toolbox_client()
80
+
81
+ async def _connect_toolbox_client(self):
82
+ try:
83
+ if self.toolsets is not None or self.tool_name is not None:
84
+ self.__core_client = ToolboxClient(
85
+ url=self.toolbox_url,
86
+ client_headers=self.headers,
87
+ )
88
+ self._core_client_initialized = True
89
+
90
+ if self.toolsets is not None:
91
+ # Load multiple toolsets
92
+ all_functions = await self.load_multiple_toolsets(toolset_names=self.toolsets)
93
+ # Replace functions dict with filtered subset
94
+ filtered_functions = {func.name: func for func in all_functions}
95
+ self.functions = filtered_functions
96
+ elif self.tool_name is not None:
97
+ tool = await self.load_tool(tool_name=self.tool_name)
98
+ # Replace functions dict with just this single tool
99
+ self.functions = {tool.name: tool}
100
+ except Exception as e:
101
+ raise RuntimeError(f"Failed to connect to ToolboxClient: {e}") from e
102
+
103
+ def _handle_auth_params(
104
+ self,
105
+ auth_token_getters: dict[str, Callable[[], str]] = {},
106
+ auth_tokens: Optional[dict[str, Callable[[], str]]] = None,
107
+ auth_headers: Optional[dict[str, Callable[[], str]]] = None,
108
+ ):
109
+ """handle authentication parameters for toolbox-core client"""
110
+ if auth_tokens:
111
+ if auth_token_getters:
112
+ warn(
113
+ "Both `auth_token_getters` and `auth_tokens` are provided. `auth_tokens` is deprecated, and `auth_token_getters` will be used.",
114
+ DeprecationWarning,
115
+ )
116
+ else:
117
+ warn(
118
+ "Argument `auth_tokens` is deprecated. Use `auth_token_getters` instead.",
119
+ DeprecationWarning,
120
+ )
121
+ auth_token_getters = auth_tokens
122
+
123
+ if auth_headers:
124
+ if auth_token_getters:
125
+ warn(
126
+ "Both `auth_token_getters` and `auth_headers` are provided. `auth_headers` is deprecated, and `auth_token_getters` will be used.",
127
+ DeprecationWarning,
128
+ )
129
+ else:
130
+ warn(
131
+ "Argument `auth_headers` is deprecated. Use `auth_token_getters` instead.",
132
+ DeprecationWarning,
133
+ )
134
+ auth_token_getters = auth_headers
135
+ return auth_token_getters
136
+
137
+ async def load_tool(
138
+ self,
139
+ tool_name: str,
140
+ auth_token_getters: dict[str, Callable[[], str]] = {},
141
+ auth_tokens: Optional[dict[str, Callable[[], str]]] = None,
142
+ auth_headers: Optional[dict[str, Callable[[], str]]] = None,
143
+ bound_params: dict[str, Union[Any, Callable[[], Any]]] = {},
144
+ ) -> Function:
145
+ """Loads the tool with the given tool name from the Toolbox service.
146
+
147
+ Args:
148
+ tool_name (str): The name of the tool to load.
149
+ auth_token_getters (dict[str, Callable[[], str]], optional): A mapping of authentication source names to functions that retrieve ID tokens. Defaults to {}.
150
+ auth_tokens (Optional[dict[str, Callable[[], str]]], optional): Deprecated. Use `auth_token_getters` instead.
151
+ auth_headers (Optional[dict[str, Callable[[], str]]], optional): Deprecated. Use `auth_token_getters` instead.
152
+ bound_params (dict[str, Union[Any, Callable[[], Any]]], optional): A mapping of parameter names to their bound values. Defaults to {}.
153
+
154
+ Raises:
155
+ RuntimeError: If the tool is not found in the MCP functions registry.
156
+
157
+ Returns:
158
+ Function: The loaded tool function.
159
+ """
160
+ auth_token_getters = self._handle_auth_params(
161
+ auth_token_getters=auth_token_getters,
162
+ auth_tokens=auth_tokens,
163
+ auth_headers=auth_headers,
164
+ )
165
+
166
+ core_sync_tool = await self.__core_client.load_tool(
167
+ name=tool_name,
168
+ auth_token_getters=auth_token_getters,
169
+ bound_params=bound_params,
170
+ )
171
+ # Return the Function object from our MCP functions registry
172
+ if core_sync_tool._name in self.functions:
173
+ return self.functions[core_sync_tool._name]
174
+ else:
175
+ raise RuntimeError(f"Tool '{tool_name}' was not found in MCP functions registry")
176
+
177
+ async def load_toolset(
178
+ self,
179
+ toolset_name: Optional[str] = None,
180
+ auth_token_getters: dict[str, Callable[[], str]] = {},
181
+ auth_tokens: Optional[dict[str, Callable[[], str]]] = None,
182
+ auth_headers: Optional[dict[str, Callable[[], str]]] = None,
183
+ bound_params: dict[str, Union[Any, Callable[[], Any]]] = {},
184
+ strict: bool = False,
185
+ ) -> List[Function]:
186
+ """Loads tools from the configured toolset.
187
+
188
+ Args:
189
+ toolset_name (Optional[str], optional): The name of the toolset to load. Defaults to None.
190
+ auth_token_getters (dict[str, Callable[[], str]], optional): A mapping of authentication source names to functions that retrieve ID tokens. Defaults to {}.
191
+ auth_tokens (Optional[dict[str, Callable[[], str]]], optional): Deprecated. Use `auth_token_getters` instead.
192
+ auth_headers (Optional[dict[str, Callable[[], str]]], optional): Deprecated. Use `auth_token_getters` instead.
193
+ bound_params (dict[str, Union[Any, Callable[[], Any]]], optional): A mapping of parameter names to their bound values. Defaults to {}.
194
+ strict (bool, optional): If True, raises an error if *any* loaded tool instance fails
195
+ to utilize all of the given parameters or auth tokens. (if any
196
+ provided). If False (default), raises an error only if a
197
+ user-provided parameter or auth token cannot be applied to *any*
198
+ loaded tool across the set.
199
+
200
+ Returns:
201
+ List[Function]: A list of all tools loaded from the Toolbox.
202
+ """
203
+ auth_token_getters = self._handle_auth_params(
204
+ auth_token_getters=auth_token_getters,
205
+ auth_tokens=auth_tokens,
206
+ auth_headers=auth_headers,
207
+ )
208
+
209
+ core_sync_tools = await self.__core_client.load_toolset(
210
+ name=toolset_name,
211
+ auth_token_getters=auth_token_getters,
212
+ bound_params=bound_params,
213
+ strict=strict,
214
+ )
215
+
216
+ tools = []
217
+ for core_sync_tool in core_sync_tools:
218
+ if core_sync_tool._name in self.functions:
219
+ tools.append(self.functions[core_sync_tool._name])
220
+ else:
221
+ logger.debug(f"Tool '{core_sync_tool._name}' from toolset '{toolset_name}' not available in MCP server")
222
+ return tools
223
+
224
+ async def load_multiple_toolsets(
225
+ self,
226
+ toolset_names: List[str],
227
+ auth_token_getters: dict[str, Callable[[], str]] = {},
228
+ bound_params: dict[str, Union[Any, Callable[[], Any]]] = {},
229
+ strict: bool = False,
230
+ ) -> List[Function]:
231
+ """Load tools from multiple toolsets.
232
+
233
+ Args:
234
+ toolset_names (List[str]): A list of toolset names to load.
235
+ auth_token_getters (dict[str, Callable[[], str]], optional): A mapping of authentication source names to functions that retrieve ID tokens. Defaults to {}.
236
+ bound_params (dict[str, Union[Any, Callable[[], Any]]], optional): A mapping of parameter names to their bound values. Defaults to {}.
237
+ strict (bool, optional): If True, raises an error if *any* loaded tool instance fails to utilize all of the given parameters or auth tokens. Defaults to False.
238
+
239
+ Returns:
240
+ List[Function]: A list of all tools loaded from the specified toolsets.
241
+ """
242
+ all_tools = []
243
+ for toolset_name in toolset_names:
244
+ tools = await self.load_toolset(
245
+ toolset_name=toolset_name,
246
+ auth_token_getters=auth_token_getters,
247
+ bound_params=bound_params,
248
+ strict=strict,
249
+ )
250
+ all_tools.extend(tools)
251
+ return all_tools
252
+
253
+ async def close(self):
254
+ """Close the underlying asynchronous client."""
255
+ if self._core_client_initialized and hasattr(self, "_MCPToolbox__core_client"):
256
+ await self.__core_client.close()
257
+ await super().close()
258
+
259
+ async def load_toolset_safe(self, toolset_name: str) -> List[str]:
260
+ """Safely load a toolset and return tool names."""
261
+ try:
262
+ tools = await self.load_toolset(toolset_name)
263
+ return [tool.name for tool in tools]
264
+ except Exception as e:
265
+ raise RuntimeError(f"Failed to load toolset '{toolset_name}': {e}") from e
266
+
267
+ def get_client(self) -> ToolboxClient:
268
+ """Get the underlying ToolboxClient."""
269
+ if not self._core_client_initialized:
270
+ raise RuntimeError("ToolboxClient not initialized. Call connect() first.")
271
+ return self.__core_client
272
+
273
+ async def __aenter__(self):
274
+ """Initialize the direct toolbox client."""
275
+ await super().__aenter__()
276
+ await self.connect()
277
+ return self
278
+
279
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
280
+ """Clean up the toolbox client."""
281
+ # Close ToolboxClient first, then MCP client
282
+ if self._core_client_initialized and hasattr(self, "_MCPToolbox__core_client"):
283
+ await self.__core_client.close()
284
+ await super().__aexit__(exc_type, exc_val, exc_tb)