autobyteus 1.1.2__py3-none-any.whl → 1.1.4__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.
- autobyteus/agent/agent.py +1 -1
- autobyteus/agent/bootstrap_steps/__init__.py +2 -0
- autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +2 -0
- autobyteus/agent/bootstrap_steps/mcp_server_prewarming_step.py +71 -0
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +4 -2
- autobyteus/agent/context/agent_config.py +36 -5
- autobyteus/agent/events/worker_event_dispatcher.py +1 -2
- autobyteus/agent/handlers/inter_agent_message_event_handler.py +1 -1
- autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +2 -2
- autobyteus/agent/handlers/tool_result_event_handler.py +48 -20
- autobyteus/agent/handlers/user_input_message_event_handler.py +1 -1
- autobyteus/agent/input_processor/__init__.py +1 -7
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +41 -12
- autobyteus/agent/message/context_file_type.py +6 -0
- autobyteus/agent/message/send_message_to.py +68 -99
- autobyteus/agent/phases/discover.py +2 -1
- autobyteus/agent/runtime/agent_worker.py +25 -34
- autobyteus/agent/shutdown_steps/__init__.py +17 -0
- autobyteus/agent/shutdown_steps/agent_shutdown_orchestrator.py +63 -0
- autobyteus/agent/shutdown_steps/base_shutdown_step.py +33 -0
- autobyteus/agent/shutdown_steps/llm_instance_cleanup_step.py +45 -0
- autobyteus/agent/shutdown_steps/mcp_server_cleanup_step.py +32 -0
- autobyteus/agent/tool_execution_result_processor/__init__.py +9 -0
- autobyteus/agent/tool_execution_result_processor/base_processor.py +46 -0
- autobyteus/agent/tool_execution_result_processor/processor_definition.py +36 -0
- autobyteus/agent/tool_execution_result_processor/processor_meta.py +36 -0
- autobyteus/agent/tool_execution_result_processor/processor_registry.py +70 -0
- autobyteus/agent/workspace/base_workspace.py +17 -2
- autobyteus/cli/__init__.py +1 -1
- autobyteus/cli/cli_display.py +1 -1
- autobyteus/cli/workflow_tui/__init__.py +4 -0
- autobyteus/cli/workflow_tui/app.py +210 -0
- autobyteus/cli/workflow_tui/state.py +189 -0
- autobyteus/cli/workflow_tui/widgets/__init__.py +6 -0
- autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +149 -0
- autobyteus/cli/workflow_tui/widgets/focus_pane.py +335 -0
- autobyteus/cli/workflow_tui/widgets/logo.py +27 -0
- autobyteus/cli/workflow_tui/widgets/renderables.py +70 -0
- autobyteus/cli/workflow_tui/widgets/shared.py +51 -0
- autobyteus/cli/workflow_tui/widgets/status_bar.py +14 -0
- autobyteus/events/event_types.py +3 -0
- autobyteus/llm/api/lmstudio_llm.py +37 -0
- autobyteus/llm/api/openai_compatible_llm.py +20 -3
- autobyteus/llm/llm_factory.py +2 -0
- autobyteus/llm/lmstudio_provider.py +89 -0
- autobyteus/llm/providers.py +1 -0
- autobyteus/llm/token_counter/token_counter_factory.py +2 -0
- autobyteus/tools/__init__.py +2 -0
- autobyteus/tools/ask_user_input.py +2 -1
- autobyteus/tools/base_tool.py +2 -0
- autobyteus/tools/bash/bash_executor.py +2 -1
- autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +2 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +3 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -0
- autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -0
- autobyteus/tools/browser/standalone/google_search_ui.py +2 -0
- autobyteus/tools/browser/standalone/navigate_to.py +2 -0
- autobyteus/tools/browser/standalone/web_page_pdf_generator.py +3 -0
- autobyteus/tools/browser/standalone/webpage_image_downloader.py +3 -0
- autobyteus/tools/browser/standalone/webpage_reader.py +2 -0
- autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +3 -0
- autobyteus/tools/file/file_reader.py +36 -9
- autobyteus/tools/file/file_writer.py +37 -9
- autobyteus/tools/functional_tool.py +5 -4
- autobyteus/tools/image_downloader.py +2 -0
- autobyteus/tools/mcp/__init__.py +10 -7
- autobyteus/tools/mcp/call_handlers/__init__.py +0 -2
- autobyteus/tools/mcp/config_service.py +1 -6
- autobyteus/tools/mcp/factory.py +12 -26
- autobyteus/tools/mcp/server/__init__.py +16 -0
- autobyteus/tools/mcp/server/base_managed_mcp_server.py +139 -0
- autobyteus/tools/mcp/server/http_managed_mcp_server.py +29 -0
- autobyteus/tools/mcp/server/proxy.py +36 -0
- autobyteus/tools/mcp/server/stdio_managed_mcp_server.py +33 -0
- autobyteus/tools/mcp/server_instance_manager.py +93 -0
- autobyteus/tools/mcp/tool.py +28 -46
- autobyteus/tools/mcp/tool_registrar.py +179 -0
- autobyteus/tools/mcp/types.py +10 -21
- autobyteus/tools/pdf_downloader.py +2 -1
- autobyteus/tools/registry/tool_definition.py +20 -7
- autobyteus/tools/registry/tool_registry.py +75 -28
- autobyteus/tools/timer.py +2 -0
- autobyteus/tools/tool_category.py +14 -4
- autobyteus/tools/tool_meta.py +6 -1
- autobyteus/tools/tool_origin.py +10 -0
- autobyteus/workflow/agentic_workflow.py +93 -0
- autobyteus/{agent/workflow → workflow}/base_agentic_workflow.py +19 -27
- autobyteus/workflow/bootstrap_steps/__init__.py +20 -0
- autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +34 -0
- autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +23 -0
- autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +41 -0
- autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +108 -0
- autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +50 -0
- autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +25 -0
- autobyteus/workflow/context/__init__.py +17 -0
- autobyteus/workflow/context/team_manager.py +147 -0
- autobyteus/workflow/context/workflow_config.py +30 -0
- autobyteus/workflow/context/workflow_context.py +61 -0
- autobyteus/workflow/context/workflow_node_config.py +76 -0
- autobyteus/workflow/context/workflow_runtime_state.py +53 -0
- autobyteus/workflow/events/__init__.py +29 -0
- autobyteus/workflow/events/workflow_event_dispatcher.py +39 -0
- autobyteus/workflow/events/workflow_events.py +53 -0
- autobyteus/workflow/events/workflow_input_event_queue_manager.py +21 -0
- autobyteus/workflow/exceptions.py +8 -0
- autobyteus/workflow/factory/__init__.py +9 -0
- autobyteus/workflow/factory/workflow_factory.py +99 -0
- autobyteus/workflow/handlers/__init__.py +19 -0
- autobyteus/workflow/handlers/base_workflow_event_handler.py +16 -0
- autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py +61 -0
- autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +27 -0
- autobyteus/workflow/handlers/process_user_message_event_handler.py +46 -0
- autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +39 -0
- autobyteus/workflow/handlers/workflow_event_handler_registry.py +23 -0
- autobyteus/workflow/phases/__init__.py +11 -0
- autobyteus/workflow/phases/workflow_operational_phase.py +19 -0
- autobyteus/workflow/phases/workflow_phase_manager.py +48 -0
- autobyteus/workflow/runtime/__init__.py +13 -0
- autobyteus/workflow/runtime/workflow_runtime.py +82 -0
- autobyteus/workflow/runtime/workflow_worker.py +117 -0
- autobyteus/workflow/shutdown_steps/__init__.py +17 -0
- autobyteus/workflow/shutdown_steps/agent_team_shutdown_step.py +42 -0
- autobyteus/workflow/shutdown_steps/base_workflow_shutdown_step.py +16 -0
- autobyteus/workflow/shutdown_steps/bridge_cleanup_step.py +28 -0
- autobyteus/workflow/shutdown_steps/sub_workflow_shutdown_step.py +41 -0
- autobyteus/workflow/shutdown_steps/workflow_shutdown_orchestrator.py +35 -0
- autobyteus/workflow/streaming/__init__.py +26 -0
- autobyteus/workflow/streaming/agent_event_bridge.py +48 -0
- autobyteus/workflow/streaming/agent_event_multiplexer.py +70 -0
- autobyteus/workflow/streaming/workflow_event_bridge.py +50 -0
- autobyteus/workflow/streaming/workflow_event_notifier.py +83 -0
- autobyteus/workflow/streaming/workflow_event_stream.py +33 -0
- autobyteus/workflow/streaming/workflow_stream_event_payloads.py +28 -0
- autobyteus/workflow/streaming/workflow_stream_events.py +45 -0
- autobyteus/workflow/utils/__init__.py +9 -0
- autobyteus/workflow/utils/wait_for_idle.py +46 -0
- autobyteus/workflow/workflow_builder.py +151 -0
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/METADATA +16 -13
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/RECORD +156 -75
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/top_level.txt +1 -0
- examples/__init__.py +1 -0
- examples/discover_phase_transitions.py +104 -0
- examples/run_browser_agent.py +260 -0
- examples/run_google_slides_agent.py +286 -0
- examples/run_mcp_browser_client.py +174 -0
- examples/run_mcp_google_slides_client.py +270 -0
- examples/run_mcp_list_tools.py +189 -0
- examples/run_poem_writer.py +274 -0
- examples/run_sqlite_agent.py +293 -0
- examples/workflow/__init__.py +1 -0
- examples/workflow/run_basic_research_workflow.py +189 -0
- examples/workflow/run_code_review_workflow.py +269 -0
- examples/workflow/run_debate_workflow.py +212 -0
- examples/workflow/run_workflow_with_tui.py +153 -0
- autobyteus/agent/context/agent_phase_manager.py +0 -264
- autobyteus/agent/context/phases.py +0 -49
- autobyteus/agent/group/__init__.py +0 -0
- autobyteus/agent/group/agent_group.py +0 -164
- autobyteus/agent/group/agent_group_context.py +0 -81
- autobyteus/agent/input_processor/content_prefixing_input_processor.py +0 -41
- autobyteus/agent/input_processor/metadata_appending_input_processor.py +0 -34
- autobyteus/agent/input_processor/passthrough_input_processor.py +0 -33
- autobyteus/agent/workflow/__init__.py +0 -11
- autobyteus/agent/workflow/agentic_workflow.py +0 -89
- autobyteus/tools/mcp/call_handlers/sse_handler.py +0 -22
- autobyteus/tools/mcp/registrar.py +0 -323
- autobyteus/workflow/simple_task.py +0 -98
- autobyteus/workflow/task.py +0 -147
- autobyteus/workflow/workflow.py +0 -49
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/licenses/LICENSE +0 -0
autobyteus/tools/mcp/__init__.py
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
"""
|
|
3
3
|
This package implements the Model Context Protocol (MCP) integration for AutoByteUs.
|
|
4
4
|
It allows AutoByteUs to connect to external MCP servers, discover tools,
|
|
5
|
-
and register them as standard AutoByteUs tools using a
|
|
5
|
+
and register them as standard AutoByteUs tools using a stateful, server-centric
|
|
6
|
+
architecture with per-agent isolation.
|
|
6
7
|
"""
|
|
7
8
|
import logging
|
|
8
9
|
|
|
9
10
|
logger = logging.getLogger(__name__)
|
|
10
11
|
|
|
11
12
|
# The actual 'mcp' library and its components are expected to be installed
|
|
12
|
-
# in the environment and are used by the internal
|
|
13
|
+
# in the environment and are used by the internal components.
|
|
13
14
|
|
|
14
15
|
logger.info("AutoByteUs MCP integration package initialized. Expects 'mcp' library to be available.")
|
|
15
16
|
|
|
@@ -17,9 +18,9 @@ logger.info("AutoByteUs MCP integration package initialized. Expects 'mcp' libra
|
|
|
17
18
|
from .types import (
|
|
18
19
|
BaseMcpConfig,
|
|
19
20
|
StdioMcpServerConfig,
|
|
20
|
-
SseMcpServerConfig,
|
|
21
21
|
StreamableHttpMcpServerConfig,
|
|
22
|
-
McpTransportType
|
|
22
|
+
McpTransportType,
|
|
23
|
+
McpServerInstanceKey,
|
|
23
24
|
)
|
|
24
25
|
# Import McpConfigService from config_service.py
|
|
25
26
|
from .config_service import McpConfigService
|
|
@@ -28,17 +29,19 @@ from .config_service import McpConfigService
|
|
|
28
29
|
from .schema_mapper import McpSchemaMapper
|
|
29
30
|
from .tool import GenericMcpTool
|
|
30
31
|
from .factory import McpToolFactory
|
|
31
|
-
from .
|
|
32
|
+
from .tool_registrar import McpToolRegistrar
|
|
33
|
+
from .server_instance_manager import McpServerInstanceManager
|
|
32
34
|
|
|
33
35
|
__all__ = [
|
|
34
36
|
# Types from types.py
|
|
35
37
|
"BaseMcpConfig",
|
|
36
38
|
"StdioMcpServerConfig",
|
|
37
|
-
"SseMcpServerConfig",
|
|
38
39
|
"StreamableHttpMcpServerConfig",
|
|
39
40
|
"McpTransportType",
|
|
40
|
-
|
|
41
|
+
"McpServerInstanceKey",
|
|
42
|
+
# Services and Managers
|
|
41
43
|
"McpConfigService",
|
|
44
|
+
"McpServerInstanceManager",
|
|
42
45
|
# Other public components
|
|
43
46
|
"McpSchemaMapper",
|
|
44
47
|
"GenericMcpTool",
|
|
@@ -8,11 +8,9 @@ for a specific transport protocol (e.g., STDIO, Streamable HTTP).
|
|
|
8
8
|
from .base_handler import McpCallHandler
|
|
9
9
|
from .stdio_handler import StdioMcpCallHandler
|
|
10
10
|
from .streamable_http_handler import StreamableHttpMcpCallHandler
|
|
11
|
-
from .sse_handler import SseMcpCallHandler
|
|
12
11
|
|
|
13
12
|
__all__ = [
|
|
14
13
|
"McpCallHandler",
|
|
15
14
|
"StdioMcpCallHandler",
|
|
16
15
|
"StreamableHttpMcpCallHandler",
|
|
17
|
-
"SseMcpCallHandler",
|
|
18
16
|
]
|
|
@@ -8,7 +8,6 @@ from typing import List, Dict, Any, Optional, Union, Type
|
|
|
8
8
|
from .types import (
|
|
9
9
|
BaseMcpConfig,
|
|
10
10
|
StdioMcpServerConfig,
|
|
11
|
-
SseMcpServerConfig,
|
|
12
11
|
StreamableHttpMcpServerConfig,
|
|
13
12
|
McpTransportType
|
|
14
13
|
)
|
|
@@ -38,7 +37,7 @@ class McpConfigService(metaclass=SingletonMeta):
|
|
|
38
37
|
@staticmethod
|
|
39
38
|
def _create_specific_config(server_id: str, transport_type: McpTransportType, config_data: Dict[str, Any]) -> BaseMcpConfig:
|
|
40
39
|
"""
|
|
41
|
-
Creates a specific McpServerConfig (Stdio,
|
|
40
|
+
Creates a specific McpServerConfig (Stdio, StreamableHttp) based on transport_type.
|
|
42
41
|
The 'server_id' is injected.
|
|
43
42
|
Parameters from nested structures like 'stdio_params' are un-nested.
|
|
44
43
|
"""
|
|
@@ -50,7 +49,6 @@ class McpConfigService(metaclass=SingletonMeta):
|
|
|
50
49
|
|
|
51
50
|
transport_specific_params_key_map = {
|
|
52
51
|
McpTransportType.STDIO: "stdio_params",
|
|
53
|
-
McpTransportType.SSE: "sse_params",
|
|
54
52
|
McpTransportType.STREAMABLE_HTTP: "streamable_http_params"
|
|
55
53
|
}
|
|
56
54
|
|
|
@@ -62,7 +60,6 @@ class McpConfigService(metaclass=SingletonMeta):
|
|
|
62
60
|
constructor_params.update(specific_params_dict)
|
|
63
61
|
|
|
64
62
|
constructor_params.pop(transport_specific_params_key_map.get(McpTransportType.STDIO), None)
|
|
65
|
-
constructor_params.pop(transport_specific_params_key_map.get(McpTransportType.SSE), None)
|
|
66
63
|
constructor_params.pop(transport_specific_params_key_map.get(McpTransportType.STREAMABLE_HTTP), None)
|
|
67
64
|
constructor_params.pop('transport_type', None)
|
|
68
65
|
|
|
@@ -75,8 +72,6 @@ class McpConfigService(metaclass=SingletonMeta):
|
|
|
75
72
|
try:
|
|
76
73
|
if transport_type == McpTransportType.STDIO:
|
|
77
74
|
return StdioMcpServerConfig(**constructor_params)
|
|
78
|
-
elif transport_type == McpTransportType.SSE:
|
|
79
|
-
return SseMcpServerConfig(**constructor_params)
|
|
80
75
|
elif transport_type == McpTransportType.STREAMABLE_HTTP:
|
|
81
76
|
return StreamableHttpMcpServerConfig(**constructor_params)
|
|
82
77
|
else:
|
autobyteus/tools/mcp/factory.py
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
import logging
|
|
3
3
|
from typing import Optional, TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from .tool import GenericMcpTool
|
|
6
6
|
from autobyteus.tools.factory.tool_factory import ToolFactory
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
|
-
from autobyteus.tools.mcp.call_handlers.base_handler import McpCallHandler
|
|
10
|
-
from autobyteus.tools.mcp.types import BaseMcpConfig
|
|
11
9
|
from autobyteus.tools.parameter_schema import ParameterSchema
|
|
12
10
|
from autobyteus.tools.tool_config import ToolConfig
|
|
13
11
|
from autobyteus.tools.base_tool import BaseTool
|
|
@@ -18,52 +16,40 @@ class McpToolFactory(ToolFactory):
|
|
|
18
16
|
"""
|
|
19
17
|
A dedicated factory for creating configured instances of GenericMcpTool.
|
|
20
18
|
|
|
21
|
-
This factory captures the
|
|
22
|
-
|
|
23
|
-
instantiate a GenericMcpTool when requested by the ToolRegistry.
|
|
19
|
+
This factory captures the key identifiers of a remote tool (server_id,
|
|
20
|
+
remote_tool_name) and its schema information at the time of discovery.
|
|
24
21
|
"""
|
|
25
22
|
def __init__(self,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
mcp_call_handler: 'McpCallHandler',
|
|
23
|
+
server_id: str,
|
|
24
|
+
remote_tool_name: str,
|
|
29
25
|
registered_tool_name: str,
|
|
30
26
|
tool_description: str,
|
|
31
27
|
tool_argument_schema: 'ParameterSchema'):
|
|
32
28
|
"""
|
|
33
|
-
Initializes the factory with the
|
|
29
|
+
Initializes the factory with the identifiers and schema of a specific remote tool.
|
|
34
30
|
"""
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
37
|
-
self._mcp_call_handler = mcp_call_handler
|
|
31
|
+
self._server_id = server_id
|
|
32
|
+
self._remote_tool_name = remote_tool_name
|
|
38
33
|
self._registered_tool_name = registered_tool_name
|
|
39
34
|
self._tool_description = tool_description
|
|
40
35
|
self._tool_argument_schema = tool_argument_schema
|
|
41
36
|
|
|
42
37
|
logger.debug(
|
|
43
|
-
f"McpToolFactory created for remote tool '{self.
|
|
44
|
-
f"on server '{self.
|
|
38
|
+
f"McpToolFactory created for remote tool '{self._remote_tool_name}' "
|
|
39
|
+
f"on server '{self._server_id}' (to be registered as '{self._registered_tool_name}')."
|
|
45
40
|
)
|
|
46
41
|
|
|
47
42
|
def create_tool(self, config: Optional['ToolConfig'] = None) -> 'BaseTool':
|
|
48
43
|
"""
|
|
49
44
|
Creates and returns a new instance of GenericMcpTool using the
|
|
50
45
|
configuration captured by this factory.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
config: An optional ToolConfig. This is part of the standard factory
|
|
54
|
-
interface but is not used by this specific factory, as all
|
|
55
|
-
configuration is provided during initialization.
|
|
56
|
-
|
|
57
|
-
Returns:
|
|
58
|
-
A configured instance of GenericMcpTool.
|
|
59
46
|
"""
|
|
60
47
|
if config:
|
|
61
48
|
logger.debug(f"McpToolFactory for '{self._registered_tool_name}' received a ToolConfig, which will be ignored.")
|
|
62
49
|
|
|
63
50
|
return GenericMcpTool(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
mcp_call_handler=self._mcp_call_handler,
|
|
51
|
+
server_id=self._server_id,
|
|
52
|
+
remote_tool_name=self._remote_tool_name,
|
|
67
53
|
name=self._registered_tool_name,
|
|
68
54
|
description=self._tool_description,
|
|
69
55
|
argument_schema=self._tool_argument_schema
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/tools/mcp/server/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
This package contains the core abstractions for managing connections to remote MCP servers.
|
|
4
|
+
"""
|
|
5
|
+
from .base_managed_mcp_server import BaseManagedMcpServer, ServerState
|
|
6
|
+
from .stdio_managed_mcp_server import StdioManagedMcpServer
|
|
7
|
+
from .http_managed_mcp_server import HttpManagedMcpServer
|
|
8
|
+
from .proxy import McpServerProxy
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"BaseManagedMcpServer",
|
|
12
|
+
"ServerState",
|
|
13
|
+
"StdioManagedMcpServer",
|
|
14
|
+
"HttpManagedMcpServer",
|
|
15
|
+
"McpServerProxy",
|
|
16
|
+
]
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/tools/mcp/server/base_managed_mcp_server.py
|
|
2
|
+
import logging
|
|
3
|
+
import asyncio
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Dict, Any, List, Optional
|
|
7
|
+
from contextlib import AsyncExitStack
|
|
8
|
+
|
|
9
|
+
from mcp import ClientSession, types as mcp_types
|
|
10
|
+
|
|
11
|
+
from ..types import BaseMcpConfig
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class ServerState(str, Enum):
|
|
16
|
+
"""Enumerates the possible connection states of a BaseManagedMcpServer."""
|
|
17
|
+
DISCONNECTED = "disconnected"
|
|
18
|
+
CONNECTING = "connecting"
|
|
19
|
+
CONNECTED = "connected"
|
|
20
|
+
FAILED = "failed"
|
|
21
|
+
CLOSED = "closed"
|
|
22
|
+
|
|
23
|
+
class BaseManagedMcpServer(ABC):
|
|
24
|
+
"""
|
|
25
|
+
Abstract base class representing a connection to a remote MCP server.
|
|
26
|
+
It manages the entire lifecycle and state of a single server connection.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# --- Attributes ---
|
|
30
|
+
_config: BaseMcpConfig
|
|
31
|
+
_state: ServerState
|
|
32
|
+
_connection_lock: asyncio.Lock
|
|
33
|
+
_client_session: Optional[ClientSession]
|
|
34
|
+
_exit_stack: AsyncExitStack
|
|
35
|
+
|
|
36
|
+
# --- Initialization ---
|
|
37
|
+
def __init__(self, config: BaseMcpConfig):
|
|
38
|
+
self._config = config
|
|
39
|
+
self._state = ServerState.DISCONNECTED
|
|
40
|
+
self._connection_lock = asyncio.Lock()
|
|
41
|
+
self._client_session = None
|
|
42
|
+
self._exit_stack = AsyncExitStack()
|
|
43
|
+
|
|
44
|
+
# --- Public Properties ---
|
|
45
|
+
@property
|
|
46
|
+
def server_id(self) -> str:
|
|
47
|
+
return self._config.server_id
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def config(self) -> BaseMcpConfig:
|
|
51
|
+
return self._config
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def state(self) -> ServerState:
|
|
55
|
+
return self._state
|
|
56
|
+
|
|
57
|
+
# --- Abstract Methods ---
|
|
58
|
+
@abstractmethod
|
|
59
|
+
async def _create_client_session(self) -> ClientSession:
|
|
60
|
+
"""
|
|
61
|
+
Transport-specific logic to establish a connection and return a ClientSession.
|
|
62
|
+
This method should leverage self._exit_stack to manage resources.
|
|
63
|
+
"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
# --- Public API Methods ---
|
|
67
|
+
async def connect(self) -> None:
|
|
68
|
+
"""Public method to establish a connection to the server. Idempotent."""
|
|
69
|
+
if self._state == ServerState.CONNECTED:
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
async with self._connection_lock:
|
|
73
|
+
# Re-check state after acquiring lock
|
|
74
|
+
if self._state == ServerState.CONNECTED:
|
|
75
|
+
return
|
|
76
|
+
if self._state == ServerState.CONNECTING:
|
|
77
|
+
logger.debug(f"Connection already in progress for '{self.server_id}'. Awaiting completion.")
|
|
78
|
+
# A more advanced implementation might use an asyncio.Event to wait here.
|
|
79
|
+
# For now, serializing via the lock is sufficient.
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
logger.info(f"Connecting to MCP server '{self.server_id}'...")
|
|
83
|
+
self._state = ServerState.CONNECTING
|
|
84
|
+
try:
|
|
85
|
+
# The exit stack must be fresh for each connection attempt.
|
|
86
|
+
self._exit_stack = AsyncExitStack()
|
|
87
|
+
self._client_session = await self._create_client_session()
|
|
88
|
+
self._state = ServerState.CONNECTED
|
|
89
|
+
logger.info(f"Successfully connected to MCP server '{self.server_id}'.")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
self._state = ServerState.FAILED
|
|
92
|
+
logger.error(f"Failed to connect to MCP server '{self.server_id}': {e}", exc_info=True)
|
|
93
|
+
# Clean up any partially established resources on failure
|
|
94
|
+
if self._exit_stack:
|
|
95
|
+
await self._exit_stack.aclose()
|
|
96
|
+
self._client_session = None
|
|
97
|
+
raise
|
|
98
|
+
|
|
99
|
+
async def close(self) -> None:
|
|
100
|
+
"""Public method to gracefully close the connection to the server."""
|
|
101
|
+
async with self._connection_lock:
|
|
102
|
+
if self._state in [ServerState.DISCONNECTED, ServerState.CLOSED]:
|
|
103
|
+
logger.debug(f"Server '{self.server_id}' is already closed or disconnected. No action taken.")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
logger.info(f"Closing connection to MCP server '{self.server_id}'...")
|
|
107
|
+
self._state = ServerState.CLOSED
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
if self._exit_stack:
|
|
111
|
+
await self._exit_stack.aclose()
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.error(f"Error during resource cleanup for server '{self.server_id}': {e}", exc_info=True)
|
|
114
|
+
|
|
115
|
+
self._client_session = None
|
|
116
|
+
logger.info(f"Connection to MCP server '{self.server_id}' closed.")
|
|
117
|
+
|
|
118
|
+
async def list_remote_tools(self) -> List[mcp_types.Tool]:
|
|
119
|
+
"""Connects if needed and fetches the list of raw tool objects."""
|
|
120
|
+
if self._state != ServerState.CONNECTED:
|
|
121
|
+
await self.connect()
|
|
122
|
+
|
|
123
|
+
if not self._client_session:
|
|
124
|
+
raise RuntimeError(f"Cannot list tools: client session not available for server '{self.server_id}'.")
|
|
125
|
+
|
|
126
|
+
logger.debug(f"Listing remote tools on server '{self.server_id}'.")
|
|
127
|
+
result = await self._client_session.list_tools()
|
|
128
|
+
return result.tools
|
|
129
|
+
|
|
130
|
+
async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
|
|
131
|
+
"""Connects if needed and executes a tool call on the remote server."""
|
|
132
|
+
if self._state != ServerState.CONNECTED:
|
|
133
|
+
await self.connect()
|
|
134
|
+
|
|
135
|
+
if not self._client_session:
|
|
136
|
+
raise RuntimeError(f"Cannot call tool: client session not available for server '{self.server_id}'.")
|
|
137
|
+
|
|
138
|
+
logger.debug(f"Calling remote tool '{tool_name}' on server '{self.server_id}'.")
|
|
139
|
+
return await self._client_session.call_tool(tool_name, arguments)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/tools/mcp/server/http_managed_mcp_server.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from mcp import ClientSession
|
|
6
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
7
|
+
|
|
8
|
+
from .base_managed_mcp_server import BaseManagedMcpServer
|
|
9
|
+
from ..types import StreamableHttpMcpServerConfig
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class HttpManagedMcpServer(BaseManagedMcpServer):
|
|
14
|
+
"""Manages the lifecycle of a streamable_http-based MCP server."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, config: StreamableHttpMcpServerConfig):
|
|
17
|
+
super().__init__(config)
|
|
18
|
+
|
|
19
|
+
async def _create_client_session(self) -> ClientSession:
|
|
20
|
+
"""Connects to a remote HTTP endpoint and establishes a client session."""
|
|
21
|
+
config = cast(StreamableHttpMcpServerConfig, self._config)
|
|
22
|
+
|
|
23
|
+
logger.debug(f"Establishing HTTP connection for server '{self.server_id}' to URL: {config.url}")
|
|
24
|
+
read_stream, write_stream = await self._exit_stack.enter_async_context(
|
|
25
|
+
streamablehttp_client(config.url, headers=config.headers)
|
|
26
|
+
)
|
|
27
|
+
session = await self._exit_stack.enter_async_context(ClientSession(read_stream, write_stream))
|
|
28
|
+
logger.debug(f"ClientSession established for HTTP server '{self.server_id}'.")
|
|
29
|
+
return session
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/tools/mcp/server/proxy.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
from ..server_instance_manager import McpServerInstanceManager
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class McpServerProxy:
|
|
10
|
+
"""
|
|
11
|
+
A proxy object that provides the interface of a BaseManagedMcpServer
|
|
12
|
+
but delegates the actual work to a real server instance retrieved from the
|
|
13
|
+
McpServerInstanceManager. This decouples the tool from the manager.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, agent_id: str, server_id: str):
|
|
16
|
+
if not agent_id or not server_id:
|
|
17
|
+
raise ValueError("McpServerProxy requires both agent_id and server_id.")
|
|
18
|
+
self._agent_id = agent_id
|
|
19
|
+
self._server_id = server_id
|
|
20
|
+
self._instance_manager = McpServerInstanceManager()
|
|
21
|
+
logger.debug(f"McpServerProxy created for agent '{agent_id}' and server '{server_id}'.")
|
|
22
|
+
|
|
23
|
+
async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
|
|
24
|
+
"""
|
|
25
|
+
Gets the real server instance from the manager and delegates the tool call.
|
|
26
|
+
"""
|
|
27
|
+
logger.debug(f"Proxy: Getting server instance for agent '{self._agent_id}', server '{self._server_id}'.")
|
|
28
|
+
# The manager handles the logic of creating or returning a cached instance.
|
|
29
|
+
real_server_instance = self._instance_manager.get_server_instance(
|
|
30
|
+
agent_id=self._agent_id,
|
|
31
|
+
server_id=self._server_id
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
logger.debug(f"Proxy: Delegating 'call_tool({tool_name})' to real server instance.")
|
|
35
|
+
# The real instance handles its own connection state.
|
|
36
|
+
return await real_server_instance.call_tool(tool_name, arguments)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/tools/mcp/server/stdio_managed_mcp_server.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from mcp import ClientSession, StdioServerParameters
|
|
6
|
+
from mcp.client.stdio import stdio_client
|
|
7
|
+
|
|
8
|
+
from .base_managed_mcp_server import BaseManagedMcpServer
|
|
9
|
+
from ..types import StdioMcpServerConfig
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class StdioManagedMcpServer(BaseManagedMcpServer):
|
|
14
|
+
"""Manages the lifecycle of a stdio-based MCP server."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, config: StdioMcpServerConfig):
|
|
17
|
+
super().__init__(config)
|
|
18
|
+
|
|
19
|
+
async def _create_client_session(self) -> ClientSession:
|
|
20
|
+
"""Starts a subprocess and establishes a client session over its stdio."""
|
|
21
|
+
config = cast(StdioMcpServerConfig, self._config)
|
|
22
|
+
stdio_params = StdioServerParameters(
|
|
23
|
+
command=config.command,
|
|
24
|
+
args=config.args,
|
|
25
|
+
env=config.env,
|
|
26
|
+
cwd=config.cwd
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger.debug(f"Establishing stdio connection for server '{self.server_id}' with command: {config.command}")
|
|
30
|
+
read_stream, write_stream = await self._exit_stack.enter_async_context(stdio_client(stdio_params))
|
|
31
|
+
session = await self._exit_stack.enter_async_context(ClientSession(read_stream, write_stream))
|
|
32
|
+
logger.debug(f"ClientSession established for stdio server '{self.server_id}'.")
|
|
33
|
+
return session
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/tools/mcp/server_instance_manager.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, List, AsyncIterator
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
|
|
6
|
+
from autobyteus.utils.singleton import SingletonMeta
|
|
7
|
+
from .config_service import McpConfigService
|
|
8
|
+
from .server import BaseManagedMcpServer, StdioManagedMcpServer, HttpManagedMcpServer
|
|
9
|
+
from .types import McpTransportType, McpServerInstanceKey, BaseMcpConfig
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class McpServerInstanceManager(metaclass=SingletonMeta):
|
|
14
|
+
"""
|
|
15
|
+
Manages the lifecycle of BaseManagedMcpServer instances, providing
|
|
16
|
+
isolated server connections on a per-agent, per-server_id basis.
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self._config_service = McpConfigService()
|
|
20
|
+
self._active_servers: Dict[McpServerInstanceKey, BaseManagedMcpServer] = {}
|
|
21
|
+
logger.info("McpServerInstanceManager initialized.")
|
|
22
|
+
|
|
23
|
+
def _create_server_instance(self, server_config: BaseMcpConfig) -> BaseManagedMcpServer:
|
|
24
|
+
"""Factory method to create a server instance from a config."""
|
|
25
|
+
if server_config.transport_type == McpTransportType.STDIO:
|
|
26
|
+
return StdioManagedMcpServer(server_config)
|
|
27
|
+
elif server_config.transport_type == McpTransportType.STREAMABLE_HTTP:
|
|
28
|
+
return HttpManagedMcpServer(server_config)
|
|
29
|
+
else:
|
|
30
|
+
raise NotImplementedError(f"No ManagedMcpServer implementation for transport type '{server_config.transport_type}'.")
|
|
31
|
+
|
|
32
|
+
def get_server_instance(self, agent_id: str, server_id: str) -> BaseManagedMcpServer:
|
|
33
|
+
"""
|
|
34
|
+
Retrieves or creates a dedicated, long-lived managed server instance
|
|
35
|
+
for a given agent and server ID.
|
|
36
|
+
"""
|
|
37
|
+
instance_key = McpServerInstanceKey(agent_id=agent_id, server_id=server_id)
|
|
38
|
+
|
|
39
|
+
if instance_key in self._active_servers:
|
|
40
|
+
return self._active_servers[instance_key]
|
|
41
|
+
|
|
42
|
+
logger.info(f"Creating new persistent server instance for {instance_key}.")
|
|
43
|
+
config = self._config_service.get_config(server_id)
|
|
44
|
+
if not config:
|
|
45
|
+
raise ValueError(f"No configuration found for server_id '{server_id}'.")
|
|
46
|
+
|
|
47
|
+
server_instance = self._create_server_instance(config)
|
|
48
|
+
self._active_servers[instance_key] = server_instance
|
|
49
|
+
return server_instance
|
|
50
|
+
|
|
51
|
+
@asynccontextmanager
|
|
52
|
+
async def managed_discovery_session(self, server_config: BaseMcpConfig) -> AsyncIterator[BaseManagedMcpServer]:
|
|
53
|
+
"""
|
|
54
|
+
Provides a temporary server instance for a one-shot operation like discovery.
|
|
55
|
+
Guarantees the instance is closed upon exiting the context.
|
|
56
|
+
This method uses the provided config object directly and does not look it up
|
|
57
|
+
in the config service, making it suitable for stateless previews.
|
|
58
|
+
"""
|
|
59
|
+
if not server_config:
|
|
60
|
+
raise ValueError("A valid BaseMcpConfig object must be provided to managed_discovery_session.")
|
|
61
|
+
|
|
62
|
+
logger.debug(f"Creating temporary discovery instance for server '{server_config.server_id}'.")
|
|
63
|
+
temp_server_instance = self._create_server_instance(server_config)
|
|
64
|
+
try:
|
|
65
|
+
yield temp_server_instance
|
|
66
|
+
finally:
|
|
67
|
+
logger.debug(f"Closing temporary discovery instance for server '{server_config.server_id}'.")
|
|
68
|
+
await temp_server_instance.close()
|
|
69
|
+
|
|
70
|
+
async def cleanup_mcp_server_instances_for_agent(self, agent_id: str):
|
|
71
|
+
"""
|
|
72
|
+
Closes all active MCP server instances and removes them from the cache for a specific agent.
|
|
73
|
+
"""
|
|
74
|
+
logger.info(f"Cleaning up all MCP server instances for agent '{agent_id}'.")
|
|
75
|
+
keys_to_remove: List[McpServerInstanceKey] = []
|
|
76
|
+
for instance_key, server_instance in self._active_servers.items():
|
|
77
|
+
if instance_key.agent_id == agent_id:
|
|
78
|
+
try:
|
|
79
|
+
await server_instance.close()
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.error(f"Error closing MCP server '{instance_key.server_id}' for agent '{instance_key.agent_id}': {e}", exc_info=True)
|
|
82
|
+
keys_to_remove.append(instance_key)
|
|
83
|
+
|
|
84
|
+
for key in keys_to_remove:
|
|
85
|
+
del self._active_servers[key]
|
|
86
|
+
logger.info(f"Finished cleaning up MCP server instances for agent '{agent_id}'.")
|
|
87
|
+
|
|
88
|
+
async def cleanup_all_mcp_server_instances(self):
|
|
89
|
+
"""Closes all active MCP server instances for all agents and clears the cache."""
|
|
90
|
+
logger.info("Cleaning up all active MCP server instances.")
|
|
91
|
+
agent_ids = {key.agent_id for key in self._active_servers.keys()}
|
|
92
|
+
for agent_id in agent_ids:
|
|
93
|
+
await self.cleanup_mcp_server_instances_for_agent(agent_id)
|
autobyteus/tools/mcp/tool.py
CHANGED
|
@@ -4,98 +4,80 @@ from typing import Any, Optional, TYPE_CHECKING
|
|
|
4
4
|
|
|
5
5
|
from autobyteus.tools.base_tool import BaseTool
|
|
6
6
|
from autobyteus.tools.parameter_schema import ParameterSchema
|
|
7
|
+
from .server.proxy import McpServerProxy
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
10
|
from autobyteus.agent.context import AgentContext
|
|
10
|
-
from .call_handlers.base_handler import McpCallHandler
|
|
11
|
-
from .types import BaseMcpConfig
|
|
12
11
|
|
|
13
12
|
logger = logging.getLogger(__name__)
|
|
14
13
|
|
|
15
14
|
class GenericMcpTool(BaseTool):
|
|
16
15
|
"""
|
|
17
16
|
A generic tool wrapper for executing tools on a remote MCP server.
|
|
18
|
-
This tool
|
|
19
|
-
|
|
17
|
+
This tool is a lightweight blueprint. At execution time, it uses a proxy
|
|
18
|
+
to interact with the server, completely hiding the connection management logic.
|
|
20
19
|
"""
|
|
21
20
|
|
|
22
21
|
def __init__(self,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
mcp_call_handler: 'McpCallHandler',
|
|
22
|
+
server_id: str,
|
|
23
|
+
remote_tool_name: str,
|
|
26
24
|
name: str,
|
|
27
25
|
description: str,
|
|
28
26
|
argument_schema: ParameterSchema):
|
|
29
27
|
"""
|
|
30
|
-
Initializes the GenericMcpTool instance.
|
|
28
|
+
Initializes the GenericMcpTool instance with identifiers.
|
|
31
29
|
"""
|
|
32
30
|
super().__init__()
|
|
33
31
|
|
|
34
|
-
self.
|
|
35
|
-
self.
|
|
36
|
-
self._mcp_call_handler = mcp_call_handler
|
|
32
|
+
self._server_id = server_id
|
|
33
|
+
self._remote_tool_name = remote_tool_name
|
|
37
34
|
|
|
35
|
+
# Instance-specific properties for schema generation
|
|
38
36
|
self._instance_name = name
|
|
39
37
|
self._instance_description = description
|
|
40
38
|
self._instance_argument_schema = argument_schema
|
|
41
39
|
|
|
42
|
-
# Override the base class's schema-related methods with instance-specific
|
|
43
|
-
# versions for correct validation and usage generation.
|
|
44
40
|
self.get_name = self.get_instance_name
|
|
45
41
|
self.get_description = self.get_instance_description
|
|
46
42
|
self.get_argument_schema = self.get_instance_argument_schema
|
|
47
43
|
|
|
48
|
-
logger.info(f"GenericMcpTool instance created for remote tool '{
|
|
44
|
+
logger.info(f"GenericMcpTool instance created for remote tool '{remote_tool_name}' on server '{self._server_id}'. "
|
|
49
45
|
f"Registered in AutoByteUs as '{self._instance_name}'.")
|
|
50
46
|
|
|
51
47
|
# --- Getters for instance-specific data ---
|
|
48
|
+
def get_instance_name(self) -> str: return self._instance_name
|
|
49
|
+
def get_instance_description(self) -> str: return self._instance_description
|
|
50
|
+
def get_instance_argument_schema(self) -> ParameterSchema: return self._instance_argument_schema
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
return self._instance_name
|
|
55
|
-
|
|
56
|
-
def get_instance_description(self) -> str:
|
|
57
|
-
return self._instance_description
|
|
58
|
-
|
|
59
|
-
def get_instance_argument_schema(self) -> ParameterSchema:
|
|
60
|
-
return self._instance_argument_schema
|
|
61
|
-
|
|
62
|
-
# --- Base class methods that are NOT overridden at instance level ---
|
|
63
|
-
|
|
52
|
+
# --- Base class methods (class-level, not instance-level) ---
|
|
64
53
|
@classmethod
|
|
65
|
-
def get_name(cls) -> str:
|
|
66
|
-
return "GenericMcpTool"
|
|
67
|
-
|
|
54
|
+
def get_name(cls) -> str: return "GenericMcpTool"
|
|
68
55
|
@classmethod
|
|
69
|
-
def get_description(cls) -> str:
|
|
70
|
-
return "A generic wrapper for executing tools on a remote MCP server. Specifics are instance-based."
|
|
71
|
-
|
|
56
|
+
def get_description(cls) -> str: return "A generic wrapper for executing remote MCP tools."
|
|
72
57
|
@classmethod
|
|
73
|
-
def get_argument_schema(cls) -> Optional[ParameterSchema]:
|
|
74
|
-
return None
|
|
58
|
+
def get_argument_schema(cls) -> Optional[ParameterSchema]: return None
|
|
75
59
|
|
|
76
60
|
async def _execute(self, context: 'AgentContext', **kwargs: Any) -> Any:
|
|
77
61
|
"""
|
|
78
|
-
|
|
62
|
+
Creates a proxy for the remote server and executes the tool call.
|
|
63
|
+
The proxy handles all interaction with the McpServerInstanceManager.
|
|
79
64
|
"""
|
|
65
|
+
agent_id = context.agent_id
|
|
80
66
|
tool_name_for_log = self.get_instance_name()
|
|
81
|
-
logger.info(f"GenericMcpTool '{tool_name_for_log}': Delegating call for remote tool '{self._mcp_remote_tool_name}' "
|
|
82
|
-
f"on server '{self._mcp_server_config.server_id}' to handler.")
|
|
83
67
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
raise RuntimeError("McpCallHandler not available in GenericMcpTool instance.")
|
|
87
|
-
|
|
68
|
+
logger.info(f"GenericMcpTool '{tool_name_for_log}': Creating proxy for agent '{agent_id}' and server '{self._server_id}'.")
|
|
69
|
+
|
|
88
70
|
try:
|
|
89
|
-
# The
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
71
|
+
# The proxy is created on-demand for each execution.
|
|
72
|
+
proxy = McpServerProxy(agent_id=agent_id, server_id=self._server_id)
|
|
73
|
+
|
|
74
|
+
return await proxy.call_tool(
|
|
75
|
+
tool_name=self._remote_tool_name,
|
|
93
76
|
arguments=kwargs
|
|
94
77
|
)
|
|
95
78
|
except Exception as e:
|
|
96
79
|
logger.error(
|
|
97
|
-
f"
|
|
80
|
+
f"Execution failed for tool '{tool_name_for_log}' on server '{self._server_id}' for agent '{agent_id}': {e}",
|
|
98
81
|
exc_info=True
|
|
99
82
|
)
|
|
100
|
-
# Re-raise to ensure the agent knows the tool call failed.
|
|
101
83
|
raise
|