agno 2.2.13__py3-none-any.whl → 2.4.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 (383) hide show
  1. agno/agent/__init__.py +6 -0
  2. agno/agent/agent.py +5252 -3145
  3. agno/agent/remote.py +525 -0
  4. agno/api/api.py +2 -0
  5. agno/client/__init__.py +3 -0
  6. agno/client/a2a/__init__.py +10 -0
  7. agno/client/a2a/client.py +554 -0
  8. agno/client/a2a/schemas.py +112 -0
  9. agno/client/a2a/utils.py +369 -0
  10. agno/client/os.py +2669 -0
  11. agno/compression/__init__.py +3 -0
  12. agno/compression/manager.py +247 -0
  13. agno/culture/manager.py +2 -2
  14. agno/db/base.py +927 -6
  15. agno/db/dynamo/dynamo.py +788 -2
  16. agno/db/dynamo/schemas.py +128 -0
  17. agno/db/dynamo/utils.py +26 -3
  18. agno/db/firestore/firestore.py +674 -50
  19. agno/db/firestore/schemas.py +41 -0
  20. agno/db/firestore/utils.py +25 -10
  21. agno/db/gcs_json/gcs_json_db.py +506 -3
  22. agno/db/gcs_json/utils.py +14 -2
  23. agno/db/in_memory/in_memory_db.py +203 -4
  24. agno/db/in_memory/utils.py +14 -2
  25. agno/db/json/json_db.py +498 -2
  26. agno/db/json/utils.py +14 -2
  27. agno/db/migrations/manager.py +199 -0
  28. agno/db/migrations/utils.py +19 -0
  29. agno/db/migrations/v1_to_v2.py +54 -16
  30. agno/db/migrations/versions/__init__.py +0 -0
  31. agno/db/migrations/versions/v2_3_0.py +977 -0
  32. agno/db/mongo/async_mongo.py +1013 -39
  33. agno/db/mongo/mongo.py +684 -4
  34. agno/db/mongo/schemas.py +48 -0
  35. agno/db/mongo/utils.py +17 -0
  36. agno/db/mysql/__init__.py +2 -1
  37. agno/db/mysql/async_mysql.py +2958 -0
  38. agno/db/mysql/mysql.py +722 -53
  39. agno/db/mysql/schemas.py +77 -11
  40. agno/db/mysql/utils.py +151 -8
  41. agno/db/postgres/async_postgres.py +1254 -137
  42. agno/db/postgres/postgres.py +2316 -93
  43. agno/db/postgres/schemas.py +153 -21
  44. agno/db/postgres/utils.py +22 -7
  45. agno/db/redis/redis.py +531 -3
  46. agno/db/redis/schemas.py +36 -0
  47. agno/db/redis/utils.py +31 -15
  48. agno/db/schemas/evals.py +1 -0
  49. agno/db/schemas/memory.py +20 -9
  50. agno/db/singlestore/schemas.py +70 -1
  51. agno/db/singlestore/singlestore.py +737 -74
  52. agno/db/singlestore/utils.py +13 -3
  53. agno/db/sqlite/async_sqlite.py +1069 -89
  54. agno/db/sqlite/schemas.py +133 -1
  55. agno/db/sqlite/sqlite.py +2203 -165
  56. agno/db/sqlite/utils.py +21 -11
  57. agno/db/surrealdb/models.py +25 -0
  58. agno/db/surrealdb/surrealdb.py +603 -1
  59. agno/db/utils.py +60 -0
  60. agno/eval/__init__.py +26 -3
  61. agno/eval/accuracy.py +25 -12
  62. agno/eval/agent_as_judge.py +871 -0
  63. agno/eval/base.py +29 -0
  64. agno/eval/performance.py +10 -4
  65. agno/eval/reliability.py +22 -13
  66. agno/eval/utils.py +2 -1
  67. agno/exceptions.py +42 -0
  68. agno/hooks/__init__.py +3 -0
  69. agno/hooks/decorator.py +164 -0
  70. agno/integrations/discord/client.py +13 -2
  71. agno/knowledge/__init__.py +4 -0
  72. agno/knowledge/chunking/code.py +90 -0
  73. agno/knowledge/chunking/document.py +65 -4
  74. agno/knowledge/chunking/fixed.py +4 -1
  75. agno/knowledge/chunking/markdown.py +102 -11
  76. agno/knowledge/chunking/recursive.py +2 -2
  77. agno/knowledge/chunking/semantic.py +130 -48
  78. agno/knowledge/chunking/strategy.py +18 -0
  79. agno/knowledge/embedder/azure_openai.py +0 -1
  80. agno/knowledge/embedder/google.py +1 -1
  81. agno/knowledge/embedder/mistral.py +1 -1
  82. agno/knowledge/embedder/nebius.py +1 -1
  83. agno/knowledge/embedder/openai.py +16 -12
  84. agno/knowledge/filesystem.py +412 -0
  85. agno/knowledge/knowledge.py +4261 -1199
  86. agno/knowledge/protocol.py +134 -0
  87. agno/knowledge/reader/arxiv_reader.py +3 -2
  88. agno/knowledge/reader/base.py +9 -7
  89. agno/knowledge/reader/csv_reader.py +91 -42
  90. agno/knowledge/reader/docx_reader.py +9 -10
  91. agno/knowledge/reader/excel_reader.py +225 -0
  92. agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
  93. agno/knowledge/reader/firecrawl_reader.py +3 -2
  94. agno/knowledge/reader/json_reader.py +16 -22
  95. agno/knowledge/reader/markdown_reader.py +15 -14
  96. agno/knowledge/reader/pdf_reader.py +33 -28
  97. agno/knowledge/reader/pptx_reader.py +9 -10
  98. agno/knowledge/reader/reader_factory.py +135 -1
  99. agno/knowledge/reader/s3_reader.py +8 -16
  100. agno/knowledge/reader/tavily_reader.py +3 -3
  101. agno/knowledge/reader/text_reader.py +15 -14
  102. agno/knowledge/reader/utils/__init__.py +17 -0
  103. agno/knowledge/reader/utils/spreadsheet.py +114 -0
  104. agno/knowledge/reader/web_search_reader.py +8 -65
  105. agno/knowledge/reader/website_reader.py +16 -13
  106. agno/knowledge/reader/wikipedia_reader.py +36 -3
  107. agno/knowledge/reader/youtube_reader.py +3 -2
  108. agno/knowledge/remote_content/__init__.py +33 -0
  109. agno/knowledge/remote_content/config.py +266 -0
  110. agno/knowledge/remote_content/remote_content.py +105 -17
  111. agno/knowledge/utils.py +76 -22
  112. agno/learn/__init__.py +71 -0
  113. agno/learn/config.py +463 -0
  114. agno/learn/curate.py +185 -0
  115. agno/learn/machine.py +725 -0
  116. agno/learn/schemas.py +1114 -0
  117. agno/learn/stores/__init__.py +38 -0
  118. agno/learn/stores/decision_log.py +1156 -0
  119. agno/learn/stores/entity_memory.py +3275 -0
  120. agno/learn/stores/learned_knowledge.py +1583 -0
  121. agno/learn/stores/protocol.py +117 -0
  122. agno/learn/stores/session_context.py +1217 -0
  123. agno/learn/stores/user_memory.py +1495 -0
  124. agno/learn/stores/user_profile.py +1220 -0
  125. agno/learn/utils.py +209 -0
  126. agno/media.py +22 -6
  127. agno/memory/__init__.py +14 -1
  128. agno/memory/manager.py +223 -8
  129. agno/memory/strategies/__init__.py +15 -0
  130. agno/memory/strategies/base.py +66 -0
  131. agno/memory/strategies/summarize.py +196 -0
  132. agno/memory/strategies/types.py +37 -0
  133. agno/models/aimlapi/aimlapi.py +17 -0
  134. agno/models/anthropic/claude.py +434 -59
  135. agno/models/aws/bedrock.py +121 -20
  136. agno/models/aws/claude.py +131 -274
  137. agno/models/azure/ai_foundry.py +10 -6
  138. agno/models/azure/openai_chat.py +33 -10
  139. agno/models/base.py +1162 -561
  140. agno/models/cerebras/cerebras.py +120 -24
  141. agno/models/cerebras/cerebras_openai.py +21 -2
  142. agno/models/cohere/chat.py +65 -6
  143. agno/models/cometapi/cometapi.py +18 -1
  144. agno/models/dashscope/dashscope.py +2 -3
  145. agno/models/deepinfra/deepinfra.py +18 -1
  146. agno/models/deepseek/deepseek.py +69 -3
  147. agno/models/fireworks/fireworks.py +18 -1
  148. agno/models/google/gemini.py +959 -89
  149. agno/models/google/utils.py +22 -0
  150. agno/models/groq/groq.py +48 -18
  151. agno/models/huggingface/huggingface.py +17 -6
  152. agno/models/ibm/watsonx.py +16 -6
  153. agno/models/internlm/internlm.py +18 -1
  154. agno/models/langdb/langdb.py +13 -1
  155. agno/models/litellm/chat.py +88 -9
  156. agno/models/litellm/litellm_openai.py +18 -1
  157. agno/models/message.py +24 -5
  158. agno/models/meta/llama.py +40 -13
  159. agno/models/meta/llama_openai.py +22 -21
  160. agno/models/metrics.py +12 -0
  161. agno/models/mistral/mistral.py +8 -4
  162. agno/models/n1n/__init__.py +3 -0
  163. agno/models/n1n/n1n.py +57 -0
  164. agno/models/nebius/nebius.py +6 -7
  165. agno/models/nvidia/nvidia.py +20 -3
  166. agno/models/ollama/__init__.py +2 -0
  167. agno/models/ollama/chat.py +17 -6
  168. agno/models/ollama/responses.py +100 -0
  169. agno/models/openai/__init__.py +2 -0
  170. agno/models/openai/chat.py +117 -26
  171. agno/models/openai/open_responses.py +46 -0
  172. agno/models/openai/responses.py +110 -32
  173. agno/models/openrouter/__init__.py +2 -0
  174. agno/models/openrouter/openrouter.py +67 -2
  175. agno/models/openrouter/responses.py +146 -0
  176. agno/models/perplexity/perplexity.py +19 -1
  177. agno/models/portkey/portkey.py +7 -6
  178. agno/models/requesty/requesty.py +19 -2
  179. agno/models/response.py +20 -2
  180. agno/models/sambanova/sambanova.py +20 -3
  181. agno/models/siliconflow/siliconflow.py +19 -2
  182. agno/models/together/together.py +20 -3
  183. agno/models/vercel/v0.py +20 -3
  184. agno/models/vertexai/claude.py +124 -4
  185. agno/models/vllm/vllm.py +19 -14
  186. agno/models/xai/xai.py +19 -2
  187. agno/os/app.py +467 -137
  188. agno/os/auth.py +253 -5
  189. agno/os/config.py +22 -0
  190. agno/os/interfaces/a2a/a2a.py +7 -6
  191. agno/os/interfaces/a2a/router.py +635 -26
  192. agno/os/interfaces/a2a/utils.py +32 -33
  193. agno/os/interfaces/agui/agui.py +5 -3
  194. agno/os/interfaces/agui/router.py +26 -16
  195. agno/os/interfaces/agui/utils.py +97 -57
  196. agno/os/interfaces/base.py +7 -7
  197. agno/os/interfaces/slack/router.py +16 -7
  198. agno/os/interfaces/slack/slack.py +7 -7
  199. agno/os/interfaces/whatsapp/router.py +35 -7
  200. agno/os/interfaces/whatsapp/security.py +3 -1
  201. agno/os/interfaces/whatsapp/whatsapp.py +11 -8
  202. agno/os/managers.py +326 -0
  203. agno/os/mcp.py +652 -79
  204. agno/os/middleware/__init__.py +4 -0
  205. agno/os/middleware/jwt.py +718 -115
  206. agno/os/middleware/trailing_slash.py +27 -0
  207. agno/os/router.py +105 -1558
  208. agno/os/routers/agents/__init__.py +3 -0
  209. agno/os/routers/agents/router.py +655 -0
  210. agno/os/routers/agents/schema.py +288 -0
  211. agno/os/routers/components/__init__.py +3 -0
  212. agno/os/routers/components/components.py +475 -0
  213. agno/os/routers/database.py +155 -0
  214. agno/os/routers/evals/evals.py +111 -18
  215. agno/os/routers/evals/schemas.py +38 -5
  216. agno/os/routers/evals/utils.py +80 -11
  217. agno/os/routers/health.py +3 -3
  218. agno/os/routers/knowledge/knowledge.py +284 -35
  219. agno/os/routers/knowledge/schemas.py +14 -2
  220. agno/os/routers/memory/memory.py +274 -11
  221. agno/os/routers/memory/schemas.py +44 -3
  222. agno/os/routers/metrics/metrics.py +30 -15
  223. agno/os/routers/metrics/schemas.py +10 -6
  224. agno/os/routers/registry/__init__.py +3 -0
  225. agno/os/routers/registry/registry.py +337 -0
  226. agno/os/routers/session/session.py +143 -14
  227. agno/os/routers/teams/__init__.py +3 -0
  228. agno/os/routers/teams/router.py +550 -0
  229. agno/os/routers/teams/schema.py +280 -0
  230. agno/os/routers/traces/__init__.py +3 -0
  231. agno/os/routers/traces/schemas.py +414 -0
  232. agno/os/routers/traces/traces.py +549 -0
  233. agno/os/routers/workflows/__init__.py +3 -0
  234. agno/os/routers/workflows/router.py +757 -0
  235. agno/os/routers/workflows/schema.py +139 -0
  236. agno/os/schema.py +157 -584
  237. agno/os/scopes.py +469 -0
  238. agno/os/settings.py +3 -0
  239. agno/os/utils.py +574 -185
  240. agno/reasoning/anthropic.py +85 -1
  241. agno/reasoning/azure_ai_foundry.py +93 -1
  242. agno/reasoning/deepseek.py +102 -2
  243. agno/reasoning/default.py +6 -7
  244. agno/reasoning/gemini.py +87 -3
  245. agno/reasoning/groq.py +109 -2
  246. agno/reasoning/helpers.py +6 -7
  247. agno/reasoning/manager.py +1238 -0
  248. agno/reasoning/ollama.py +93 -1
  249. agno/reasoning/openai.py +115 -1
  250. agno/reasoning/vertexai.py +85 -1
  251. agno/registry/__init__.py +3 -0
  252. agno/registry/registry.py +68 -0
  253. agno/remote/__init__.py +3 -0
  254. agno/remote/base.py +581 -0
  255. agno/run/__init__.py +2 -4
  256. agno/run/agent.py +134 -19
  257. agno/run/base.py +49 -1
  258. agno/run/cancel.py +65 -52
  259. agno/run/cancellation_management/__init__.py +9 -0
  260. agno/run/cancellation_management/base.py +78 -0
  261. agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
  262. agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
  263. agno/run/requirement.py +181 -0
  264. agno/run/team.py +111 -19
  265. agno/run/workflow.py +2 -1
  266. agno/session/agent.py +57 -92
  267. agno/session/summary.py +1 -1
  268. agno/session/team.py +62 -115
  269. agno/session/workflow.py +353 -57
  270. agno/skills/__init__.py +17 -0
  271. agno/skills/agent_skills.py +377 -0
  272. agno/skills/errors.py +32 -0
  273. agno/skills/loaders/__init__.py +4 -0
  274. agno/skills/loaders/base.py +27 -0
  275. agno/skills/loaders/local.py +216 -0
  276. agno/skills/skill.py +65 -0
  277. agno/skills/utils.py +107 -0
  278. agno/skills/validator.py +277 -0
  279. agno/table.py +10 -0
  280. agno/team/__init__.py +5 -1
  281. agno/team/remote.py +447 -0
  282. agno/team/team.py +3769 -2202
  283. agno/tools/brandfetch.py +27 -18
  284. agno/tools/browserbase.py +225 -16
  285. agno/tools/crawl4ai.py +3 -0
  286. agno/tools/duckduckgo.py +25 -71
  287. agno/tools/exa.py +0 -21
  288. agno/tools/file.py +14 -13
  289. agno/tools/file_generation.py +12 -6
  290. agno/tools/firecrawl.py +15 -7
  291. agno/tools/function.py +94 -113
  292. agno/tools/google_bigquery.py +11 -2
  293. agno/tools/google_drive.py +4 -3
  294. agno/tools/knowledge.py +9 -4
  295. agno/tools/mcp/mcp.py +301 -18
  296. agno/tools/mcp/multi_mcp.py +269 -14
  297. agno/tools/mem0.py +11 -10
  298. agno/tools/memory.py +47 -46
  299. agno/tools/mlx_transcribe.py +10 -7
  300. agno/tools/models/nebius.py +5 -5
  301. agno/tools/models_labs.py +20 -10
  302. agno/tools/nano_banana.py +151 -0
  303. agno/tools/parallel.py +0 -7
  304. agno/tools/postgres.py +76 -36
  305. agno/tools/python.py +14 -6
  306. agno/tools/reasoning.py +30 -23
  307. agno/tools/redshift.py +406 -0
  308. agno/tools/shopify.py +1519 -0
  309. agno/tools/spotify.py +919 -0
  310. agno/tools/tavily.py +4 -1
  311. agno/tools/toolkit.py +253 -18
  312. agno/tools/websearch.py +93 -0
  313. agno/tools/website.py +1 -1
  314. agno/tools/wikipedia.py +1 -1
  315. agno/tools/workflow.py +56 -48
  316. agno/tools/yfinance.py +12 -11
  317. agno/tracing/__init__.py +12 -0
  318. agno/tracing/exporter.py +161 -0
  319. agno/tracing/schemas.py +276 -0
  320. agno/tracing/setup.py +112 -0
  321. agno/utils/agent.py +251 -10
  322. agno/utils/cryptography.py +22 -0
  323. agno/utils/dttm.py +33 -0
  324. agno/utils/events.py +264 -7
  325. agno/utils/hooks.py +111 -3
  326. agno/utils/http.py +161 -2
  327. agno/utils/mcp.py +49 -8
  328. agno/utils/media.py +22 -1
  329. agno/utils/models/ai_foundry.py +9 -2
  330. agno/utils/models/claude.py +20 -5
  331. agno/utils/models/cohere.py +9 -2
  332. agno/utils/models/llama.py +9 -2
  333. agno/utils/models/mistral.py +4 -2
  334. agno/utils/os.py +0 -0
  335. agno/utils/print_response/agent.py +99 -16
  336. agno/utils/print_response/team.py +223 -24
  337. agno/utils/print_response/workflow.py +0 -2
  338. agno/utils/prompts.py +8 -6
  339. agno/utils/remote.py +23 -0
  340. agno/utils/response.py +1 -13
  341. agno/utils/string.py +91 -2
  342. agno/utils/team.py +62 -12
  343. agno/utils/tokens.py +657 -0
  344. agno/vectordb/base.py +15 -2
  345. agno/vectordb/cassandra/cassandra.py +1 -1
  346. agno/vectordb/chroma/__init__.py +2 -1
  347. agno/vectordb/chroma/chromadb.py +468 -23
  348. agno/vectordb/clickhouse/clickhousedb.py +1 -1
  349. agno/vectordb/couchbase/couchbase.py +6 -2
  350. agno/vectordb/lancedb/lance_db.py +7 -38
  351. agno/vectordb/lightrag/lightrag.py +7 -6
  352. agno/vectordb/milvus/milvus.py +118 -84
  353. agno/vectordb/mongodb/__init__.py +2 -1
  354. agno/vectordb/mongodb/mongodb.py +14 -31
  355. agno/vectordb/pgvector/pgvector.py +120 -66
  356. agno/vectordb/pineconedb/pineconedb.py +2 -19
  357. agno/vectordb/qdrant/__init__.py +2 -1
  358. agno/vectordb/qdrant/qdrant.py +33 -56
  359. agno/vectordb/redis/__init__.py +2 -1
  360. agno/vectordb/redis/redisdb.py +19 -31
  361. agno/vectordb/singlestore/singlestore.py +17 -9
  362. agno/vectordb/surrealdb/surrealdb.py +2 -38
  363. agno/vectordb/weaviate/__init__.py +2 -1
  364. agno/vectordb/weaviate/weaviate.py +7 -3
  365. agno/workflow/__init__.py +5 -1
  366. agno/workflow/agent.py +2 -2
  367. agno/workflow/condition.py +12 -10
  368. agno/workflow/loop.py +28 -9
  369. agno/workflow/parallel.py +21 -13
  370. agno/workflow/remote.py +362 -0
  371. agno/workflow/router.py +12 -9
  372. agno/workflow/step.py +261 -36
  373. agno/workflow/steps.py +12 -8
  374. agno/workflow/types.py +40 -77
  375. agno/workflow/workflow.py +939 -213
  376. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
  377. agno-2.4.3.dist-info/RECORD +677 -0
  378. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
  379. agno/tools/googlesearch.py +0 -98
  380. agno/tools/memori.py +0 -339
  381. agno-2.2.13.dist-info/RECORD +0 -575
  382. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
  383. {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
agno/tools/mcp/mcp.py CHANGED
@@ -1,14 +1,21 @@
1
+ import inspect
2
+ import time
1
3
  import weakref
2
4
  from dataclasses import asdict
3
5
  from datetime import timedelta
4
- from typing import Any, Literal, Optional, Union
6
+ from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Tuple, Union
5
7
 
6
8
  from agno.tools import Toolkit
7
9
  from agno.tools.function import Function
8
10
  from agno.tools.mcp.params import SSEClientParams, StreamableHTTPClientParams
9
- from agno.utils.log import log_debug, log_error, log_info
11
+ from agno.utils.log import log_debug, log_error, log_info, log_warning
10
12
  from agno.utils.mcp import get_entrypoint_for_tool, prepare_command
11
13
 
14
+ if TYPE_CHECKING:
15
+ from agno.agent import Agent
16
+ from agno.run import RunContext
17
+ from agno.team.team import Team
18
+
12
19
  try:
13
20
  from mcp import ClientSession, StdioServerParameters
14
21
  from mcp.client.sse import sse_client
@@ -35,7 +42,7 @@ class MCPTools(Toolkit):
35
42
  *,
36
43
  url: Optional[str] = None,
37
44
  env: Optional[dict[str, str]] = None,
38
- transport: Literal["stdio", "sse", "streamable-http"] = "stdio",
45
+ transport: Optional[Literal["stdio", "sse", "streamable-http"]] = None,
39
46
  server_params: Optional[Union[StdioServerParameters, SSEClientParams, StreamableHTTPClientParams]] = None,
40
47
  session: Optional[ClientSession] = None,
41
48
  timeout_seconds: int = 10,
@@ -43,7 +50,8 @@ class MCPTools(Toolkit):
43
50
  include_tools: Optional[list[str]] = None,
44
51
  exclude_tools: Optional[list[str]] = None,
45
52
  refresh_connection: bool = False,
46
- tool_name_prefix: Optional[str] = "",
53
+ tool_name_prefix: Optional[str] = None,
54
+ header_provider: Optional[Callable[..., dict[str, Any]]] = None,
47
55
  **kwargs,
48
56
  ):
49
57
  """
@@ -59,11 +67,24 @@ class MCPTools(Toolkit):
59
67
  timeout_seconds: Read timeout in seconds for the MCP client
60
68
  include_tools: Optional list of tool names to include (if None, includes all)
61
69
  exclude_tools: Optional list of tool names to exclude (if None, excludes none)
62
- transport: The transport protocol to use, either "stdio" or "sse" or "streamable-http"
70
+ transport: The transport protocol to use, either "stdio" or "sse" or "streamable-http".
71
+ Defaults to "streamable-http" when url is provided, otherwise defaults to "stdio".
63
72
  refresh_connection: If True, the connection and tools will be refreshed on each run
73
+ header_provider: Optional function to generate dynamic HTTP headers.
74
+ Only relevant with HTTP transports (Streamable HTTP or SSE).
75
+ Creates a new session per agent run with dynamic headers merged into connection config.
64
76
  """
65
77
  super().__init__(name="MCPTools", **kwargs)
66
78
 
79
+ if url is not None:
80
+ if transport is None:
81
+ transport = "streamable-http"
82
+ elif transport == "stdio":
83
+ log_warning(
84
+ "Transport cannot be 'stdio' when url is provided. Setting transport to 'streamable-http' instead."
85
+ )
86
+ transport = "streamable-http"
87
+
67
88
  if transport == "sse":
68
89
  log_info("SSE as a standalone transport is deprecated. Please use Streamable HTTP instead.")
69
90
 
@@ -104,12 +125,17 @@ class MCPTools(Toolkit):
104
125
  "If using the streamable-http transport, server_params must be an instance of StreamableHTTPClientParams."
105
126
  )
106
127
 
128
+ self.transport = transport
129
+
130
+ self.header_provider = None
131
+ if self._is_valid_header_provider(header_provider):
132
+ self.header_provider = header_provider
133
+
107
134
  self.timeout_seconds = timeout_seconds
108
135
  self.session: Optional[ClientSession] = session
109
136
  self.server_params: Optional[Union[StdioServerParameters, SSEClientParams, StreamableHTTPClientParams]] = (
110
137
  server_params
111
138
  )
112
- self.transport = transport
113
139
  self.url = url
114
140
 
115
141
  # Merge provided env with system env
@@ -135,6 +161,12 @@ class MCPTools(Toolkit):
135
161
  self._context = None
136
162
  self._session_context = None
137
163
 
164
+ # Session management for per-agent-run sessions with dynamic headers
165
+ # Maps run_id to (session, timestamp) for TTL-based cleanup
166
+ self._run_sessions: dict[str, Tuple[ClientSession, float]] = {}
167
+ self._run_session_contexts: dict[str, Any] = {} # Maps run_id to session context managers
168
+ self._session_ttl_seconds: float = 300.0 # 5 minutes TTL for MCP sessions
169
+
138
170
  def cleanup():
139
171
  """Cancel active connections"""
140
172
  if self._connection_task and not self._connection_task.done():
@@ -147,6 +179,234 @@ class MCPTools(Toolkit):
147
179
  def initialized(self) -> bool:
148
180
  return self._initialized
149
181
 
182
+ def _is_valid_header_provider(self, header_provider: Optional[Callable[..., dict[str, Any]]]) -> bool:
183
+ """Logic to validate a given header_provider function.
184
+
185
+ Args:
186
+ header_provider: The header_provider function to validate
187
+
188
+ Raises:
189
+ Exception: If there is an error validating the header_provider.
190
+ """
191
+ if not header_provider:
192
+ return False
193
+
194
+ if self.transport not in ["sse", "streamable-http"]:
195
+ log_warning(
196
+ f"header_provider specified but transport is '{self.transport}'. "
197
+ "Dynamic headers only work with 'sse' or 'streamable-http' transports. "
198
+ "The header_provider logic will be ignored."
199
+ )
200
+ return False
201
+
202
+ log_debug("Dynamic header support enabled for MCP tools")
203
+ return True
204
+
205
+ def _call_header_provider(
206
+ self,
207
+ run_context: Optional["RunContext"] = None,
208
+ agent: Optional["Agent"] = None,
209
+ team: Optional["Team"] = None,
210
+ ) -> dict[str, Any]:
211
+ """Call the header_provider with run_context, agent, and/or team based on its signature.
212
+
213
+ Args:
214
+ run_context: The RunContext for the current agent run
215
+ agent: The Agent instance (if running within an agent)
216
+ team: The Team instance (if running within a team)
217
+
218
+ Returns:
219
+ dict[str, Any]: The headers returned by the header_provider
220
+ """
221
+ header_provider = getattr(self, "header_provider", None)
222
+ if header_provider is None:
223
+ return {}
224
+
225
+ try:
226
+ sig = inspect.signature(header_provider)
227
+ param_names = set(sig.parameters.keys())
228
+
229
+ # Build kwargs based on what the function accepts
230
+ call_kwargs: dict[str, Any] = {}
231
+
232
+ if "run_context" in param_names:
233
+ call_kwargs["run_context"] = run_context
234
+ if "agent" in param_names:
235
+ call_kwargs["agent"] = agent
236
+ if "team" in param_names:
237
+ call_kwargs["team"] = team
238
+
239
+ # Check if function accepts **kwargs (VAR_KEYWORD)
240
+ has_var_keyword = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values())
241
+
242
+ if has_var_keyword:
243
+ # Pass all available context to **kwargs
244
+ call_kwargs = {"run_context": run_context, "agent": agent, "team": team}
245
+ return header_provider(**call_kwargs)
246
+ elif call_kwargs:
247
+ return header_provider(**call_kwargs)
248
+ else:
249
+ # Function takes no recognized parameters - check for positional
250
+ positional_params = [
251
+ p
252
+ for p in sig.parameters.values()
253
+ if p.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD)
254
+ ]
255
+ if positional_params:
256
+ # Legacy support: pass run_context as first positional arg
257
+ return header_provider(run_context)
258
+ else:
259
+ # Function takes no parameters
260
+ return header_provider()
261
+ except Exception as e:
262
+ log_warning(f"Error calling header_provider: {e}")
263
+ return {}
264
+
265
+ async def _cleanup_stale_sessions(self) -> None:
266
+ """Clean up sessions older than TTL to prevent memory leaks."""
267
+ if not self._run_sessions:
268
+ return
269
+
270
+ now = time.time()
271
+ stale_run_ids = [
272
+ run_id
273
+ for run_id, (_, created_at) in self._run_sessions.items()
274
+ if now - created_at > self._session_ttl_seconds
275
+ ]
276
+
277
+ for run_id in stale_run_ids:
278
+ log_debug(f"Cleaning up stale MCP sessions for run_id={run_id}")
279
+ await self.cleanup_run_session(run_id)
280
+
281
+ async def get_session_for_run(
282
+ self,
283
+ run_context: Optional["RunContext"] = None,
284
+ agent: Optional["Agent"] = None,
285
+ team: Optional["Team"] = None,
286
+ ) -> ClientSession:
287
+ """
288
+ Get or create a session for the given run context.
289
+
290
+ If header_provider is set and run_context is provided, creates a new session
291
+ with dynamic headers merged into the connection config.
292
+
293
+ Args:
294
+ run_context: The RunContext for the current agent run
295
+ agent: The Agent instance (if running within an agent)
296
+ team: The Team instance (if running within a team)
297
+
298
+ Returns:
299
+ ClientSession for the run
300
+ """
301
+ # If no header_provider or no run_context, use the default session
302
+ if not self.header_provider or not run_context:
303
+ if self.session is None:
304
+ raise ValueError("Session is not initialized")
305
+ return self.session
306
+
307
+ # Lazy cleanup of stale sessions
308
+ await self._cleanup_stale_sessions()
309
+
310
+ # Check if we already have a session for this run
311
+ run_id = run_context.run_id
312
+ if run_id in self._run_sessions:
313
+ session, _ = self._run_sessions[run_id]
314
+ return session
315
+
316
+ # Create a new session with dynamic headers for this run
317
+ log_debug(f"Creating new session for run_id={run_id} with dynamic headers")
318
+
319
+ # Generate dynamic headers from the provider
320
+ dynamic_headers = self._call_header_provider(run_context=run_context, agent=agent, team=team)
321
+
322
+ # Create new session with merged headers based on transport type
323
+ if self.transport == "sse":
324
+ sse_params = asdict(self.server_params) if self.server_params is not None else {} # type: ignore
325
+ if "url" not in sse_params:
326
+ sse_params["url"] = self.url
327
+
328
+ # Merge dynamic headers into existing headers
329
+ existing_headers = sse_params.get("headers", {})
330
+ sse_params["headers"] = {**existing_headers, **dynamic_headers}
331
+
332
+ context = sse_client(**sse_params) # type: ignore
333
+ client_timeout = min(self.timeout_seconds, sse_params.get("timeout", self.timeout_seconds))
334
+
335
+ elif self.transport == "streamable-http":
336
+ streamable_http_params = asdict(self.server_params) if self.server_params is not None else {} # type: ignore
337
+ if "url" not in streamable_http_params:
338
+ streamable_http_params["url"] = self.url
339
+
340
+ # Merge dynamic headers into existing headers
341
+ existing_headers = streamable_http_params.get("headers", {})
342
+ streamable_http_params["headers"] = {**existing_headers, **dynamic_headers}
343
+
344
+ context = streamablehttp_client(**streamable_http_params) # type: ignore
345
+ params_timeout = streamable_http_params.get("timeout", self.timeout_seconds)
346
+ if isinstance(params_timeout, timedelta):
347
+ params_timeout = int(params_timeout.total_seconds())
348
+ client_timeout = min(self.timeout_seconds, params_timeout)
349
+ else:
350
+ # stdio doesn't support headers, fall back to default session
351
+ log_warning(f"Cannot use dynamic headers with {self.transport} transport, using default session")
352
+ if self.session is None:
353
+ raise ValueError("Session is not initialized")
354
+ return self.session
355
+
356
+ # Enter the context and create session
357
+ session_params = await context.__aenter__() # type: ignore
358
+ read, write = session_params[0:2]
359
+
360
+ session_context = ClientSession(read, write, read_timeout_seconds=timedelta(seconds=client_timeout)) # type: ignore
361
+ session = await session_context.__aenter__() # type: ignore
362
+
363
+ # Initialize the session
364
+ await session.initialize()
365
+
366
+ # Store the session with timestamp and context for cleanup
367
+ self._run_sessions[run_id] = (session, time.time())
368
+ self._run_session_contexts[run_id] = (context, session_context)
369
+
370
+ return session
371
+
372
+ async def cleanup_run_session(self, run_id: str) -> None:
373
+ """
374
+ Clean up the session for a specific run.
375
+
376
+ Note: Cleanup may fail due to async context manager limitations when
377
+ contexts are entered/exited across different tasks. Errors are logged
378
+ but not raised.
379
+ """
380
+ if run_id not in self._run_sessions:
381
+ return
382
+
383
+ try:
384
+ # Get the context managers
385
+ context, session_context = self._run_session_contexts.get(run_id, (None, None))
386
+
387
+ # Try to clean up session context
388
+ # Silently ignore cleanup errors - these are harmless
389
+ if session_context is not None:
390
+ try:
391
+ await session_context.__aexit__(None, None, None)
392
+ except (RuntimeError, Exception):
393
+ pass # Silently ignore
394
+
395
+ # Try to clean up transport context
396
+ if context is not None:
397
+ try:
398
+ await context.__aexit__(None, None, None)
399
+ except (RuntimeError, Exception):
400
+ pass # Silently ignore
401
+
402
+ # Remove from tracking regardless of cleanup success
403
+ # The connections will be cleaned up by garbage collection
404
+ del self._run_sessions[run_id]
405
+ del self._run_session_contexts[run_id]
406
+
407
+ except Exception:
408
+ pass # Silently ignore all cleanup errors
409
+
150
410
  async def is_alive(self) -> bool:
151
411
  if self.session is None:
152
412
  return False
@@ -227,17 +487,36 @@ class MCPTools(Toolkit):
227
487
  if not self._initialized:
228
488
  return
229
489
 
230
- try:
231
- if self._session_context is not None:
232
- await self._session_context.__aexit__(None, None, None)
233
- self.session = None
234
- self._session_context = None
235
-
236
- if self._context is not None:
237
- await self._context.__aexit__(None, None, None)
238
- self._context = None
239
- except (RuntimeError, BaseException) as e:
240
- log_error(f"Failed to close MCP connection: {e}")
490
+ import warnings
491
+
492
+ # Suppress async generator cleanup warnings
493
+ with warnings.catch_warnings():
494
+ warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*async_generator.*")
495
+ warnings.filterwarnings("ignore", message=".*cancel scope.*")
496
+
497
+ try:
498
+ # Clean up all per-run sessions first
499
+ run_ids = list(self._run_sessions.keys())
500
+ for run_id in run_ids:
501
+ await self.cleanup_run_session(run_id)
502
+
503
+ # Clean up the main session
504
+ if self._session_context is not None:
505
+ try:
506
+ await self._session_context.__aexit__(None, None, None)
507
+ except (RuntimeError, Exception):
508
+ pass # Silently ignore cleanup errors
509
+ self.session = None
510
+ self._session_context = None
511
+
512
+ if self._context is not None:
513
+ try:
514
+ await self._context.__aexit__(None, None, None)
515
+ except (RuntimeError, Exception):
516
+ pass # Silently ignore cleanup errors
517
+ self._context = None
518
+ except (RuntimeError, BaseException):
519
+ pass # Silently ignore all cleanup errors
241
520
 
242
521
  self._initialized = False
243
522
 
@@ -290,7 +569,11 @@ class MCPTools(Toolkit):
290
569
  for tool in filtered_tools:
291
570
  try:
292
571
  # Get an entrypoint for the tool
293
- entrypoint = get_entrypoint_for_tool(tool, self.session) # type: ignore
572
+ entrypoint = get_entrypoint_for_tool(
573
+ tool=tool,
574
+ session=self.session, # type: ignore
575
+ mcp_tools_instance=self,
576
+ )
294
577
  # Create a Function for the tool
295
578
  f = Function(
296
579
  name=tool_name_prefix + tool.name,