aip-agents-binary 0.0.0__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 (447) hide show
  1. aip_agents/__init__.py +66 -0
  2. aip_agents/a2a/__init__.py +19 -0
  3. aip_agents/a2a/server/__init__.py +10 -0
  4. aip_agents/a2a/server/base_executor.py +1125 -0
  5. aip_agents/a2a/server/google_adk_executor.py +198 -0
  6. aip_agents/a2a/server/langflow_executor.py +180 -0
  7. aip_agents/a2a/server/langgraph_executor.py +270 -0
  8. aip_agents/a2a/types.py +232 -0
  9. aip_agents/a2ui/__init__.py +26 -0
  10. aip_agents/a2ui/catalog_provider.py +109 -0
  11. aip_agents/a2ui/examples/0.8/booking_form.json +1 -0
  12. aip_agents/a2ui/examples/0.8/confirmation.json +1 -0
  13. aip_agents/a2ui/examples/0.8/single_column_list.json +1 -0
  14. aip_agents/a2ui/examples/0.9/booking_form.json +1 -0
  15. aip_agents/a2ui/examples/0.9/confirmation.json +1 -0
  16. aip_agents/a2ui/examples/0.9/single_column_list.json +1 -0
  17. aip_agents/a2ui/middleware.py +754 -0
  18. aip_agents/a2ui/models.py +153 -0
  19. aip_agents/a2ui/public_url.py +138 -0
  20. aip_agents/a2ui/stream_filter.py +67 -0
  21. aip_agents/agent/__init__.py +72 -0
  22. aip_agents/agent/base_agent.py +1437 -0
  23. aip_agents/agent/base_langgraph_agent.py +4048 -0
  24. aip_agents/agent/google_adk_agent.py +932 -0
  25. aip_agents/agent/google_adk_constants.py +11 -0
  26. aip_agents/agent/graph_runtime.py +185 -0
  27. aip_agents/agent/hitl/__init__.py +24 -0
  28. aip_agents/agent/hitl/config.py +28 -0
  29. aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
  30. aip_agents/agent/hitl/manager.py +532 -0
  31. aip_agents/agent/hitl/models.py +18 -0
  32. aip_agents/agent/hitl/prompt/__init__.py +9 -0
  33. aip_agents/agent/hitl/prompt/base.py +42 -0
  34. aip_agents/agent/hitl/prompt/deferred.py +73 -0
  35. aip_agents/agent/hitl/registry.py +149 -0
  36. aip_agents/agent/interface.py +138 -0
  37. aip_agents/agent/interfaces.py +65 -0
  38. aip_agents/agent/langflow_agent.py +464 -0
  39. aip_agents/agent/langgraph_react_agent.py +3741 -0
  40. aip_agents/agent/model_invocation_adapter.py +346 -0
  41. aip_agents/agent/system_instruction_context.py +34 -0
  42. aip_agents/agent/tool_execution_engine.py +587 -0
  43. aip_agents/audio_interface/__init__.py +30 -0
  44. aip_agents/audio_interface/audio_agent_adapter.py +254 -0
  45. aip_agents/audio_interface/config.py +146 -0
  46. aip_agents/audio_interface/errors.py +15 -0
  47. aip_agents/audio_interface/livekit_audio_session.py +504 -0
  48. aip_agents/audio_interface/livekit_realtime_audio_session.py +142 -0
  49. aip_agents/audio_interface/session_factory.py +40 -0
  50. aip_agents/clients/__init__.py +10 -0
  51. aip_agents/clients/langflow/__init__.py +10 -0
  52. aip_agents/clients/langflow/client.py +477 -0
  53. aip_agents/clients/langflow/types.py +18 -0
  54. aip_agents/common/__init__.py +5 -0
  55. aip_agents/common/callable_introspection.py +17 -0
  56. aip_agents/constants.py +23 -0
  57. aip_agents/context/__init__.py +5 -0
  58. aip_agents/context/budget.py +237 -0
  59. aip_agents/context/compaction.py +339 -0
  60. aip_agents/context/metadata.py +169 -0
  61. aip_agents/context/offload.py +449 -0
  62. aip_agents/context/preparation.py +196 -0
  63. aip_agents/context/prompt.py +61 -0
  64. aip_agents/context/runtime.py +149 -0
  65. aip_agents/context/session_recall.py +69 -0
  66. aip_agents/context/state.py +48 -0
  67. aip_agents/context/utils.py +44 -0
  68. aip_agents/credentials/manager.py +132 -0
  69. aip_agents/examples/__init__.py +5 -0
  70. aip_agents/examples/audio_livekit_session.py +116 -0
  71. aip_agents/examples/compare_streaming_client.py +783 -0
  72. aip_agents/examples/compare_streaming_server.py +141 -0
  73. aip_agents/examples/gl_connectors_smoke.py +148 -0
  74. aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
  75. aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
  76. aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
  77. aip_agents/examples/hello_world_a2a_google_adk_server.py +78 -0
  78. aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
  79. aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
  80. aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
  81. aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
  82. aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
  83. aip_agents/examples/hello_world_a2a_langchain_reference_server.py +104 -0
  84. aip_agents/examples/hello_world_a2a_langchain_server.py +78 -0
  85. aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +77 -0
  86. aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
  87. aip_agents/examples/hello_world_a2a_langflow_server.py +80 -0
  88. aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
  89. aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
  90. aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +91 -0
  91. aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
  92. aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
  93. aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
  94. aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
  95. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
  96. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
  97. aip_agents/examples/hello_world_a2a_langgraph_server.py +83 -0
  98. aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +78 -0
  99. aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +131 -0
  100. aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
  101. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
  102. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +250 -0
  103. aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
  104. aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +79 -0
  105. aip_agents/examples/hello_world_filesystem.py +162 -0
  106. aip_agents/examples/hello_world_google_adk.py +41 -0
  107. aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
  108. aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
  109. aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
  110. aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
  111. aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
  112. aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
  113. aip_agents/examples/hello_world_google_adk_stream.py +44 -0
  114. aip_agents/examples/hello_world_langchain.py +28 -0
  115. aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
  116. aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
  117. aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
  118. aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
  119. aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
  120. aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
  121. aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
  122. aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
  123. aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
  124. aip_agents/examples/hello_world_langchain_stream.py +36 -0
  125. aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
  126. aip_agents/examples/hello_world_langflow_agent.py +163 -0
  127. aip_agents/examples/hello_world_langgraph.py +39 -0
  128. aip_agents/examples/hello_world_langgraph_gl_connector_twitter.py +44 -0
  129. aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
  130. aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
  131. aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
  132. aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
  133. aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
  134. aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
  135. aip_agents/examples/hello_world_langgraph_skills.py +62 -0
  136. aip_agents/examples/hello_world_langgraph_stream.py +43 -0
  137. aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
  138. aip_agents/examples/hello_world_model_switch_cli.py +210 -0
  139. aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
  140. aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
  141. aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
  142. aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
  143. aip_agents/examples/hello_world_multi_agent_tool_output_client.py +57 -0
  144. aip_agents/examples/hello_world_multi_agent_tool_output_server.py +131 -0
  145. aip_agents/examples/hello_world_pii_logger.py +21 -0
  146. aip_agents/examples/hello_world_ptc.py +49 -0
  147. aip_agents/examples/hello_world_ptc_custom_tools.py +83 -0
  148. aip_agents/examples/hello_world_sentry.py +133 -0
  149. aip_agents/examples/hello_world_step_limits.py +273 -0
  150. aip_agents/examples/hello_world_stock_a2a_server.py +100 -0
  151. aip_agents/examples/hello_world_tool_output_client.py +55 -0
  152. aip_agents/examples/hello_world_tool_output_server.py +113 -0
  153. aip_agents/examples/hitl_demo.py +724 -0
  154. aip_agents/examples/mcp_configs/configs.py +63 -0
  155. aip_agents/examples/mcp_servers/common.py +76 -0
  156. aip_agents/examples/mcp_servers/mcp_name.py +29 -0
  157. aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
  158. aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
  159. aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
  160. aip_agents/examples/mcp_servers/mcp_time.py +10 -0
  161. aip_agents/examples/pii_demo_langgraph_client.py +69 -0
  162. aip_agents/examples/pii_demo_langgraph_server.py +125 -0
  163. aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
  164. aip_agents/examples/pii_demo_multi_agent_server.py +246 -0
  165. aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
  166. aip_agents/examples/todolist_planning_a2a_langgraph_server.py +87 -0
  167. aip_agents/examples/tools/__init__.py +27 -0
  168. aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
  169. aip_agents/examples/tools/adk_weather_tool.py +60 -0
  170. aip_agents/examples/tools/data_generator_tool.py +103 -0
  171. aip_agents/examples/tools/data_visualization_tool.py +312 -0
  172. aip_agents/examples/tools/image_artifact_tool.py +136 -0
  173. aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
  174. aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
  175. aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
  176. aip_agents/examples/tools/langchain_weather_tool.py +48 -0
  177. aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
  178. aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
  179. aip_agents/examples/tools/multiply_tool.py +43 -0
  180. aip_agents/examples/tools/pii_demo_tools.py +189 -0
  181. aip_agents/examples/tools/random_chart_tool.py +142 -0
  182. aip_agents/examples/tools/serper_tool.py +202 -0
  183. aip_agents/examples/tools/stock_tools.py +82 -0
  184. aip_agents/examples/tools/table_generator_tool.py +167 -0
  185. aip_agents/examples/tools/time_tool.py +82 -0
  186. aip_agents/examples/tools/weather_forecast_tool.py +38 -0
  187. aip_agents/guardrails/__init__.py +83 -0
  188. aip_agents/guardrails/engines/__init__.py +69 -0
  189. aip_agents/guardrails/engines/base.py +90 -0
  190. aip_agents/guardrails/engines/nemo.py +101 -0
  191. aip_agents/guardrails/engines/phrase_matcher.py +113 -0
  192. aip_agents/guardrails/exceptions.py +39 -0
  193. aip_agents/guardrails/manager.py +163 -0
  194. aip_agents/guardrails/middleware.py +199 -0
  195. aip_agents/guardrails/schemas.py +63 -0
  196. aip_agents/guardrails/utils.py +45 -0
  197. aip_agents/integration/__init__.py +43 -0
  198. aip_agents/integration/agent.py +58 -0
  199. aip_agents/integration/guardrails.py +52 -0
  200. aip_agents/integration/hitl.py +71 -0
  201. aip_agents/integration/ptc.py +95 -0
  202. aip_agents/integration/skills.py +37 -0
  203. aip_agents/integration/storage.py +81 -0
  204. aip_agents/integration/version.py +103 -0
  205. aip_agents/mcp/__init__.py +1 -0
  206. aip_agents/mcp/client/__init__.py +60 -0
  207. aip_agents/mcp/client/base_mcp_client.py +377 -0
  208. aip_agents/mcp/client/connection_manager.py +231 -0
  209. aip_agents/mcp/client/errors.py +119 -0
  210. aip_agents/mcp/client/google_adk/__init__.py +45 -0
  211. aip_agents/mcp/client/google_adk/client.py +383 -0
  212. aip_agents/mcp/client/langchain/__init__.py +11 -0
  213. aip_agents/mcp/client/langchain/client.py +265 -0
  214. aip_agents/mcp/client/persistent_session.py +704 -0
  215. aip_agents/mcp/client/session_pool.py +358 -0
  216. aip_agents/mcp/client/transports.py +288 -0
  217. aip_agents/mcp/utils/__init__.py +7 -0
  218. aip_agents/mcp/utils/config_validator.py +139 -0
  219. aip_agents/memory/__init__.py +14 -0
  220. aip_agents/memory/adapters/__init__.py +10 -0
  221. aip_agents/memory/adapters/base_adapter.py +815 -0
  222. aip_agents/memory/adapters/mem0.py +286 -0
  223. aip_agents/memory/base.py +84 -0
  224. aip_agents/memory/constants.py +49 -0
  225. aip_agents/memory/factory.py +86 -0
  226. aip_agents/memory/guidance.py +20 -0
  227. aip_agents/memory/simple_memory.py +47 -0
  228. aip_agents/middleware/__init__.py +65 -0
  229. aip_agents/middleware/backends/__init__.py +72 -0
  230. aip_agents/middleware/backends/in_memory.py +578 -0
  231. aip_agents/middleware/backends/local_disk.py +1203 -0
  232. aip_agents/middleware/backends/protocol.py +353 -0
  233. aip_agents/middleware/backends/sandbox.py +1431 -0
  234. aip_agents/middleware/backends/sandbox_files.py +113 -0
  235. aip_agents/middleware/backends/utils.py +293 -0
  236. aip_agents/middleware/base.py +239 -0
  237. aip_agents/middleware/filesystem.py +690 -0
  238. aip_agents/middleware/manager.py +291 -0
  239. aip_agents/middleware/memory.py +532 -0
  240. aip_agents/middleware/pii_middleware.py +192 -0
  241. aip_agents/middleware/schema.py +19 -0
  242. aip_agents/middleware/skills.py +872 -0
  243. aip_agents/middleware/task_delegation.py +1656 -0
  244. aip_agents/middleware/todolist.py +276 -0
  245. aip_agents/middleware/tools/__init__.py +23 -0
  246. aip_agents/middleware/tools/edit_file.py +70 -0
  247. aip_agents/middleware/tools/execute.py +428 -0
  248. aip_agents/middleware/tools/grep_file.py +79 -0
  249. aip_agents/middleware/tools/ls.py +71 -0
  250. aip_agents/middleware/tools/read_file.py +239 -0
  251. aip_agents/middleware/tools/skill_load.py +193 -0
  252. aip_agents/middleware/tools/skill_resource.py +338 -0
  253. aip_agents/middleware/tools/write_file.py +114 -0
  254. aip_agents/middleware/vision.py +322 -0
  255. aip_agents/ptc/__init__.py +87 -0
  256. aip_agents/ptc/custom_tools.py +511 -0
  257. aip_agents/ptc/custom_tools_payload.py +499 -0
  258. aip_agents/ptc/custom_tools_templates/__init__.py +1 -0
  259. aip_agents/ptc/custom_tools_templates/custom_build_function.py.template +24 -0
  260. aip_agents/ptc/custom_tools_templates/custom_init.py.template +26 -0
  261. aip_agents/ptc/custom_tools_templates/custom_invoke.py.template +149 -0
  262. aip_agents/ptc/custom_tools_templates/custom_registry.py.template +112 -0
  263. aip_agents/ptc/custom_tools_templates/custom_sources_init.py.template +7 -0
  264. aip_agents/ptc/custom_tools_templates/custom_wrapper.py.template +87 -0
  265. aip_agents/ptc/doc_gen.py +122 -0
  266. aip_agents/ptc/exceptions.py +57 -0
  267. aip_agents/ptc/executor.py +627 -0
  268. aip_agents/ptc/mcp/__init__.py +45 -0
  269. aip_agents/ptc/mcp/sandbox_bridge.py +679 -0
  270. aip_agents/ptc/mcp/templates/__init__.py +1 -0
  271. aip_agents/ptc/mcp/templates/mcp_client.py.template +240 -0
  272. aip_agents/ptc/naming.py +430 -0
  273. aip_agents/ptc/payload.py +26 -0
  274. aip_agents/ptc/prompt_builder.py +674 -0
  275. aip_agents/ptc/ptc_helper.py +16 -0
  276. aip_agents/ptc/sandbox_bridge.py +297 -0
  277. aip_agents/ptc/template_utils.py +33 -0
  278. aip_agents/ptc/templates/__init__.py +1 -0
  279. aip_agents/ptc/templates/ptc_helper.py.template +134 -0
  280. aip_agents/ptc/tool_def_helpers.py +101 -0
  281. aip_agents/ptc/tool_enrichment.py +163 -0
  282. aip_agents/resilience/__init__.py +10 -0
  283. aip_agents/resilience/base.py +46 -0
  284. aip_agents/resilience/tool.py +1661 -0
  285. aip_agents/sandbox/__init__.py +62 -0
  286. aip_agents/sandbox/defaults.py +220 -0
  287. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1-datasaur.Dockerfile +120 -0
  288. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1.Dockerfile +100 -0
  289. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2-datasaur.Dockerfile +143 -0
  290. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2.Dockerfile +119 -0
  291. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3-datasaur.Dockerfile +173 -0
  292. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3.Dockerfile +130 -0
  293. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.Dockerfile +164 -0
  294. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.patch_js_context.py +41 -0
  295. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.requirements.txt +29 -0
  296. aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2.Dockerfile +87 -0
  297. aip_agents/sandbox/e2b_runtime.py +27 -0
  298. aip_agents/sandbox/image_registry.py +98 -0
  299. aip_agents/sandbox/providers/__init__.py +76 -0
  300. aip_agents/sandbox/providers/base.py +392 -0
  301. aip_agents/sandbox/providers/bedrock.py +28 -0
  302. aip_agents/sandbox/providers/e2b.py +230 -0
  303. aip_agents/sandbox/providers/opensandbox.py +163 -0
  304. aip_agents/sandbox/sandbox_runtime.py +967 -0
  305. aip_agents/sandbox/template_builder.py +240 -0
  306. aip_agents/sandbox/types.py +141 -0
  307. aip_agents/sandbox/validation.py +50 -0
  308. aip_agents/schema/__init__.py +106 -0
  309. aip_agents/schema/a2a.py +58 -0
  310. aip_agents/schema/agent.py +117 -0
  311. aip_agents/schema/artifacts.py +75 -0
  312. aip_agents/schema/attachments.py +425 -0
  313. aip_agents/schema/hitl.py +157 -0
  314. aip_agents/schema/langgraph.py +47 -0
  315. aip_agents/schema/model_id.py +103 -0
  316. aip_agents/schema/model_usage.py +50 -0
  317. aip_agents/schema/runtime_metadata.py +78 -0
  318. aip_agents/schema/step_limit.py +108 -0
  319. aip_agents/schema/storage.py +40 -0
  320. aip_agents/schema/tool_payloads.py +68 -0
  321. aip_agents/sentry/__init__.py +11 -0
  322. aip_agents/sentry/sentry.py +267 -0
  323. aip_agents/skills/__init__.py +30 -0
  324. aip_agents/skills/errors.py +110 -0
  325. aip_agents/skills/installer.py +479 -0
  326. aip_agents/skills/models.py +769 -0
  327. aip_agents/skills/stager.py +224 -0
  328. aip_agents/skills/validation.py +53 -0
  329. aip_agents/storage/__init__.py +41 -0
  330. aip_agents/storage/base.py +85 -0
  331. aip_agents/storage/clients/__init__.py +12 -0
  332. aip_agents/storage/clients/minio_client.py +318 -0
  333. aip_agents/storage/config.py +62 -0
  334. aip_agents/storage/providers/__init__.py +15 -0
  335. aip_agents/storage/providers/base.py +106 -0
  336. aip_agents/storage/providers/memory.py +114 -0
  337. aip_agents/storage/providers/object_storage.py +214 -0
  338. aip_agents/tools/__init__.py +65 -0
  339. aip_agents/tools/agent_schedule_tool.py +392 -0
  340. aip_agents/tools/browser_use/__init__.py +83 -0
  341. aip_agents/tools/browser_use/action_parser.py +104 -0
  342. aip_agents/tools/browser_use/browser_use_tool.py +1118 -0
  343. aip_agents/tools/browser_use/llm_config.py +120 -0
  344. aip_agents/tools/browser_use/minio_storage.py +203 -0
  345. aip_agents/tools/browser_use/schemas.py +121 -0
  346. aip_agents/tools/browser_use/session.py +13 -0
  347. aip_agents/tools/browser_use/session_errors.py +132 -0
  348. aip_agents/tools/browser_use/steel_session_recording.py +321 -0
  349. aip_agents/tools/browser_use/streaming.py +263 -0
  350. aip_agents/tools/browser_use/structured_data_parser.py +257 -0
  351. aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
  352. aip_agents/tools/browser_use/types.py +37 -0
  353. aip_agents/tools/code_sandbox/__init__.py +48 -0
  354. aip_agents/tools/code_sandbox/code_sandbox_tool.py +350 -0
  355. aip_agents/tools/code_sandbox/constant.py +13 -0
  356. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +346 -0
  357. aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +65 -0
  358. aip_agents/tools/computer_use/__init__.py +75 -0
  359. aip_agents/tools/computer_use/computer_use_tool.py +910 -0
  360. aip_agents/tools/computer_use/schemas.py +78 -0
  361. aip_agents/tools/computer_use/streaming.py +198 -0
  362. aip_agents/tools/computer_use/types.py +32 -0
  363. aip_agents/tools/constants.py +177 -0
  364. aip_agents/tools/date_range_tool.py +554 -0
  365. aip_agents/tools/document_loader/__init__.py +44 -0
  366. aip_agents/tools/document_loader/base_reader.py +302 -0
  367. aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
  368. aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
  369. aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
  370. aip_agents/tools/document_loader/pdf_splitter.py +169 -0
  371. aip_agents/tools/execute_ptc_code.py +582 -0
  372. aip_agents/tools/gl_connector/__init__.py +5 -0
  373. aip_agents/tools/gl_connector/tool.py +518 -0
  374. aip_agents/tools/gl_connector_tools.py +170 -0
  375. aip_agents/tools/memory_search/__init__.py +29 -0
  376. aip_agents/tools/memory_search/base.py +213 -0
  377. aip_agents/tools/memory_search/mem0.py +424 -0
  378. aip_agents/tools/memory_search/schema.py +81 -0
  379. aip_agents/tools/memory_search_tool.py +34 -0
  380. aip_agents/tools/streaming_common.py +577 -0
  381. aip_agents/tools/time_tool.py +117 -0
  382. aip_agents/tools/tool_config_injector.py +300 -0
  383. aip_agents/tools/web_search/__init__.py +15 -0
  384. aip_agents/tools/web_search/serper_tool.py +187 -0
  385. aip_agents/types/__init__.py +73 -0
  386. aip_agents/types/a2a_events.py +13 -0
  387. aip_agents/utils/__init__.py +91 -0
  388. aip_agents/utils/a2a_connector.py +1758 -0
  389. aip_agents/utils/artifact_helpers.py +542 -0
  390. aip_agents/utils/artifacts/__init__.py +20 -0
  391. aip_agents/utils/artifacts/tracker.py +367 -0
  392. aip_agents/utils/attachment_helpers.py +178 -0
  393. aip_agents/utils/constants.py +22 -0
  394. aip_agents/utils/datetime/__init__.py +34 -0
  395. aip_agents/utils/datetime/normalization.py +231 -0
  396. aip_agents/utils/datetime/timezone.py +206 -0
  397. aip_agents/utils/env_loader.py +27 -0
  398. aip_agents/utils/event_handler_registry.py +58 -0
  399. aip_agents/utils/file_prompt_utils.py +276 -0
  400. aip_agents/utils/file_watchers/__init__.py +33 -0
  401. aip_agents/utils/file_watchers/base.py +46 -0
  402. aip_agents/utils/file_watchers/e2b.py +128 -0
  403. aip_agents/utils/file_watchers/factory.py +39 -0
  404. aip_agents/utils/file_watchers/ls_diff.py +88 -0
  405. aip_agents/utils/file_watchers/sandbox.py +66 -0
  406. aip_agents/utils/file_watchers/watchdog.py +97 -0
  407. aip_agents/utils/final_response_builder.py +211 -0
  408. aip_agents/utils/formatter_llm_client.py +258 -0
  409. aip_agents/utils/langgraph/__init__.py +19 -0
  410. aip_agents/utils/langgraph/converter.py +399 -0
  411. aip_agents/utils/langgraph/delegation_context.py +22 -0
  412. aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
  413. aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
  414. aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
  415. aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1329 -0
  416. aip_agents/utils/langgraph/tool_output_management.py +1047 -0
  417. aip_agents/utils/logger.py +195 -0
  418. aip_agents/utils/metadata/__init__.py +27 -0
  419. aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
  420. aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
  421. aip_agents/utils/metadata/activity_narrative/builder.py +822 -0
  422. aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
  423. aip_agents/utils/metadata/activity_narrative/context.py +49 -0
  424. aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
  425. aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
  426. aip_agents/utils/metadata/schemas/__init__.py +16 -0
  427. aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
  428. aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
  429. aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
  430. aip_agents/utils/metadata_helper.py +363 -0
  431. aip_agents/utils/name_preprocessor/__init__.py +17 -0
  432. aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
  433. aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
  434. aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
  435. aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
  436. aip_agents/utils/pii/__init__.py +29 -0
  437. aip_agents/utils/pii/pii_handler.py +555 -0
  438. aip_agents/utils/pii/pii_helper.py +225 -0
  439. aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
  440. aip_agents/utils/reference_helper.py +273 -0
  441. aip_agents/utils/sse_chunk_transformer.py +836 -0
  442. aip_agents/utils/step_limit_manager.py +265 -0
  443. aip_agents/utils/token_usage_helper.py +531 -0
  444. aip_agents_binary-0.0.0.dist-info/METADATA +825 -0
  445. aip_agents_binary-0.0.0.dist-info/RECORD +447 -0
  446. aip_agents_binary-0.0.0.dist-info/WHEEL +5 -0
  447. aip_agents_binary-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,377 @@
1
+ """Base MCP Client for aip-agents with Session Persistence.
2
+
3
+ This base class provides persistent session management for MCP connections and serves
4
+ as the foundation for SDK-specific MCP clients (LangChain, Google ADK, etc.). It handles
5
+ session pooling, initialization, tool discovery, and cleanup while leaving SDK-specific
6
+ tool conversion to subclasses.
7
+
8
+ Authors:
9
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
10
+ """
11
+
12
+ import asyncio
13
+ from abc import ABC, abstractmethod
14
+ from typing import Any
15
+
16
+ from gllm_tools.mcp.client.client import MCPClient
17
+ from gllm_tools.mcp.client.config import MCPConfiguration
18
+ from gllm_tools.mcp.client.resource import MCPResource
19
+ from mcp.types import CallToolResult, Tool
20
+
21
+ from aip_agents.mcp.client.persistent_session import PersistentMCPSession
22
+ from aip_agents.mcp.client.session_pool import MCPSessionPool
23
+ from aip_agents.utils.logger import get_logger
24
+
25
+ logger = get_logger(__name__)
26
+
27
+
28
+ class BaseMCPClient(MCPClient, ABC):
29
+ """Base MCP Client with persistent session management for aip-agents.
30
+
31
+ This class provides:
32
+ - Persistent session management across tool calls
33
+ - One-time tool registration and caching
34
+ - Automatic connection health monitoring and reconnection
35
+ - Centralized cleanup of all MCP resources
36
+ - Generic tool discovery from all configured MCP servers
37
+
38
+ Subclasses should implement SDK-specific tool conversion in _process_tool() method.
39
+ """
40
+
41
+ def __init__(self, servers: dict[str, MCPConfiguration]):
42
+ """Initialize the base MCP client with session pool.
43
+
44
+ Args:
45
+ servers (dict[str, MCPConfiguration]): Dictionary of MCP server configurations by server name
46
+ """
47
+ super().__init__(servers)
48
+ self.session_pool = MCPSessionPool()
49
+ self._tools_cache: list[Tool] = []
50
+ self._initialized = False
51
+ # Guard against concurrent initialization
52
+ self._init_lock: asyncio.Lock = asyncio.Lock()
53
+
54
+ async def initialize(self) -> None:
55
+ """Initialize all MCP sessions and cache tools once.
56
+
57
+ This method is idempotent and only performs initialization if not already done.
58
+ It establishes persistent connections to all configured MCP servers and caches
59
+ available tools for efficient access.
60
+
61
+ Raises:
62
+ Exception: If any session initialization fails
63
+ """
64
+ if self._initialized:
65
+ logger.debug("BaseMCPClient already initialized, skipping")
66
+ return
67
+
68
+ # Ensure only one concurrent initializer proceeds
69
+ async with self._init_lock:
70
+ if self._initialized:
71
+ logger.debug("BaseMCPClient already initialized (post-lock), skipping")
72
+ return
73
+
74
+ logger.info(f"Initializing BaseMCPClient with {len(self.servers)} MCP servers")
75
+
76
+ try:
77
+ # Initialize all persistent sessions concurrently
78
+ await self.session_pool.initialize_all_sessions(self.servers)
79
+
80
+ # Cache all available tools from all sessions
81
+ self._tools_cache = await self.session_pool.get_all_tools()
82
+
83
+ self._initialized = True
84
+ logger.info(
85
+ f"BaseMCPClient initialization complete: "
86
+ f"{len(self._tools_cache)} tools cached from {self.session_pool.session_count} sessions"
87
+ )
88
+ except asyncio.CancelledError:
89
+ logger.info("BaseMCPClient initialization cancelled; running cleanup")
90
+ await self.cleanup()
91
+ raise
92
+ except Exception as e:
93
+ logger.error(f"Failed to initialize BaseMCPClient: {e}", exc_info=True)
94
+ # Cleanup any partially initialized sessions on failure
95
+ await self.cleanup()
96
+ raise
97
+
98
+ @abstractmethod
99
+ async def get_tools(self, server: str | None = None) -> list[Any]:
100
+ """Get framework-specific tools from MCP servers.
101
+
102
+ This method must be implemented by subclasses to provide framework-specific
103
+ tool conversion (e.g., StructuredTool for LangChain, FunctionTool for Google ADK).
104
+
105
+ Args:
106
+ server (str | None): Optional server name to filter tools from a specific server.
107
+ If None, returns tools from all configured servers.
108
+
109
+ Returns:
110
+ list[Any]: List of framework-specific tool objects.
111
+ """
112
+ pass # pragma: no cover # Abstract method - cannot be executed directly
113
+
114
+ async def get_raw_mcp_tools(self, server: str | None = None) -> list[Tool]:
115
+ """Get raw MCP tools - for subclasses to perform framework-specific conversions.
116
+
117
+ This method provides access to the cached raw MCP Tool objects.
118
+ Subclasses use this to convert to framework-specific tools.
119
+
120
+ Args:
121
+ server (str | None): Optional server name to filter tools from a specific server.
122
+ If None, returns tools from all configured servers.
123
+
124
+ Returns:
125
+ list[Tool]: List of raw MCP Tool objects. Empty list if not initialized or no tools available.
126
+ """
127
+ if not self._initialized:
128
+ await self.initialize()
129
+
130
+ if server:
131
+ if server not in self.servers:
132
+ logger.warning(f"Server '{server}' not found in configuration")
133
+ return []
134
+
135
+ try:
136
+ session = self.session_pool.get_session(server)
137
+ return await session.list_tools()
138
+ except KeyError:
139
+ logger.warning(f"No active session found for server '{server}'")
140
+ return []
141
+ except Exception as e:
142
+ logger.error(f"Failed to get tools from server '{server}': {e}")
143
+ return []
144
+ else:
145
+ return self._tools_cache.copy()
146
+
147
+ def get_tools_count(self, server: str | None = None) -> int:
148
+ """Get count of raw MCP tools without expensive copying.
149
+
150
+ This is an efficient way to get tool counts for logging/metrics
151
+ without the overhead of copying tool lists.
152
+
153
+ Args:
154
+ server (str | None): Optional server name to filter tools from a specific server.
155
+ If None, returns count from all configured servers.
156
+
157
+ Returns:
158
+ int: Count of raw MCP tools available.
159
+ """
160
+ if not self._initialized:
161
+ return 0
162
+
163
+ if server:
164
+ if server not in self.servers:
165
+ return 0
166
+ try:
167
+ session = self.session_pool.get_session(server)
168
+ return session.get_tools_count()
169
+ except KeyError:
170
+ return 0
171
+ except Exception as e:
172
+ logger.error(f"Failed to get tools count from server '{server}': {e}")
173
+ return 0
174
+ else:
175
+ return len(self._tools_cache)
176
+
177
+ async def get_session(self, server_name: str) -> PersistentMCPSession:
178
+ """Get a persistent session for a specific server.
179
+
180
+ Args:
181
+ server_name (str): The name of the MCP server
182
+
183
+ Returns:
184
+ PersistentMCPSession: Persistent MCP session for the specified server
185
+
186
+ Raises:
187
+ KeyError: If the server is not configured or no active session exists
188
+ """
189
+ if not self._initialized:
190
+ await self.initialize()
191
+
192
+ try:
193
+ return self.session_pool.get_session(server_name)
194
+ except KeyError:
195
+ logger.error(f"Server '{server_name}' not found in configuration")
196
+ raise
197
+ except Exception as e:
198
+ logger.error(f"Failed to get session for server '{server_name}': {e}")
199
+ raise
200
+
201
+ async def call_tool(self, server_name: str, tool_name: str, arguments: dict[str, Any]) -> CallToolResult:
202
+ """Execute a tool on a specific MCP server using persistent session.
203
+
204
+ Args:
205
+ server_name (str): The MCP server to execute the tool on
206
+ tool_name (str): The name of the tool to execute
207
+ arguments (dict[str, Any]): Arguments for the tool execution
208
+
209
+ Returns:
210
+ CallToolResult: The result of the tool execution
211
+
212
+ Raises:
213
+ KeyError: If the server doesn't exist
214
+ Exception: If tool execution fails
215
+ """
216
+ if not self._initialized:
217
+ await self.initialize()
218
+
219
+ try:
220
+ session = self.session_pool.get_session(server_name)
221
+ return await session.call_tool(tool_name, arguments)
222
+ except KeyError:
223
+ logger.error(f"Server '{server_name}' not found in configuration")
224
+ raise
225
+ except Exception as e:
226
+ logger.error(f"Failed to call tool '{tool_name}' on server '{server_name}': {e}")
227
+ raise
228
+
229
+ async def read_resource(self, server_name: str, resource_uri: str) -> Any:
230
+ """Read an MCP resource from a specific server using persistent session.
231
+
232
+ Args:
233
+ server_name (str): The MCP server to read the resource from
234
+ resource_uri (str): The URI of the resource to read
235
+
236
+ Returns:
237
+ Any: The resource content
238
+
239
+ Raises:
240
+ KeyError: If the server doesn't exist
241
+ Exception: If resource reading fails
242
+ """
243
+ if not self._initialized:
244
+ await self.initialize()
245
+
246
+ try:
247
+ session = self.session_pool.get_session(server_name)
248
+ return await session.read_resource(resource_uri)
249
+ except KeyError:
250
+ logger.error(f"Server '{server_name}' not found in configuration")
251
+ raise
252
+ except Exception as e:
253
+ logger.error(f"Failed to read resource '{resource_uri}' from server '{server_name}': {e}")
254
+ raise
255
+
256
+ async def cleanup(self) -> None:
257
+ """Clean up all MCP resources and close sessions.
258
+
259
+ This method properly closes all persistent sessions and cleans up resources.
260
+ It can be called multiple times safely.
261
+ """
262
+ should_cleanup_pool = self._initialized or self.session_pool.session_count > 0
263
+
264
+ if should_cleanup_pool:
265
+ logger.info("Cleaning up BaseMCPClient resources")
266
+ cleanup_error = None
267
+ try:
268
+ await self.session_pool.close_all_sessions()
269
+ except TimeoutError as e:
270
+ cleanup_error = e
271
+ logger.warning(f"Timeout during BaseMCPClient cleanup: {e}")
272
+ except Exception as e:
273
+ cleanup_error = e
274
+ logger.error(f"Unexpected error during BaseMCPClient cleanup: {e}", exc_info=True)
275
+ # Don't re-raise - cleanup should be best-effort
276
+ finally:
277
+ # Always clear cache and mark as not initialized, even if session cleanup failed
278
+ self._tools_cache.clear()
279
+ self._initialized = False
280
+ if cleanup_error:
281
+ logger.info("BaseMCPClient cleanup completed (session cleanup had errors)")
282
+ else:
283
+ logger.info("BaseMCPClient cleanup completed successfully")
284
+ else:
285
+ self._tools_cache.clear()
286
+ self._initialized = False
287
+ logger.debug("BaseMCPClient cleanup called with no active sessions, skipping pool close")
288
+
289
+ @property
290
+ def is_initialized(self) -> bool:
291
+ """Check if the client is fully initialized.
292
+
293
+ Returns:
294
+ bool: True if sessions are initialized and tools are cached, False otherwise
295
+ """
296
+ return self._initialized
297
+
298
+ @property
299
+ def active_sessions(self) -> dict[str, PersistentMCPSession]:
300
+ """Get all active persistent sessions.
301
+
302
+ Returns:
303
+ dict[str, PersistentMCPSession]: Dictionary of active sessions by server name
304
+ """
305
+ if not self._initialized:
306
+ return {}
307
+ return self.session_pool.get_all_active_sessions()
308
+
309
+ def get_session_count(self) -> int:
310
+ """Get the number of active MCP sessions.
311
+
312
+ Returns:
313
+ Number of active persistent sessions
314
+ """
315
+ return self.session_pool.session_count
316
+
317
+ def _get_annotated_server(self, resource: MCPResource) -> str | None:
318
+ """Extract server name from resource annotations if valid.
319
+
320
+ Args:
321
+ resource (MCPResource): The MCP resource.
322
+
323
+ Returns:
324
+ str | None: Annotated server name if valid and configured, else None.
325
+ """
326
+ if not self.servers:
327
+ return None
328
+
329
+ annotations = getattr(resource, "annotations", None)
330
+ if annotations and hasattr(annotations, "server") and annotations.server:
331
+ annotated = annotations.server
332
+ if annotated in self.servers:
333
+ return annotated
334
+ logger.warning(f"Resource annotation requested server '{annotated}' which is not configured; falling back")
335
+ return None
336
+
337
+ def _get_default_server(self) -> str:
338
+ """Get the default server name (first configured server).
339
+
340
+ Returns:
341
+ str: First server name.
342
+
343
+ Raises:
344
+ ValueError: If no servers configured.
345
+ """
346
+ if not self.servers:
347
+ raise ValueError("No MCP servers configured; cannot resolve server for resource")
348
+
349
+ server_list = list(self.servers.keys())
350
+ if len(server_list) > 1:
351
+ logger.warning(
352
+ "Multiple MCP servers configured but resource provided no annotation; "
353
+ f"defaulting to first server '{server_list[0]}'"
354
+ )
355
+ return server_list[0]
356
+
357
+ def _determine_server_name_for_resource(self, resource: MCPResource) -> str:
358
+ """Determine the server name for an MCP resource.
359
+
360
+ Args:
361
+ resource (MCPResource): The MCP resource to determine server for.
362
+
363
+ Returns:
364
+ str: Server name string.
365
+
366
+ Raises:
367
+ ValueError: If cannot determine server.
368
+ """
369
+ try:
370
+ annotated = self._get_annotated_server(resource)
371
+ if annotated:
372
+ return annotated
373
+ return self._get_default_server()
374
+ except Exception as e:
375
+ error_msg = f"Failed to determine server for resource {resource.uri}: {e}"
376
+ logger.error(error_msg)
377
+ raise ValueError(error_msg) from e
@@ -0,0 +1,231 @@
1
+ """MCP Connection Manager for persistent connection lifecycle management.
2
+
3
+ This module implements the connection manager pattern inspired by mcp-use library
4
+ to avoid cancel scope issues and provide persistent connections.
5
+
6
+ Authors:
7
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
8
+ """
9
+
10
+ import asyncio
11
+ import inspect
12
+ from typing import Any
13
+
14
+ from gllm_tools.mcp.client.config import MCPConfiguration
15
+
16
+ from aip_agents.mcp.client.errors import MCPServerStartupError
17
+ from aip_agents.mcp.client.transports import TransportType, create_transport
18
+ from aip_agents.utils.logger import get_logger
19
+
20
+ logger = get_logger(__name__)
21
+
22
+
23
+ class MCPConnectionManager:
24
+ """Manages MCP connection lifecycle following mcp-use patterns.
25
+
26
+ This connection manager handles the transport connection lifecycle in a background
27
+ task to avoid cancel scope issues during cleanup. It supports automatic transport
28
+ negotiation (HTTP -> SSE fallback) and graceful shutdown. Invalid explicit transports
29
+ are normalized via aliases (e.g., 'streamable_http' -> 'http') or fall back to
30
+ auto-detection with a warning.
31
+ """
32
+
33
+ TRANSPORT_ALIASES = {
34
+ "http": TransportType.HTTP,
35
+ "streamable-http": TransportType.HTTP,
36
+ "sse": TransportType.SSE,
37
+ "stdio": TransportType.STDIO,
38
+ }
39
+
40
+ def __init__(self, server_name: str, config: MCPConfiguration):
41
+ """Initialize connection manager.
42
+
43
+ Args:
44
+ server_name (str): Name of the MCP server
45
+ config (MCPConfiguration): MCP server configuration
46
+ """
47
+ self.server_name = server_name
48
+ self.config = config
49
+ self._task = None
50
+ self._connection = None
51
+ self._transport = None
52
+ self._stop_event = asyncio.Event()
53
+ self._ready_event = asyncio.Event()
54
+ self._done_event = asyncio.Event()
55
+ self._exception = None
56
+ self.transport_type = None
57
+ # Configurable retry settings (MCP-specific, defaults to reasonable values)
58
+ self.max_retries = config.get("max_retries", 3)
59
+ self.initial_retry_delay = config.get("initial_retry_delay", 1.0)
60
+
61
+ async def start(self) -> tuple[Any, Any]:
62
+ """Start connection in background task.
63
+
64
+ For HTTP/SSE transports, establishes connection directly to avoid anyio context issues.
65
+ For stdio transport, uses background task to manage subprocess lifecycle.
66
+
67
+ Returns:
68
+ tuple[Any, Any]: Tuple of (read_stream, write_stream) for ClientSession
69
+
70
+ Raises:
71
+ Exception: If connection establishment fails
72
+ """
73
+ logger.debug(f"Starting connection manager for {self.server_name}")
74
+
75
+ # Determine transport type first
76
+ self.transport_type = self._get_transport_type()
77
+
78
+ # For HTTP/SSE: connect directly (no background task needed)
79
+ # This avoids anyio.BrokenResourceError when streams cross task boundaries
80
+ if self.transport_type in (TransportType.HTTP, TransportType.SSE):
81
+ await self._establish_connection()
82
+ return self._connection
83
+
84
+ # For stdio: use background task to manage subprocess
85
+ self._task = asyncio.create_task(self._connection_task())
86
+ await self._ready_event.wait()
87
+
88
+ if self._exception:
89
+ raise self._exception
90
+
91
+ return self._connection
92
+
93
+ async def stop(self) -> None:
94
+ """Stop connection gracefully."""
95
+ logger.debug(f"Stopping connection manager for {self.server_name}")
96
+
97
+ # For HTTP/SSE (no background task), just close transport
98
+ if self.transport_type in (TransportType.HTTP, TransportType.SSE):
99
+ if self._transport:
100
+ try:
101
+ close_result = self._transport.close()
102
+ if inspect.isawaitable(close_result):
103
+ await close_result
104
+ except Exception as exc:
105
+ logger.warning(f"Failed to close transport cleanly for {self.server_name}: {exc}")
106
+ self._connection = None
107
+ return
108
+
109
+ # For stdio (with background task), wait for task to finish
110
+ if self._task and not self._task.done():
111
+ self._stop_event.set()
112
+ try:
113
+ await asyncio.wait_for(self._task, timeout=5.0)
114
+ except TimeoutError:
115
+ logger.warning(f"Connection manager for {self.server_name} did not stop gracefully")
116
+ self._task.cancel()
117
+ await self._done_event.wait()
118
+
119
+ @property
120
+ def is_connected(self) -> bool:
121
+ """Check if connection is active.
122
+
123
+ Returns:
124
+ bool: True if connected, False otherwise
125
+ """
126
+ # For HTTP/SSE (no background task), just check if connection exists
127
+ if self.transport_type in (TransportType.HTTP, TransportType.SSE):
128
+ return self._connection is not None
129
+
130
+ # For stdio (with background task), check task status too
131
+ return (
132
+ self._connection is not None
133
+ and self._task is not None
134
+ and not self._task.done()
135
+ and not self._stop_event.is_set()
136
+ )
137
+
138
+ def _auto_detect_transport_type(self) -> TransportType:
139
+ """Auto-detect transport type based on configuration.
140
+
141
+ Returns:
142
+ TransportType: Detected transport type
143
+ """
144
+ if "command" in self.config:
145
+ return TransportType.STDIO
146
+ elif "url" in self.config:
147
+ url = self.config["url"]
148
+ if url.endswith("/sse"):
149
+ return TransportType.SSE
150
+ else:
151
+ return TransportType.HTTP
152
+ else:
153
+ return TransportType.STDIO
154
+
155
+ def _get_transport_type(self) -> TransportType:
156
+ """Determine the transport type to use, prioritizing explicit config with aliases.
157
+
158
+ Returns:
159
+ TransportType: Transport type enum
160
+
161
+ Notes:
162
+ Invalid explicit transports trigger a warning and fallback to auto-detection; no exception raised.
163
+ """
164
+ explicit_transport = self.config.get("transport", "").lower().replace("_", "-")
165
+ if explicit_transport in self.TRANSPORT_ALIASES:
166
+ return self.TRANSPORT_ALIASES[explicit_transport]
167
+
168
+ if explicit_transport:
169
+ logger.warning(f"Unknown explicit transport '{explicit_transport}'. Falling back to auto-detection.")
170
+
171
+ return self._auto_detect_transport_type()
172
+
173
+ async def _establish_connection(self) -> None:
174
+ """Establish connection based on transport preference with fallback.
175
+
176
+ Uses configurable retries with exponential backoff for transient failures.
177
+
178
+ Raises:
179
+ ConnectionError: If all connection attempts fail
180
+ """
181
+ # transport_type may already be set by start() for HTTP/SSE
182
+ if not self.transport_type:
183
+ self.transport_type = self._get_transport_type()
184
+ details = f"URL: {self.config.get('url', 'N/A')}, Command: {self.config.get('command', 'N/A')}"
185
+ logger.info(f"Establishing connection to {self.server_name} via {self.transport_type} ({details})")
186
+
187
+ retry_delay = self.initial_retry_delay
188
+ for attempt in range(self.max_retries):
189
+ self._transport = create_transport(self.server_name, self.config, self.transport_type)
190
+ try:
191
+ read_stream, write_stream, _ = await self._transport.connect()
192
+ self._connection = (read_stream, write_stream)
193
+ logger.info(f"Connection established on attempt {attempt + 1}")
194
+ return
195
+ except ValueError:
196
+ # Config validation errors should not be retried
197
+ raise
198
+ except MCPServerStartupError:
199
+ raise
200
+ except Exception as e:
201
+ logger.warning(f"Connection attempt {attempt + 1} failed: {e}")
202
+ if attempt < self.max_retries - 1:
203
+ await asyncio.sleep(retry_delay)
204
+ retry_delay *= 2 # Exponential backoff
205
+ else:
206
+ raise ConnectionError(
207
+ f"Failed to establish connection to {self.server_name} "
208
+ f"after {self.max_retries} attempts: {str(e)}"
209
+ ) from e
210
+
211
+ async def _connection_task(self) -> None:
212
+ """Background task that manages the connection lifecycle."""
213
+ try:
214
+ await self._establish_connection()
215
+ self._ready_event.set()
216
+ await self._stop_event.wait()
217
+ except Exception as e:
218
+ logger.error(f"Connection failed for {self.server_name}: {e}", exc_info=True)
219
+ self._exception = e
220
+ self._ready_event.set()
221
+ finally:
222
+ if self._transport:
223
+ try:
224
+ close_result = self._transport.close()
225
+ if inspect.isawaitable(close_result):
226
+ await close_result
227
+ except Exception as exc: # pragma: no cover - defensive
228
+ logger.warning("Failed to close transport cleanly for %s: %s", self.server_name, exc)
229
+ self._connection = None
230
+ self._done_event.set()
231
+ logger.debug(f"Connection manager cleanup complete for {self.server_name}")