agentpool 2.1.9__py3-none-any.whl → 2.5.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 (311) hide show
  1. acp/__init__.py +13 -4
  2. acp/acp_requests.py +20 -77
  3. acp/agent/connection.py +8 -0
  4. acp/agent/implementations/debug_server/debug_server.py +6 -2
  5. acp/agent/protocol.py +6 -0
  6. acp/bridge/README.md +15 -2
  7. acp/bridge/__init__.py +3 -2
  8. acp/bridge/__main__.py +60 -19
  9. acp/bridge/ws_server.py +173 -0
  10. acp/bridge/ws_server_cli.py +89 -0
  11. acp/client/connection.py +38 -29
  12. acp/client/implementations/default_client.py +3 -2
  13. acp/client/implementations/headless_client.py +2 -2
  14. acp/connection.py +2 -2
  15. acp/notifications.py +20 -50
  16. acp/schema/__init__.py +2 -0
  17. acp/schema/agent_responses.py +21 -0
  18. acp/schema/client_requests.py +3 -3
  19. acp/schema/session_state.py +63 -29
  20. acp/stdio.py +39 -9
  21. acp/task/supervisor.py +2 -2
  22. acp/transports.py +362 -2
  23. acp/utils.py +17 -4
  24. agentpool/__init__.py +6 -1
  25. agentpool/agents/__init__.py +2 -0
  26. agentpool/agents/acp_agent/acp_agent.py +407 -277
  27. agentpool/agents/acp_agent/acp_converters.py +196 -38
  28. agentpool/agents/acp_agent/client_handler.py +191 -26
  29. agentpool/agents/acp_agent/session_state.py +17 -6
  30. agentpool/agents/agent.py +607 -572
  31. agentpool/agents/agui_agent/__init__.py +0 -2
  32. agentpool/agents/agui_agent/agui_agent.py +176 -110
  33. agentpool/agents/agui_agent/agui_converters.py +0 -131
  34. agentpool/agents/agui_agent/helpers.py +3 -4
  35. agentpool/agents/base_agent.py +632 -17
  36. agentpool/agents/claude_code_agent/FORKING.md +191 -0
  37. agentpool/agents/claude_code_agent/__init__.py +13 -1
  38. agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
  39. agentpool/agents/claude_code_agent/converters.py +74 -143
  40. agentpool/agents/claude_code_agent/history.py +474 -0
  41. agentpool/agents/claude_code_agent/models.py +77 -0
  42. agentpool/agents/claude_code_agent/static_info.py +100 -0
  43. agentpool/agents/claude_code_agent/usage.py +242 -0
  44. agentpool/agents/context.py +40 -0
  45. agentpool/agents/events/__init__.py +24 -0
  46. agentpool/agents/events/builtin_handlers.py +67 -1
  47. agentpool/agents/events/event_emitter.py +32 -2
  48. agentpool/agents/events/events.py +104 -3
  49. agentpool/agents/events/infer_info.py +145 -0
  50. agentpool/agents/events/processors.py +254 -0
  51. agentpool/agents/interactions.py +41 -6
  52. agentpool/agents/modes.py +67 -0
  53. agentpool/agents/slashed_agent.py +5 -4
  54. agentpool/agents/tool_call_accumulator.py +213 -0
  55. agentpool/agents/tool_wrapping.py +18 -6
  56. agentpool/common_types.py +56 -21
  57. agentpool/config_resources/__init__.py +38 -1
  58. agentpool/config_resources/acp_assistant.yml +2 -2
  59. agentpool/config_resources/agents.yml +3 -0
  60. agentpool/config_resources/agents_template.yml +1 -0
  61. agentpool/config_resources/claude_code_agent.yml +10 -6
  62. agentpool/config_resources/external_acp_agents.yml +2 -1
  63. agentpool/delegation/base_team.py +4 -30
  64. agentpool/delegation/pool.py +136 -289
  65. agentpool/delegation/team.py +58 -57
  66. agentpool/delegation/teamrun.py +51 -55
  67. agentpool/diagnostics/__init__.py +53 -0
  68. agentpool/diagnostics/lsp_manager.py +1593 -0
  69. agentpool/diagnostics/lsp_proxy.py +41 -0
  70. agentpool/diagnostics/lsp_proxy_script.py +229 -0
  71. agentpool/diagnostics/models.py +398 -0
  72. agentpool/functional/run.py +10 -4
  73. agentpool/mcp_server/__init__.py +0 -2
  74. agentpool/mcp_server/client.py +76 -32
  75. agentpool/mcp_server/conversions.py +54 -13
  76. agentpool/mcp_server/manager.py +34 -54
  77. agentpool/mcp_server/registries/official_registry_client.py +35 -1
  78. agentpool/mcp_server/tool_bridge.py +186 -139
  79. agentpool/messaging/__init__.py +0 -2
  80. agentpool/messaging/compaction.py +72 -197
  81. agentpool/messaging/connection_manager.py +11 -10
  82. agentpool/messaging/event_manager.py +5 -5
  83. agentpool/messaging/message_container.py +6 -30
  84. agentpool/messaging/message_history.py +99 -8
  85. agentpool/messaging/messagenode.py +52 -14
  86. agentpool/messaging/messages.py +54 -35
  87. agentpool/messaging/processing.py +12 -22
  88. agentpool/models/__init__.py +1 -1
  89. agentpool/models/acp_agents/base.py +6 -24
  90. agentpool/models/acp_agents/mcp_capable.py +126 -157
  91. agentpool/models/acp_agents/non_mcp.py +129 -95
  92. agentpool/models/agents.py +98 -76
  93. agentpool/models/agui_agents.py +1 -1
  94. agentpool/models/claude_code_agents.py +144 -19
  95. agentpool/models/file_parsing.py +0 -1
  96. agentpool/models/manifest.py +113 -50
  97. agentpool/prompts/conversion_manager.py +1 -1
  98. agentpool/prompts/prompts.py +5 -2
  99. agentpool/repomap.py +1 -1
  100. agentpool/resource_providers/__init__.py +11 -1
  101. agentpool/resource_providers/aggregating.py +56 -5
  102. agentpool/resource_providers/base.py +70 -4
  103. agentpool/resource_providers/codemode/code_executor.py +72 -5
  104. agentpool/resource_providers/codemode/helpers.py +2 -2
  105. agentpool/resource_providers/codemode/provider.py +64 -12
  106. agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
  107. agentpool/resource_providers/codemode/remote_provider.py +9 -12
  108. agentpool/resource_providers/filtering.py +3 -1
  109. agentpool/resource_providers/mcp_provider.py +89 -12
  110. agentpool/resource_providers/plan_provider.py +228 -46
  111. agentpool/resource_providers/pool.py +7 -3
  112. agentpool/resource_providers/resource_info.py +111 -0
  113. agentpool/resource_providers/static.py +4 -2
  114. agentpool/sessions/__init__.py +4 -1
  115. agentpool/sessions/manager.py +33 -5
  116. agentpool/sessions/models.py +59 -6
  117. agentpool/sessions/protocol.py +28 -0
  118. agentpool/sessions/session.py +11 -55
  119. agentpool/skills/registry.py +13 -8
  120. agentpool/storage/manager.py +572 -49
  121. agentpool/talk/registry.py +4 -4
  122. agentpool/talk/talk.py +9 -10
  123. agentpool/testing.py +538 -20
  124. agentpool/tool_impls/__init__.py +6 -0
  125. agentpool/tool_impls/agent_cli/__init__.py +42 -0
  126. agentpool/tool_impls/agent_cli/tool.py +95 -0
  127. agentpool/tool_impls/bash/__init__.py +64 -0
  128. agentpool/tool_impls/bash/helpers.py +35 -0
  129. agentpool/tool_impls/bash/tool.py +171 -0
  130. agentpool/tool_impls/delete_path/__init__.py +70 -0
  131. agentpool/tool_impls/delete_path/tool.py +142 -0
  132. agentpool/tool_impls/download_file/__init__.py +80 -0
  133. agentpool/tool_impls/download_file/tool.py +183 -0
  134. agentpool/tool_impls/execute_code/__init__.py +55 -0
  135. agentpool/tool_impls/execute_code/tool.py +163 -0
  136. agentpool/tool_impls/grep/__init__.py +80 -0
  137. agentpool/tool_impls/grep/tool.py +200 -0
  138. agentpool/tool_impls/list_directory/__init__.py +73 -0
  139. agentpool/tool_impls/list_directory/tool.py +197 -0
  140. agentpool/tool_impls/question/__init__.py +42 -0
  141. agentpool/tool_impls/question/tool.py +127 -0
  142. agentpool/tool_impls/read/__init__.py +104 -0
  143. agentpool/tool_impls/read/tool.py +305 -0
  144. agentpool/tools/__init__.py +2 -1
  145. agentpool/tools/base.py +114 -34
  146. agentpool/tools/manager.py +57 -1
  147. agentpool/ui/base.py +2 -2
  148. agentpool/ui/mock_provider.py +2 -2
  149. agentpool/ui/stdlib_provider.py +2 -2
  150. agentpool/utils/file_watcher.py +269 -0
  151. agentpool/utils/identifiers.py +121 -0
  152. agentpool/utils/pydantic_ai_helpers.py +46 -0
  153. agentpool/utils/streams.py +616 -2
  154. agentpool/utils/subprocess_utils.py +155 -0
  155. agentpool/utils/token_breakdown.py +461 -0
  156. agentpool/vfs_registry.py +7 -2
  157. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
  158. agentpool-2.5.0.dist-info/RECORD +579 -0
  159. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
  160. agentpool_cli/__main__.py +24 -0
  161. agentpool_cli/create.py +1 -1
  162. agentpool_cli/serve_acp.py +100 -21
  163. agentpool_cli/serve_agui.py +87 -0
  164. agentpool_cli/serve_opencode.py +119 -0
  165. agentpool_cli/ui.py +557 -0
  166. agentpool_commands/__init__.py +42 -5
  167. agentpool_commands/agents.py +75 -2
  168. agentpool_commands/history.py +62 -0
  169. agentpool_commands/mcp.py +176 -0
  170. agentpool_commands/models.py +56 -3
  171. agentpool_commands/pool.py +260 -0
  172. agentpool_commands/session.py +1 -1
  173. agentpool_commands/text_sharing/__init__.py +119 -0
  174. agentpool_commands/text_sharing/base.py +123 -0
  175. agentpool_commands/text_sharing/github_gist.py +80 -0
  176. agentpool_commands/text_sharing/opencode.py +462 -0
  177. agentpool_commands/text_sharing/paste_rs.py +59 -0
  178. agentpool_commands/text_sharing/pastebin.py +116 -0
  179. agentpool_commands/text_sharing/shittycodingagent.py +112 -0
  180. agentpool_commands/tools.py +57 -0
  181. agentpool_commands/utils.py +80 -30
  182. agentpool_config/__init__.py +30 -2
  183. agentpool_config/agentpool_tools.py +498 -0
  184. agentpool_config/builtin_tools.py +77 -22
  185. agentpool_config/commands.py +24 -1
  186. agentpool_config/compaction.py +258 -0
  187. agentpool_config/converters.py +1 -1
  188. agentpool_config/event_handlers.py +42 -0
  189. agentpool_config/events.py +1 -1
  190. agentpool_config/forward_targets.py +1 -4
  191. agentpool_config/jinja.py +3 -3
  192. agentpool_config/mcp_server.py +132 -6
  193. agentpool_config/nodes.py +1 -1
  194. agentpool_config/observability.py +44 -0
  195. agentpool_config/session.py +0 -3
  196. agentpool_config/storage.py +82 -38
  197. agentpool_config/task.py +3 -3
  198. agentpool_config/tools.py +11 -22
  199. agentpool_config/toolsets.py +109 -233
  200. agentpool_server/a2a_server/agent_worker.py +307 -0
  201. agentpool_server/a2a_server/server.py +23 -18
  202. agentpool_server/acp_server/acp_agent.py +234 -181
  203. agentpool_server/acp_server/commands/acp_commands.py +151 -156
  204. agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
  205. agentpool_server/acp_server/event_converter.py +651 -0
  206. agentpool_server/acp_server/input_provider.py +53 -10
  207. agentpool_server/acp_server/server.py +24 -90
  208. agentpool_server/acp_server/session.py +173 -331
  209. agentpool_server/acp_server/session_manager.py +8 -34
  210. agentpool_server/agui_server/server.py +3 -1
  211. agentpool_server/mcp_server/server.py +5 -2
  212. agentpool_server/opencode_server/.rules +95 -0
  213. agentpool_server/opencode_server/ENDPOINTS.md +401 -0
  214. agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
  215. agentpool_server/opencode_server/__init__.py +19 -0
  216. agentpool_server/opencode_server/command_validation.py +172 -0
  217. agentpool_server/opencode_server/converters.py +975 -0
  218. agentpool_server/opencode_server/dependencies.py +24 -0
  219. agentpool_server/opencode_server/input_provider.py +421 -0
  220. agentpool_server/opencode_server/models/__init__.py +250 -0
  221. agentpool_server/opencode_server/models/agent.py +53 -0
  222. agentpool_server/opencode_server/models/app.py +72 -0
  223. agentpool_server/opencode_server/models/base.py +26 -0
  224. agentpool_server/opencode_server/models/common.py +23 -0
  225. agentpool_server/opencode_server/models/config.py +37 -0
  226. agentpool_server/opencode_server/models/events.py +821 -0
  227. agentpool_server/opencode_server/models/file.py +88 -0
  228. agentpool_server/opencode_server/models/mcp.py +44 -0
  229. agentpool_server/opencode_server/models/message.py +179 -0
  230. agentpool_server/opencode_server/models/parts.py +323 -0
  231. agentpool_server/opencode_server/models/provider.py +81 -0
  232. agentpool_server/opencode_server/models/pty.py +43 -0
  233. agentpool_server/opencode_server/models/question.py +56 -0
  234. agentpool_server/opencode_server/models/session.py +111 -0
  235. agentpool_server/opencode_server/routes/__init__.py +29 -0
  236. agentpool_server/opencode_server/routes/agent_routes.py +473 -0
  237. agentpool_server/opencode_server/routes/app_routes.py +202 -0
  238. agentpool_server/opencode_server/routes/config_routes.py +302 -0
  239. agentpool_server/opencode_server/routes/file_routes.py +571 -0
  240. agentpool_server/opencode_server/routes/global_routes.py +94 -0
  241. agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
  242. agentpool_server/opencode_server/routes/message_routes.py +761 -0
  243. agentpool_server/opencode_server/routes/permission_routes.py +63 -0
  244. agentpool_server/opencode_server/routes/pty_routes.py +300 -0
  245. agentpool_server/opencode_server/routes/question_routes.py +128 -0
  246. agentpool_server/opencode_server/routes/session_routes.py +1276 -0
  247. agentpool_server/opencode_server/routes/tui_routes.py +139 -0
  248. agentpool_server/opencode_server/server.py +475 -0
  249. agentpool_server/opencode_server/state.py +151 -0
  250. agentpool_server/opencode_server/time_utils.py +8 -0
  251. agentpool_storage/__init__.py +12 -0
  252. agentpool_storage/base.py +184 -2
  253. agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
  254. agentpool_storage/claude_provider/__init__.py +42 -0
  255. agentpool_storage/claude_provider/provider.py +1089 -0
  256. agentpool_storage/file_provider.py +278 -15
  257. agentpool_storage/memory_provider.py +193 -12
  258. agentpool_storage/models.py +3 -0
  259. agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
  260. agentpool_storage/opencode_provider/__init__.py +16 -0
  261. agentpool_storage/opencode_provider/helpers.py +414 -0
  262. agentpool_storage/opencode_provider/provider.py +895 -0
  263. agentpool_storage/project_store.py +325 -0
  264. agentpool_storage/session_store.py +26 -6
  265. agentpool_storage/sql_provider/__init__.py +4 -2
  266. agentpool_storage/sql_provider/models.py +48 -0
  267. agentpool_storage/sql_provider/sql_provider.py +269 -3
  268. agentpool_storage/sql_provider/utils.py +12 -13
  269. agentpool_storage/zed_provider/__init__.py +16 -0
  270. agentpool_storage/zed_provider/helpers.py +281 -0
  271. agentpool_storage/zed_provider/models.py +130 -0
  272. agentpool_storage/zed_provider/provider.py +442 -0
  273. agentpool_storage/zed_provider.py +803 -0
  274. agentpool_toolsets/__init__.py +0 -2
  275. agentpool_toolsets/builtin/__init__.py +2 -12
  276. agentpool_toolsets/builtin/code.py +96 -57
  277. agentpool_toolsets/builtin/debug.py +118 -48
  278. agentpool_toolsets/builtin/execution_environment.py +115 -230
  279. agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
  280. agentpool_toolsets/builtin/skills.py +9 -4
  281. agentpool_toolsets/builtin/subagent_tools.py +64 -51
  282. agentpool_toolsets/builtin/workers.py +4 -2
  283. agentpool_toolsets/composio_toolset.py +2 -2
  284. agentpool_toolsets/entry_points.py +3 -1
  285. agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
  286. agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
  287. agentpool_toolsets/fsspec_toolset/grep.py +99 -7
  288. agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
  289. agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
  290. agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
  291. agentpool_toolsets/mcp_discovery/__init__.py +5 -0
  292. agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
  293. agentpool_toolsets/mcp_discovery/toolset.py +511 -0
  294. agentpool_toolsets/mcp_run_toolset.py +87 -12
  295. agentpool_toolsets/notifications.py +33 -33
  296. agentpool_toolsets/openapi.py +3 -1
  297. agentpool_toolsets/search_toolset.py +3 -1
  298. agentpool-2.1.9.dist-info/RECORD +0 -474
  299. agentpool_config/resources.py +0 -33
  300. agentpool_server/acp_server/acp_tools.py +0 -43
  301. agentpool_server/acp_server/commands/spawn.py +0 -210
  302. agentpool_storage/text_log_provider.py +0 -275
  303. agentpool_toolsets/builtin/agent_management.py +0 -239
  304. agentpool_toolsets/builtin/chain.py +0 -288
  305. agentpool_toolsets/builtin/history.py +0 -36
  306. agentpool_toolsets/builtin/integration.py +0 -85
  307. agentpool_toolsets/builtin/tool_management.py +0 -90
  308. agentpool_toolsets/builtin/user_interaction.py +0 -52
  309. agentpool_toolsets/semantic_memory_toolset.py +0 -536
  310. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
  311. {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,7 +3,6 @@
3
3
  from agentpool.mcp_server.client import MCPClient
4
4
  from agentpool.mcp_server.tool_bridge import (
5
5
  BridgeConfig,
6
- ToolBridgeRegistry,
7
6
  ToolManagerBridge,
8
7
  create_tool_bridge,
9
8
  )
@@ -11,7 +10,6 @@ from agentpool.mcp_server.tool_bridge import (
11
10
  __all__ = [
12
11
  "BridgeConfig",
13
12
  "MCPClient",
14
- "ToolBridgeRegistry",
15
13
  "ToolManagerBridge",
16
14
  "create_tool_bridge",
17
15
  ]
@@ -28,7 +28,7 @@ from agentpool.log import get_logger
28
28
  from agentpool.mcp_server.constants import MCP_TO_LOGGING
29
29
  from agentpool.mcp_server.helpers import extract_text_content, mcp_tool_to_fn_schema
30
30
  from agentpool.mcp_server.message_handler import MCPMessageHandler
31
- from agentpool.tools.base import Tool
31
+ from agentpool.tools.base import FunctionTool
32
32
  from agentpool.utils.signatures import create_modified_signature
33
33
  from agentpool_config.mcp_server import (
34
34
  SSEMCPServerConfig,
@@ -56,6 +56,7 @@ if TYPE_CHECKING:
56
56
  Implementation,
57
57
  Prompt as MCPPrompt,
58
58
  Resource as MCPResource,
59
+ ResourceTemplate,
59
60
  TextResourceContents,
60
61
  Tool as MCPTool,
61
62
  )
@@ -106,12 +107,17 @@ class MCPClient:
106
107
  """Check if client is connected by examining session state."""
107
108
  return self._client.is_connected()
108
109
 
110
+ def _ensure_connected(self) -> None:
111
+ """Ensure client is connected, raise RuntimeError if not."""
112
+ if not self.connected:
113
+ msg = "Not connected to MCP server"
114
+ raise RuntimeError(msg)
115
+
109
116
  async def __aenter__(self) -> Self:
110
117
  """Enter context manager."""
111
118
  try:
112
119
  # First attempt with configured auth
113
120
  await self._client.__aenter__() # type: ignore[no-untyped-call]
114
-
115
121
  except Exception as first_error:
116
122
  # OAuth fallback for HTTP/SSE if not already using OAuth
117
123
  if not isinstance(self.config, StdioMCPServerConfig) and not self.config.auth.oauth:
@@ -237,26 +243,24 @@ class MCPClient:
237
243
  )
238
244
 
239
245
  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)
246
+ """Get available tools directly from the server.
244
247
 
248
+ Tools are filtered based on the server config's enabled_tools/disabled_tools settings.
249
+ """
250
+ self._ensure_connected()
245
251
  try:
246
252
  tools = await self._client.list_tools()
247
- logger.debug("Listed tools from MCP server", num_tools=len(tools))
253
+ filtered = [t for t in tools if self.config.is_tool_allowed(t.name)]
254
+ logger.debug("Listed tools from MCP server", total=len(tools), filtered=len(filtered))
248
255
  except Exception as e: # noqa: BLE001
249
256
  logger.warning("Failed to list tools", error=e)
250
257
  return []
251
258
  else:
252
- return tools
259
+ return filtered
253
260
 
254
261
  async def list_prompts(self) -> list[MCPPrompt]:
255
262
  """Get available prompts from the server."""
256
- if not self.connected:
257
- msg = "Not connected to MCP server"
258
- raise RuntimeError(msg)
259
-
263
+ self._ensure_connected()
260
264
  try:
261
265
  return await self._client.list_prompts()
262
266
  except Exception as e: # noqa: BLE001
@@ -265,31 +269,69 @@ class MCPClient:
265
269
 
266
270
  async def list_resources(self) -> list[MCPResource]:
267
271
  """Get available resources from the server."""
268
- if not self.connected:
269
- msg = "Not connected to MCP server"
270
- raise RuntimeError(msg)
271
-
272
+ self._ensure_connected()
272
273
  try:
273
274
  return await self._client.list_resources()
274
275
  except Exception as e:
275
276
  msg = f"Failed to list resources: {e}"
276
277
  raise RuntimeError(msg) from e
277
278
 
279
+ async def list_resource_templates(self) -> list[ResourceTemplate]:
280
+ """Get available resource templates from the server.
281
+
282
+ Resource templates are URI patterns with placeholders that can be
283
+ expanded to create concrete resource URIs.
284
+
285
+ Example template: "file:///{path}" -> expand with path="config.json"
286
+ -> "file:///config.json" which can then be read.
287
+
288
+ TODO: Integrate resource templates into the ResourceInfo system.
289
+ Currently templates are separate from resources - we need to decide:
290
+ - Should templates appear in list_resources() with a flag?
291
+ - Should ResourceInfo.read() accept kwargs for template expansion?
292
+ - Should templates have their own ResourceTemplateInfo class?
293
+
294
+ Returns:
295
+ List of resource templates from the server
296
+ """
297
+ self._ensure_connected()
298
+ try:
299
+ return await self._client.list_resource_templates()
300
+ except Exception as e:
301
+ msg = f"Failed to list resource templates: {e}"
302
+ raise RuntimeError(msg) from e
303
+
304
+ async def read_resource(self, uri: str) -> list[TextResourceContents | BlobResourceContents]:
305
+ """Read resource content by URI.
306
+
307
+ Args:
308
+ uri: URI of the resource to read
309
+
310
+ Returns:
311
+ List of resource contents (text or blob)
312
+
313
+ Raises:
314
+ RuntimeError: If not connected or read fails
315
+ """
316
+ self._ensure_connected()
317
+ try:
318
+ return await self._client.read_resource(uri)
319
+ except Exception as e:
320
+ msg = f"Failed to read resource {uri!r}: {e}"
321
+ raise RuntimeError(msg) from e
322
+
278
323
  async def get_prompt(
279
324
  self, name: str, arguments: dict[str, str] | None = None
280
325
  ) -> GetPromptResult:
281
326
  """Get a specific prompt's content."""
282
- if not self.connected:
283
- msg = "Not connected to MCP server"
284
- raise RuntimeError(msg)
285
-
327
+ self._ensure_connected()
286
328
  try:
287
329
  return await self._client.get_prompt_mcp(name, arguments)
288
330
  except Exception as e:
289
331
  msg = f"Failed to get prompt {name!r}: {e}"
290
332
  raise RuntimeError(msg) from e
291
333
 
292
- def convert_tool(self, tool: MCPTool) -> Tool:
334
+ def convert_tool(self, tool: MCPTool) -> FunctionTool:
293
335
  """Create a properly typed callable from MCP tool schema."""
294
336
 
295
337
  async def tool_callable(
@@ -310,7 +352,6 @@ class MCPClient:
310
352
  schema = mcp_tool_to_fn_schema(tool)
311
353
  fn_schema = FunctionSchema.from_dict(schema)
312
354
  sig = fn_schema.to_python_signature()
313
-
314
355
  tool_callable.__signature__ = create_modified_signature( # type: ignore[attr-defined]
315
356
  sig, inject={"ctx": RunContext, "agent_ctx": AgentContext}
316
357
  )
@@ -322,7 +363,7 @@ class MCPClient:
322
363
  tool_callable.__annotations__ = annotations
323
364
  tool_callable.__name__ = tool.name
324
365
  tool_callable.__doc__ = tool.description or "No description provided."
325
- return Tool.from_callable(tool_callable, source="mcp")
366
+ return FunctionTool.from_callable(tool_callable, source="mcp")
326
367
 
327
368
  async def call_tool(
328
369
  self,
@@ -332,10 +373,7 @@ class MCPClient:
332
373
  agent_ctx: AgentContext[Any] | None = None,
333
374
  ) -> ToolReturn | str | Any:
334
375
  """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
-
376
+ self._ensure_connected()
339
377
  # Create progress handler that bridges to AgentContext if available
340
378
  progress_handler = None
341
379
  if agent_ctx:
@@ -376,9 +414,18 @@ class MCPClient:
376
414
 
377
415
  self._current_elicitation_handler = elicitation_handler
378
416
 
417
+ # Prepare metadata to pass tool_call_id to the MCP server
418
+ meta = None
419
+ if agent_ctx and agent_ctx.tool_call_id:
420
+ # Use the same key that tool_bridge expects: "claudecode/toolUseId"
421
+ # Ensure it's a string (handles both real values and mocks)
422
+ tool_call_id = str(agent_ctx.tool_call_id) if agent_ctx.tool_call_id else None
423
+ if tool_call_id:
424
+ meta = {"claudecode/toolUseId": tool_call_id}
425
+
379
426
  try:
380
427
  result = await self._client.call_tool(
381
- name, arguments, progress_handler=progress_handler
428
+ name, arguments, progress_handler=progress_handler, meta=meta
382
429
  )
383
430
  content = await self._from_mcp_content(result.content)
384
431
  # Decision logic for return type
@@ -415,10 +462,7 @@ class MCPClient:
415
462
  if __name__ == "__main__":
416
463
  path = "/home/phil65/dev/oss/agentpool/tests/mcp_server/server.py"
417
464
  # path = Path(__file__).parent / "test_mcp_server.py"
418
- config = StdioMCPServerConfig(
419
- command="uv",
420
- args=["run", str(path)],
421
- )
465
+ config = StdioMCPServerConfig(command="uv", args=["run", str(path)])
422
466
 
423
467
  async def main() -> None:
424
468
  async with MCPClient(config=config) as mcp_client:
@@ -5,16 +5,6 @@ from __future__ import annotations
5
5
  import base64
6
6
  from typing import TYPE_CHECKING, Any, assert_never
7
7
 
8
- from mcp.types import (
9
- AudioContent,
10
- BlobResourceContents,
11
- EmbeddedResource,
12
- ImageContent,
13
- PromptMessage,
14
- ResourceLink,
15
- TextContent,
16
- TextResourceContents,
17
- )
18
8
  from pydantic_ai import (
19
9
  AudioUrl,
20
10
  BinaryContent,
@@ -35,9 +25,14 @@ if TYPE_CHECKING:
35
25
  from collections.abc import Sequence
36
26
 
37
27
  from fastmcp import Client
38
- from mcp.types import ContentBlock
39
- from pydantic_ai import ModelRequestPart, ModelResponsePart
40
-
28
+ from mcp.types import (
29
+ BlobResourceContents,
30
+ ContentBlock,
31
+ PromptMessage,
32
+ SamplingMessage,
33
+ TextResourceContents,
34
+ )
35
+ from pydantic_ai import ModelRequestPart, ModelResponsePart, UserContent
41
36
 
42
37
  logger = get_logger(__name__)
43
38
 
@@ -46,6 +41,13 @@ def to_mcp_messages(
46
41
  part: ModelRequestPart | ModelResponsePart,
47
42
  ) -> list[PromptMessage]:
48
43
  """Convert internal PromptMessage to MCP PromptMessage."""
44
+ from mcp.types import (
45
+ AudioContent,
46
+ ImageContent,
47
+ PromptMessage,
48
+ TextContent,
49
+ )
50
+
49
51
  messages = []
50
52
  match part:
51
53
  case UserPromptPart(content=str() as c):
@@ -94,6 +96,24 @@ def _url_from_mime_type(uri: str, mime_type: str | None) -> FileUrl:
94
96
  return DocumentUrl(url=uri)
95
97
 
96
98
 
99
+ def sampling_messages_to_user_content(msgs: list[SamplingMessage]) -> list[UserContent]:
100
+ from mcp import types
101
+
102
+ # Convert messages to prompts for the agent
103
+ prompts: list[UserContent] = []
104
+ for mcp_msg in msgs:
105
+ match mcp_msg.content:
106
+ case types.TextContent(text=text):
107
+ prompts.append(text)
108
+ case types.ImageContent(data=data, mimeType=mime_type):
109
+ binary_data = base64.b64decode(data)
110
+ prompts.append(BinaryImage(data=binary_data, media_type=mime_type))
111
+ case types.AudioContent(data=data, mimeType=mime_type):
112
+ binary_data = base64.b64decode(data)
113
+ prompts.append(BinaryContent(data=binary_data, media_type=mime_type))
114
+ return prompts
115
+
116
+
97
117
  async def from_mcp_content(
98
118
  mcp_content: Sequence[ContentBlock | TextResourceContents | BlobResourceContents],
99
119
  client: Client[Any] | None = None,
@@ -103,6 +123,16 @@ async def from_mcp_content(
103
123
  If a FastMCP client is given, this function will try to resolve the ResourceLinks.
104
124
 
105
125
  """
126
+ from mcp.types import (
127
+ AudioContent,
128
+ BlobResourceContents,
129
+ EmbeddedResource,
130
+ ImageContent,
131
+ ResourceLink,
132
+ TextContent,
133
+ TextResourceContents,
134
+ )
135
+
106
136
  contents: list[Any] = []
107
137
 
108
138
  for block in mcp_content:
@@ -151,6 +181,17 @@ async def from_mcp_content(
151
181
 
152
182
 
153
183
  def content_block_as_text(content: ContentBlock) -> str:
184
+
185
+ from mcp.types import (
186
+ AudioContent,
187
+ BlobResourceContents,
188
+ EmbeddedResource,
189
+ ImageContent,
190
+ ResourceLink,
191
+ TextContent,
192
+ TextResourceContents,
193
+ )
194
+
154
195
  match content:
155
196
  case TextContent(text=text):
156
197
  return text
@@ -3,12 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
- import base64
7
6
  from contextlib import AsyncExitStack
8
7
  from typing import TYPE_CHECKING, Any, Self, cast
9
8
 
10
9
  import anyio
11
- from pydantic_ai import BinaryContent, BinaryImage, UsageLimits
12
10
 
13
11
  from agentpool.log import get_logger
14
12
  from agentpool.resource_providers import AggregatingResourceProvider, ResourceProvider
@@ -23,7 +21,6 @@ if TYPE_CHECKING:
23
21
  from mcp import types
24
22
  from mcp.shared.context import RequestContext
25
23
  from mcp.types import SamplingMessage
26
- from pydantic_ai import UserContent
27
24
 
28
25
  from agentpool_config.mcp_server import MCPServerConfig
29
26
 
@@ -38,6 +35,7 @@ class MCPManager:
38
35
  self,
39
36
  name: str = "mcp",
40
37
  owner: str | None = None,
38
+ sampling_model: str = "openai:gpt-5-nano",
41
39
  servers: Sequence[MCPServerConfig | str] | None = None,
42
40
  accessible_roots: list[str] | None = None,
43
41
  ) -> None:
@@ -47,6 +45,7 @@ class MCPManager:
47
45
  for server in servers or []:
48
46
  self.add_server_config(server)
49
47
  self.providers: list[MCPResourceProvider] = []
48
+ self.sampling_model = sampling_model
50
49
  self.aggregating_provider = AggregatingResourceProvider(
51
50
  providers=cast(list[ResourceProvider], self.providers),
52
51
  name=f"{name}_aggregated",
@@ -64,7 +63,7 @@ class MCPManager:
64
63
 
65
64
  async def __aenter__(self) -> Self:
66
65
  try:
67
- if tasks := [self._setup_server(server) for server in self.servers]:
66
+ if tasks := [self.setup_server(server) for server in self.servers]:
68
67
  await asyncio.gather(*tasks)
69
68
  except Exception as e:
70
69
  await self.__aexit__(type(e), e, e.__traceback__)
@@ -88,45 +87,52 @@ class MCPManager:
88
87
  context: RequestContext[Any, Any, Any],
89
88
  ) -> str:
90
89
  """Handle MCP sampling by creating a new agent with specified preferences."""
91
- from mcp import types
92
-
93
90
  from agentpool.agents import Agent
91
+ from agentpool.mcp_server.conversions import sampling_messages_to_user_content
94
92
 
95
- # Convert messages to prompts for the agent
96
- prompts: list[UserContent] = []
97
- for mcp_msg in messages:
98
- match mcp_msg.content:
99
- case types.TextContent(text=text):
100
- prompts.append(text)
101
- case types.ImageContent(data=data, mimeType=mime_type):
102
- binary_data = base64.b64decode(data)
103
- prompts.append(BinaryImage(data=binary_data, media_type=mime_type))
104
- case types.AudioContent(data=data, mimeType=mime_type):
105
- binary_data = base64.b64decode(data)
106
- prompts.append(BinaryContent(data=binary_data, media_type=mime_type))
107
-
108
- # Extract model from preferences
109
- model = None
93
+ prompts = sampling_messages_to_user_content(messages)
94
+ model = self.sampling_model
110
95
  if (prefs := params.modelPreferences) and prefs.hints and prefs.hints[0].name:
111
- model = prefs.hints[0].name
96
+ model = prefs.hints[0].name # Extract model from preferences
112
97
  # Create usage limits from sampling parameters
113
- limits = UsageLimits(output_tokens_limit=params.maxTokens, request_limit=1)
98
+ # limits = UsageLimits(output_tokens_limit=params.maxTokens, request_limit=1)
99
+ # TODO: Re-add per-turn usage_limits once implemented for all agents
114
100
  # TODO: Apply temperature from params.temperature
115
101
  sys_prompt = params.systemPrompt or ""
116
102
  agent = Agent(name="sampling-agent", model=model, system_prompt=sys_prompt, session=False)
117
103
  try:
118
104
  async with agent:
119
- result = await agent.run(*prompts, store_history=False, usage_limits=limits)
105
+ result = await agent.run(*prompts, store_history=False)
120
106
  return result.content
121
107
 
122
108
  except Exception as e:
123
109
  logger.exception("Sampling failed")
124
110
  return f"Sampling failed: {e!s}"
125
111
 
126
- async def _setup_server(self, config: MCPServerConfig) -> None:
127
- """Set up a single MCP server resource provider."""
112
+ async def setup_server(
113
+ self, config: MCPServerConfig, *, add_to_config: bool = False
114
+ ) -> MCPResourceProvider | None:
115
+ """Set up a single MCP server resource provider.
116
+
117
+ Args:
118
+ config: MCP server configuration
119
+ add_to_config: If True, also add config to self.servers list and
120
+ raise ValueError if config is disabled
121
+
122
+ Returns:
123
+ The provider if created, None if config is disabled (only when add_to_config=False)
124
+
125
+ Raises:
126
+ ValueError: If add_to_config=True and config is disabled
127
+ """
128
128
  if not config.enabled:
129
- return
129
+ if add_to_config:
130
+ msg = f"Server config {config.client_id} is disabled"
131
+ raise ValueError(msg)
132
+ return None
133
+
134
+ if add_to_config:
135
+ self.add_server_config(config)
130
136
 
131
137
  provider = MCPResourceProvider(
132
138
  server=config,
@@ -138,6 +144,7 @@ class MCPManager:
138
144
  )
139
145
  provider = await self.exit_stack.enter_async_context(provider)
140
146
  self.providers.append(provider)
147
+ return provider
141
148
 
142
149
  def get_mcp_providers(self) -> list[MCPResourceProvider]:
143
150
  """Get all MCP resource providers managed by this manager."""
@@ -147,33 +154,6 @@ class MCPManager:
147
154
  """Get the aggregating provider that contains all MCP providers."""
148
155
  return self.aggregating_provider
149
156
 
150
- async def setup_server_runtime(self, config: MCPServerConfig) -> MCPResourceProvider:
151
- """Set up a single MCP server at runtime while manager is running.
152
-
153
- Returns:
154
- The newly created and initialized MCPResourceProvider
155
- """
156
- if not config.enabled:
157
- msg = f"Server config {config.client_id} is disabled"
158
- raise ValueError(msg)
159
-
160
- # Add the config first
161
- self.add_server_config(config)
162
- provider = MCPResourceProvider(
163
- server=config,
164
- name=f"{self.name}_{config.client_id}",
165
- owner=self.owner,
166
- source="pool" if self.owner == "pool" else "node",
167
- sampling_callback=self._sampling_callback,
168
- accessible_roots=self._accessible_roots,
169
- )
170
- provider = await self.exit_stack.enter_async_context(provider)
171
- self.providers.append(provider)
172
- # Note: AggregatingResourceProvider automatically sees the new provider
173
- # since it references self.providers list
174
-
175
- return provider
176
-
177
157
  async def cleanup(self) -> None:
178
158
  """Clean up all MCP connections and providers."""
179
159
  try:
@@ -51,6 +51,9 @@ class RegistryTransport(Schema):
51
51
  url: str | None = None
52
52
  """URL for HTTP transports."""
53
53
 
54
+ headers: list[dict[str, Any]] = Field(default_factory=list)
55
+ """Request headers."""
56
+
54
57
 
55
58
  class RegistryPackage(Schema):
56
59
  """Package information for installing an MCP server."""
@@ -75,6 +78,9 @@ class RegistryPackage(Schema):
75
78
  package_arguments: list[dict[str, Any]] = Field(default_factory=list, alias="packageArguments")
76
79
  """Package arguments."""
77
80
 
81
+ runtime_arguments: list[dict[str, Any]] = Field(default_factory=list, alias="runtimeArguments")
82
+ """Runtime arguments."""
83
+
78
84
  runtime_hint: str | None = Field(None, alias="runtimeHint")
79
85
  """Runtime hint."""
80
86
 
@@ -97,6 +103,22 @@ class RegistryRemote(Schema):
97
103
  headers: list[dict[str, Any]] = Field(default_factory=list)
98
104
  """Request headers."""
99
105
 
106
+ variables: dict[str, Any] = Field(default_factory=dict)
107
+ """URL template variables."""
108
+
109
+
110
+ class RegistryIcon(Schema):
111
+ """Icon configuration for a server."""
112
+
113
+ src: str
114
+ """Icon source URL."""
115
+
116
+ theme: str | None = None
117
+ """Theme variant (light, dark)."""
118
+
119
+ mime_type: str | None = Field(None, alias="mimeType")
120
+ """MIME type of the icon (e.g., image/png)."""
121
+
100
122
 
101
123
  class RegistryServer(Schema):
102
124
  """MCP server entry from the registry."""
@@ -110,7 +132,7 @@ class RegistryServer(Schema):
110
132
  version: str
111
133
  """Server version."""
112
134
 
113
- repository: RegistryRepository
135
+ repository: RegistryRepository | None = None
114
136
  """Repository information."""
115
137
 
116
138
  packages: list[RegistryPackage] = Field(default_factory=list)
@@ -122,6 +144,18 @@ class RegistryServer(Schema):
122
144
  schema_: str | None = Field(None, alias="$schema")
123
145
  """JSON schema URL."""
124
146
 
147
+ title: str | None = None
148
+ """Human-readable display title."""
149
+
150
+ website_url: str | None = Field(None, alias="websiteUrl")
151
+ """Website URL for documentation."""
152
+
153
+ icons: list[RegistryIcon] = Field(default_factory=list)
154
+ """Server icons for different themes."""
155
+
156
+ meta: dict[str, Any] = Field(default_factory=dict, alias="_meta")
157
+ """Internal metadata (can appear at server level too)."""
158
+
125
159
  def get_preferred_transport(self) -> TransportType:
126
160
  """Select optimal transport method based on availability and performance."""
127
161
  # Prefer local packages for better performance/security