ag2 0.9.7__py3-none-any.whl → 0.9.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.

Potentially problematic release.


This version of ag2 might be problematic. Click here for more details.

Files changed (236) hide show
  1. {ag2-0.9.7.dist-info → ag2-0.9.9.dist-info}/METADATA +102 -75
  2. ag2-0.9.9.dist-info/RECORD +387 -0
  3. autogen/__init__.py +1 -2
  4. autogen/_website/generate_api_references.py +4 -5
  5. autogen/_website/generate_mkdocs.py +9 -15
  6. autogen/_website/notebook_processor.py +13 -14
  7. autogen/_website/process_notebooks.py +10 -10
  8. autogen/_website/utils.py +5 -4
  9. autogen/agentchat/agent.py +13 -13
  10. autogen/agentchat/assistant_agent.py +7 -6
  11. autogen/agentchat/contrib/agent_eval/agent_eval.py +3 -3
  12. autogen/agentchat/contrib/agent_eval/critic_agent.py +3 -3
  13. autogen/agentchat/contrib/agent_eval/quantifier_agent.py +3 -3
  14. autogen/agentchat/contrib/agent_eval/subcritic_agent.py +3 -3
  15. autogen/agentchat/contrib/agent_optimizer.py +3 -3
  16. autogen/agentchat/contrib/capabilities/generate_images.py +11 -11
  17. autogen/agentchat/contrib/capabilities/teachability.py +15 -15
  18. autogen/agentchat/contrib/capabilities/transforms.py +17 -18
  19. autogen/agentchat/contrib/capabilities/transforms_util.py +5 -5
  20. autogen/agentchat/contrib/capabilities/vision_capability.py +4 -3
  21. autogen/agentchat/contrib/captainagent/agent_builder.py +30 -30
  22. autogen/agentchat/contrib/captainagent/captainagent.py +22 -21
  23. autogen/agentchat/contrib/captainagent/tool_retriever.py +2 -3
  24. autogen/agentchat/contrib/gpt_assistant_agent.py +9 -9
  25. autogen/agentchat/contrib/graph_rag/document.py +3 -3
  26. autogen/agentchat/contrib/graph_rag/falkor_graph_query_engine.py +3 -3
  27. autogen/agentchat/contrib/graph_rag/falkor_graph_rag_capability.py +6 -6
  28. autogen/agentchat/contrib/graph_rag/graph_query_engine.py +3 -3
  29. autogen/agentchat/contrib/graph_rag/neo4j_graph_query_engine.py +5 -11
  30. autogen/agentchat/contrib/graph_rag/neo4j_graph_rag_capability.py +6 -6
  31. autogen/agentchat/contrib/graph_rag/neo4j_native_graph_query_engine.py +7 -7
  32. autogen/agentchat/contrib/graph_rag/neo4j_native_graph_rag_capability.py +6 -6
  33. autogen/agentchat/contrib/img_utils.py +1 -1
  34. autogen/agentchat/contrib/llamaindex_conversable_agent.py +11 -11
  35. autogen/agentchat/contrib/llava_agent.py +18 -4
  36. autogen/agentchat/contrib/math_user_proxy_agent.py +11 -11
  37. autogen/agentchat/contrib/multimodal_conversable_agent.py +8 -8
  38. autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py +6 -5
  39. autogen/agentchat/contrib/rag/chromadb_query_engine.py +22 -26
  40. autogen/agentchat/contrib/rag/llamaindex_query_engine.py +14 -17
  41. autogen/agentchat/contrib/rag/mongodb_query_engine.py +27 -37
  42. autogen/agentchat/contrib/rag/query_engine.py +7 -5
  43. autogen/agentchat/contrib/retrieve_assistant_agent.py +5 -5
  44. autogen/agentchat/contrib/retrieve_user_proxy_agent.py +8 -7
  45. autogen/agentchat/contrib/society_of_mind_agent.py +15 -14
  46. autogen/agentchat/contrib/swarm_agent.py +76 -98
  47. autogen/agentchat/contrib/text_analyzer_agent.py +7 -7
  48. autogen/agentchat/contrib/vectordb/base.py +10 -18
  49. autogen/agentchat/contrib/vectordb/chromadb.py +2 -1
  50. autogen/agentchat/contrib/vectordb/couchbase.py +18 -20
  51. autogen/agentchat/contrib/vectordb/mongodb.py +6 -5
  52. autogen/agentchat/contrib/vectordb/pgvectordb.py +40 -41
  53. autogen/agentchat/contrib/vectordb/qdrant.py +5 -5
  54. autogen/agentchat/contrib/web_surfer.py +20 -19
  55. autogen/agentchat/conversable_agent.py +292 -290
  56. autogen/agentchat/group/context_str.py +1 -3
  57. autogen/agentchat/group/context_variables.py +15 -25
  58. autogen/agentchat/group/group_tool_executor.py +10 -10
  59. autogen/agentchat/group/group_utils.py +15 -15
  60. autogen/agentchat/group/guardrails.py +7 -7
  61. autogen/agentchat/group/handoffs.py +19 -36
  62. autogen/agentchat/group/multi_agent_chat.py +7 -7
  63. autogen/agentchat/group/on_condition.py +4 -7
  64. autogen/agentchat/group/on_context_condition.py +4 -7
  65. autogen/agentchat/group/patterns/auto.py +8 -7
  66. autogen/agentchat/group/patterns/manual.py +7 -6
  67. autogen/agentchat/group/patterns/pattern.py +13 -12
  68. autogen/agentchat/group/patterns/random.py +3 -3
  69. autogen/agentchat/group/patterns/round_robin.py +3 -3
  70. autogen/agentchat/group/reply_result.py +2 -4
  71. autogen/agentchat/group/speaker_selection_result.py +5 -5
  72. autogen/agentchat/group/targets/group_chat_target.py +7 -6
  73. autogen/agentchat/group/targets/group_manager_target.py +4 -4
  74. autogen/agentchat/group/targets/transition_target.py +2 -1
  75. autogen/agentchat/groupchat.py +60 -63
  76. autogen/agentchat/realtime/experimental/audio_adapters/twilio_audio_adapter.py +4 -4
  77. autogen/agentchat/realtime/experimental/audio_adapters/websocket_audio_adapter.py +4 -4
  78. autogen/agentchat/realtime/experimental/clients/gemini/client.py +7 -7
  79. autogen/agentchat/realtime/experimental/clients/oai/base_client.py +8 -8
  80. autogen/agentchat/realtime/experimental/clients/oai/rtc_client.py +6 -6
  81. autogen/agentchat/realtime/experimental/clients/realtime_client.py +10 -9
  82. autogen/agentchat/realtime/experimental/realtime_agent.py +10 -9
  83. autogen/agentchat/realtime/experimental/realtime_observer.py +3 -3
  84. autogen/agentchat/realtime/experimental/realtime_swarm.py +44 -44
  85. autogen/agentchat/user_proxy_agent.py +10 -9
  86. autogen/agentchat/utils.py +3 -3
  87. autogen/agents/contrib/time/time_reply_agent.py +6 -5
  88. autogen/agents/contrib/time/time_tool_agent.py +2 -1
  89. autogen/agents/experimental/deep_research/deep_research.py +3 -3
  90. autogen/agents/experimental/discord/discord.py +2 -2
  91. autogen/agents/experimental/document_agent/chroma_query_engine.py +29 -44
  92. autogen/agents/experimental/document_agent/docling_doc_ingest_agent.py +9 -14
  93. autogen/agents/experimental/document_agent/document_agent.py +15 -16
  94. autogen/agents/experimental/document_agent/document_conditions.py +3 -3
  95. autogen/agents/experimental/document_agent/document_utils.py +5 -9
  96. autogen/agents/experimental/document_agent/inmemory_query_engine.py +14 -20
  97. autogen/agents/experimental/document_agent/parser_utils.py +4 -4
  98. autogen/agents/experimental/document_agent/url_utils.py +14 -23
  99. autogen/agents/experimental/reasoning/reasoning_agent.py +33 -33
  100. autogen/agents/experimental/slack/slack.py +2 -2
  101. autogen/agents/experimental/telegram/telegram.py +2 -3
  102. autogen/agents/experimental/websurfer/websurfer.py +4 -4
  103. autogen/agents/experimental/wikipedia/wikipedia.py +5 -7
  104. autogen/browser_utils.py +8 -8
  105. autogen/cache/abstract_cache_base.py +5 -5
  106. autogen/cache/cache.py +12 -12
  107. autogen/cache/cache_factory.py +4 -4
  108. autogen/cache/cosmos_db_cache.py +9 -9
  109. autogen/cache/disk_cache.py +6 -6
  110. autogen/cache/in_memory_cache.py +4 -4
  111. autogen/cache/redis_cache.py +4 -4
  112. autogen/code_utils.py +18 -18
  113. autogen/coding/base.py +6 -6
  114. autogen/coding/docker_commandline_code_executor.py +9 -9
  115. autogen/coding/func_with_reqs.py +7 -6
  116. autogen/coding/jupyter/base.py +3 -3
  117. autogen/coding/jupyter/docker_jupyter_server.py +3 -4
  118. autogen/coding/jupyter/import_utils.py +3 -3
  119. autogen/coding/jupyter/jupyter_client.py +5 -5
  120. autogen/coding/jupyter/jupyter_code_executor.py +3 -4
  121. autogen/coding/jupyter/local_jupyter_server.py +2 -6
  122. autogen/coding/local_commandline_code_executor.py +8 -7
  123. autogen/coding/markdown_code_extractor.py +1 -2
  124. autogen/coding/utils.py +1 -2
  125. autogen/doc_utils.py +3 -2
  126. autogen/environments/docker_python_environment.py +19 -29
  127. autogen/environments/python_environment.py +8 -17
  128. autogen/environments/system_python_environment.py +3 -4
  129. autogen/environments/venv_python_environment.py +8 -12
  130. autogen/environments/working_directory.py +1 -2
  131. autogen/events/agent_events.py +106 -109
  132. autogen/events/base_event.py +6 -5
  133. autogen/events/client_events.py +15 -14
  134. autogen/events/helpers.py +1 -1
  135. autogen/events/print_event.py +4 -5
  136. autogen/fast_depends/_compat.py +10 -15
  137. autogen/fast_depends/core/build.py +17 -36
  138. autogen/fast_depends/core/model.py +64 -113
  139. autogen/fast_depends/dependencies/model.py +2 -1
  140. autogen/fast_depends/dependencies/provider.py +3 -2
  141. autogen/fast_depends/library/model.py +4 -4
  142. autogen/fast_depends/schema.py +7 -7
  143. autogen/fast_depends/use.py +17 -25
  144. autogen/fast_depends/utils.py +10 -30
  145. autogen/formatting_utils.py +6 -6
  146. autogen/graph_utils.py +1 -4
  147. autogen/import_utils.py +38 -27
  148. autogen/interop/crewai/crewai.py +2 -2
  149. autogen/interop/interoperable.py +2 -2
  150. autogen/interop/langchain/langchain_chat_model_factory.py +3 -2
  151. autogen/interop/langchain/langchain_tool.py +2 -6
  152. autogen/interop/litellm/litellm_config_factory.py +6 -7
  153. autogen/interop/pydantic_ai/pydantic_ai.py +4 -7
  154. autogen/interop/registry.py +2 -1
  155. autogen/io/base.py +5 -5
  156. autogen/io/run_response.py +33 -32
  157. autogen/io/websockets.py +6 -5
  158. autogen/json_utils.py +1 -2
  159. autogen/llm_config/__init__.py +11 -0
  160. autogen/llm_config/client.py +58 -0
  161. autogen/llm_config/config.py +384 -0
  162. autogen/llm_config/entry.py +154 -0
  163. autogen/logger/base_logger.py +4 -3
  164. autogen/logger/file_logger.py +2 -1
  165. autogen/logger/logger_factory.py +2 -2
  166. autogen/logger/logger_utils.py +2 -2
  167. autogen/logger/sqlite_logger.py +2 -1
  168. autogen/math_utils.py +4 -5
  169. autogen/mcp/__main__.py +6 -6
  170. autogen/mcp/helpers.py +4 -4
  171. autogen/mcp/mcp_client.py +170 -29
  172. autogen/mcp/mcp_proxy/fastapi_code_generator_helpers.py +3 -4
  173. autogen/mcp/mcp_proxy/mcp_proxy.py +23 -26
  174. autogen/mcp/mcp_proxy/operation_grouping.py +4 -5
  175. autogen/mcp/mcp_proxy/operation_renaming.py +6 -10
  176. autogen/mcp/mcp_proxy/security.py +2 -3
  177. autogen/messages/agent_messages.py +96 -98
  178. autogen/messages/base_message.py +6 -5
  179. autogen/messages/client_messages.py +15 -14
  180. autogen/messages/print_message.py +4 -5
  181. autogen/oai/__init__.py +1 -2
  182. autogen/oai/anthropic.py +42 -41
  183. autogen/oai/bedrock.py +68 -57
  184. autogen/oai/cerebras.py +26 -25
  185. autogen/oai/client.py +113 -139
  186. autogen/oai/client_utils.py +3 -3
  187. autogen/oai/cohere.py +34 -11
  188. autogen/oai/gemini.py +39 -17
  189. autogen/oai/gemini_types.py +11 -12
  190. autogen/oai/groq.py +22 -10
  191. autogen/oai/mistral.py +17 -11
  192. autogen/oai/oai_models/__init__.py +14 -2
  193. autogen/oai/oai_models/_models.py +2 -2
  194. autogen/oai/oai_models/chat_completion.py +13 -14
  195. autogen/oai/oai_models/chat_completion_message.py +11 -9
  196. autogen/oai/oai_models/chat_completion_message_tool_call.py +26 -3
  197. autogen/oai/oai_models/chat_completion_token_logprob.py +3 -4
  198. autogen/oai/oai_models/completion_usage.py +8 -9
  199. autogen/oai/ollama.py +19 -9
  200. autogen/oai/openai_responses.py +40 -17
  201. autogen/oai/openai_utils.py +48 -38
  202. autogen/oai/together.py +29 -14
  203. autogen/retrieve_utils.py +6 -7
  204. autogen/runtime_logging.py +5 -4
  205. autogen/token_count_utils.py +7 -4
  206. autogen/tools/contrib/time/time.py +0 -1
  207. autogen/tools/dependency_injection.py +5 -6
  208. autogen/tools/experimental/browser_use/browser_use.py +10 -10
  209. autogen/tools/experimental/code_execution/python_code_execution.py +5 -7
  210. autogen/tools/experimental/crawl4ai/crawl4ai.py +12 -15
  211. autogen/tools/experimental/deep_research/deep_research.py +9 -8
  212. autogen/tools/experimental/duckduckgo/duckduckgo_search.py +5 -11
  213. autogen/tools/experimental/firecrawl/firecrawl_tool.py +98 -115
  214. autogen/tools/experimental/google/authentication/credentials_local_provider.py +1 -1
  215. autogen/tools/experimental/google/drive/drive_functions.py +4 -4
  216. autogen/tools/experimental/google/drive/toolkit.py +5 -5
  217. autogen/tools/experimental/google_search/google_search.py +5 -5
  218. autogen/tools/experimental/google_search/youtube_search.py +5 -5
  219. autogen/tools/experimental/messageplatform/discord/discord.py +8 -12
  220. autogen/tools/experimental/messageplatform/slack/slack.py +14 -20
  221. autogen/tools/experimental/messageplatform/telegram/telegram.py +8 -12
  222. autogen/tools/experimental/perplexity/perplexity_search.py +18 -29
  223. autogen/tools/experimental/reliable/reliable.py +68 -74
  224. autogen/tools/experimental/searxng/searxng_search.py +20 -19
  225. autogen/tools/experimental/tavily/tavily_search.py +12 -19
  226. autogen/tools/experimental/web_search_preview/web_search_preview.py +13 -7
  227. autogen/tools/experimental/wikipedia/wikipedia.py +7 -10
  228. autogen/tools/function_utils.py +7 -7
  229. autogen/tools/tool.py +8 -6
  230. autogen/types.py +2 -2
  231. autogen/version.py +1 -1
  232. ag2-0.9.7.dist-info/RECORD +0 -421
  233. autogen/llm_config.py +0 -385
  234. {ag2-0.9.7.dist-info → ag2-0.9.9.dist-info}/WHEEL +0 -0
  235. {ag2-0.9.7.dist-info → ag2-0.9.9.dist-info}/licenses/LICENSE +0 -0
  236. {ag2-0.9.7.dist-info → ag2-0.9.9.dist-info}/licenses/NOTICE.md +0 -0
autogen/mcp/__main__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
  import logging
5
- from typing import Annotated, Literal, Optional
5
+ from typing import Annotated, Literal
6
6
 
7
7
  from .. import __version__
8
8
  from ..import_utils import optional_import_block, require_optional_import
@@ -27,7 +27,7 @@ def create_typer_app() -> "typer.Typer":
27
27
  @app.callback()
28
28
  def callback(
29
29
  version: Annotated[
30
- Optional[bool],
30
+ bool | None,
31
31
  typer.Option("--version", help="Show the version and exit.", callback=version_callback),
32
32
  ] = None,
33
33
  ) -> None:
@@ -41,19 +41,19 @@ def create_typer_app() -> "typer.Typer":
41
41
  @app.command()
42
42
  def create(
43
43
  openapi_specification: Annotated[
44
- Optional[str],
44
+ str | None,
45
45
  "Specification of the OpenAPI to use for the proxy generation.",
46
46
  ] = None,
47
47
  openapi_url: Annotated[
48
- Optional[str],
48
+ str | None,
49
49
  "URL to the OpenAPI specification to use for the proxy generation.",
50
50
  ] = None,
51
51
  client_source_path: Annotated[
52
- Optional[str],
52
+ str | None,
53
53
  "Path to the generated proxy client source code.",
54
54
  ] = None,
55
55
  server_url: Annotated[
56
- Optional[str],
56
+ str | None,
57
57
  "Comma-separated list of server URLs to use for the proxy generation.",
58
58
  ] = None,
59
59
  configuration_type: Annotated[
autogen/mcp/helpers.py CHANGED
@@ -5,21 +5,21 @@ import asyncio
5
5
  import os
6
6
  import signal
7
7
  from asyncio.subprocess import PIPE, Process, create_subprocess_exec
8
+ from collections.abc import AsyncGenerator
8
9
  from contextlib import asynccontextmanager
9
- from typing import AsyncGenerator, Dict, Optional
10
10
 
11
11
 
12
12
  @asynccontextmanager
13
13
  async def run_streamable_http_client(
14
- *, mcp_server_path: str, env_vars: Optional[Dict[str, str]] = None, startup_wait_secs: float = 5.0
14
+ *, mcp_server_path: str, env_vars: dict[str, str] | None = None, startup_wait_secs: float = 5.0
15
15
  ) -> AsyncGenerator[Process, None]:
16
- """
17
- Async context manager to run a Python subprocess for streamable-http with custom env vars.
16
+ """Async context manager to run a Python subprocess for streamable-http with custom env vars.
18
17
 
19
18
  Args:
20
19
  mcp_server_path: Path to the Python script to run.
21
20
  env_vars: Environment variables to export to the subprocess.
22
21
  startup_wait_secs: Time to wait for the server to start (in seconds).
22
+
23
23
  Yields:
24
24
  An asyncio.subprocess.Process object.
25
25
  """
autogen/mcp/mcp_client.py CHANGED
@@ -3,38 +3,153 @@
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
5
 
6
- import sys
7
- from datetime import datetime
6
+ from collections.abc import AsyncIterator
7
+ from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager
8
+ from datetime import datetime, timedelta
8
9
  from pathlib import Path
9
- from typing import Annotated, Any, Optional, Union
10
+ from typing import Annotated, Any, Literal, Protocol, cast
10
11
 
11
12
  import anyio
12
- from pydantic import BaseModel
13
+ from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
14
+ from mcp.client.session import ClientSession
15
+ from mcp.client.sse import sse_client
16
+ from mcp.client.stdio import StdioServerParameters, stdio_client
17
+ from pydantic import AnyUrl, BaseModel, Field
13
18
 
14
19
  from ..doc_utils import export_module
15
20
  from ..import_utils import optional_import_block, require_optional_import
16
21
  from ..tools import Tool, Toolkit
17
22
 
18
23
  with optional_import_block():
19
- from mcp import ClientSession
20
- from mcp.types import (
21
- CallToolResult,
22
- ReadResourceResult,
23
- ResourceTemplate,
24
- TextContent,
25
- )
26
- from mcp.types import (
27
- Tool as MCPTool,
28
- )
24
+ from mcp.shared.message import SessionMessage
25
+ from mcp.types import CallToolResult, ReadResourceResult, ResourceTemplate, TextContent
26
+ from mcp.types import Tool as MCPTool
29
27
 
30
28
  __all__ = ["ResultSaved", "create_toolkit"]
31
29
 
30
+ # Type definitions
31
+ EncodingErrorHandlerType = Literal["strict", "ignore", "replace"]
32
+
33
+ # Default constants
34
+ DEFAULT_TEXT_ENCODING = "utf-8"
35
+ DEFAULT_TEXT_ENCODING_ERROR_HANDLER: EncodingErrorHandlerType = "strict"
36
+ DEFAULT_HTTP_REQUEST_TIMEOUT = 5
37
+ DEFAULT_SSE_EVENT_READ_TIMEOUT = 60 * 5
38
+ DEFAULT_STREAMABLE_HTTP_REQUEST_TIMEOUT = timedelta(seconds=30)
39
+ DEFAULT_STREAMABLE_HTTP_SSE_EVENT_READ_TIMEOUT = timedelta(seconds=60 * 5)
40
+
41
+
42
+ class SessionConfigProtocol(Protocol):
43
+ """Protocol for session configuration classes that can create MCP sessions."""
44
+
45
+ server_name: str
46
+
47
+ @asynccontextmanager
48
+ async def create_session(self, exit_stack: AsyncExitStack) -> AsyncIterator[ClientSession]:
49
+ """Create a session using the given exit stack."""
50
+ try:
51
+ yield cast(ClientSession, None) # placeholder yield to satisfy AsyncIterator type
52
+ except Exception:
53
+ raise NotImplementedError
54
+
55
+
56
+ class BasicSessionConfig(BaseModel):
57
+ """Basic session configuration."""
58
+
59
+ server_name: str = Field(..., description="Name of the server")
60
+
61
+ async def initialize(
62
+ self,
63
+ client: AbstractAsyncContextManager[
64
+ tuple[
65
+ MemoryObjectReceiveStream[SessionMessage | Exception],
66
+ MemoryObjectSendStream[SessionMessage],
67
+ ]
68
+ ],
69
+ exit_stack: AsyncExitStack,
70
+ ) -> ClientSession:
71
+ """Initialize the session."""
72
+ reader, writer = await exit_stack.enter_async_context(client)
73
+ session = cast(
74
+ ClientSession,
75
+ await exit_stack.enter_async_context(ClientSession(reader, writer)),
76
+ )
77
+ return session
78
+
79
+
80
+ class SseConfig(BasicSessionConfig):
81
+ """Configuration for a single SSE MCP server."""
82
+
83
+ url: str = Field(..., description="URL of the SSE server")
84
+ headers: dict[str, Any] | None = Field(default=None, description="HTTP headers to send to the SSE endpoint")
85
+ timeout: float = Field(default=DEFAULT_HTTP_REQUEST_TIMEOUT, description="HTTP timeout")
86
+ sse_read_timeout: float = Field(default=DEFAULT_SSE_EVENT_READ_TIMEOUT, description="SSE read timeout")
87
+
88
+ @asynccontextmanager
89
+ async def create_session(self, exit_stack: AsyncExitStack) -> AsyncIterator[ClientSession]:
90
+ """
91
+ Create a new session to an MCP server using SSE transport.
92
+
93
+ Args:
94
+ exit_stack: AsyncExitStack for managing async resources
95
+
96
+ Yields:
97
+ ClientSession: The MCP client session
98
+ """
99
+ # Create and store the connection
100
+ client = sse_client(self.url, self.headers, self.timeout, self.sse_read_timeout)
101
+ yield await self.initialize(client, exit_stack)
102
+
103
+
104
+ class StdioConfig(BasicSessionConfig):
105
+ """Configuration for a single stdio MCP server."""
106
+
107
+ command: str = Field(..., description="Command to execute")
108
+ args: list[str] = Field(..., description="Arguments for the command")
109
+ transport: Literal["stdio"] = Field(default="stdio", description="Transport type")
110
+ environment: dict[str, str] | None = Field(default=None, description="Environment variables")
111
+ working_dir: str | Path | None = Field(default=None, description="Working directory")
112
+ encoding: str = Field(default=DEFAULT_TEXT_ENCODING, description="Character encoding")
113
+ encoding_error_handler: EncodingErrorHandlerType = Field(
114
+ default=DEFAULT_TEXT_ENCODING_ERROR_HANDLER, description="How to handle encoding errors"
115
+ )
116
+ session_options: dict[str, Any] | None = Field(default=None, description="Additional session options")
117
+
118
+ @asynccontextmanager
119
+ async def create_session(self, exit_stack: AsyncExitStack) -> AsyncIterator[ClientSession]:
120
+ """
121
+ Create a new session to an MCP server using stdio transport.
122
+
123
+ Args:
124
+ exit_stack: AsyncExitStack for managing async resources
125
+
126
+ Yields:
127
+ ClientSession: The MCP client session
128
+ """
129
+ client = stdio_client(
130
+ StdioServerParameters(
131
+ command=self.command,
132
+ args=self.args,
133
+ env=self.environment,
134
+ encoding=self.encoding,
135
+ encoding_error_handler=self.encoding_error_handler,
136
+ )
137
+ )
138
+ yield await self.initialize(client, exit_stack)
139
+
140
+
141
+ class MCPConfig(BaseModel):
142
+ """Configuration for multiple MCP sessions using stdio transport."""
143
+
144
+ # we should use final classes to allow pydantic to validate the type
145
+ servers: list[SseConfig | StdioConfig] = Field(..., description="List of stdio & sse server configurations")
146
+
32
147
 
33
148
  class MCPClient:
34
149
  @staticmethod
35
150
  def _convert_call_tool_result( # type: ignore[no-any-unimported]
36
151
  call_tool_result: "CallToolResult", # type: ignore[no-any-unimported]
37
- ) -> tuple[Union[str, list[str]], Any]:
152
+ ) -> tuple[str | list[str], Any]:
38
153
  text_contents: list[TextContent] = [] # type: ignore[no-any-unimported]
39
154
  non_text_contents = []
40
155
  for content in call_tool_result.content:
@@ -43,7 +158,7 @@ class MCPClient:
43
158
  else:
44
159
  non_text_contents.append(content)
45
160
 
46
- tool_content: Union[str, list[str]] = [content.text for content in text_contents]
161
+ tool_content: str | list[str] = [content.text for content in text_contents]
47
162
  if len(text_contents) == 1:
48
163
  tool_content = tool_content[0]
49
164
 
@@ -65,7 +180,7 @@ class MCPClient:
65
180
 
66
181
  async def call_tool( # type: ignore[no-any-unimported]
67
182
  **arguments: dict[str, Any],
68
- ) -> tuple[Union[str, list[str]], Any]:
183
+ ) -> tuple[str | list[str], Any]:
69
184
  call_tool_result = await session.call_tool(tool.name, arguments)
70
185
  return MCPClient._convert_call_tool_result(call_tool_result)
71
186
 
@@ -83,7 +198,7 @@ class MCPClient:
83
198
  cls,
84
199
  resource_template: Any,
85
200
  session: "ClientSession",
86
- resource_download_folder: Optional[Path],
201
+ resource_download_folder: Path | None,
87
202
  **kwargs: Any,
88
203
  ) -> Tool:
89
204
  if not isinstance(resource_template, ResourceTemplate):
@@ -97,8 +212,8 @@ Here is the correct format for the URI template:
97
212
  {mcp_resource.uriTemplate}
98
213
  """
99
214
 
100
- async def call_resource(uri: Annotated[str, uri_description]) -> Union[ReadResourceResult, ResultSaved]: # type: ignore[no-any-unimported]
101
- result = await session.read_resource(uri)
215
+ async def call_resource(uri: Annotated[str, uri_description]) -> ReadResourceResult | ResultSaved: # type: ignore[no-any-unimported]
216
+ result = await session.read_resource(AnyUrl(uri))
102
217
 
103
218
  if not resource_download_folder:
104
219
  return result
@@ -131,7 +246,7 @@ Here is the correct format for the URI template:
131
246
  *,
132
247
  use_mcp_tools: bool,
133
248
  use_mcp_resources: bool,
134
- resource_download_folder: Optional[Path],
249
+ resource_download_folder: Path | None,
135
250
  ) -> Toolkit: # type: ignore[no-any-unimported]
136
251
  """Load all available MCP tools and convert them to AG2 Toolkit."""
137
252
  all_ag2_tools: list[Tool] = []
@@ -156,10 +271,7 @@ Here is the correct format for the URI template:
156
271
  return Toolkit(tools=all_ag2_tools)
157
272
 
158
273
  @classmethod
159
- def get_unsupported_reason(cls) -> Optional[str]:
160
- if sys.version_info < (3, 10):
161
- return "This submodule is only supported for Python versions 3.10 and above"
162
-
274
+ def get_unsupported_reason(cls) -> str | None:
163
275
  with optional_import_block() as result:
164
276
  import mcp # noqa: F401
165
277
 
@@ -169,13 +281,42 @@ Here is the correct format for the URI template:
169
281
  return None
170
282
 
171
283
 
284
+ class MCPClientSessionManager:
285
+ """
286
+ A class to manage MCP client sessions.
287
+ """
288
+
289
+ def __init__(self) -> None:
290
+ """Initialize the MCP client session manager."""
291
+ self.exit_stack = AsyncExitStack()
292
+ self.sessions: dict[str, ClientSession] = {}
293
+
294
+ @asynccontextmanager
295
+ async def open_session(
296
+ self,
297
+ config: SessionConfigProtocol,
298
+ ) -> AsyncIterator[ClientSession]:
299
+ """Open a new session to an MCP server based on configuration.
300
+
301
+ Args:
302
+ config: SessionConfigProtocol object containing session configuration
303
+
304
+ Yields:
305
+ ClientSession: The MCP client session
306
+ """
307
+ async with config.create_session(self.exit_stack) as session:
308
+ await session.initialize()
309
+ self.sessions[config.server_name] = session
310
+ yield session
311
+
312
+
172
313
  @export_module("autogen.mcp")
173
314
  async def create_toolkit(
174
315
  session: "ClientSession",
175
316
  *,
176
317
  use_mcp_tools: bool = True,
177
318
  use_mcp_resources: bool = True,
178
- resource_download_folder: Optional[Union[Path, str]] = None,
319
+ resource_download_folder: Path | str | None = None,
179
320
  ) -> Toolkit: # type: ignore[no-any-unimported]
180
321
  """Create a toolkit from the MCP client session.
181
322
 
@@ -184,12 +325,12 @@ async def create_toolkit(
184
325
  use_mcp_tools (bool): Whether to include MCP tools in the toolkit.
185
326
  use_mcp_resources (bool): Whether to include MCP resources in the toolkit.
186
327
  resource_download_folder (Optional[Union[Path, str]]): The folder to download files to.
328
+
187
329
  Returns:
188
330
  Toolkit: The toolkit containing the converted tools.
189
331
  """
190
- if resource_download_folder:
191
- if isinstance(resource_download_folder, str):
192
- resource_download_folder = Path(resource_download_folder)
332
+ if resource_download_folder is not None:
333
+ resource_download_folder = Path(resource_download_folder)
193
334
  await anyio.to_thread.run_sync(lambda: resource_download_folder.mkdir(parents=True, exist_ok=True))
194
335
 
195
336
  return await MCPClient.load_mcp_toolkit(
@@ -4,7 +4,6 @@
4
4
  from collections.abc import Iterator
5
5
  from contextlib import contextmanager
6
6
  from functools import cached_property
7
- from typing import Optional, Union
8
7
 
9
8
  from ...import_utils import optional_import_block
10
9
 
@@ -24,7 +23,7 @@ __all__ = ["SUCCESFUL_IMPORT", "patch_get_parameter_type"]
24
23
  @contextmanager
25
24
  def patch_get_parameter_type() -> Iterator[None]:
26
25
  class ArgumentWithDescription(Argument): # type: ignore[misc]
27
- description: Optional[str] = None
26
+ description: str | None = None
28
27
 
29
28
  @cached_property
30
29
  def argument(self) -> str:
@@ -43,10 +42,10 @@ def patch_get_parameter_type() -> Iterator[None]:
43
42
 
44
43
  def get_parameter_type(
45
44
  self: OpenAPIParser,
46
- parameters: Union[ReferenceObject, ParameterObject],
45
+ parameters: ReferenceObject | ParameterObject,
47
46
  snake_case: bool,
48
47
  path: list[str],
49
- ) -> Optional[Argument]:
48
+ ) -> Argument | None:
50
49
  # get the original argument
51
50
  argument = original_get_parameter_type(self, parameters, snake_case, path)
52
51
 
@@ -8,7 +8,7 @@ import json
8
8
  import re
9
9
  import sys
10
10
  import tempfile
11
- from collections.abc import Iterable, Iterator, Mapping
11
+ from collections.abc import Callable, Iterable, Iterator, Mapping
12
12
  from contextlib import contextmanager
13
13
  from functools import wraps
14
14
  from logging import getLogger
@@ -17,10 +17,7 @@ from types import ModuleType
17
17
  from typing import (
18
18
  TYPE_CHECKING,
19
19
  Any,
20
- Callable,
21
20
  Literal,
22
- Optional,
23
- Union,
24
21
  )
25
22
 
26
23
  import requests
@@ -49,7 +46,7 @@ logger = getLogger(__name__)
49
46
 
50
47
 
51
48
  @contextmanager
52
- def optional_temp_path(path: Optional[str] = None) -> Iterator[Path]:
49
+ def optional_temp_path(path: str | None = None) -> Iterator[Path]:
53
50
  if path is None:
54
51
  with tempfile.TemporaryDirectory() as temp_dir:
55
52
  yield Path(temp_dir)
@@ -74,7 +71,7 @@ def add_to_builtins(new_globals: dict[str, Any]) -> Iterator[None]:
74
71
 
75
72
 
76
73
  class MCPProxy:
77
- def __init__(self, servers: list[dict[str, Any]], title: Optional[str] = None, **kwargs: Any) -> None:
74
+ def __init__(self, servers: list[dict[str, Any]], title: str | None = None, **kwargs: Any) -> None:
78
75
  """Proxy class to generate client from OpenAPI schema."""
79
76
  self._servers = servers
80
77
  self._title = title or "MCP Proxy"
@@ -83,7 +80,7 @@ class MCPProxy:
83
80
  self._globals: dict[str, Any] = {}
84
81
 
85
82
  self._security: dict[str, list[BaseSecurity]] = {}
86
- self._security_params: dict[Optional[str], BaseSecurityParameters] = {}
83
+ self._security_params: dict[str | None, BaseSecurityParameters] = {}
87
84
  self._tags: set[str] = set()
88
85
 
89
86
  self._function_group: dict[str, list[str]] = {}
@@ -100,7 +97,7 @@ class MCPProxy:
100
97
  return result
101
98
 
102
99
  @staticmethod
103
- def _get_params(path: str, func: Callable[..., Any]) -> tuple[set[str], set[str], Optional[str], bool]:
100
+ def _get_params(path: str, func: Callable[..., Any]) -> tuple[set[str], set[str], str | None, bool]:
104
101
  sig = inspect.signature(func)
105
102
 
106
103
  params_names = set(sig.parameters.keys())
@@ -118,11 +115,11 @@ class MCPProxy:
118
115
  return q_params, path_params, body, security
119
116
 
120
117
  def get_mcp(self, **settings: Any) -> "FastMCP":
121
- mcp = FastMCP(title=self._title, **settings)
118
+ mcp = FastMCP(name=self._title, **settings) # newer mcp
122
119
 
123
120
  for func in self._registered_funcs:
124
121
  try:
125
- mcp.tool()(func) # type: ignore [no-untyped-call]
122
+ mcp.tool()(func)
126
123
  except PydanticInvalidForJsonSchema as e:
127
124
  logger.warning("Could not register function %s: %s", func.__name__, e)
128
125
 
@@ -157,7 +154,7 @@ class MCPProxy:
157
154
 
158
155
  return url, params, body_dict
159
156
 
160
- def set_security_params(self, security_params: BaseSecurityParameters, name: Optional[str] = None) -> None:
157
+ def set_security_params(self, security_params: BaseSecurityParameters, name: str | None = None) -> None:
161
158
  if name is not None:
162
159
  security = self._security.get(name)
163
160
  if security is None:
@@ -180,7 +177,7 @@ class MCPProxy:
180
177
  return match_security
181
178
  raise ValueError(f"Security parameters {security_params} does not match any given security {security}")
182
179
 
183
- def _get_security_params(self, name: str) -> tuple[Optional[BaseSecurityParameters], Optional[BaseSecurity]]:
180
+ def _get_security_params(self, name: str) -> tuple[BaseSecurityParameters | None, BaseSecurity | None]:
184
181
  # check if security is set for the method
185
182
  security = self._security.get(name)
186
183
  if not security:
@@ -203,8 +200,8 @@ class MCPProxy:
203
200
  self,
204
201
  method: Literal["put", "get", "post", "head", "delete", "patch"],
205
202
  path: str,
206
- description: Optional[str] = None,
207
- security: Optional[list[BaseSecurity]] = None,
203
+ description: str | None = None,
204
+ security: list[BaseSecurity] | None = None,
208
205
  **kwargs: Any,
209
206
  ) -> Callable[..., dict[str, Any]]:
210
207
  def decorator(func: Callable[..., Any]) -> Callable[..., dict[str, Any]]:
@@ -275,7 +272,7 @@ class MCPProxy:
275
272
  input_text: str,
276
273
  output_dir: Path,
277
274
  disable_timestamp: bool = False,
278
- custom_visitors: Optional[list[Path]] = None,
275
+ custom_visitors: list[Path] | None = None,
279
276
  ) -> str:
280
277
  if custom_visitors is None:
281
278
  custom_visitors = []
@@ -318,10 +315,10 @@ class MCPProxy:
318
315
  def create(
319
316
  cls,
320
317
  *,
321
- openapi_specification: Optional[str] = None,
322
- openapi_url: Optional[str] = None,
323
- client_source_path: Optional[str] = None,
324
- servers: Optional[list[dict[str, Any]]] = None,
318
+ openapi_specification: str | None = None,
319
+ openapi_url: str | None = None,
320
+ client_source_path: str | None = None,
321
+ servers: list[dict[str, Any]] | None = None,
325
322
  rename_functions: bool = False,
326
323
  group_functions: bool = False,
327
324
  configuration_type: Literal["json", "yaml"] = "json",
@@ -392,7 +389,7 @@ class MCPProxy:
392
389
 
393
390
  def dump_configurations(self, output_dir: Path) -> None:
394
391
  for tag in self._function_group:
395
- output_file = output_dir / "mcp_config_{}.json".format(tag)
392
+ output_file = output_dir / f"mcp_config_{tag}.json"
396
393
 
397
394
  functions = [
398
395
  registered_function
@@ -458,8 +455,8 @@ class MCPProxy:
458
455
 
459
456
  def _get_functions_to_register(
460
457
  self,
461
- functions: Optional[Iterable[Union[str, Mapping[str, Mapping[str, str]]]]] = None,
462
- ) -> dict[Callable[..., Any], dict[str, Union[str, None]]]:
458
+ functions: Iterable[str | Mapping[str, Mapping[str, str]]] | None = None,
459
+ ) -> dict[Callable[..., Any], dict[str, str | None]]:
463
460
  if functions is None:
464
461
  return {
465
462
  f: {
@@ -469,7 +466,7 @@ class MCPProxy:
469
466
  for f in self._registered_funcs
470
467
  }
471
468
 
472
- functions_with_name_desc: dict[str, dict[str, Union[str, None]]] = {}
469
+ functions_with_name_desc: dict[str, dict[str, str | None]] = {}
473
470
 
474
471
  for f in functions:
475
472
  if isinstance(f, str):
@@ -485,7 +482,7 @@ class MCPProxy:
485
482
  else:
486
483
  raise ValueError(f"Invalid type {type(f)} for function {f}")
487
484
 
488
- funcs_to_register: dict[Callable[..., Any], dict[str, Union[str, None]]] = {
485
+ funcs_to_register: dict[Callable[..., Any], dict[str, str | None]] = {
489
486
  f: functions_with_name_desc[f.__name__]
490
487
  for f in self._registered_funcs
491
488
  if f.__name__ in functions_with_name_desc
@@ -528,7 +525,7 @@ class MCPProxy:
528
525
  def _register_for_llm(
529
526
  self,
530
527
  agent: "ConversableAgent",
531
- functions: Optional[Iterable[Union[str, Mapping[str, Mapping[str, str]]]]] = None,
528
+ functions: Iterable[str | Mapping[str, Mapping[str, str]]] | None = None,
532
529
  ) -> None:
533
530
  funcs_to_register = self._get_functions_to_register(functions)
534
531
 
@@ -543,7 +540,7 @@ class MCPProxy:
543
540
  def _register_for_execution(
544
541
  self,
545
542
  agent: "ConversableAgent",
546
- functions: Optional[Iterable[Union[str, Mapping[str, Mapping[str, str]]]]] = None,
543
+ functions: Iterable[str | Mapping[str, Mapping[str, str]]] | None = None,
547
544
  ) -> None:
548
545
  funcs_to_register = self._get_functions_to_register(functions)
549
546
 
@@ -5,7 +5,6 @@
5
5
  import json
6
6
  import logging
7
7
  from pathlib import Path
8
- from typing import Dict, List
9
8
 
10
9
  from autogen.import_utils import optional_import_block
11
10
 
@@ -58,11 +57,11 @@ GROUP_ASSIGNMENT_MESSAGE = (
58
57
  )
59
58
 
60
59
 
61
- def chunk_list(items: List, size: int) -> List[List]:
60
+ def chunk_list(items: list, size: int) -> list[list]:
62
61
  return [items[i : i + size] for i in range(0, len(items), size)]
63
62
 
64
63
 
65
- def discover_groups(operations: List["Operation"], chunk_size: int = 30) -> Dict[str, str]:
64
+ def discover_groups(operations: list["Operation"], chunk_size: int = 30) -> dict[str, str]:
66
65
  llm_config = LLMConfig.get_current_llm_config().copy()
67
66
 
68
67
  for config in llm_config.config_list:
@@ -130,13 +129,13 @@ def assign_operation_to_group(operation: "Operation", groups: dict[str, str]) ->
130
129
  return groups
131
130
 
132
131
 
133
- def refine_group_names(groups: Dict[str, str]) -> Dict[str, str]:
132
+ def refine_group_names(groups: dict[str, str]) -> dict[str, str]:
134
133
  # Optional: normalize names, merge similar ones (e.g., using embeddings or string similarity)
135
134
  # Placeholder for now:
136
135
  return groups
137
136
 
138
137
 
139
- def custom_visitor(parser: "OpenAPIParser", model_path: Path) -> Dict[str, object]:
138
+ def custom_visitor(parser: "OpenAPIParser", model_path: Path) -> dict[str, object]:
140
139
  operations = sorted(parser.operations.values(), key=lambda op: op.path)
141
140
 
142
141
  # ---- PASS 1: DISCOVER GROUPS ----
@@ -4,7 +4,6 @@
4
4
 
5
5
  import logging
6
6
  from pathlib import Path
7
- from typing import Dict, List
8
7
 
9
8
  from autogen.import_utils import optional_import_block
10
9
 
@@ -28,9 +27,8 @@ SYSTEM_MESSAGE = (
28
27
  )
29
28
 
30
29
 
31
- def validate_function_name(name: str, taken_names: List[str]) -> str:
32
- """
33
- Validate the generated function name against length, format, and uniqueness constraints.
30
+ def validate_function_name(name: str, taken_names: list[str]) -> str:
31
+ """Validate the generated function name against length, format, and uniqueness constraints.
34
32
 
35
33
  Returns:
36
34
  'exit' if the name is valid, or an error message string otherwise.
@@ -44,9 +42,8 @@ def validate_function_name(name: str, taken_names: List[str]) -> str:
44
42
  return "exit"
45
43
 
46
44
 
47
- def get_new_function_name(operation: "Operation", taken_names: List[str]) -> str:
48
- """
49
- Ask an AI agent to generate a new function name for a given OpenAPI operation.
45
+ def get_new_function_name(operation: "Operation", taken_names: list[str]) -> str:
46
+ """Ask an AI agent to generate a new function name for a given OpenAPI operation.
50
47
 
51
48
  Args:
52
49
  operation: The OpenAPI operation that needs renaming.
@@ -88,9 +85,8 @@ def get_new_function_name(operation: "Operation", taken_names: List[str]) -> str
88
85
  return response.summary
89
86
 
90
87
 
91
- def custom_visitor(parser: "OpenAPIParser", model_path: Path) -> Dict[str, object]:
92
- """
93
- Visits and optionally renames operations in the OpenAPI parser.
88
+ def custom_visitor(parser: "OpenAPIParser", model_path: Path) -> dict[str, object]:
89
+ """Visits and optionally renames operations in the OpenAPI parser.
94
90
 
95
91
  Args:
96
92
  parser: An OpenAPIParser instance containing API operations.
@@ -4,11 +4,10 @@
4
4
  import base64
5
5
  import json
6
6
  import logging
7
- from typing import Any, ClassVar, Literal, Optional
7
+ from typing import Any, ClassVar, Literal, TypeAlias
8
8
 
9
9
  import requests
10
10
  from pydantic import BaseModel, model_validator
11
- from typing_extensions import TypeAlias
12
11
 
13
12
  # Get the logger
14
13
  logger = logging.getLogger(__name__)
@@ -343,7 +342,7 @@ class OAuth2PasswordBearer(BaseSecurity):
343
342
 
344
343
  username: str = "USERNAME"
345
344
  password: str = "PASSWORD"
346
- bearer_token: Optional[str] = None
345
+ bearer_token: str | None = None
347
346
  token_url: str = "TOKEN_URL"
348
347
 
349
348
  # @model_validator(mode="before")