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.
- acp/__init__.py +13 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/bridge/README.md +15 -2
- acp/bridge/__init__.py +3 -2
- acp/bridge/__main__.py +60 -19
- acp/bridge/ws_server.py +173 -0
- acp/bridge/ws_server_cli.py +89 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +20 -50
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/stdio.py +39 -9
- acp/task/supervisor.py +2 -2
- acp/transports.py +362 -2
- acp/utils.py +17 -4
- agentpool/__init__.py +6 -1
- agentpool/agents/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +407 -277
- agentpool/agents/acp_agent/acp_converters.py +196 -38
- agentpool/agents/acp_agent/client_handler.py +191 -26
- agentpool/agents/acp_agent/session_state.py +17 -6
- agentpool/agents/agent.py +607 -572
- agentpool/agents/agui_agent/__init__.py +0 -2
- agentpool/agents/agui_agent/agui_agent.py +176 -110
- agentpool/agents/agui_agent/agui_converters.py +0 -131
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +632 -17
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
- agentpool/agents/claude_code_agent/converters.py +74 -143
- agentpool/agents/claude_code_agent/history.py +474 -0
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/context.py +40 -0
- agentpool/agents/events/__init__.py +24 -0
- agentpool/agents/events/builtin_handlers.py +67 -1
- agentpool/agents/events/event_emitter.py +32 -2
- agentpool/agents/events/events.py +104 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +67 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_call_accumulator.py +213 -0
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +56 -21
- agentpool/config_resources/__init__.py +38 -1
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +10 -6
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +136 -289
- agentpool/delegation/team.py +58 -57
- agentpool/delegation/teamrun.py +51 -55
- agentpool/diagnostics/__init__.py +53 -0
- agentpool/diagnostics/lsp_manager.py +1593 -0
- agentpool/diagnostics/lsp_proxy.py +41 -0
- agentpool/diagnostics/lsp_proxy_script.py +229 -0
- agentpool/diagnostics/models.py +398 -0
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/__init__.py +0 -2
- agentpool/mcp_server/client.py +76 -32
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +34 -54
- agentpool/mcp_server/registries/official_registry_client.py +35 -1
- agentpool/mcp_server/tool_bridge.py +186 -139
- agentpool/messaging/__init__.py +0 -2
- agentpool/messaging/compaction.py +72 -197
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +99 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +54 -35
- agentpool/messaging/processing.py +12 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -24
- agentpool/models/acp_agents/mcp_capable.py +126 -157
- agentpool/models/acp_agents/non_mcp.py +129 -95
- agentpool/models/agents.py +98 -76
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +144 -19
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +113 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/repomap.py +1 -1
- agentpool/resource_providers/__init__.py +11 -1
- agentpool/resource_providers/aggregating.py +56 -5
- agentpool/resource_providers/base.py +70 -4
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +89 -12
- agentpool/resource_providers/plan_provider.py +228 -46
- agentpool/resource_providers/pool.py +7 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +4 -2
- agentpool/sessions/__init__.py +4 -1
- agentpool/sessions/manager.py +33 -5
- agentpool/sessions/models.py +59 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/skills/registry.py +13 -8
- agentpool/storage/manager.py +572 -49
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +538 -20
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/file_watcher.py +269 -0
- agentpool/utils/identifiers.py +121 -0
- agentpool/utils/pydantic_ai_helpers.py +46 -0
- agentpool/utils/streams.py +616 -2
- agentpool/utils/subprocess_utils.py +155 -0
- agentpool/utils/token_breakdown.py +461 -0
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
- agentpool-2.5.0.dist-info/RECORD +579 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +24 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +100 -21
- agentpool_cli/serve_agui.py +87 -0
- agentpool_cli/serve_opencode.py +119 -0
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +42 -5
- agentpool_commands/agents.py +75 -2
- agentpool_commands/history.py +62 -0
- agentpool_commands/mcp.py +176 -0
- agentpool_commands/models.py +56 -3
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/tools.py +57 -0
- agentpool_commands/utils.py +80 -30
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/builtin_tools.py +77 -22
- agentpool_config/commands.py +24 -1
- agentpool_config/compaction.py +258 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +132 -6
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +82 -38
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -22
- agentpool_config/toolsets.py +109 -233
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +234 -181
- agentpool_server/acp_server/commands/acp_commands.py +151 -156
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +24 -90
- agentpool_server/acp_server/session.py +173 -331
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/.rules +95 -0
- agentpool_server/opencode_server/ENDPOINTS.md +401 -0
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +19 -0
- agentpool_server/opencode_server/command_validation.py +172 -0
- agentpool_server/opencode_server/converters.py +975 -0
- agentpool_server/opencode_server/dependencies.py +24 -0
- agentpool_server/opencode_server/input_provider.py +421 -0
- agentpool_server/opencode_server/models/__init__.py +250 -0
- agentpool_server/opencode_server/models/agent.py +53 -0
- agentpool_server/opencode_server/models/app.py +72 -0
- agentpool_server/opencode_server/models/base.py +26 -0
- agentpool_server/opencode_server/models/common.py +23 -0
- agentpool_server/opencode_server/models/config.py +37 -0
- agentpool_server/opencode_server/models/events.py +821 -0
- agentpool_server/opencode_server/models/file.py +88 -0
- agentpool_server/opencode_server/models/mcp.py +44 -0
- agentpool_server/opencode_server/models/message.py +179 -0
- agentpool_server/opencode_server/models/parts.py +323 -0
- agentpool_server/opencode_server/models/provider.py +81 -0
- agentpool_server/opencode_server/models/pty.py +43 -0
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +111 -0
- agentpool_server/opencode_server/routes/__init__.py +29 -0
- agentpool_server/opencode_server/routes/agent_routes.py +473 -0
- agentpool_server/opencode_server/routes/app_routes.py +202 -0
- agentpool_server/opencode_server/routes/config_routes.py +302 -0
- agentpool_server/opencode_server/routes/file_routes.py +571 -0
- agentpool_server/opencode_server/routes/global_routes.py +94 -0
- agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
- agentpool_server/opencode_server/routes/message_routes.py +761 -0
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +300 -0
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +1276 -0
- agentpool_server/opencode_server/routes/tui_routes.py +139 -0
- agentpool_server/opencode_server/server.py +475 -0
- agentpool_server/opencode_server/state.py +151 -0
- agentpool_server/opencode_server/time_utils.py +8 -0
- agentpool_storage/__init__.py +12 -0
- agentpool_storage/base.py +184 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/claude_provider/provider.py +1089 -0
- agentpool_storage/file_provider.py +278 -15
- agentpool_storage/memory_provider.py +193 -12
- agentpool_storage/models.py +3 -0
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/project_store.py +325 -0
- agentpool_storage/session_store.py +26 -6
- agentpool_storage/sql_provider/__init__.py +4 -2
- agentpool_storage/sql_provider/models.py +48 -0
- agentpool_storage/sql_provider/sql_provider.py +269 -3
- agentpool_storage/sql_provider/utils.py +12 -13
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -12
- agentpool_toolsets/builtin/code.py +96 -57
- agentpool_toolsets/builtin/debug.py +118 -48
- agentpool_toolsets/builtin/execution_environment.py +115 -230
- agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
- agentpool_toolsets/builtin/skills.py +9 -4
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
- agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
- agentpool_toolsets/fsspec_toolset/grep.py +99 -7
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
- agentpool_toolsets/mcp_discovery/__init__.py +5 -0
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +511 -0
- agentpool_toolsets/mcp_run_toolset.py +87 -12
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool-2.1.9.dist-info/RECORD +0 -474
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/text_log_provider.py +0 -275
- agentpool_toolsets/builtin/agent_management.py +0 -239
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/history.py +0 -36
- agentpool_toolsets/builtin/integration.py +0 -85
- agentpool_toolsets/builtin/tool_management.py +0 -90
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
agentpool/mcp_server/__init__.py
CHANGED
|
@@ -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
|
]
|
agentpool/mcp_server/client.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
259
|
+
return filtered
|
|
253
260
|
|
|
254
261
|
async def list_prompts(self) -> list[MCPPrompt]:
|
|
255
262
|
"""Get available prompts from the server."""
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) ->
|
|
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
|
|
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
|
-
|
|
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
|
|
39
|
-
|
|
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
|
agentpool/mcp_server/manager.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
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
|
|
127
|
-
|
|
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
|
-
|
|
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
|