agno 2.2.1__py3-none-any.whl → 2.2.3__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 (69) hide show
  1. agno/agent/agent.py +735 -574
  2. agno/culture/manager.py +22 -24
  3. agno/db/async_postgres/__init__.py +1 -1
  4. agno/db/dynamo/dynamo.py +0 -2
  5. agno/db/firestore/firestore.py +0 -2
  6. agno/db/gcs_json/gcs_json_db.py +0 -4
  7. agno/db/gcs_json/utils.py +0 -24
  8. agno/db/in_memory/in_memory_db.py +0 -3
  9. agno/db/json/json_db.py +4 -10
  10. agno/db/json/utils.py +0 -24
  11. agno/db/mongo/__init__.py +15 -1
  12. agno/db/mongo/async_mongo.py +1999 -0
  13. agno/db/mongo/mongo.py +0 -2
  14. agno/db/mysql/mysql.py +0 -3
  15. agno/db/postgres/__init__.py +1 -1
  16. agno/db/{async_postgres → postgres}/async_postgres.py +19 -22
  17. agno/db/postgres/postgres.py +7 -10
  18. agno/db/postgres/utils.py +106 -2
  19. agno/db/redis/redis.py +0 -2
  20. agno/db/singlestore/singlestore.py +0 -3
  21. agno/db/sqlite/__init__.py +2 -1
  22. agno/db/sqlite/async_sqlite.py +2269 -0
  23. agno/db/sqlite/sqlite.py +0 -2
  24. agno/db/sqlite/utils.py +96 -0
  25. agno/db/surrealdb/surrealdb.py +0 -6
  26. agno/knowledge/knowledge.py +3 -3
  27. agno/knowledge/reader/reader_factory.py +16 -0
  28. agno/knowledge/reader/tavily_reader.py +194 -0
  29. agno/memory/manager.py +28 -25
  30. agno/models/anthropic/claude.py +63 -6
  31. agno/models/base.py +251 -32
  32. agno/models/response.py +69 -0
  33. agno/os/router.py +7 -5
  34. agno/os/routers/memory/memory.py +2 -1
  35. agno/os/routers/memory/schemas.py +5 -2
  36. agno/os/schema.py +25 -20
  37. agno/os/utils.py +9 -2
  38. agno/run/agent.py +23 -30
  39. agno/run/base.py +17 -1
  40. agno/run/team.py +23 -29
  41. agno/run/workflow.py +17 -12
  42. agno/session/agent.py +3 -0
  43. agno/session/summary.py +4 -1
  44. agno/session/team.py +1 -1
  45. agno/team/team.py +599 -367
  46. agno/tools/dalle.py +2 -4
  47. agno/tools/eleven_labs.py +23 -25
  48. agno/tools/function.py +40 -0
  49. agno/tools/mcp/__init__.py +10 -0
  50. agno/tools/mcp/mcp.py +324 -0
  51. agno/tools/mcp/multi_mcp.py +347 -0
  52. agno/tools/mcp/params.py +24 -0
  53. agno/tools/slack.py +18 -3
  54. agno/tools/tavily.py +146 -0
  55. agno/utils/agent.py +366 -1
  56. agno/utils/mcp.py +92 -2
  57. agno/utils/media.py +166 -1
  58. agno/utils/print_response/workflow.py +17 -1
  59. agno/utils/team.py +89 -1
  60. agno/workflow/step.py +0 -1
  61. agno/workflow/types.py +10 -15
  62. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/METADATA +28 -25
  63. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/RECORD +66 -62
  64. agno/db/async_postgres/schemas.py +0 -139
  65. agno/db/async_postgres/utils.py +0 -347
  66. agno/tools/mcp.py +0 -679
  67. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/WHEEL +0 -0
  68. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/licenses/LICENSE +0 -0
  69. {agno-2.2.1.dist-info → agno-2.2.3.dist-info}/top_level.txt +0 -0
agno/tools/dalle.py CHANGED
@@ -1,10 +1,8 @@
1
1
  from os import getenv
2
- from typing import Any, List, Literal, Optional, Union
2
+ from typing import Any, List, Literal, Optional
3
3
  from uuid import uuid4
4
4
 
5
- from agno.agent import Agent
6
5
  from agno.media import Image
7
- from agno.team.team import Team
8
6
  from agno.tools import Toolkit
9
7
  from agno.tools.function import ToolResult
10
8
  from agno.utils.log import log_debug, logger
@@ -64,7 +62,7 @@ class DalleTools(Toolkit):
64
62
  # - Add support for saving images
65
63
  # - Add support for editing images
66
64
 
67
- def create_image(self, agent: Union[Agent, Team], prompt: str) -> ToolResult:
65
+ def create_image(self, prompt: str) -> ToolResult:
68
66
  """Use this function to generate an image for a prompt.
69
67
 
70
68
  Args:
agno/tools/eleven_labs.py CHANGED
@@ -1,4 +1,3 @@
1
- from base64 import b64encode
2
1
  from io import BytesIO
3
2
  from os import getenv, path
4
3
  from pathlib import Path
@@ -10,7 +9,7 @@ from agno.media import Audio
10
9
  from agno.team.team import Team
11
10
  from agno.tools import Toolkit
12
11
  from agno.tools.function import ToolResult
13
- from agno.utils.log import logger
12
+ from agno.utils.log import log_error, log_info
14
13
 
15
14
  try:
16
15
  from elevenlabs import ElevenLabs # type: ignore
@@ -48,7 +47,7 @@ class ElevenLabsTools(Toolkit):
48
47
  ):
49
48
  self.api_key = api_key or getenv("ELEVEN_LABS_API_KEY")
50
49
  if not self.api_key:
51
- logger.error("ELEVEN_LABS_API_KEY not set. Please set the ELEVEN_LABS_API_KEY environment variable.")
50
+ log_error("ELEVEN_LABS_API_KEY not set. Please set the ELEVEN_LABS_API_KEY environment variable.")
52
51
 
53
52
  self.target_directory = target_directory
54
53
  self.voice_id = voice_id
@@ -73,7 +72,7 @@ class ElevenLabsTools(Toolkit):
73
72
 
74
73
  def get_voices(self) -> str:
75
74
  """
76
- Use this function to get all the voices available.
75
+ Get all the voices available.
77
76
 
78
77
  Returns:
79
78
  result (list): A list of voices that have an ID, name and description.
@@ -94,20 +93,19 @@ class ElevenLabsTools(Toolkit):
94
93
  return str(response)
95
94
 
96
95
  except Exception as e:
97
- logger.error(f"Failed to fetch voices: {e}")
96
+ log_error(f"Failed to fetch voices: {e}")
98
97
  return f"Error: {e}"
99
98
 
100
- def _process_audio(self, audio_generator: Iterator[bytes]) -> str:
101
- # Step 1: Write audio data to BytesIO
99
+ def _process_audio(self, audio_generator: Iterator[bytes]) -> bytes:
102
100
  audio_bytes = BytesIO()
103
101
  for chunk in audio_generator:
104
102
  audio_bytes.write(chunk)
105
- audio_bytes.seek(0) # Rewind the stream
106
103
 
107
- # Step 2: Encode as Base64
108
- base64_audio = b64encode(audio_bytes.read()).decode("utf-8")
104
+ # Read bytes
105
+ audio_bytes.seek(0)
106
+ audio_data = audio_bytes.read()
109
107
 
110
- # Step 3: Optionally save to disk if target_directory exists
108
+ # Save to disk if target_directory exists
111
109
  if self.target_directory:
112
110
  # Determine file extension based on output format
113
111
  if self.output_format.startswith("mp3"):
@@ -122,19 +120,19 @@ class ElevenLabsTools(Toolkit):
122
120
  output_filename = f"{uuid4()}.{extension}"
123
121
  output_path = path.join(self.target_directory, output_filename)
124
122
 
125
- # Write from BytesIO to disk
126
- audio_bytes.seek(0) # Reset the BytesIO stream again
127
123
  with open(output_path, "wb") as f:
128
- f.write(audio_bytes.read())
124
+ f.write(audio_data)
129
125
 
130
- return base64_audio
126
+ log_info(f"Audio saved to: {output_path}")
127
+
128
+ return audio_data
131
129
 
132
130
  def generate_sound_effect(self, prompt: str, duration_seconds: Optional[float] = None) -> ToolResult:
133
131
  """
134
- Use this function to generate sound effect audio from a text prompt.
132
+ Generate a sound effect from a text description.
135
133
 
136
134
  Args:
137
- prompt (str): Text to generate audio from.
135
+ prompt (str): Description of the sound effect
138
136
  duration_seconds (Optional[float]): Duration in seconds to generate audio from. Has to be between 0.5 and 22.
139
137
  Returns:
140
138
  ToolResult: A ToolResult containing the generated audio or error message.
@@ -144,27 +142,27 @@ class ElevenLabsTools(Toolkit):
144
142
  text=prompt, duration_seconds=duration_seconds
145
143
  )
146
144
 
147
- base64_audio = self._process_audio(audio_generator)
145
+ audio_data = self._process_audio(audio_generator)
148
146
 
149
147
  # Create AudioArtifact
150
148
  audio_artifact = Audio(
151
149
  id=str(uuid4()),
152
- base64_audio=base64_audio,
150
+ content=audio_data,
153
151
  mime_type="audio/mpeg",
154
152
  )
155
153
 
156
154
  return ToolResult(
157
- content="Audio generated successfully",
155
+ content="Sound effect generated successfully",
158
156
  audios=[audio_artifact],
159
157
  )
160
158
 
161
159
  except Exception as e:
162
- logger.error(f"Failed to generate audio: {e}")
160
+ log_error(f"Failed to generate sound effect: {e}")
163
161
  return ToolResult(content=f"Error: {e}")
164
162
 
165
163
  def text_to_speech(self, agent: Union[Agent, Team], prompt: str) -> ToolResult:
166
164
  """
167
- Use this function to convert text to speech audio.
165
+ Convert text to speech.
168
166
 
169
167
  Args:
170
168
  prompt (str): Text to generate audio from.
@@ -179,12 +177,12 @@ class ElevenLabsTools(Toolkit):
179
177
  output_format=self.output_format,
180
178
  )
181
179
 
182
- base64_audio = self._process_audio(audio_generator)
180
+ audio_data = self._process_audio(audio_generator)
183
181
 
184
182
  # Create AudioArtifact
185
183
  audio_artifact = Audio(
186
184
  id=str(uuid4()),
187
- base64_audio=base64_audio,
185
+ content=audio_data,
188
186
  mime_type="audio/mpeg",
189
187
  )
190
188
 
@@ -194,5 +192,5 @@ class ElevenLabsTools(Toolkit):
194
192
  )
195
193
 
196
194
  except Exception as e:
197
- logger.error(f"Failed to generate audio: {e}")
195
+ log_error(f"Failed to generate audio: {e}")
198
196
  return ToolResult(content=f"Error: {e}")
agno/tools/function.py CHANGED
@@ -139,6 +139,46 @@ class Function(BaseModel):
139
139
  include={"name", "description", "parameters", "strict", "requires_confirmation", "external_execution"},
140
140
  )
141
141
 
142
+ def model_copy(self, *, deep: bool = False) -> "Function":
143
+ """
144
+ Override model_copy to handle callable fields that can't be deep copied (pickled).
145
+ Callables should always be shallow copied (referenced), not deep copied.
146
+ """
147
+ # For deep copy, we need to handle callable fields specially
148
+ if deep:
149
+ # Fields that should NOT be deep copied (callables and complex objects)
150
+ shallow_fields = {
151
+ "entrypoint",
152
+ "pre_hook",
153
+ "post_hook",
154
+ "tool_hooks",
155
+ "_agent",
156
+ "_team",
157
+ }
158
+
159
+ # Create a copy with shallow references to callable fields
160
+ copied_data = {}
161
+ for field_name, field_value in self.__dict__.items():
162
+ if field_name in shallow_fields:
163
+ # Shallow copy - just reference the same object
164
+ copied_data[field_name] = field_value
165
+ elif field_name == "parameters":
166
+ # Deep copy the parameters dict
167
+ from copy import deepcopy
168
+
169
+ copied_data[field_name] = deepcopy(field_value)
170
+ else:
171
+ # For simple types, just copy the value
172
+ copied_data[field_name] = field_value
173
+
174
+ # Create new instance with copied data
175
+ new_instance = self.__class__.model_construct(**copied_data)
176
+
177
+ return new_instance
178
+ else:
179
+ # For shallow copy, use the default Pydantic behavior
180
+ return super().model_copy(deep=False)
181
+
142
182
  @classmethod
143
183
  def from_callable(cls, c: Callable, name: Optional[str] = None, strict: bool = False) -> "Function":
144
184
  from inspect import getdoc, signature
@@ -0,0 +1,10 @@
1
+ from agno.tools.mcp.mcp import MCPTools
2
+ from agno.tools.mcp.multi_mcp import MultiMCPTools
3
+ from agno.tools.mcp.params import SSEClientParams, StreamableHTTPClientParams
4
+
5
+ __all__ = [
6
+ "MCPTools",
7
+ "MultiMCPTools",
8
+ "StreamableHTTPClientParams",
9
+ "SSEClientParams",
10
+ ]
agno/tools/mcp/mcp.py ADDED
@@ -0,0 +1,324 @@
1
+ import weakref
2
+ from dataclasses import asdict
3
+ from datetime import timedelta
4
+ from typing import Any, Literal, Optional, Union
5
+
6
+ from agno.tools import Toolkit
7
+ from agno.tools.function import Function
8
+ from agno.tools.mcp.params import SSEClientParams, StreamableHTTPClientParams
9
+ from agno.utils.log import log_debug, log_error, log_info
10
+ from agno.utils.mcp import get_entrypoint_for_tool, prepare_command
11
+
12
+ try:
13
+ from mcp import ClientSession, StdioServerParameters
14
+ from mcp.client.sse import sse_client
15
+ from mcp.client.stdio import get_default_environment, stdio_client
16
+ from mcp.client.streamable_http import streamablehttp_client
17
+ except (ImportError, ModuleNotFoundError):
18
+ raise ImportError("`mcp` not installed. Please install using `pip install mcp`")
19
+
20
+
21
+ class MCPTools(Toolkit):
22
+ """
23
+ A toolkit for integrating Model Context Protocol (MCP) servers with Agno agents.
24
+ This allows agents to access tools, resources, and prompts exposed by MCP servers.
25
+
26
+ Can be used in three ways:
27
+ 1. Direct initialization with a ClientSession
28
+ 2. As an async context manager with StdioServerParameters
29
+ 3. As an async context manager with SSE or Streamable HTTP client parameters
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ command: Optional[str] = None,
35
+ *,
36
+ url: Optional[str] = None,
37
+ env: Optional[dict[str, str]] = None,
38
+ transport: Literal["stdio", "sse", "streamable-http"] = "stdio",
39
+ server_params: Optional[Union[StdioServerParameters, SSEClientParams, StreamableHTTPClientParams]] = None,
40
+ session: Optional[ClientSession] = None,
41
+ timeout_seconds: int = 10,
42
+ client=None,
43
+ include_tools: Optional[list[str]] = None,
44
+ exclude_tools: Optional[list[str]] = None,
45
+ refresh_connection: bool = False,
46
+ **kwargs,
47
+ ):
48
+ """
49
+ Initialize the MCP toolkit.
50
+
51
+ Args:
52
+ session: An initialized MCP ClientSession connected to an MCP server
53
+ server_params: Parameters for creating a new session
54
+ command: The command to run to start the server. Should be used in conjunction with env.
55
+ url: The URL endpoint for SSE or Streamable HTTP connection when transport is "sse" or "streamable-http".
56
+ env: The environment variables to pass to the server. Should be used in conjunction with command.
57
+ client: The underlying MCP client (optional, used to prevent garbage collection)
58
+ timeout_seconds: Read timeout in seconds for the MCP client
59
+ include_tools: Optional list of tool names to include (if None, includes all)
60
+ exclude_tools: Optional list of tool names to exclude (if None, excludes none)
61
+ transport: The transport protocol to use, either "stdio" or "sse" or "streamable-http"
62
+ refresh_connection: If True, the connection and tools will be refreshed on each run
63
+ """
64
+ super().__init__(name="MCPTools", **kwargs)
65
+
66
+ if transport == "sse":
67
+ log_info("SSE as a standalone transport is deprecated. Please use Streamable HTTP instead.")
68
+
69
+ # Set these after `__init__` to bypass the `_check_tools_filters`
70
+ # because tools are not available until `initialize()` is called.
71
+ self.include_tools = include_tools
72
+ self.exclude_tools = exclude_tools
73
+ self.refresh_connection = refresh_connection
74
+
75
+ if session is None and server_params is None:
76
+ if transport == "sse" and url is None:
77
+ raise ValueError("One of 'url' or 'server_params' parameters must be provided when using SSE transport")
78
+ if transport == "stdio" and command is None:
79
+ raise ValueError(
80
+ "One of 'command' or 'server_params' parameters must be provided when using stdio transport"
81
+ )
82
+ if transport == "streamable-http" and url is None:
83
+ raise ValueError(
84
+ "One of 'url' or 'server_params' parameters must be provided when using Streamable HTTP transport"
85
+ )
86
+
87
+ # Ensure the received server_params are valid for the given transport
88
+ if server_params is not None:
89
+ if transport == "sse":
90
+ if not isinstance(server_params, SSEClientParams):
91
+ raise ValueError(
92
+ "If using the SSE transport, server_params must be an instance of SSEClientParams."
93
+ )
94
+ elif transport == "stdio":
95
+ if not isinstance(server_params, StdioServerParameters):
96
+ raise ValueError(
97
+ "If using the stdio transport, server_params must be an instance of StdioServerParameters."
98
+ )
99
+ elif transport == "streamable-http":
100
+ if not isinstance(server_params, StreamableHTTPClientParams):
101
+ raise ValueError(
102
+ "If using the streamable-http transport, server_params must be an instance of StreamableHTTPClientParams."
103
+ )
104
+
105
+ self.timeout_seconds = timeout_seconds
106
+ self.session: Optional[ClientSession] = session
107
+ self.server_params: Optional[Union[StdioServerParameters, SSEClientParams, StreamableHTTPClientParams]] = (
108
+ server_params
109
+ )
110
+ self.transport = transport
111
+ self.url = url
112
+
113
+ # Merge provided env with system env
114
+ if env is not None:
115
+ env = {
116
+ **get_default_environment(),
117
+ **env,
118
+ }
119
+ else:
120
+ env = get_default_environment()
121
+
122
+ if command is not None and transport not in ["sse", "streamable-http"]:
123
+ parts = prepare_command(command)
124
+ cmd = parts[0]
125
+ arguments = parts[1:] if len(parts) > 1 else []
126
+ self.server_params = StdioServerParameters(command=cmd, args=arguments, env=env)
127
+
128
+ self._client = client
129
+
130
+ self._initialized = False
131
+ self._connection_task = None
132
+ self._active_contexts: list[Any] = []
133
+ self._context = None
134
+ self._session_context = None
135
+
136
+ def cleanup():
137
+ """Cancel active connections"""
138
+ if self._connection_task and not self._connection_task.done():
139
+ self._connection_task.cancel()
140
+
141
+ # Setup cleanup logic before the instance is garbage collected
142
+ self._cleanup_finalizer = weakref.finalize(self, cleanup)
143
+
144
+ @property
145
+ def initialized(self) -> bool:
146
+ return self._initialized
147
+
148
+ async def is_alive(self) -> bool:
149
+ if self.session is None:
150
+ return False
151
+ try:
152
+ await self.session.send_ping()
153
+ return True
154
+ except (RuntimeError, BaseException):
155
+ return False
156
+
157
+ async def connect(self, force: bool = False):
158
+ """Initialize a MCPTools instance and connect to the contextual MCP server"""
159
+
160
+ if force:
161
+ # Clean up the session and context so we force a new connection
162
+ self.session = None
163
+ self._context = None
164
+ self._session_context = None
165
+ self._initialized = False
166
+ self._connection_task = None
167
+ self._active_contexts = []
168
+
169
+ if self._initialized:
170
+ return
171
+
172
+ try:
173
+ await self._connect()
174
+ except (RuntimeError, BaseException) as e:
175
+ log_error(f"Failed to connect to {str(self)}: {e}")
176
+
177
+ async def _connect(self) -> None:
178
+ """Connects to the MCP server and initializes the tools"""
179
+
180
+ if self._initialized:
181
+ return
182
+
183
+ if self.session is not None:
184
+ await self.initialize()
185
+ return
186
+
187
+ # Create a new studio session
188
+ if self.transport == "sse":
189
+ sse_params = asdict(self.server_params) if self.server_params is not None else {} # type: ignore
190
+ if "url" not in sse_params:
191
+ sse_params["url"] = self.url
192
+ self._context = sse_client(**sse_params) # type: ignore
193
+ client_timeout = min(self.timeout_seconds, sse_params.get("timeout", self.timeout_seconds))
194
+
195
+ # Create a new streamable HTTP session
196
+ elif self.transport == "streamable-http":
197
+ streamable_http_params = asdict(self.server_params) if self.server_params is not None else {} # type: ignore
198
+ if "url" not in streamable_http_params:
199
+ streamable_http_params["url"] = self.url
200
+ self._context = streamablehttp_client(**streamable_http_params) # type: ignore
201
+ params_timeout = streamable_http_params.get("timeout", self.timeout_seconds)
202
+ if isinstance(params_timeout, timedelta):
203
+ params_timeout = int(params_timeout.total_seconds())
204
+ client_timeout = min(self.timeout_seconds, params_timeout)
205
+
206
+ else:
207
+ if self.server_params is None:
208
+ raise ValueError("server_params must be provided when using stdio transport.")
209
+ self._context = stdio_client(self.server_params) # type: ignore
210
+ client_timeout = self.timeout_seconds
211
+
212
+ session_params = await self._context.__aenter__() # type: ignore
213
+ self._active_contexts.append(self._context)
214
+ read, write = session_params[0:2]
215
+
216
+ self._session_context = ClientSession(read, write, read_timeout_seconds=timedelta(seconds=client_timeout)) # type: ignore
217
+ self.session = await self._session_context.__aenter__() # type: ignore
218
+ self._active_contexts.append(self._session_context)
219
+
220
+ # Initialize with the new session
221
+ await self.initialize()
222
+
223
+ async def close(self) -> None:
224
+ """Close the MCP connection and clean up resources"""
225
+ if not self._initialized:
226
+ return
227
+
228
+ try:
229
+ if self._session_context is not None:
230
+ await self._session_context.__aexit__(None, None, None)
231
+ self.session = None
232
+ self._session_context = None
233
+
234
+ if self._context is not None:
235
+ await self._context.__aexit__(None, None, None)
236
+ self._context = None
237
+ except (RuntimeError, BaseException) as e:
238
+ log_error(f"Failed to close MCP connection: {e}")
239
+
240
+ self._initialized = False
241
+
242
+ async def __aenter__(self) -> "MCPTools":
243
+ await self._connect()
244
+ return self
245
+
246
+ async def __aexit__(self, _exc_type, _exc_val, _exc_tb):
247
+ """Exit the async context manager."""
248
+ if self._session_context is not None:
249
+ await self._session_context.__aexit__(_exc_type, _exc_val, _exc_tb)
250
+ self.session = None
251
+ self._session_context = None
252
+
253
+ if self._context is not None:
254
+ await self._context.__aexit__(_exc_type, _exc_val, _exc_tb)
255
+ self._context = None
256
+
257
+ self._initialized = False
258
+
259
+ async def build_tools(self) -> None:
260
+ """Build the tools for the MCP toolkit"""
261
+ if self.session is None:
262
+ raise ValueError("Session is not initialized")
263
+
264
+ try:
265
+ # Get the list of tools from the MCP server
266
+ available_tools = await self.session.list_tools() # type: ignore
267
+
268
+ self._check_tools_filters(
269
+ available_tools=[tool.name for tool in available_tools.tools],
270
+ include_tools=self.include_tools,
271
+ exclude_tools=self.exclude_tools,
272
+ )
273
+
274
+ # Filter tools based on include/exclude lists
275
+ filtered_tools = []
276
+ for tool in available_tools.tools:
277
+ if self.exclude_tools and tool.name in self.exclude_tools:
278
+ continue
279
+ if self.include_tools is None or tool.name in self.include_tools:
280
+ filtered_tools.append(tool)
281
+
282
+ # Register the tools with the toolkit
283
+ for tool in filtered_tools:
284
+ try:
285
+ # Get an entrypoint for the tool
286
+ entrypoint = get_entrypoint_for_tool(tool, self.session) # type: ignore
287
+ # Create a Function for the tool
288
+ f = Function(
289
+ name=tool.name,
290
+ description=tool.description,
291
+ parameters=tool.inputSchema,
292
+ entrypoint=entrypoint,
293
+ # Set skip_entrypoint_processing to True to avoid processing the entrypoint
294
+ skip_entrypoint_processing=True,
295
+ )
296
+
297
+ # Register the Function with the toolkit
298
+ self.functions[f.name] = f
299
+ log_debug(f"Function: {f.name} registered with {self.name}")
300
+ except Exception as e:
301
+ log_error(f"Failed to register tool {tool.name}: {e}")
302
+
303
+ except (RuntimeError, BaseException) as e:
304
+ log_error(f"Failed to get tools for {str(self)}: {e}")
305
+ raise
306
+
307
+ async def initialize(self) -> None:
308
+ """Initialize the MCP toolkit by getting available tools from the MCP server"""
309
+ if self._initialized:
310
+ return
311
+
312
+ try:
313
+ if self.session is None:
314
+ raise ValueError("Session is not initialized")
315
+
316
+ # Initialize the session if not already initialized
317
+ await self.session.initialize()
318
+
319
+ await self.build_tools()
320
+
321
+ self._initialized = True
322
+
323
+ except (RuntimeError, BaseException) as e:
324
+ log_error(f"Failed to initialize MCP toolkit: {e}")