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
@@ -1,9 +1,12 @@
1
+ import inspect
2
+ import time
3
+ import warnings
1
4
  import weakref
2
5
  from contextlib import AsyncExitStack
3
6
  from dataclasses import asdict
4
7
  from datetime import timedelta
5
8
  from types import TracebackType
6
- from typing import List, Literal, Optional, Union
9
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Literal, Optional, Tuple, Union
7
10
 
8
11
  from agno.tools import Toolkit
9
12
  from agno.tools.function import Function
@@ -11,6 +14,11 @@ from agno.tools.mcp.params import SSEClientParams, StreamableHTTPClientParams
11
14
  from agno.utils.log import log_debug, log_error, log_info, log_warning
12
15
  from agno.utils.mcp import get_entrypoint_for_tool, prepare_command
13
16
 
17
+ if TYPE_CHECKING:
18
+ from agno.agent import Agent
19
+ from agno.run import RunContext
20
+ from agno.team.team import Team
21
+
14
22
  try:
15
23
  from mcp import ClientSession, StdioServerParameters
16
24
  from mcp.client.sse import sse_client
@@ -47,6 +55,7 @@ class MultiMCPTools(Toolkit):
47
55
  exclude_tools: Optional[list[str]] = None,
48
56
  refresh_connection: bool = False,
49
57
  allow_partial_failure: bool = False,
58
+ header_provider: Optional[Callable[..., dict[str, Any]]] = None,
50
59
  **kwargs,
51
60
  ):
52
61
  """
@@ -64,7 +73,14 @@ class MultiMCPTools(Toolkit):
64
73
  exclude_tools: Optional list of tool names to exclude (if None, excludes none).
65
74
  allow_partial_failure: If True, allows toolkit to initialize even if some MCP servers fail to connect. If False, any failure will raise an exception.
66
75
  refresh_connection: If True, the connection and tools will be refreshed on each run
76
+ header_provider: Header provider function for all servers. Takes RunContext and returns dict of HTTP headers.
67
77
  """
78
+ warnings.warn(
79
+ "The MultiMCPTools class is deprecated and will be removed in a future version. Please use multiple MCPTools instances instead.",
80
+ DeprecationWarning,
81
+ stacklevel=2,
82
+ )
83
+
68
84
  super().__init__(name="MultiMCPTools", **kwargs)
69
85
 
70
86
  if urls_transports is not None:
@@ -86,6 +102,16 @@ class MultiMCPTools(Toolkit):
86
102
  self.exclude_tools = exclude_tools
87
103
  self.refresh_connection = refresh_connection
88
104
 
105
+ self.header_provider = header_provider
106
+
107
+ # Validate header_provider signature
108
+ if header_provider:
109
+ try:
110
+ # Just verify we can inspect the signature - no parameter requirements
111
+ inspect.signature(header_provider)
112
+ except Exception as e:
113
+ log_warning(f"Could not validate header_provider signature: {e}")
114
+
89
115
  if server_params_list is None and commands is None and urls is None:
90
116
  raise ValueError("Either server_params_list or commands or urls must be provided")
91
117
 
@@ -130,6 +156,14 @@ class MultiMCPTools(Toolkit):
130
156
  self._connection_task = None
131
157
  self._successful_connections = 0
132
158
  self._sessions: list[ClientSession] = []
159
+ self._session_to_server_idx: Dict[int, int] = {} # Maps session list index to server params index
160
+
161
+ # Session management for per-agent-run sessions with dynamic headers
162
+ # For MultiMCP, we track sessions per (run_id, server_idx) since we have multiple servers
163
+ # Maps (run_id, server_idx) to (session, timestamp) for TTL-based cleanup
164
+ self._run_sessions: Dict[Tuple[str, int], Tuple[ClientSession, float]] = {}
165
+ self._run_session_contexts: Dict[Tuple[str, int], Any] = {} # Maps (run_id, server_idx) to context managers
166
+ self._session_ttl_seconds: float = 300.0 # 5 minutes default TTL
133
167
 
134
168
  self.allow_partial_failure = allow_partial_failure
135
169
 
@@ -153,6 +187,205 @@ class MultiMCPTools(Toolkit):
153
187
  except (RuntimeError, BaseException):
154
188
  return False
155
189
 
190
+ def _call_header_provider(
191
+ self,
192
+ run_context: Optional["RunContext"] = None,
193
+ agent: Optional["Agent"] = None,
194
+ team: Optional["Team"] = None,
195
+ ) -> dict[str, Any]:
196
+ """Call the header_provider with run_context, agent, and/or team based on its signature.
197
+
198
+ Args:
199
+ run_context: The RunContext for the current agent run
200
+ agent: The Agent instance (if running within an agent)
201
+ team: The Team instance (if running within a team)
202
+
203
+ Returns:
204
+ dict[str, Any]: The headers returned by the header_provider
205
+ """
206
+ header_provider = getattr(self, "header_provider", None)
207
+ if header_provider is None:
208
+ return {}
209
+
210
+ try:
211
+ sig = inspect.signature(header_provider)
212
+ param_names = set(sig.parameters.keys())
213
+
214
+ # Build kwargs based on what the function accepts
215
+ call_kwargs: dict[str, Any] = {}
216
+
217
+ if "run_context" in param_names:
218
+ call_kwargs["run_context"] = run_context
219
+ if "agent" in param_names:
220
+ call_kwargs["agent"] = agent
221
+ if "team" in param_names:
222
+ call_kwargs["team"] = team
223
+
224
+ # Check if function accepts **kwargs (VAR_KEYWORD)
225
+ has_var_keyword = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values())
226
+
227
+ if has_var_keyword:
228
+ # Pass all available context to **kwargs
229
+ call_kwargs = {"run_context": run_context, "agent": agent, "team": team}
230
+ return header_provider(**call_kwargs)
231
+ elif call_kwargs:
232
+ return header_provider(**call_kwargs)
233
+ else:
234
+ # Function takes no recognized parameters - check for positional
235
+ positional_params = [
236
+ p
237
+ for p in sig.parameters.values()
238
+ if p.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD)
239
+ ]
240
+ if positional_params:
241
+ # Legacy support: pass run_context as first positional arg
242
+ return header_provider(run_context)
243
+ else:
244
+ # Function takes no parameters
245
+ return header_provider()
246
+ except Exception as e:
247
+ log_warning(f"Error calling header_provider: {e}")
248
+ return {}
249
+
250
+ async def _cleanup_stale_sessions(self) -> None:
251
+ """Clean up sessions older than TTL to prevent memory leaks."""
252
+ if not self._run_sessions:
253
+ return
254
+
255
+ now = time.time()
256
+ stale_keys = [
257
+ cache_key
258
+ for cache_key, (_, created_at) in self._run_sessions.items()
259
+ if now - created_at > self._session_ttl_seconds
260
+ ]
261
+
262
+ for run_id, server_idx in stale_keys:
263
+ log_debug(f"Cleaning up stale session for run_id={run_id}, server_idx={server_idx}")
264
+ await self.cleanup_run_session(run_id, server_idx)
265
+
266
+ async def get_session_for_run(
267
+ self,
268
+ run_context: Optional["RunContext"] = None,
269
+ server_idx: int = 0,
270
+ agent: Optional["Agent"] = None,
271
+ team: Optional["Team"] = None,
272
+ ) -> ClientSession:
273
+ """
274
+ Get or create a session for the given run_context and server index.
275
+
276
+ If header_provider is configured and run_context is provided, this creates
277
+ a new session with dynamic headers for this specific agent run and server.
278
+
279
+ Args:
280
+ run_context: The RunContext containing user_id, metadata, etc.
281
+ server_idx: Index of the server in self._sessions list
282
+ agent: The Agent instance (if running within an agent)
283
+ team: The Team instance (if running within a team)
284
+
285
+ Returns:
286
+ ClientSession: Either the default session or a per-run session with dynamic headers
287
+ """
288
+ # If no header_provider or no run_context, use the default session
289
+ if not self.header_provider or not run_context:
290
+ # Return the default session for this server
291
+ if server_idx < len(self._sessions):
292
+ return self._sessions[server_idx]
293
+ raise ValueError(f"Server index {server_idx} out of range")
294
+
295
+ # Lazy cleanup of stale sessions
296
+ await self._cleanup_stale_sessions()
297
+
298
+ # Check if we already have a session for this (run_id, server_idx)
299
+ run_id = run_context.run_id
300
+ cache_key = (run_id, server_idx)
301
+ if cache_key in self._run_sessions:
302
+ session, _ = self._run_sessions[cache_key]
303
+ return session
304
+
305
+ # Create a new session with dynamic headers for this run and server
306
+ log_debug(f"Creating new session for run_id={run_id}, server_idx={server_idx} with dynamic headers")
307
+
308
+ # Generate dynamic headers from the provider
309
+ dynamic_headers = self._call_header_provider(run_context=run_context, agent=agent, team=team)
310
+
311
+ # Get the server params for this server index
312
+ if server_idx >= len(self.server_params_list):
313
+ raise ValueError(f"Server index {server_idx} out of range")
314
+
315
+ server_params = self.server_params_list[server_idx]
316
+
317
+ # Create new session with merged headers based on transport type
318
+ if isinstance(server_params, SSEClientParams):
319
+ params_dict = asdict(server_params)
320
+ existing_headers = params_dict.get("headers") or {}
321
+ params_dict["headers"] = {**existing_headers, **dynamic_headers}
322
+
323
+ context = sse_client(**params_dict) # type: ignore
324
+ client_timeout = min(self.timeout_seconds, params_dict.get("timeout", self.timeout_seconds))
325
+
326
+ elif isinstance(server_params, StreamableHTTPClientParams):
327
+ params_dict = asdict(server_params)
328
+ existing_headers = params_dict.get("headers") or {}
329
+ params_dict["headers"] = {**existing_headers, **dynamic_headers}
330
+
331
+ context = streamablehttp_client(**params_dict) # type: ignore
332
+ params_timeout = params_dict.get("timeout", self.timeout_seconds)
333
+ if isinstance(params_timeout, timedelta):
334
+ params_timeout = int(params_timeout.total_seconds())
335
+ client_timeout = min(self.timeout_seconds, params_timeout)
336
+ else:
337
+ # stdio doesn't support headers, fall back to default session
338
+ log_warning(
339
+ f"Cannot use dynamic headers with stdio transport for server {server_idx}, using default session"
340
+ )
341
+ if server_idx < len(self._sessions):
342
+ return self._sessions[server_idx]
343
+ raise ValueError(f"Server index {server_idx} out of range")
344
+
345
+ # Enter the context and create session
346
+ session_params = await context.__aenter__() # type: ignore
347
+ read, write = session_params[0:2]
348
+
349
+ session_context = ClientSession(read, write, read_timeout_seconds=timedelta(seconds=client_timeout)) # type: ignore
350
+ session = await session_context.__aenter__() # type: ignore
351
+
352
+ # Initialize the session
353
+ await session.initialize()
354
+
355
+ # Store the session with timestamp and context for cleanup
356
+ self._run_sessions[cache_key] = (session, time.time())
357
+ self._run_session_contexts[cache_key] = (context, session_context)
358
+
359
+ return session
360
+
361
+ async def cleanup_run_session(self, run_id: str, server_idx: int) -> None:
362
+ """Clean up a per-run session."""
363
+ cache_key = (run_id, server_idx)
364
+ if cache_key not in self._run_sessions:
365
+ return
366
+
367
+ try:
368
+ context, session_context = self._run_session_contexts[cache_key]
369
+
370
+ # Exit session context - silently ignore errors
371
+ try:
372
+ await session_context.__aexit__(None, None, None)
373
+ except (RuntimeError, Exception):
374
+ pass # Silently ignore
375
+
376
+ # Exit transport context - silently ignore errors
377
+ try:
378
+ await context.__aexit__(None, None, None)
379
+ except (RuntimeError, Exception):
380
+ pass # Silently ignore
381
+
382
+ except Exception:
383
+ pass # Silently ignore all cleanup errors
384
+ finally:
385
+ # Remove from cache
386
+ self._run_sessions.pop(cache_key, None)
387
+ self._run_session_contexts.pop(cache_key, None)
388
+
156
389
  async def connect(self, force: bool = False):
157
390
  """Initialize a MultiMCPTools instance and connect to the MCP servers"""
158
391
 
@@ -214,7 +447,7 @@ class MultiMCPTools(Toolkit):
214
447
 
215
448
  server_connection_errors = []
216
449
 
217
- for server_params in self.server_params_list:
450
+ for server_idx, server_params in enumerate(self.server_params_list):
218
451
  try:
219
452
  # Handle stdio connections
220
453
  if isinstance(server_params, StdioServerParameters):
@@ -223,7 +456,7 @@ class MultiMCPTools(Toolkit):
223
456
  session = await self._async_exit_stack.enter_async_context(
224
457
  ClientSession(read, write, read_timeout_seconds=timedelta(seconds=self.timeout_seconds))
225
458
  )
226
- await self.initialize(session)
459
+ await self.initialize(session, server_idx)
227
460
  self._successful_connections += 1
228
461
 
229
462
  # Handle SSE connections
@@ -233,7 +466,7 @@ class MultiMCPTools(Toolkit):
233
466
  )
234
467
  read, write = client_connection
235
468
  session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
236
- await self.initialize(session)
469
+ await self.initialize(session, server_idx)
237
470
  self._successful_connections += 1
238
471
 
239
472
  # Handle Streamable HTTP connections
@@ -243,7 +476,7 @@ class MultiMCPTools(Toolkit):
243
476
  )
244
477
  read, write = client_connection[0:2]
245
478
  session = await self._async_exit_stack.enter_async_context(ClientSession(read, write))
246
- await self.initialize(session)
479
+ await self.initialize(session, server_idx)
247
480
  self._successful_connections += 1
248
481
 
249
482
  except Exception as e:
@@ -268,13 +501,26 @@ class MultiMCPTools(Toolkit):
268
501
  if not self._initialized:
269
502
  return
270
503
 
271
- try:
272
- await self._async_exit_stack.aclose()
273
- self._sessions = []
274
- self._successful_connections = 0
504
+ import warnings
275
505
 
276
- except (RuntimeError, BaseException) as e:
277
- log_error(f"Failed to close MCP connections: {e}")
506
+ # Suppress async generator cleanup warnings
507
+ with warnings.catch_warnings():
508
+ warnings.filterwarnings("ignore", category=RuntimeWarning, message=".*async_generator.*")
509
+ warnings.filterwarnings("ignore", message=".*cancel scope.*")
510
+
511
+ try:
512
+ # Clean up all per-run sessions first
513
+ cache_keys = list(self._run_sessions.keys())
514
+ for run_id, server_idx in cache_keys:
515
+ await self.cleanup_run_session(run_id, server_idx)
516
+
517
+ # Clean up main sessions
518
+ await self._async_exit_stack.aclose()
519
+ self._sessions = []
520
+ self._successful_connections = 0
521
+
522
+ except (RuntimeError, BaseException):
523
+ pass # Silently ignore all cleanup errors
278
524
 
279
525
  self._initialized = False
280
526
 
@@ -298,7 +544,7 @@ class MultiMCPTools(Toolkit):
298
544
  self._successful_connections = 0
299
545
 
300
546
  async def build_tools(self) -> None:
301
- for session in self._sessions:
547
+ for session_list_idx, session in enumerate(self._sessions):
302
548
  # Get the list of tools from the MCP server
303
549
  available_tools = await session.list_tools()
304
550
 
@@ -314,7 +560,12 @@ class MultiMCPTools(Toolkit):
314
560
  for tool in filtered_tools:
315
561
  try:
316
562
  # Get an entrypoint for the tool
317
- entrypoint = get_entrypoint_for_tool(tool, session)
563
+ entrypoint = get_entrypoint_for_tool(
564
+ tool=tool,
565
+ session=session,
566
+ mcp_tools_instance=self, # Pass self to enable dynamic headers
567
+ server_idx=session_list_idx, # Pass session list index for session lookup
568
+ )
318
569
 
319
570
  # Create a Function for the tool
320
571
  f = Function(
@@ -333,14 +584,18 @@ class MultiMCPTools(Toolkit):
333
584
  log_error(f"Failed to register tool {tool.name}: {e}")
334
585
  raise
335
586
 
336
- async def initialize(self, session: ClientSession) -> None:
587
+ async def initialize(self, session: ClientSession, server_idx: int = 0) -> None:
337
588
  """Initialize the MCP toolkit by getting available tools from the MCP server"""
338
589
 
339
590
  try:
340
591
  # Initialize the session if not already initialized
341
592
  await session.initialize()
342
593
 
594
+ # Track which server index this session belongs to
595
+ session_list_idx = len(self._sessions)
343
596
  self._sessions.append(session)
597
+ self._session_to_server_idx[session_list_idx] = server_idx
598
+
344
599
  self._initialized = True
345
600
  except Exception as e:
346
601
  log_error(f"Failed to get MCP tools: {e}")
agno/tools/mem0.py CHANGED
@@ -2,6 +2,7 @@ import json
2
2
  from os import getenv
3
3
  from typing import Any, Dict, List, Optional, Union
4
4
 
5
+ from agno.run import RunContext
5
6
  from agno.tools import Toolkit
6
7
  from agno.utils.log import log_debug, log_error, log_warning
7
8
 
@@ -68,13 +69,13 @@ class Mem0Tools(Toolkit):
68
69
  def _get_user_id(
69
70
  self,
70
71
  method_name: str,
71
- session_state: Dict[str, Any],
72
+ run_context: RunContext,
72
73
  ) -> str:
73
74
  """Resolve the user ID"""
74
75
  resolved_user_id = self.user_id
75
76
  if not resolved_user_id:
76
77
  try:
77
- resolved_user_id = session_state.get("current_user_id")
78
+ resolved_user_id = run_context.user_id
78
79
  except Exception:
79
80
  pass
80
81
  if not resolved_user_id:
@@ -85,7 +86,7 @@ class Mem0Tools(Toolkit):
85
86
 
86
87
  def add_memory(
87
88
  self,
88
- session_state,
89
+ run_context: RunContext,
89
90
  content: Union[str, Dict[str, str]],
90
91
  ) -> str:
91
92
  """Add facts to the user's memory.
@@ -98,7 +99,7 @@ class Mem0Tools(Toolkit):
98
99
  str: JSON-encoded Mem0 response or an error message.
99
100
  """
100
101
 
101
- resolved_user_id = self._get_user_id("add_memory", session_state=session_state)
102
+ resolved_user_id = self._get_user_id("add_memory", run_context=run_context)
102
103
  if isinstance(resolved_user_id, str) and resolved_user_id.startswith("Error in add_memory:"):
103
104
  return resolved_user_id
104
105
  try:
@@ -121,12 +122,12 @@ class Mem0Tools(Toolkit):
121
122
 
122
123
  def search_memory(
123
124
  self,
124
- session_state: Dict[str, Any],
125
+ run_context: RunContext,
125
126
  query: str,
126
127
  ) -> str:
127
128
  """Semantic search for *query* across the user's stored memories."""
128
129
 
129
- resolved_user_id = self._get_user_id("search_memory", session_state=session_state)
130
+ resolved_user_id = self._get_user_id("search_memory", run_context=run_context)
130
131
  if isinstance(resolved_user_id, str) and resolved_user_id.startswith("Error in search_memory:"):
131
132
  return resolved_user_id
132
133
  try:
@@ -151,10 +152,10 @@ class Mem0Tools(Toolkit):
151
152
  log_error(f"Error searching memory: {e}")
152
153
  return f"Error searching memory: {e}"
153
154
 
154
- def get_all_memories(self, session_state: Dict[str, Any]) -> str:
155
+ def get_all_memories(self, run_context: RunContext) -> str:
155
156
  """Return **all** memories for the current user as a JSON string."""
156
157
 
157
- resolved_user_id = self._get_user_id("get_all_memories", session_state=session_state)
158
+ resolved_user_id = self._get_user_id("get_all_memories", run_context=run_context)
158
159
  if isinstance(resolved_user_id, str) and resolved_user_id.startswith("Error in get_all_memories:"):
159
160
  return resolved_user_id
160
161
  try:
@@ -177,10 +178,10 @@ class Mem0Tools(Toolkit):
177
178
  log_error(f"Error getting all memories: {e}")
178
179
  return f"Error getting all memories: {e}"
179
180
 
180
- def delete_all_memories(self, session_state: Dict[str, Any]) -> str:
181
+ def delete_all_memories(self, run_context: RunContext) -> str:
181
182
  """Delete *all* memories associated with the current user"""
182
183
 
183
- resolved_user_id = self._get_user_id("delete_all_memories", session_state=session_state)
184
+ resolved_user_id = self._get_user_id("delete_all_memories", run_context=run_context)
184
185
  if isinstance(resolved_user_id, str) and resolved_user_id.startswith("Error in delete_all_memories:"):
185
186
  error_msg = resolved_user_id
186
187
  log_error(error_msg)