agentpool 2.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (474) hide show
  1. acp/README.md +64 -0
  2. acp/__init__.py +172 -0
  3. acp/__main__.py +10 -0
  4. acp/acp_requests.py +285 -0
  5. acp/agent/__init__.py +6 -0
  6. acp/agent/connection.py +256 -0
  7. acp/agent/implementations/__init__.py +6 -0
  8. acp/agent/implementations/debug_server/__init__.py +1 -0
  9. acp/agent/implementations/debug_server/cli.py +79 -0
  10. acp/agent/implementations/debug_server/debug.html +234 -0
  11. acp/agent/implementations/debug_server/debug_server.py +496 -0
  12. acp/agent/implementations/testing.py +91 -0
  13. acp/agent/protocol.py +65 -0
  14. acp/bridge/README.md +162 -0
  15. acp/bridge/__init__.py +6 -0
  16. acp/bridge/__main__.py +91 -0
  17. acp/bridge/bridge.py +246 -0
  18. acp/bridge/py.typed +0 -0
  19. acp/bridge/settings.py +15 -0
  20. acp/client/__init__.py +7 -0
  21. acp/client/connection.py +251 -0
  22. acp/client/implementations/__init__.py +7 -0
  23. acp/client/implementations/default_client.py +185 -0
  24. acp/client/implementations/headless_client.py +266 -0
  25. acp/client/implementations/noop_client.py +110 -0
  26. acp/client/protocol.py +61 -0
  27. acp/connection.py +280 -0
  28. acp/exceptions.py +46 -0
  29. acp/filesystem.py +524 -0
  30. acp/notifications.py +832 -0
  31. acp/py.typed +0 -0
  32. acp/schema/__init__.py +265 -0
  33. acp/schema/agent_plan.py +30 -0
  34. acp/schema/agent_requests.py +126 -0
  35. acp/schema/agent_responses.py +256 -0
  36. acp/schema/base.py +39 -0
  37. acp/schema/capabilities.py +230 -0
  38. acp/schema/client_requests.py +247 -0
  39. acp/schema/client_responses.py +96 -0
  40. acp/schema/common.py +81 -0
  41. acp/schema/content_blocks.py +188 -0
  42. acp/schema/mcp.py +82 -0
  43. acp/schema/messages.py +171 -0
  44. acp/schema/notifications.py +82 -0
  45. acp/schema/protocol_stuff.md +3 -0
  46. acp/schema/session_state.py +160 -0
  47. acp/schema/session_updates.py +419 -0
  48. acp/schema/slash_commands.py +51 -0
  49. acp/schema/terminal.py +15 -0
  50. acp/schema/tool_call.py +347 -0
  51. acp/stdio.py +250 -0
  52. acp/task/__init__.py +53 -0
  53. acp/task/debug.py +197 -0
  54. acp/task/dispatcher.py +93 -0
  55. acp/task/queue.py +69 -0
  56. acp/task/sender.py +82 -0
  57. acp/task/state.py +87 -0
  58. acp/task/supervisor.py +93 -0
  59. acp/terminal_handle.py +30 -0
  60. acp/tool_call_reporter.py +199 -0
  61. acp/tool_call_state.py +178 -0
  62. acp/transports.py +104 -0
  63. acp/utils.py +240 -0
  64. agentpool/__init__.py +63 -0
  65. agentpool/__main__.py +7 -0
  66. agentpool/agents/__init__.py +30 -0
  67. agentpool/agents/acp_agent/__init__.py +5 -0
  68. agentpool/agents/acp_agent/acp_agent.py +837 -0
  69. agentpool/agents/acp_agent/acp_converters.py +294 -0
  70. agentpool/agents/acp_agent/client_handler.py +317 -0
  71. agentpool/agents/acp_agent/session_state.py +44 -0
  72. agentpool/agents/agent.py +1264 -0
  73. agentpool/agents/agui_agent/__init__.py +19 -0
  74. agentpool/agents/agui_agent/agui_agent.py +677 -0
  75. agentpool/agents/agui_agent/agui_converters.py +423 -0
  76. agentpool/agents/agui_agent/chunk_transformer.py +204 -0
  77. agentpool/agents/agui_agent/event_types.py +83 -0
  78. agentpool/agents/agui_agent/helpers.py +192 -0
  79. agentpool/agents/architect.py +71 -0
  80. agentpool/agents/base_agent.py +177 -0
  81. agentpool/agents/claude_code_agent/__init__.py +11 -0
  82. agentpool/agents/claude_code_agent/claude_code_agent.py +1021 -0
  83. agentpool/agents/claude_code_agent/converters.py +243 -0
  84. agentpool/agents/context.py +105 -0
  85. agentpool/agents/events/__init__.py +61 -0
  86. agentpool/agents/events/builtin_handlers.py +129 -0
  87. agentpool/agents/events/event_emitter.py +320 -0
  88. agentpool/agents/events/events.py +561 -0
  89. agentpool/agents/events/tts_handlers.py +186 -0
  90. agentpool/agents/interactions.py +419 -0
  91. agentpool/agents/slashed_agent.py +244 -0
  92. agentpool/agents/sys_prompts.py +178 -0
  93. agentpool/agents/tool_wrapping.py +184 -0
  94. agentpool/base_provider.py +28 -0
  95. agentpool/common_types.py +226 -0
  96. agentpool/config_resources/__init__.py +16 -0
  97. agentpool/config_resources/acp_assistant.yml +24 -0
  98. agentpool/config_resources/agents.yml +109 -0
  99. agentpool/config_resources/agents_template.yml +18 -0
  100. agentpool/config_resources/agui_test.yml +18 -0
  101. agentpool/config_resources/claude_code_agent.yml +16 -0
  102. agentpool/config_resources/claude_style_subagent.md +30 -0
  103. agentpool/config_resources/external_acp_agents.yml +77 -0
  104. agentpool/config_resources/opencode_style_subagent.md +19 -0
  105. agentpool/config_resources/tts_test_agents.yml +78 -0
  106. agentpool/delegation/__init__.py +8 -0
  107. agentpool/delegation/base_team.py +504 -0
  108. agentpool/delegation/message_flow_tracker.py +39 -0
  109. agentpool/delegation/pool.py +1129 -0
  110. agentpool/delegation/team.py +325 -0
  111. agentpool/delegation/teamrun.py +343 -0
  112. agentpool/docs/__init__.py +5 -0
  113. agentpool/docs/gen_examples.py +42 -0
  114. agentpool/docs/utils.py +370 -0
  115. agentpool/functional/__init__.py +20 -0
  116. agentpool/functional/py.typed +0 -0
  117. agentpool/functional/run.py +80 -0
  118. agentpool/functional/structure.py +136 -0
  119. agentpool/hooks/__init__.py +20 -0
  120. agentpool/hooks/agent_hooks.py +247 -0
  121. agentpool/hooks/base.py +119 -0
  122. agentpool/hooks/callable.py +140 -0
  123. agentpool/hooks/command.py +180 -0
  124. agentpool/hooks/prompt.py +122 -0
  125. agentpool/jinja_filters.py +132 -0
  126. agentpool/log.py +224 -0
  127. agentpool/mcp_server/__init__.py +17 -0
  128. agentpool/mcp_server/client.py +429 -0
  129. agentpool/mcp_server/constants.py +32 -0
  130. agentpool/mcp_server/conversions.py +172 -0
  131. agentpool/mcp_server/helpers.py +47 -0
  132. agentpool/mcp_server/manager.py +232 -0
  133. agentpool/mcp_server/message_handler.py +164 -0
  134. agentpool/mcp_server/registries/__init__.py +1 -0
  135. agentpool/mcp_server/registries/official_registry_client.py +345 -0
  136. agentpool/mcp_server/registries/pulsemcp_client.py +88 -0
  137. agentpool/mcp_server/tool_bridge.py +548 -0
  138. agentpool/messaging/__init__.py +58 -0
  139. agentpool/messaging/compaction.py +928 -0
  140. agentpool/messaging/connection_manager.py +319 -0
  141. agentpool/messaging/context.py +66 -0
  142. agentpool/messaging/event_manager.py +426 -0
  143. agentpool/messaging/events.py +39 -0
  144. agentpool/messaging/message_container.py +209 -0
  145. agentpool/messaging/message_history.py +491 -0
  146. agentpool/messaging/messagenode.py +377 -0
  147. agentpool/messaging/messages.py +655 -0
  148. agentpool/messaging/processing.py +76 -0
  149. agentpool/mime_utils.py +95 -0
  150. agentpool/models/__init__.py +21 -0
  151. agentpool/models/acp_agents/__init__.py +22 -0
  152. agentpool/models/acp_agents/base.py +308 -0
  153. agentpool/models/acp_agents/mcp_capable.py +790 -0
  154. agentpool/models/acp_agents/non_mcp.py +842 -0
  155. agentpool/models/agents.py +450 -0
  156. agentpool/models/agui_agents.py +89 -0
  157. agentpool/models/claude_code_agents.py +238 -0
  158. agentpool/models/file_agents.py +116 -0
  159. agentpool/models/file_parsing.py +367 -0
  160. agentpool/models/manifest.py +658 -0
  161. agentpool/observability/__init__.py +9 -0
  162. agentpool/observability/observability_registry.py +97 -0
  163. agentpool/prompts/__init__.py +1 -0
  164. agentpool/prompts/base.py +27 -0
  165. agentpool/prompts/builtin_provider.py +75 -0
  166. agentpool/prompts/conversion_manager.py +95 -0
  167. agentpool/prompts/convert.py +96 -0
  168. agentpool/prompts/manager.py +204 -0
  169. agentpool/prompts/parts/zed.md +33 -0
  170. agentpool/prompts/prompts.py +581 -0
  171. agentpool/py.typed +0 -0
  172. agentpool/queries/tree-sitter-language-pack/README.md +7 -0
  173. agentpool/queries/tree-sitter-language-pack/arduino-tags.scm +5 -0
  174. agentpool/queries/tree-sitter-language-pack/c-tags.scm +9 -0
  175. agentpool/queries/tree-sitter-language-pack/chatito-tags.scm +16 -0
  176. agentpool/queries/tree-sitter-language-pack/clojure-tags.scm +7 -0
  177. agentpool/queries/tree-sitter-language-pack/commonlisp-tags.scm +122 -0
  178. agentpool/queries/tree-sitter-language-pack/cpp-tags.scm +15 -0
  179. agentpool/queries/tree-sitter-language-pack/csharp-tags.scm +26 -0
  180. agentpool/queries/tree-sitter-language-pack/d-tags.scm +26 -0
  181. agentpool/queries/tree-sitter-language-pack/dart-tags.scm +92 -0
  182. agentpool/queries/tree-sitter-language-pack/elisp-tags.scm +5 -0
  183. agentpool/queries/tree-sitter-language-pack/elixir-tags.scm +54 -0
  184. agentpool/queries/tree-sitter-language-pack/elm-tags.scm +19 -0
  185. agentpool/queries/tree-sitter-language-pack/gleam-tags.scm +41 -0
  186. agentpool/queries/tree-sitter-language-pack/go-tags.scm +42 -0
  187. agentpool/queries/tree-sitter-language-pack/java-tags.scm +20 -0
  188. agentpool/queries/tree-sitter-language-pack/javascript-tags.scm +88 -0
  189. agentpool/queries/tree-sitter-language-pack/lua-tags.scm +34 -0
  190. agentpool/queries/tree-sitter-language-pack/matlab-tags.scm +10 -0
  191. agentpool/queries/tree-sitter-language-pack/ocaml-tags.scm +115 -0
  192. agentpool/queries/tree-sitter-language-pack/ocaml_interface-tags.scm +98 -0
  193. agentpool/queries/tree-sitter-language-pack/pony-tags.scm +39 -0
  194. agentpool/queries/tree-sitter-language-pack/properties-tags.scm +5 -0
  195. agentpool/queries/tree-sitter-language-pack/python-tags.scm +14 -0
  196. agentpool/queries/tree-sitter-language-pack/r-tags.scm +21 -0
  197. agentpool/queries/tree-sitter-language-pack/racket-tags.scm +12 -0
  198. agentpool/queries/tree-sitter-language-pack/ruby-tags.scm +64 -0
  199. agentpool/queries/tree-sitter-language-pack/rust-tags.scm +60 -0
  200. agentpool/queries/tree-sitter-language-pack/solidity-tags.scm +43 -0
  201. agentpool/queries/tree-sitter-language-pack/swift-tags.scm +51 -0
  202. agentpool/queries/tree-sitter-language-pack/udev-tags.scm +20 -0
  203. agentpool/queries/tree-sitter-languages/README.md +24 -0
  204. agentpool/queries/tree-sitter-languages/c-tags.scm +9 -0
  205. agentpool/queries/tree-sitter-languages/c_sharp-tags.scm +46 -0
  206. agentpool/queries/tree-sitter-languages/cpp-tags.scm +15 -0
  207. agentpool/queries/tree-sitter-languages/dart-tags.scm +91 -0
  208. agentpool/queries/tree-sitter-languages/elisp-tags.scm +8 -0
  209. agentpool/queries/tree-sitter-languages/elixir-tags.scm +54 -0
  210. agentpool/queries/tree-sitter-languages/elm-tags.scm +19 -0
  211. agentpool/queries/tree-sitter-languages/fortran-tags.scm +15 -0
  212. agentpool/queries/tree-sitter-languages/go-tags.scm +30 -0
  213. agentpool/queries/tree-sitter-languages/haskell-tags.scm +3 -0
  214. agentpool/queries/tree-sitter-languages/hcl-tags.scm +77 -0
  215. agentpool/queries/tree-sitter-languages/java-tags.scm +20 -0
  216. agentpool/queries/tree-sitter-languages/javascript-tags.scm +88 -0
  217. agentpool/queries/tree-sitter-languages/julia-tags.scm +60 -0
  218. agentpool/queries/tree-sitter-languages/kotlin-tags.scm +27 -0
  219. agentpool/queries/tree-sitter-languages/matlab-tags.scm +10 -0
  220. agentpool/queries/tree-sitter-languages/ocaml-tags.scm +115 -0
  221. agentpool/queries/tree-sitter-languages/ocaml_interface-tags.scm +98 -0
  222. agentpool/queries/tree-sitter-languages/php-tags.scm +26 -0
  223. agentpool/queries/tree-sitter-languages/python-tags.scm +12 -0
  224. agentpool/queries/tree-sitter-languages/ql-tags.scm +26 -0
  225. agentpool/queries/tree-sitter-languages/ruby-tags.scm +64 -0
  226. agentpool/queries/tree-sitter-languages/rust-tags.scm +60 -0
  227. agentpool/queries/tree-sitter-languages/scala-tags.scm +65 -0
  228. agentpool/queries/tree-sitter-languages/typescript-tags.scm +41 -0
  229. agentpool/queries/tree-sitter-languages/zig-tags.scm +3 -0
  230. agentpool/repomap.py +1231 -0
  231. agentpool/resource_providers/__init__.py +17 -0
  232. agentpool/resource_providers/aggregating.py +54 -0
  233. agentpool/resource_providers/base.py +172 -0
  234. agentpool/resource_providers/codemode/__init__.py +9 -0
  235. agentpool/resource_providers/codemode/code_executor.py +215 -0
  236. agentpool/resource_providers/codemode/default_prompt.py +19 -0
  237. agentpool/resource_providers/codemode/helpers.py +83 -0
  238. agentpool/resource_providers/codemode/progress_executor.py +212 -0
  239. agentpool/resource_providers/codemode/provider.py +150 -0
  240. agentpool/resource_providers/codemode/remote_mcp_execution.py +143 -0
  241. agentpool/resource_providers/codemode/remote_provider.py +171 -0
  242. agentpool/resource_providers/filtering.py +42 -0
  243. agentpool/resource_providers/mcp_provider.py +246 -0
  244. agentpool/resource_providers/plan_provider.py +196 -0
  245. agentpool/resource_providers/pool.py +69 -0
  246. agentpool/resource_providers/static.py +289 -0
  247. agentpool/running/__init__.py +20 -0
  248. agentpool/running/decorators.py +56 -0
  249. agentpool/running/discovery.py +101 -0
  250. agentpool/running/executor.py +284 -0
  251. agentpool/running/injection.py +111 -0
  252. agentpool/running/py.typed +0 -0
  253. agentpool/running/run_nodes.py +87 -0
  254. agentpool/server.py +122 -0
  255. agentpool/sessions/__init__.py +13 -0
  256. agentpool/sessions/manager.py +302 -0
  257. agentpool/sessions/models.py +71 -0
  258. agentpool/sessions/session.py +239 -0
  259. agentpool/sessions/store.py +163 -0
  260. agentpool/skills/__init__.py +5 -0
  261. agentpool/skills/manager.py +120 -0
  262. agentpool/skills/registry.py +210 -0
  263. agentpool/skills/skill.py +36 -0
  264. agentpool/storage/__init__.py +17 -0
  265. agentpool/storage/manager.py +419 -0
  266. agentpool/storage/serialization.py +136 -0
  267. agentpool/talk/__init__.py +13 -0
  268. agentpool/talk/registry.py +128 -0
  269. agentpool/talk/stats.py +159 -0
  270. agentpool/talk/talk.py +604 -0
  271. agentpool/tasks/__init__.py +20 -0
  272. agentpool/tasks/exceptions.py +25 -0
  273. agentpool/tasks/registry.py +33 -0
  274. agentpool/testing.py +129 -0
  275. agentpool/text_templates/__init__.py +39 -0
  276. agentpool/text_templates/system_prompt.jinja +30 -0
  277. agentpool/text_templates/tool_call_default.jinja +13 -0
  278. agentpool/text_templates/tool_call_markdown.jinja +25 -0
  279. agentpool/text_templates/tool_call_simple.jinja +5 -0
  280. agentpool/tools/__init__.py +16 -0
  281. agentpool/tools/base.py +269 -0
  282. agentpool/tools/exceptions.py +9 -0
  283. agentpool/tools/manager.py +255 -0
  284. agentpool/tools/tool_call_info.py +87 -0
  285. agentpool/ui/__init__.py +2 -0
  286. agentpool/ui/base.py +89 -0
  287. agentpool/ui/mock_provider.py +81 -0
  288. agentpool/ui/stdlib_provider.py +150 -0
  289. agentpool/utils/__init__.py +44 -0
  290. agentpool/utils/baseregistry.py +185 -0
  291. agentpool/utils/count_tokens.py +62 -0
  292. agentpool/utils/dag.py +184 -0
  293. agentpool/utils/importing.py +206 -0
  294. agentpool/utils/inspection.py +334 -0
  295. agentpool/utils/model_capabilities.py +25 -0
  296. agentpool/utils/network.py +28 -0
  297. agentpool/utils/now.py +22 -0
  298. agentpool/utils/parse_time.py +87 -0
  299. agentpool/utils/result_utils.py +35 -0
  300. agentpool/utils/signatures.py +305 -0
  301. agentpool/utils/streams.py +112 -0
  302. agentpool/utils/tasks.py +186 -0
  303. agentpool/vfs_registry.py +250 -0
  304. agentpool-2.1.9.dist-info/METADATA +336 -0
  305. agentpool-2.1.9.dist-info/RECORD +474 -0
  306. agentpool-2.1.9.dist-info/WHEEL +4 -0
  307. agentpool-2.1.9.dist-info/entry_points.txt +14 -0
  308. agentpool-2.1.9.dist-info/licenses/LICENSE +22 -0
  309. agentpool_cli/__init__.py +34 -0
  310. agentpool_cli/__main__.py +66 -0
  311. agentpool_cli/agent.py +175 -0
  312. agentpool_cli/cli_types.py +23 -0
  313. agentpool_cli/common.py +163 -0
  314. agentpool_cli/create.py +175 -0
  315. agentpool_cli/history.py +217 -0
  316. agentpool_cli/log.py +78 -0
  317. agentpool_cli/py.typed +0 -0
  318. agentpool_cli/run.py +84 -0
  319. agentpool_cli/serve_acp.py +177 -0
  320. agentpool_cli/serve_api.py +69 -0
  321. agentpool_cli/serve_mcp.py +74 -0
  322. agentpool_cli/serve_vercel.py +233 -0
  323. agentpool_cli/store.py +171 -0
  324. agentpool_cli/task.py +84 -0
  325. agentpool_cli/utils.py +104 -0
  326. agentpool_cli/watch.py +54 -0
  327. agentpool_commands/__init__.py +180 -0
  328. agentpool_commands/agents.py +199 -0
  329. agentpool_commands/base.py +45 -0
  330. agentpool_commands/commands.py +58 -0
  331. agentpool_commands/completers.py +110 -0
  332. agentpool_commands/connections.py +175 -0
  333. agentpool_commands/markdown_utils.py +31 -0
  334. agentpool_commands/models.py +62 -0
  335. agentpool_commands/prompts.py +78 -0
  336. agentpool_commands/py.typed +0 -0
  337. agentpool_commands/read.py +77 -0
  338. agentpool_commands/resources.py +210 -0
  339. agentpool_commands/session.py +48 -0
  340. agentpool_commands/tools.py +269 -0
  341. agentpool_commands/utils.py +189 -0
  342. agentpool_commands/workers.py +163 -0
  343. agentpool_config/__init__.py +53 -0
  344. agentpool_config/builtin_tools.py +265 -0
  345. agentpool_config/commands.py +237 -0
  346. agentpool_config/conditions.py +301 -0
  347. agentpool_config/converters.py +30 -0
  348. agentpool_config/durable.py +331 -0
  349. agentpool_config/event_handlers.py +600 -0
  350. agentpool_config/events.py +153 -0
  351. agentpool_config/forward_targets.py +251 -0
  352. agentpool_config/hook_conditions.py +331 -0
  353. agentpool_config/hooks.py +241 -0
  354. agentpool_config/jinja.py +206 -0
  355. agentpool_config/knowledge.py +41 -0
  356. agentpool_config/loaders.py +350 -0
  357. agentpool_config/mcp_server.py +243 -0
  358. agentpool_config/nodes.py +202 -0
  359. agentpool_config/observability.py +191 -0
  360. agentpool_config/output_types.py +55 -0
  361. agentpool_config/pool_server.py +267 -0
  362. agentpool_config/prompt_hubs.py +105 -0
  363. agentpool_config/prompts.py +185 -0
  364. agentpool_config/py.typed +0 -0
  365. agentpool_config/resources.py +33 -0
  366. agentpool_config/session.py +119 -0
  367. agentpool_config/skills.py +17 -0
  368. agentpool_config/storage.py +288 -0
  369. agentpool_config/system_prompts.py +190 -0
  370. agentpool_config/task.py +162 -0
  371. agentpool_config/teams.py +52 -0
  372. agentpool_config/tools.py +112 -0
  373. agentpool_config/toolsets.py +1033 -0
  374. agentpool_config/workers.py +86 -0
  375. agentpool_prompts/__init__.py +1 -0
  376. agentpool_prompts/braintrust_hub.py +235 -0
  377. agentpool_prompts/fabric.py +75 -0
  378. agentpool_prompts/langfuse_hub.py +79 -0
  379. agentpool_prompts/promptlayer_provider.py +59 -0
  380. agentpool_prompts/py.typed +0 -0
  381. agentpool_server/__init__.py +9 -0
  382. agentpool_server/a2a_server/__init__.py +5 -0
  383. agentpool_server/a2a_server/a2a_types.py +41 -0
  384. agentpool_server/a2a_server/server.py +190 -0
  385. agentpool_server/a2a_server/storage.py +81 -0
  386. agentpool_server/acp_server/__init__.py +22 -0
  387. agentpool_server/acp_server/acp_agent.py +786 -0
  388. agentpool_server/acp_server/acp_tools.py +43 -0
  389. agentpool_server/acp_server/commands/__init__.py +18 -0
  390. agentpool_server/acp_server/commands/acp_commands.py +594 -0
  391. agentpool_server/acp_server/commands/debug_commands.py +376 -0
  392. agentpool_server/acp_server/commands/docs_commands/__init__.py +39 -0
  393. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +169 -0
  394. agentpool_server/acp_server/commands/docs_commands/get_schema.py +176 -0
  395. agentpool_server/acp_server/commands/docs_commands/get_source.py +110 -0
  396. agentpool_server/acp_server/commands/docs_commands/git_diff.py +111 -0
  397. agentpool_server/acp_server/commands/docs_commands/helpers.py +33 -0
  398. agentpool_server/acp_server/commands/docs_commands/url_to_markdown.py +90 -0
  399. agentpool_server/acp_server/commands/spawn.py +210 -0
  400. agentpool_server/acp_server/converters.py +235 -0
  401. agentpool_server/acp_server/input_provider.py +338 -0
  402. agentpool_server/acp_server/server.py +288 -0
  403. agentpool_server/acp_server/session.py +969 -0
  404. agentpool_server/acp_server/session_manager.py +313 -0
  405. agentpool_server/acp_server/syntax_detection.py +250 -0
  406. agentpool_server/acp_server/zed_tools.md +90 -0
  407. agentpool_server/aggregating_server.py +309 -0
  408. agentpool_server/agui_server/__init__.py +11 -0
  409. agentpool_server/agui_server/server.py +128 -0
  410. agentpool_server/base.py +189 -0
  411. agentpool_server/http_server.py +164 -0
  412. agentpool_server/mcp_server/__init__.py +6 -0
  413. agentpool_server/mcp_server/server.py +314 -0
  414. agentpool_server/mcp_server/zed_wrapper.py +110 -0
  415. agentpool_server/openai_api_server/__init__.py +5 -0
  416. agentpool_server/openai_api_server/completions/__init__.py +1 -0
  417. agentpool_server/openai_api_server/completions/helpers.py +81 -0
  418. agentpool_server/openai_api_server/completions/models.py +98 -0
  419. agentpool_server/openai_api_server/responses/__init__.py +1 -0
  420. agentpool_server/openai_api_server/responses/helpers.py +74 -0
  421. agentpool_server/openai_api_server/responses/models.py +96 -0
  422. agentpool_server/openai_api_server/server.py +242 -0
  423. agentpool_server/py.typed +0 -0
  424. agentpool_storage/__init__.py +9 -0
  425. agentpool_storage/base.py +310 -0
  426. agentpool_storage/file_provider.py +378 -0
  427. agentpool_storage/formatters.py +129 -0
  428. agentpool_storage/memory_provider.py +396 -0
  429. agentpool_storage/models.py +108 -0
  430. agentpool_storage/py.typed +0 -0
  431. agentpool_storage/session_store.py +262 -0
  432. agentpool_storage/sql_provider/__init__.py +21 -0
  433. agentpool_storage/sql_provider/cli.py +146 -0
  434. agentpool_storage/sql_provider/models.py +249 -0
  435. agentpool_storage/sql_provider/queries.py +15 -0
  436. agentpool_storage/sql_provider/sql_provider.py +444 -0
  437. agentpool_storage/sql_provider/utils.py +234 -0
  438. agentpool_storage/text_log_provider.py +275 -0
  439. agentpool_toolsets/__init__.py +15 -0
  440. agentpool_toolsets/builtin/__init__.py +33 -0
  441. agentpool_toolsets/builtin/agent_management.py +239 -0
  442. agentpool_toolsets/builtin/chain.py +288 -0
  443. agentpool_toolsets/builtin/code.py +398 -0
  444. agentpool_toolsets/builtin/debug.py +291 -0
  445. agentpool_toolsets/builtin/execution_environment.py +381 -0
  446. agentpool_toolsets/builtin/file_edit/__init__.py +11 -0
  447. agentpool_toolsets/builtin/file_edit/file_edit.py +747 -0
  448. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/__init__.py +5 -0
  449. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/example_usage.py +311 -0
  450. agentpool_toolsets/builtin/file_edit/fuzzy_matcher/streaming_fuzzy_matcher.py +443 -0
  451. agentpool_toolsets/builtin/history.py +36 -0
  452. agentpool_toolsets/builtin/integration.py +85 -0
  453. agentpool_toolsets/builtin/skills.py +77 -0
  454. agentpool_toolsets/builtin/subagent_tools.py +324 -0
  455. agentpool_toolsets/builtin/tool_management.py +90 -0
  456. agentpool_toolsets/builtin/user_interaction.py +52 -0
  457. agentpool_toolsets/builtin/workers.py +128 -0
  458. agentpool_toolsets/composio_toolset.py +96 -0
  459. agentpool_toolsets/config_creation.py +192 -0
  460. agentpool_toolsets/entry_points.py +47 -0
  461. agentpool_toolsets/fsspec_toolset/__init__.py +7 -0
  462. agentpool_toolsets/fsspec_toolset/diagnostics.py +115 -0
  463. agentpool_toolsets/fsspec_toolset/grep.py +450 -0
  464. agentpool_toolsets/fsspec_toolset/helpers.py +631 -0
  465. agentpool_toolsets/fsspec_toolset/streaming_diff_parser.py +249 -0
  466. agentpool_toolsets/fsspec_toolset/toolset.py +1384 -0
  467. agentpool_toolsets/mcp_run_toolset.py +61 -0
  468. agentpool_toolsets/notifications.py +146 -0
  469. agentpool_toolsets/openapi.py +118 -0
  470. agentpool_toolsets/py.typed +0 -0
  471. agentpool_toolsets/search_toolset.py +202 -0
  472. agentpool_toolsets/semantic_memory_toolset.py +536 -0
  473. agentpool_toolsets/streaming_tools.py +265 -0
  474. agentpool_toolsets/vfs_toolset.py +124 -0
@@ -0,0 +1,429 @@
1
+ """FastMCP-based client implementation for AgentPool.
2
+
3
+ This module provides a client for communicating with MCP servers using FastMCP.
4
+ It includes support for contextual progress handlers that extend FastMCP's
5
+ standard progress callbacks with tool execution context (tool name, call ID, and input).
6
+
7
+ The key innovation is the signature injection system that allows MCP tools to work
8
+ seamlessly with PydanticAI's RunContext while providing rich progress information.
9
+
10
+ Elicitation is handled via a forwarding callback pattern: a stable callback is
11
+ registered at connection time, but it delegates to a mutable handler that can
12
+ be swapped per tool call (allowing AgentContext.handle_elicitation to be used).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import contextlib
18
+ from importlib.metadata import version
19
+ import logging
20
+ from typing import TYPE_CHECKING, Any, Self, assert_never
21
+
22
+ import anyio
23
+ from pydantic_ai import RunContext, ToolReturn
24
+ from schemez import FunctionSchema
25
+
26
+ from agentpool.agents.context import AgentContext
27
+ from agentpool.log import get_logger
28
+ from agentpool.mcp_server.constants import MCP_TO_LOGGING
29
+ from agentpool.mcp_server.helpers import extract_text_content, mcp_tool_to_fn_schema
30
+ from agentpool.mcp_server.message_handler import MCPMessageHandler
31
+ from agentpool.tools.base import Tool
32
+ from agentpool.utils.signatures import create_modified_signature
33
+ from agentpool_config.mcp_server import (
34
+ SSEMCPServerConfig,
35
+ StdioMCPServerConfig,
36
+ StreamableHTTPMCPServerConfig,
37
+ )
38
+
39
+
40
+ if TYPE_CHECKING:
41
+ from collections.abc import Awaitable, Callable, Sequence
42
+
43
+ import fastmcp
44
+ from fastmcp.client import ClientTransport
45
+ from fastmcp.client.elicitation import ElicitationHandler
46
+ from fastmcp.client.logging import LogMessage
47
+ from fastmcp.client.messages import MessageHandler, MessageHandlerT
48
+ from fastmcp.client.sampling import SamplingHandler
49
+ from mcp.shared.context import RequestContext
50
+ from mcp.types import (
51
+ BlobResourceContents,
52
+ ContentBlock,
53
+ ElicitRequestParams,
54
+ GetPromptResult,
55
+ Icon,
56
+ Implementation,
57
+ Prompt as MCPPrompt,
58
+ Resource as MCPResource,
59
+ TextResourceContents,
60
+ Tool as MCPTool,
61
+ )
62
+ from pydantic_ai import BinaryContent
63
+ from upathtools.filesystems import MCPFileSystem, MCPToolsFileSystem
64
+
65
+ from agentpool_config.mcp_server import MCPServerConfig
66
+
67
+
68
+ logger = get_logger(__name__)
69
+
70
+
71
+ class MCPClient:
72
+ """FastMCP-based client for communicating with MCP servers."""
73
+
74
+ def __init__(
75
+ self,
76
+ config: MCPServerConfig,
77
+ sampling_callback: SamplingHandler[Any, Any] | None = None,
78
+ message_handler: MessageHandlerT | MessageHandler | None = None,
79
+ accessible_roots: list[str] | None = None,
80
+ tool_change_callback: Callable[[], Awaitable[None]] | None = None,
81
+ prompt_change_callback: Callable[[], Awaitable[None]] | None = None,
82
+ resource_change_callback: Callable[[], Awaitable[None]] | None = None,
83
+ client_name: str | None = None,
84
+ client_title: str | None = None,
85
+ client_website_url: str | None = None,
86
+ client_icon_path: str | None = None,
87
+ ) -> None:
88
+ # Mutable handler swapped per call_tool for dynamic elicitation
89
+ self._current_elicitation_handler: ElicitationHandler | None = None
90
+ self.config = config
91
+ self._sampling_callback = sampling_callback
92
+ # Store message handler or mark for lazy creation
93
+ self._message_handler = message_handler
94
+ self._accessible_roots = accessible_roots or []
95
+ self._tool_change_callback = tool_change_callback
96
+ self._prompt_change_callback = prompt_change_callback
97
+ self._resource_change_callback = resource_change_callback
98
+ self._client_name = client_name
99
+ self._client_title = client_title
100
+ self._client_website_url = client_website_url
101
+ self._client_icon_path = client_icon_path
102
+ self._client = self._get_client(self.config)
103
+
104
+ @property
105
+ def connected(self) -> bool:
106
+ """Check if client is connected by examining session state."""
107
+ return self._client.is_connected()
108
+
109
+ async def __aenter__(self) -> Self:
110
+ """Enter context manager."""
111
+ try:
112
+ # First attempt with configured auth
113
+ await self._client.__aenter__() # type: ignore[no-untyped-call]
114
+
115
+ except Exception as first_error:
116
+ # OAuth fallback for HTTP/SSE if not already using OAuth
117
+ if not isinstance(self.config, StdioMCPServerConfig) and not self.config.auth.oauth:
118
+ try:
119
+ with contextlib.suppress(Exception):
120
+ await self._client.__aexit__(None, None, None) # type: ignore[no-untyped-call]
121
+ self._client = self._get_client(self.config, force_oauth=True)
122
+ await self._client.__aenter__() # type: ignore[no-untyped-call]
123
+ logger.info("Connected with OAuth fallback")
124
+ except Exception: # noqa: BLE001
125
+ raise first_error from None
126
+ else:
127
+ raise
128
+
129
+ return self
130
+
131
+ async def __aexit__(self, *args: object) -> None:
132
+ """Exit context manager and cleanup."""
133
+ try:
134
+ await self._client.__aexit__(None, None, None) # type: ignore[no-untyped-call]
135
+ except Exception as e: # noqa: BLE001
136
+ logger.warning("Error during FastMCP client cleanup", error=e)
137
+
138
+ def get_resource_fs(self) -> MCPFileSystem:
139
+ """Get a filesystem for accessing MCP resources."""
140
+ from upathtools.filesystems import MCPFileSystem
141
+
142
+ return MCPFileSystem(client=self._client)
143
+
144
+ def get_tools_fs(self) -> MCPToolsFileSystem:
145
+ """Get a filesystem for accessing MCP tools as code."""
146
+ from upathtools.filesystems import MCPToolsFileSystem
147
+
148
+ return MCPToolsFileSystem(client=self._client)
149
+
150
+ async def _log_handler(self, message: LogMessage) -> None:
151
+ """Handle server log messages."""
152
+ level = MCP_TO_LOGGING.get(message.level, logging.INFO)
153
+ logger.log(level, "MCP Server: ", data=message.data)
154
+
155
+ async def _forwarding_elicitation_callback[T](
156
+ self,
157
+ message: str,
158
+ response_type: type[T],
159
+ params: ElicitRequestParams,
160
+ context: RequestContext[Any, Any],
161
+ ) -> T | dict[str, Any] | Any:
162
+ """Forwarding callback that delegates to current handler.
163
+
164
+ This callback is registered once at connection time, but delegates to
165
+ _current_elicitation_handler which can be swapped per tool call.
166
+ """
167
+ from fastmcp.client.elicitation import ElicitResult
168
+
169
+ # Try current handler first (set per call_tool)
170
+ if self._current_elicitation_handler:
171
+ return await self._current_elicitation_handler(message, response_type, params, context)
172
+ # No handler available - decline by default
173
+ return ElicitResult(action="decline")
174
+
175
+ def _get_client(
176
+ self, config: MCPServerConfig, force_oauth: bool = False
177
+ ) -> fastmcp.Client[Any]:
178
+ """Create FastMCP client based on config."""
179
+ import fastmcp
180
+ from fastmcp.client import SSETransport, StreamableHttpTransport
181
+ from fastmcp.client.transports import StdioTransport
182
+ from mcp.types import Icon, Implementation
183
+
184
+ transport: ClientTransport
185
+ # Create transport based on config type
186
+ match config:
187
+ case StdioMCPServerConfig(command=command, args=args):
188
+ env = config.get_env_vars()
189
+ transport = StdioTransport(command=command, args=args, env=env)
190
+ oauth = False
191
+ if force_oauth:
192
+ msg = "OAuth is not supported for StdioMCPServerConfig"
193
+ raise ValueError(msg)
194
+
195
+ case SSEMCPServerConfig(url=url, headers=headers, auth=auth):
196
+ transport = SSETransport(url=url, headers=headers)
197
+ oauth = auth.oauth
198
+
199
+ case StreamableHTTPMCPServerConfig(url=url, headers=headers, auth=auth):
200
+ transport = StreamableHttpTransport(url=url, headers=headers)
201
+ oauth = auth.oauth
202
+ case _ as unreachable:
203
+ assert_never(unreachable)
204
+
205
+ # Create message handler if needed
206
+ msg_handler = self._message_handler or MCPMessageHandler(
207
+ self,
208
+ self._tool_change_callback,
209
+ self._prompt_change_callback,
210
+ self._resource_change_callback,
211
+ )
212
+
213
+ # Build client_info if client_name is provided
214
+ client_info: Implementation | None = None
215
+ if self._client_name:
216
+ icons: list[Icon] | None = None
217
+ if self._client_icon_path:
218
+ icons = [Icon(src=self._client_icon_path)]
219
+ client_info = Implementation(
220
+ name=self._client_name,
221
+ version=version("agentpool"),
222
+ title=self._client_title,
223
+ websiteUrl=self._client_website_url,
224
+ icons=icons,
225
+ )
226
+
227
+ return fastmcp.Client(
228
+ transport,
229
+ log_handler=self._log_handler,
230
+ roots=self._accessible_roots,
231
+ timeout=config.timeout,
232
+ elicitation_handler=self._forwarding_elicitation_callback,
233
+ sampling_handler=self._sampling_callback,
234
+ message_handler=msg_handler,
235
+ auth="oauth" if (force_oauth or oauth) else None,
236
+ client_info=client_info,
237
+ )
238
+
239
+ async def list_tools(self) -> list[MCPTool]:
240
+ """Get available tools directly from the server."""
241
+ if not self.connected:
242
+ msg = "Not connected to MCP server"
243
+ raise RuntimeError(msg)
244
+
245
+ try:
246
+ tools = await self._client.list_tools()
247
+ logger.debug("Listed tools from MCP server", num_tools=len(tools))
248
+ except Exception as e: # noqa: BLE001
249
+ logger.warning("Failed to list tools", error=e)
250
+ return []
251
+ else:
252
+ return tools
253
+
254
+ async def list_prompts(self) -> list[MCPPrompt]:
255
+ """Get available prompts from the server."""
256
+ if not self.connected:
257
+ msg = "Not connected to MCP server"
258
+ raise RuntimeError(msg)
259
+
260
+ try:
261
+ return await self._client.list_prompts()
262
+ except Exception as e: # noqa: BLE001
263
+ logger.debug("Failed to list prompts", error=e)
264
+ return []
265
+
266
+ async def list_resources(self) -> list[MCPResource]:
267
+ """Get available resources from the server."""
268
+ if not self.connected:
269
+ msg = "Not connected to MCP server"
270
+ raise RuntimeError(msg)
271
+
272
+ try:
273
+ return await self._client.list_resources()
274
+ except Exception as e:
275
+ msg = f"Failed to list resources: {e}"
276
+ raise RuntimeError(msg) from e
277
+
278
+ async def get_prompt(
279
+ self, name: str, arguments: dict[str, str] | None = None
280
+ ) -> GetPromptResult:
281
+ """Get a specific prompt's content."""
282
+ if not self.connected:
283
+ msg = "Not connected to MCP server"
284
+ raise RuntimeError(msg)
285
+
286
+ try:
287
+ return await self._client.get_prompt_mcp(name, arguments)
288
+ except Exception as e:
289
+ msg = f"Failed to get prompt {name!r}: {e}"
290
+ raise RuntimeError(msg) from e
291
+
292
+ def convert_tool(self, tool: MCPTool) -> Tool:
293
+ """Create a properly typed callable from MCP tool schema."""
294
+
295
+ async def tool_callable(
296
+ ctx: RunContext, agent_ctx: AgentContext[Any], **kwargs: Any
297
+ ) -> str | Any | ToolReturn:
298
+ """Dynamically generated MCP tool wrapper."""
299
+ # Filter out None values for optional params
300
+ schema_props = tool.inputSchema.get("properties", {})
301
+ required_props = set(tool.inputSchema.get("required", []))
302
+ filtered_kwargs = {
303
+ k: v
304
+ for k, v in kwargs.items()
305
+ if k in required_props or (k in schema_props and v is not None)
306
+ }
307
+ return await self.call_tool(tool.name, ctx, filtered_kwargs, agent_ctx)
308
+
309
+ # Set proper signature and annotations with both RunContext and AgentContext
310
+ schema = mcp_tool_to_fn_schema(tool)
311
+ fn_schema = FunctionSchema.from_dict(schema)
312
+ sig = fn_schema.to_python_signature()
313
+
314
+ tool_callable.__signature__ = create_modified_signature( # type: ignore[attr-defined]
315
+ sig, inject={"ctx": RunContext, "agent_ctx": AgentContext}
316
+ )
317
+ annotations = fn_schema.get_annotations()
318
+ annotations["ctx"] = RunContext
319
+ annotations["agent_ctx"] = AgentContext
320
+ # Update return annotation to support multiple types
321
+ annotations["return"] = str | Any | ToolReturn # type: ignore
322
+ tool_callable.__annotations__ = annotations
323
+ tool_callable.__name__ = tool.name
324
+ tool_callable.__doc__ = tool.description or "No description provided."
325
+ return Tool.from_callable(tool_callable, source="mcp")
326
+
327
+ async def call_tool(
328
+ self,
329
+ name: str,
330
+ run_context: RunContext,
331
+ arguments: dict[str, Any] | None = None,
332
+ agent_ctx: AgentContext[Any] | None = None,
333
+ ) -> ToolReturn | str | Any:
334
+ """Call an MCP tool with full PydanticAI return type support."""
335
+ if not self.connected:
336
+ msg = "Not connected to MCP server"
337
+ raise RuntimeError(msg)
338
+
339
+ # Create progress handler that bridges to AgentContext if available
340
+ progress_handler = None
341
+ if agent_ctx:
342
+
343
+ async def fastmcp_progress_handler(
344
+ progress: float,
345
+ total: float | None,
346
+ message: str | None,
347
+ ) -> None:
348
+ await agent_ctx.report_progress(progress, total, message or "")
349
+
350
+ progress_handler = fastmcp_progress_handler
351
+
352
+ # Set up per-call elicitation handler from AgentContext
353
+ if agent_ctx:
354
+
355
+ async def elicitation_handler[T](
356
+ message: str,
357
+ response_type: type[T] | None,
358
+ params: ElicitRequestParams,
359
+ context: RequestContext[Any, Any, Any],
360
+ ) -> T | dict[str, Any] | Any:
361
+ from fastmcp.client.elicitation import ElicitResult
362
+ from mcp.types import ElicitResult as MCPElicitResult, ErrorData
363
+
364
+ result = await agent_ctx.handle_elicitation(params)
365
+ match result:
366
+ case MCPElicitResult(action="accept", content=content):
367
+ return content
368
+ case MCPElicitResult(action="cancel"):
369
+ return ElicitResult(action="cancel")
370
+ case MCPElicitResult(action="decline"):
371
+ return ElicitResult(action="decline")
372
+ case ErrorData():
373
+ return ElicitResult(action="decline")
374
+ case _:
375
+ return ElicitResult(action="decline")
376
+
377
+ self._current_elicitation_handler = elicitation_handler
378
+
379
+ try:
380
+ result = await self._client.call_tool(
381
+ name, arguments, progress_handler=progress_handler
382
+ )
383
+ content = await self._from_mcp_content(result.content)
384
+ # Decision logic for return type
385
+ match (result.data is not None, bool(content)):
386
+ case (True, True): # Both structured data and rich content -> ToolReturn
387
+ return ToolReturn(return_value=result.data, content=content)
388
+ case (True, False): # Only structured data -> return directly
389
+ return result.data
390
+ case (False, True): # Only content -> ToolReturn with content
391
+ msg = "Tool executed successfully"
392
+ return ToolReturn(return_value=msg, content=content)
393
+ case (False, False): # Fallback to text extraction
394
+ return extract_text_content(result.content)
395
+ case _: # Handle unexpected cases
396
+ msg = f"Unexpected MCP content: {result.content}"
397
+ raise ValueError(msg) # noqa: TRY301
398
+ except Exception as e:
399
+ msg = f"MCP tool call failed: {e}"
400
+ raise RuntimeError(msg) from e
401
+ finally:
402
+ # Clear per-call handler
403
+ self._current_elicitation_handler = None
404
+
405
+ async def _from_mcp_content(
406
+ self,
407
+ mcp_content: Sequence[ContentBlock | TextResourceContents | BlobResourceContents],
408
+ ) -> list[str | BinaryContent]:
409
+ """Convert MCP content blocks to PydanticAI content types."""
410
+ from agentpool.mcp_server.conversions import from_mcp_content
411
+
412
+ return await from_mcp_content(mcp_content)
413
+
414
+
415
+ if __name__ == "__main__":
416
+ path = "/home/phil65/dev/oss/agentpool/tests/mcp_server/server.py"
417
+ # path = Path(__file__).parent / "test_mcp_server.py"
418
+ config = StdioMCPServerConfig(
419
+ command="uv",
420
+ args=["run", str(path)],
421
+ )
422
+
423
+ async def main() -> None:
424
+ async with MCPClient(config=config) as mcp_client:
425
+ # Create MCP filesystem
426
+ fs = mcp_client.get_resource_fs()
427
+ print(await fs._ls(""))
428
+
429
+ anyio.run(main)
@@ -0,0 +1,32 @@
1
+ """MCP related constants."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import TYPE_CHECKING
7
+
8
+
9
+ if TYPE_CHECKING:
10
+ import mcp
11
+
12
+
13
+ MCP_TO_LOGGING: dict[mcp.LoggingLevel, int] = {
14
+ "debug": logging.DEBUG,
15
+ "info": logging.INFO,
16
+ "notice": logging.INFO,
17
+ "warning": logging.WARNING,
18
+ "error": logging.ERROR,
19
+ "critical": logging.CRITICAL,
20
+ "alert": logging.CRITICAL,
21
+ "emergency": logging.CRITICAL,
22
+ }
23
+
24
+
25
+ # Map Python logging levels to MCP logging levels
26
+ LOGGING_TO_MCP: dict[int, mcp.LoggingLevel] = {
27
+ logging.DEBUG: "debug",
28
+ logging.INFO: "info",
29
+ logging.WARNING: "warning",
30
+ logging.ERROR: "error",
31
+ logging.CRITICAL: "critical",
32
+ }
@@ -0,0 +1,172 @@
1
+ """Conversions between internal and MCP types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ from typing import TYPE_CHECKING, Any, assert_never
7
+
8
+ from mcp.types import (
9
+ AudioContent,
10
+ BlobResourceContents,
11
+ EmbeddedResource,
12
+ ImageContent,
13
+ PromptMessage,
14
+ ResourceLink,
15
+ TextContent,
16
+ TextResourceContents,
17
+ )
18
+ from pydantic_ai import (
19
+ AudioUrl,
20
+ BinaryContent,
21
+ BinaryImage,
22
+ DocumentUrl,
23
+ FileUrl,
24
+ ImageUrl,
25
+ SystemPromptPart,
26
+ TextPart,
27
+ UserPromptPart,
28
+ VideoUrl,
29
+ )
30
+
31
+ from agentpool.log import get_logger
32
+
33
+
34
+ if TYPE_CHECKING:
35
+ from collections.abc import Sequence
36
+
37
+ from fastmcp import Client
38
+ from mcp.types import ContentBlock
39
+ from pydantic_ai import ModelRequestPart, ModelResponsePart
40
+
41
+
42
+ logger = get_logger(__name__)
43
+
44
+
45
+ def to_mcp_messages(
46
+ part: ModelRequestPart | ModelResponsePart,
47
+ ) -> list[PromptMessage]:
48
+ """Convert internal PromptMessage to MCP PromptMessage."""
49
+ messages = []
50
+ match part:
51
+ case UserPromptPart(content=str() as c):
52
+ content = TextContent(type="text", text=c)
53
+ messages.append(PromptMessage(role="user", content=content))
54
+ case UserPromptPart(content=content_items):
55
+ for item in content_items:
56
+ match item:
57
+ case BinaryContent():
58
+ if item.is_audio:
59
+ encoded = base64.b64encode(item.data).decode("utf-8")
60
+ audio = AudioContent(
61
+ type="audio", data=encoded, mimeType=item.media_type
62
+ )
63
+ messages.append(PromptMessage(role="user", content=audio))
64
+ elif item.is_image:
65
+ encoded = base64.b64encode(item.data).decode("utf-8")
66
+ image = ImageContent(
67
+ type="image", data=encoded, mimeType=item.media_type
68
+ )
69
+ messages.append(PromptMessage(role="user", content=image))
70
+ case FileUrl(url=url):
71
+ content = TextContent(type="text", text=url)
72
+ messages.append(PromptMessage(role="user", content=content))
73
+
74
+ case SystemPromptPart(content=msg):
75
+ messages.append(PromptMessage(role="user", content=TextContent(type="text", text=msg)))
76
+ case TextPart(content=msg):
77
+ messages.append(
78
+ PromptMessage(role="assistant", content=TextContent(type="text", text=msg))
79
+ )
80
+ return messages
81
+
82
+
83
+ def _url_from_mime_type(uri: str, mime_type: str | None) -> FileUrl:
84
+ """Convert URI to appropriate pydantic-ai URL type based on MIME type."""
85
+ if not mime_type:
86
+ return DocumentUrl(url=uri)
87
+
88
+ if mime_type.startswith("image/"):
89
+ return ImageUrl(url=uri)
90
+ if mime_type.startswith("audio/"):
91
+ return AudioUrl(url=uri)
92
+ if mime_type.startswith("video/"):
93
+ return VideoUrl(url=uri)
94
+ return DocumentUrl(url=uri)
95
+
96
+
97
+ async def from_mcp_content(
98
+ mcp_content: Sequence[ContentBlock | TextResourceContents | BlobResourceContents],
99
+ client: Client[Any] | None = None,
100
+ ) -> list[str | BinaryContent]:
101
+ """Convert MCP content blocks to PydanticAI content types.
102
+
103
+ If a FastMCP client is given, this function will try to resolve the ResourceLinks.
104
+
105
+ """
106
+ contents: list[Any] = []
107
+
108
+ for block in mcp_content:
109
+ match block:
110
+ case TextContent(text=text):
111
+ contents.append(text)
112
+ case TextResourceContents(text=text):
113
+ contents.append(text)
114
+ case ImageContent(data=data, mimeType=mime_type):
115
+ decoded_data = base64.b64decode(data)
116
+ img = BinaryImage(data=decoded_data, media_type=mime_type)
117
+ contents.append(img)
118
+ case AudioContent(data=data, mimeType=mime_type):
119
+ decoded_data = base64.b64decode(data)
120
+ content = BinaryContent(data=decoded_data, media_type=mime_type)
121
+ contents.append(content)
122
+ case BlobResourceContents(blob=blob):
123
+ decoded_data = base64.b64decode(blob)
124
+ mime = "application/octet-stream"
125
+ content = BinaryContent(data=decoded_data, media_type=mime)
126
+ contents.append(content)
127
+ case ResourceLink(uri=uri, mimeType=mime_type):
128
+ if client:
129
+ try:
130
+ res = await client.read_resource(uri)
131
+ nested = await from_mcp_content(res, client)
132
+ contents.extend(nested)
133
+ continue
134
+ except Exception: # noqa: BLE001
135
+ # Fallback to URL if reading fails
136
+ logger.warning("Failed to read resource", uri=uri)
137
+ # Convert to appropriate URL type based on MIME type
138
+ contents.append(_url_from_mime_type(str(uri), mime_type))
139
+ # mypy doesnt understand exhaustivness check for "nested typing", so we nest match-case
140
+ case EmbeddedResource(resource=resource):
141
+ match resource:
142
+ case TextResourceContents(text=text):
143
+ contents.append(text)
144
+ case BlobResourceContents() as blob_resource:
145
+ contents.append(f"[Binary data: {blob_resource.mimeType}]")
146
+ case _ as unreachable:
147
+ assert_never(unreachable) # ty: ignore
148
+ case _ as unreachable:
149
+ assert_never(unreachable)
150
+ return contents
151
+
152
+
153
+ def content_block_as_text(content: ContentBlock) -> str:
154
+ match content:
155
+ case TextContent(text=text):
156
+ return text
157
+ case EmbeddedResource(resource=resource):
158
+ match resource:
159
+ case TextResourceContents() as text_contents:
160
+ return text_contents.text
161
+ case BlobResourceContents() as blob_contents:
162
+ return f"[Resource: {blob_contents.uri}]"
163
+ case _ as unreachable:
164
+ assert_never(unreachable) # ty: ignore
165
+ case ResourceLink(uri=uri, description=desc):
166
+ return f"[Resource Link: {uri}] - {desc}" if desc else f"[Resource Link: {uri}]"
167
+ case ImageContent(mimeType=mime_type):
168
+ return f"[Image: {mime_type}]"
169
+ case AudioContent(mimeType=mime_type):
170
+ return f"[Audio: {mime_type}]"
171
+ case _ as unreachable:
172
+ assert_never(unreachable)
@@ -0,0 +1,47 @@
1
+ """Helper functions for MCP server client operations.
2
+
3
+ This module contains stateless utility functions that support MCP tool conversion
4
+ and content handling for PydanticAI integration.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ from agentpool.log import get_logger
12
+
13
+
14
+ if TYPE_CHECKING:
15
+ from mcp.types import ContentBlock, Tool as MCPTool
16
+
17
+
18
+ logger = get_logger(__name__)
19
+
20
+
21
+ def mcp_tool_to_fn_schema(tool: MCPTool) -> dict[str, Any]:
22
+ """Convert MCP tool to OpenAI function schema format."""
23
+ return {
24
+ "name": tool.name,
25
+ "description": tool.description or "",
26
+ "parameters": tool.inputSchema or {"type": "object", "properties": {}},
27
+ }
28
+
29
+
30
+ def extract_text_content(mcp_content: list[ContentBlock]) -> str:
31
+ """Extract text content from MCP content blocks.
32
+
33
+ Args:
34
+ mcp_content: List of MCP content blocks
35
+
36
+ Returns:
37
+ First available text content or fallback string
38
+ """
39
+ from mcp.types import TextContent
40
+
41
+ for block in mcp_content:
42
+ match block:
43
+ case TextContent(text=text):
44
+ return text
45
+
46
+ # Fallback: stringify the content
47
+ return str(mcp_content[0]) if mcp_content else "Tool executed successfully"