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.
Files changed (171) hide show
  1. autobyteus/agent/agent.py +1 -1
  2. autobyteus/agent/bootstrap_steps/__init__.py +2 -0
  3. autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +2 -0
  4. autobyteus/agent/bootstrap_steps/mcp_server_prewarming_step.py +71 -0
  5. autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +4 -2
  6. autobyteus/agent/context/agent_config.py +36 -5
  7. autobyteus/agent/events/worker_event_dispatcher.py +1 -2
  8. autobyteus/agent/handlers/inter_agent_message_event_handler.py +1 -1
  9. autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +2 -2
  10. autobyteus/agent/handlers/tool_result_event_handler.py +48 -20
  11. autobyteus/agent/handlers/user_input_message_event_handler.py +1 -1
  12. autobyteus/agent/input_processor/__init__.py +1 -7
  13. autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +41 -12
  14. autobyteus/agent/message/context_file_type.py +6 -0
  15. autobyteus/agent/message/send_message_to.py +68 -99
  16. autobyteus/agent/phases/discover.py +2 -1
  17. autobyteus/agent/runtime/agent_worker.py +25 -34
  18. autobyteus/agent/shutdown_steps/__init__.py +17 -0
  19. autobyteus/agent/shutdown_steps/agent_shutdown_orchestrator.py +63 -0
  20. autobyteus/agent/shutdown_steps/base_shutdown_step.py +33 -0
  21. autobyteus/agent/shutdown_steps/llm_instance_cleanup_step.py +45 -0
  22. autobyteus/agent/shutdown_steps/mcp_server_cleanup_step.py +32 -0
  23. autobyteus/agent/tool_execution_result_processor/__init__.py +9 -0
  24. autobyteus/agent/tool_execution_result_processor/base_processor.py +46 -0
  25. autobyteus/agent/tool_execution_result_processor/processor_definition.py +36 -0
  26. autobyteus/agent/tool_execution_result_processor/processor_meta.py +36 -0
  27. autobyteus/agent/tool_execution_result_processor/processor_registry.py +70 -0
  28. autobyteus/agent/workspace/base_workspace.py +17 -2
  29. autobyteus/cli/__init__.py +1 -1
  30. autobyteus/cli/cli_display.py +1 -1
  31. autobyteus/cli/workflow_tui/__init__.py +4 -0
  32. autobyteus/cli/workflow_tui/app.py +210 -0
  33. autobyteus/cli/workflow_tui/state.py +189 -0
  34. autobyteus/cli/workflow_tui/widgets/__init__.py +6 -0
  35. autobyteus/cli/workflow_tui/widgets/agent_list_sidebar.py +149 -0
  36. autobyteus/cli/workflow_tui/widgets/focus_pane.py +335 -0
  37. autobyteus/cli/workflow_tui/widgets/logo.py +27 -0
  38. autobyteus/cli/workflow_tui/widgets/renderables.py +70 -0
  39. autobyteus/cli/workflow_tui/widgets/shared.py +51 -0
  40. autobyteus/cli/workflow_tui/widgets/status_bar.py +14 -0
  41. autobyteus/events/event_types.py +3 -0
  42. autobyteus/llm/api/lmstudio_llm.py +37 -0
  43. autobyteus/llm/api/openai_compatible_llm.py +20 -3
  44. autobyteus/llm/llm_factory.py +2 -0
  45. autobyteus/llm/lmstudio_provider.py +89 -0
  46. autobyteus/llm/providers.py +1 -0
  47. autobyteus/llm/token_counter/token_counter_factory.py +2 -0
  48. autobyteus/tools/__init__.py +2 -0
  49. autobyteus/tools/ask_user_input.py +2 -1
  50. autobyteus/tools/base_tool.py +2 -0
  51. autobyteus/tools/bash/bash_executor.py +2 -1
  52. autobyteus/tools/browser/session_aware/browser_session_aware_navigate_to.py +2 -0
  53. autobyteus/tools/browser/session_aware/browser_session_aware_web_element_trigger.py +3 -0
  54. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_reader.py +3 -0
  55. autobyteus/tools/browser/session_aware/browser_session_aware_webpage_screenshot_taker.py +3 -0
  56. autobyteus/tools/browser/standalone/google_search_ui.py +2 -0
  57. autobyteus/tools/browser/standalone/navigate_to.py +2 -0
  58. autobyteus/tools/browser/standalone/web_page_pdf_generator.py +3 -0
  59. autobyteus/tools/browser/standalone/webpage_image_downloader.py +3 -0
  60. autobyteus/tools/browser/standalone/webpage_reader.py +2 -0
  61. autobyteus/tools/browser/standalone/webpage_screenshot_taker.py +3 -0
  62. autobyteus/tools/file/file_reader.py +36 -9
  63. autobyteus/tools/file/file_writer.py +37 -9
  64. autobyteus/tools/functional_tool.py +5 -4
  65. autobyteus/tools/image_downloader.py +2 -0
  66. autobyteus/tools/mcp/__init__.py +10 -7
  67. autobyteus/tools/mcp/call_handlers/__init__.py +0 -2
  68. autobyteus/tools/mcp/config_service.py +1 -6
  69. autobyteus/tools/mcp/factory.py +12 -26
  70. autobyteus/tools/mcp/server/__init__.py +16 -0
  71. autobyteus/tools/mcp/server/base_managed_mcp_server.py +139 -0
  72. autobyteus/tools/mcp/server/http_managed_mcp_server.py +29 -0
  73. autobyteus/tools/mcp/server/proxy.py +36 -0
  74. autobyteus/tools/mcp/server/stdio_managed_mcp_server.py +33 -0
  75. autobyteus/tools/mcp/server_instance_manager.py +93 -0
  76. autobyteus/tools/mcp/tool.py +28 -46
  77. autobyteus/tools/mcp/tool_registrar.py +179 -0
  78. autobyteus/tools/mcp/types.py +10 -21
  79. autobyteus/tools/pdf_downloader.py +2 -1
  80. autobyteus/tools/registry/tool_definition.py +20 -7
  81. autobyteus/tools/registry/tool_registry.py +75 -28
  82. autobyteus/tools/timer.py +2 -0
  83. autobyteus/tools/tool_category.py +14 -4
  84. autobyteus/tools/tool_meta.py +6 -1
  85. autobyteus/tools/tool_origin.py +10 -0
  86. autobyteus/workflow/agentic_workflow.py +93 -0
  87. autobyteus/{agent/workflow → workflow}/base_agentic_workflow.py +19 -27
  88. autobyteus/workflow/bootstrap_steps/__init__.py +20 -0
  89. autobyteus/workflow/bootstrap_steps/agent_tool_injection_step.py +34 -0
  90. autobyteus/workflow/bootstrap_steps/base_workflow_bootstrap_step.py +23 -0
  91. autobyteus/workflow/bootstrap_steps/coordinator_initialization_step.py +41 -0
  92. autobyteus/workflow/bootstrap_steps/coordinator_prompt_preparation_step.py +108 -0
  93. autobyteus/workflow/bootstrap_steps/workflow_bootstrapper.py +50 -0
  94. autobyteus/workflow/bootstrap_steps/workflow_runtime_queue_initialization_step.py +25 -0
  95. autobyteus/workflow/context/__init__.py +17 -0
  96. autobyteus/workflow/context/team_manager.py +147 -0
  97. autobyteus/workflow/context/workflow_config.py +30 -0
  98. autobyteus/workflow/context/workflow_context.py +61 -0
  99. autobyteus/workflow/context/workflow_node_config.py +76 -0
  100. autobyteus/workflow/context/workflow_runtime_state.py +53 -0
  101. autobyteus/workflow/events/__init__.py +29 -0
  102. autobyteus/workflow/events/workflow_event_dispatcher.py +39 -0
  103. autobyteus/workflow/events/workflow_events.py +53 -0
  104. autobyteus/workflow/events/workflow_input_event_queue_manager.py +21 -0
  105. autobyteus/workflow/exceptions.py +8 -0
  106. autobyteus/workflow/factory/__init__.py +9 -0
  107. autobyteus/workflow/factory/workflow_factory.py +99 -0
  108. autobyteus/workflow/handlers/__init__.py +19 -0
  109. autobyteus/workflow/handlers/base_workflow_event_handler.py +16 -0
  110. autobyteus/workflow/handlers/inter_agent_message_request_event_handler.py +61 -0
  111. autobyteus/workflow/handlers/lifecycle_workflow_event_handler.py +27 -0
  112. autobyteus/workflow/handlers/process_user_message_event_handler.py +46 -0
  113. autobyteus/workflow/handlers/tool_approval_workflow_event_handler.py +39 -0
  114. autobyteus/workflow/handlers/workflow_event_handler_registry.py +23 -0
  115. autobyteus/workflow/phases/__init__.py +11 -0
  116. autobyteus/workflow/phases/workflow_operational_phase.py +19 -0
  117. autobyteus/workflow/phases/workflow_phase_manager.py +48 -0
  118. autobyteus/workflow/runtime/__init__.py +13 -0
  119. autobyteus/workflow/runtime/workflow_runtime.py +82 -0
  120. autobyteus/workflow/runtime/workflow_worker.py +117 -0
  121. autobyteus/workflow/shutdown_steps/__init__.py +17 -0
  122. autobyteus/workflow/shutdown_steps/agent_team_shutdown_step.py +42 -0
  123. autobyteus/workflow/shutdown_steps/base_workflow_shutdown_step.py +16 -0
  124. autobyteus/workflow/shutdown_steps/bridge_cleanup_step.py +28 -0
  125. autobyteus/workflow/shutdown_steps/sub_workflow_shutdown_step.py +41 -0
  126. autobyteus/workflow/shutdown_steps/workflow_shutdown_orchestrator.py +35 -0
  127. autobyteus/workflow/streaming/__init__.py +26 -0
  128. autobyteus/workflow/streaming/agent_event_bridge.py +48 -0
  129. autobyteus/workflow/streaming/agent_event_multiplexer.py +70 -0
  130. autobyteus/workflow/streaming/workflow_event_bridge.py +50 -0
  131. autobyteus/workflow/streaming/workflow_event_notifier.py +83 -0
  132. autobyteus/workflow/streaming/workflow_event_stream.py +33 -0
  133. autobyteus/workflow/streaming/workflow_stream_event_payloads.py +28 -0
  134. autobyteus/workflow/streaming/workflow_stream_events.py +45 -0
  135. autobyteus/workflow/utils/__init__.py +9 -0
  136. autobyteus/workflow/utils/wait_for_idle.py +46 -0
  137. autobyteus/workflow/workflow_builder.py +151 -0
  138. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/METADATA +16 -13
  139. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/RECORD +156 -75
  140. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/top_level.txt +1 -0
  141. examples/__init__.py +1 -0
  142. examples/discover_phase_transitions.py +104 -0
  143. examples/run_browser_agent.py +260 -0
  144. examples/run_google_slides_agent.py +286 -0
  145. examples/run_mcp_browser_client.py +174 -0
  146. examples/run_mcp_google_slides_client.py +270 -0
  147. examples/run_mcp_list_tools.py +189 -0
  148. examples/run_poem_writer.py +274 -0
  149. examples/run_sqlite_agent.py +293 -0
  150. examples/workflow/__init__.py +1 -0
  151. examples/workflow/run_basic_research_workflow.py +189 -0
  152. examples/workflow/run_code_review_workflow.py +269 -0
  153. examples/workflow/run_debate_workflow.py +212 -0
  154. examples/workflow/run_workflow_with_tui.py +153 -0
  155. autobyteus/agent/context/agent_phase_manager.py +0 -264
  156. autobyteus/agent/context/phases.py +0 -49
  157. autobyteus/agent/group/__init__.py +0 -0
  158. autobyteus/agent/group/agent_group.py +0 -164
  159. autobyteus/agent/group/agent_group_context.py +0 -81
  160. autobyteus/agent/input_processor/content_prefixing_input_processor.py +0 -41
  161. autobyteus/agent/input_processor/metadata_appending_input_processor.py +0 -34
  162. autobyteus/agent/input_processor/passthrough_input_processor.py +0 -33
  163. autobyteus/agent/workflow/__init__.py +0 -11
  164. autobyteus/agent/workflow/agentic_workflow.py +0 -89
  165. autobyteus/tools/mcp/call_handlers/sse_handler.py +0 -22
  166. autobyteus/tools/mcp/registrar.py +0 -323
  167. autobyteus/workflow/simple_task.py +0 -98
  168. autobyteus/workflow/task.py +0 -147
  169. autobyteus/workflow/workflow.py +0 -49
  170. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/WHEEL +0 -0
  171. {autobyteus-1.1.2.dist-info → autobyteus-1.1.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,174 @@
1
+ # file: autobyteus/examples/run_mcp_browser_client.py
2
+ """
3
+ This example script demonstrates how to create a standalone MCP client in Python
4
+ to connect to and interact with the Browser MCP server.
5
+
6
+ This script uses only the 'mcp' library and standard Python libraries,
7
+ intentionally avoiding the 'autobyteus' framework abstractions.
8
+ This approach is useful for understanding the low-level communication
9
+ with an MCP server.
10
+
11
+ The script will:
12
+ 1. Define the parameters to launch the Browser MCP server (`npx @browsermcp/mcp@latest`).
13
+ 2. Start the server process and establish an stdio transport.
14
+ 3. Initialize an MCP client session.
15
+ 4. List the available tools from the server.
16
+ 5. Call the 'open_page' tool to open a website.
17
+ 6. Call the 'get_page_content' tool to retrieve the page's text.
18
+ 7. Properly clean up the session and server process.
19
+ """
20
+
21
+ import asyncio
22
+ import logging
23
+ import sys
24
+ import json
25
+ from contextlib import AsyncExitStack
26
+ from mcp import ClientSession, StdioServerParameters
27
+ from mcp.client.stdio import stdio_client
28
+
29
+ # --- Logging Setup ---
30
+ logging.basicConfig(
31
+ level=logging.INFO,
32
+ format='%(asctime)s - %(levelname)s - %(name)s - %(message)s',
33
+ stream=sys.stdout,
34
+ )
35
+ logger = logging.getLogger("mcp_browser_client")
36
+
37
+
38
+ class MCPBrowserClient:
39
+ """
40
+ A client for interacting with a Browser MCP server over stdio.
41
+ """
42
+
43
+ def __init__(self):
44
+ self.session: ClientSession | None = None
45
+ self.exit_stack = AsyncExitStack()
46
+ self.page_id: str | None = None
47
+
48
+ async def connect(self):
49
+ """
50
+ Starts the browser MCP server and connects to it.
51
+ """
52
+ logger.info("Defining server parameters for Browser MCP...")
53
+ # These parameters specify how to run the MCP server.
54
+ # This is the same command used by the `run_browser_agent.py` example.
55
+ server_params = StdioServerParameters(
56
+ command="npx",
57
+ args=["@browsermcp/mcp@latest"],
58
+ env=None,
59
+ )
60
+
61
+ logger.info(f"Starting server with command: '{server_params.command} {' '.join(server_params.args)}'")
62
+
63
+ # `stdio_client` is a context manager that starts the process
64
+ # and provides reader/writer streams for communication.
65
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
66
+ read, write = stdio_transport
67
+
68
+ logger.info("Server process started. Establishing MCP session...")
69
+
70
+ # `ClientSession` is another context manager that handles the MCP protocol.
71
+ self.session = await self.exit_stack.enter_async_context(ClientSession(read, write))
72
+
73
+ # The initialize handshake must be performed after connection.
74
+ await self.session.initialize()
75
+ logger.info("MCP session initialized successfully.")
76
+
77
+ # Let's see what tools the server offers.
78
+ response = await self.session.list_tools()
79
+ tool_names = [tool.name for tool in response.tools]
80
+ logger.info(f"Connected to server. Available tools: {tool_names}")
81
+
82
+ async def call_tool(self, tool_name: str, **kwargs):
83
+ """
84
+ A wrapper to call a tool on the server and print the result.
85
+ """
86
+ if not self.session:
87
+ raise RuntimeError("Client not connected. Call connect() first.")
88
+
89
+ logger.info(f"Calling tool '{tool_name}' with arguments: {kwargs}")
90
+ try:
91
+ result = await self.session.call_tool(tool_name, kwargs)
92
+ logger.info(f"Tool '{tool_name}' executed successfully.")
93
+
94
+ # The result content is a list of blocks. For many tools, it's a single text block.
95
+ if result.content and hasattr(result.content[0], 'text'):
96
+ tool_output = result.content[0].text
97
+ logger.info(f"--> Result from '{tool_name}':\n{tool_output[:500]}...")
98
+ return tool_output
99
+ else:
100
+ logger.info(f"--> Result from '{tool_name}' has no text content: {result.content}")
101
+ return result.content
102
+ except Exception as e:
103
+ logger.error(f"An error occurred while calling tool '{tool_name}': {e}", exc_info=True)
104
+ raise
105
+
106
+ async def cleanup(self):
107
+ """
108
+ Closes the session and stops the server process.
109
+ The AsyncExitStack handles this automatically.
110
+ """
111
+ logger.info("Cleaning up resources and closing server connection...")
112
+ await self.exit_stack.aclose()
113
+ logger.info("Cleanup complete.")
114
+
115
+ async def run_demo_flow(self):
116
+ """
117
+ Executes a simple workflow: open a page, get its content, and close it.
118
+ """
119
+ try:
120
+ # 1. Open a page
121
+ open_page_result = await self.call_tool("open_page", url="https://www.google.com/search?q=mcp+protocol")
122
+
123
+ # The 'open_page' tool returns a JSON string with the pageId.
124
+ # We need to parse it to use in subsequent calls.
125
+ if not isinstance(open_page_result, str):
126
+ logger.error(f"Expected a string from 'open_page', but got {type(open_page_result)}")
127
+ return
128
+
129
+ try:
130
+ page_info = json.loads(open_page_result)
131
+ self.page_id = page_info.get("pageId")
132
+ if not self.page_id:
133
+ logger.error("Could not find 'pageId' in the result from 'open_page'.")
134
+ return
135
+ logger.info(f"Successfully opened page. Page ID: {self.page_id}")
136
+ except (json.JSONDecodeError, AttributeError) as e:
137
+ logger.error(f"Failed to parse pageId from 'open_page' result: {e}")
138
+ return
139
+
140
+ # 2. Get the page content
141
+ # Add a small delay for the page to potentially load dynamic content.
142
+ logger.info("Waiting for 2 seconds before getting content...")
143
+ await asyncio.sleep(2)
144
+
145
+ await self.call_tool("get_page_content", pageId=self.page_id)
146
+
147
+ # 3. Close the page
148
+ await self.call_tool("close_page", pageId=self.page_id)
149
+ logger.info(f"Page {self.page_id} closed.")
150
+
151
+ except Exception as e:
152
+ logger.error(f"An error occurred during the demo flow: {e}")
153
+
154
+
155
+ async def main():
156
+ """
157
+ Main function to run the MCP Browser Client.
158
+ """
159
+ logger.info("--- Starting Standalone MCP Browser Client Example ---")
160
+ client = MCPBrowserClient()
161
+ try:
162
+ await client.connect()
163
+ await client.run_demo_flow()
164
+ finally:
165
+ await client.cleanup()
166
+ logger.info("--- Standalone MCP Browser Client Example Finished ---")
167
+
168
+
169
+ if __name__ == "__main__":
170
+ try:
171
+ asyncio.run(main())
172
+ except (KeyboardInterrupt, SystemExit):
173
+ logger.info("Script interrupted by user. Exiting.")
174
+
@@ -0,0 +1,270 @@
1
+ import asyncio
2
+ import logging
3
+ import sys
4
+ import os
5
+ import json
6
+ import argparse
7
+ from pathlib import Path
8
+ from datetime import datetime
9
+ from typing import AsyncIterator, Optional, List
10
+
11
+ # --- Boilerplate to make the script runnable from the project root ---
12
+
13
+ # Ensure the autobyteus package is discoverable
14
+ SCRIPT_DIR = Path(__file__).resolve().parent
15
+ PACKAGE_ROOT = SCRIPT_DIR.parent
16
+ if str(PACKAGE_ROOT) not in sys.path:
17
+ sys.path.insert(0, str(PACKAGE_ROOT))
18
+
19
+ # Load environment variables from .env file in the project root
20
+ try:
21
+ from dotenv import load_dotenv
22
+ env_file_path = PACKAGE_ROOT / ".env"
23
+ if env_file_path.exists():
24
+ load_dotenv(env_file_path)
25
+ print(f"Loaded environment variables from: {env_file_path}")
26
+ else:
27
+ print(f"Info: No .env file found at: {env_file_path}. Relying on exported environment variables.")
28
+ except ImportError:
29
+ print("Warning: python-dotenv not installed. Cannot load .env file.")
30
+
31
+ # --- Imports for the MCP Client Example ---
32
+
33
+ try:
34
+ # High-level components for the full workflow
35
+ from autobyteus.tools.mcp import McpToolRegistrar
36
+ from autobyteus.tools.registry import ToolRegistry, default_tool_registry, ToolDefinition
37
+ from autobyteus.agent.context import AgentContext, AgentConfig, AgentRuntimeState
38
+ from autobyteus.llm.base_llm import BaseLLM
39
+ from autobyteus.llm.utils.response_types import CompleteResponse, ChunkResponse
40
+ except ImportError as e:
41
+ print(f"Error importing autobyteus components: {e}", file=sys.stderr)
42
+ print("Please ensure that the autobyteus library is installed and accessible in your PYTHONPATH.", file=sys.stderr)
43
+ sys.exit(1)
44
+
45
+ # --- Basic Logging Setup ---
46
+ # A logger for this script
47
+ logger = logging.getLogger("mcp_client_example")
48
+
49
+ # --- Dummy LLM for creating AgentContext ---
50
+ class DummyLLM(BaseLLM):
51
+ """A dummy LLM implementation required to instantiate AgentConfig."""
52
+ def __init__(self):
53
+ # We need to provide a model and config to the BaseLLM constructor.
54
+ # Let's use a dummy model configuration.
55
+ from autobyteus.llm.models import LLMModel
56
+ from autobyteus.llm.utils.llm_config import LLMConfig
57
+ from autobyteus.llm.llm_factory import default_llm_factory
58
+
59
+ # Ensure factory is initialized to access models
60
+ default_llm_factory.ensure_initialized()
61
+
62
+ # Pick any existing model for the dummy, e.g., the first one available.
63
+ try:
64
+ # Iterating through LLMModel is now possible due to metaclass
65
+ dummy_model_instance = next(iter(LLMModel))
66
+ except StopIteration:
67
+ # This is a fallback in case no models are registered, which is unlikely but safe.
68
+ raise RuntimeError("No LLMModels are registered in the factory. Cannot create DummyLLM.")
69
+
70
+ super().__init__(model=dummy_model_instance, llm_config=LLMConfig())
71
+
72
+ def configure_system_prompt(self, system_prompt: str):
73
+ # This is on BaseLLM. My no-op implementation is fine.
74
+ super().configure_system_prompt(system_prompt)
75
+
76
+ async def _send_user_message_to_llm(self, user_message: str, image_urls: Optional[List[str]] = None, **kwargs) -> CompleteResponse:
77
+ """Dummy implementation for sending a message."""
78
+ logger.debug("DummyLLM._send_user_message_to_llm called.")
79
+ return CompleteResponse(content="This is a dummy response from a dummy LLM.", usage=None)
80
+
81
+ async def _stream_user_message_to_llm(
82
+ self, user_message: str, image_urls: Optional[List[str]] = None, **kwargs
83
+ ) -> AsyncIterator[ChunkResponse]:
84
+ """Dummy implementation for streaming a message."""
85
+ logger.debug("DummyLLM._stream_user_message_to_llm called.")
86
+ yield ChunkResponse(content="This is a dummy response from a dummy LLM.", is_complete=True, usage=None)
87
+
88
+
89
+ def setup_logging(debug: bool = False):
90
+ """Configures logging for the script."""
91
+ log_level = logging.DEBUG if debug else logging.INFO
92
+ root_logger = logging.getLogger()
93
+ if root_logger.hasHandlers():
94
+ for handler in root_logger.handlers[:]:
95
+ root_logger.removeHandler(handler)
96
+ logging.basicConfig(
97
+ level=log_level,
98
+ format='%(asctime)s - %(levelname)s - %(name)s - %(message)s',
99
+ stream=sys.stdout,
100
+ )
101
+ if debug:
102
+ logging.getLogger("autobyteus").setLevel(logging.DEBUG)
103
+ logger.info("Debug logging enabled.")
104
+ else:
105
+ logging.getLogger("autobyteus").setLevel(logging.INFO)
106
+
107
+ # --- Environment Variable Checks ---
108
+ def check_required_env_vars():
109
+ """Checks for environment variables required by this example and returns them."""
110
+ required_vars = {
111
+ "script_path": "TEST_GOOGLE_SLIDES_MCP_SCRIPT_PATH",
112
+ "google_client_id": "GOOGLE_CLIENT_ID",
113
+ "google_client_secret": "GOOGLE_CLIENT_SECRET",
114
+ "google_refresh_token": "GOOGLE_REFRESH_TOKEN",
115
+ }
116
+ env_values = {}
117
+ missing_vars = []
118
+ for key, var_name in required_vars.items():
119
+ value = os.environ.get(var_name)
120
+ if not value:
121
+ missing_vars.append(var_name)
122
+ else:
123
+ env_values[key] = value
124
+ if missing_vars:
125
+ logger.error("This example requires the following environment variables to be set: %s", missing_vars)
126
+ sys.exit(1)
127
+ if not Path(env_values["script_path"]).exists():
128
+ logger.error(f"The script path specified by TEST_GOOGLE_SLIDES_MCP_SCRIPT_PATH does not exist: {env_values['script_path']}")
129
+ sys.exit(1)
130
+ return env_values
131
+
132
+ def print_tool_definitions(tool_definitions: List[ToolDefinition]):
133
+ """Iterates through a list of tool definitions and prints their JSON schema."""
134
+ print("\n--- Registered Tool Schemas (from ToolDefinition) ---")
135
+ for tool_definition in sorted(tool_definitions, key=lambda d: d.name):
136
+ try:
137
+ tool_json_schema = tool_definition.get_usage_json()
138
+ print(f"\n# Tool: {tool_definition.name}")
139
+ print(json.dumps(tool_json_schema, indent=2))
140
+ except Exception as e:
141
+ print(f"\n# Tool: {tool_definition.name}")
142
+ print(f" Error getting schema from definition: {e}")
143
+ print("\n--------------------------------------------------------\n")
144
+
145
+
146
+ async def main():
147
+ """
148
+ Main function demonstrating the full end-to-end MCP integration workflow.
149
+ """
150
+ logger.info("--- Starting MCP Integration Workflow Example ---")
151
+
152
+ env_vars = check_required_env_vars()
153
+
154
+ # 1. Instantiate the core MCP and registry components.
155
+ tool_registry = default_tool_registry
156
+ registrar = McpToolRegistrar()
157
+
158
+ # 2. Define the configuration for the MCP server as a dictionary.
159
+ server_id = "google-slides-mcp"
160
+ google_slides_mcp_config_dict = {
161
+ server_id: {
162
+ "transport_type": "stdio",
163
+ "stdio_params": {
164
+ "command": "node",
165
+ "args": [env_vars["script_path"]],
166
+ "env": {
167
+ "GOOGLE_CLIENT_ID": env_vars["google_client_id"],
168
+ "GOOGLE_CLIENT_SECRET": env_vars["google_client_secret"],
169
+ "GOOGLE_REFRESH_TOKEN": env_vars["google_refresh_token"],
170
+ }
171
+ },
172
+ "enabled": True,
173
+ "tool_name_prefix": "gslides",
174
+ }
175
+ }
176
+
177
+ try:
178
+ # 3. Discover and register tools by passing the config dictionary directly.
179
+ logger.info(f"Performing targeted discovery for remote tools from server '{server_id}'...")
180
+ await registrar.discover_and_register_tools(mcp_config=google_slides_mcp_config_dict)
181
+ # Use the ToolRegistry to get tools by their source server ID.
182
+ registered_tool_defs = tool_registry.get_tools_by_mcp_server(server_id)
183
+ logger.info(f"Tool registration complete. Discovered tools: {[t.name for t in registered_tool_defs]}")
184
+
185
+ # 4. Create an instance of a specific tool using the ToolRegistry.
186
+ create_tool_name = "gslides_create_presentation"
187
+ summarize_tool_name = "gslides_summarize_presentation"
188
+
189
+ if create_tool_name not in tool_registry.list_tool_names():
190
+ logger.error(f"Tool '{create_tool_name}' was not found in the registry. Aborting.")
191
+ return
192
+
193
+ logger.info(f"Creating an instance of the '{create_tool_name}' tool from the registry...")
194
+ create_presentation_tool = tool_registry.create_tool(create_tool_name)
195
+
196
+ logger.info(f"Creating an instance of the '{summarize_tool_name}' tool from the registry...")
197
+ summarize_presentation_tool = tool_registry.create_tool(summarize_tool_name)
198
+
199
+ # 5. Execute the tool using its standard .execute() method.
200
+ presentation_title = f"AutoByteUs E2E Demo - {datetime.now().isoformat()}"
201
+ logger.info(f"Executing '{create_tool_name}' with title: '{presentation_title}'")
202
+
203
+ dummy_llm = DummyLLM()
204
+ dummy_config = AgentConfig(
205
+ name="mcp_example_runner_agent",
206
+ role="tool_runner",
207
+ description="A dummy agent config for running tools outside of a full agent.",
208
+ llm_instance=dummy_llm,
209
+ system_prompt="N/A",
210
+ tools=[]
211
+ )
212
+ dummy_state = AgentRuntimeState(agent_id="mcp_example_runner")
213
+ dummy_context = AgentContext(agent_id="mcp_example_runner", config=dummy_config, state=dummy_state)
214
+
215
+ create_result = await create_presentation_tool.execute(
216
+ context=dummy_context,
217
+ title=presentation_title
218
+ )
219
+
220
+ if not isinstance(create_result, str):
221
+ raise ValueError(f"Unexpected result type from tool '{create_tool_name}'. Expected a JSON string. Got: {type(create_result)}")
222
+
223
+ presentation_object = json.loads(create_result)
224
+ actual_presentation_id = presentation_object.get("presentationId")
225
+
226
+ if not actual_presentation_id:
227
+ raise ValueError(f"Could not find 'presentationId' in the response. Response: {create_result[:200]}...")
228
+
229
+ logger.info(f"Tool '{create_tool_name}' executed. Extracted Presentation ID: {actual_presentation_id}")
230
+
231
+ # 6. Execute the second tool.
232
+ logger.info(f"Executing '{summarize_tool_name}' for presentation ID: {actual_presentation_id}")
233
+ summary_result = await summarize_presentation_tool.execute(
234
+ context=dummy_context,
235
+ presentationId=actual_presentation_id
236
+ )
237
+
238
+ if not isinstance(summary_result, str):
239
+ raise ValueError(f"Unexpected result type from tool '{summarize_tool_name}'. Got: {type(summary_result)}")
240
+
241
+ logger.info(f"Tool '{summarize_tool_name}' executed successfully.")
242
+ print("\n--- Presentation Summary ---")
243
+ print(summary_result)
244
+ print("--------------------------\n")
245
+
246
+ # 7. Print all tool schemas for verification
247
+ print_tool_definitions(registered_tool_defs)
248
+
249
+ except Exception as e:
250
+ logger.error(f"An error occurred during the workflow: {e}", exc_info=True)
251
+
252
+ logger.info("--- MCP Integration Workflow Example Finished ---")
253
+
254
+
255
+ if __name__ == "__main__":
256
+ parser = argparse.ArgumentParser(description="Run the full MCP registration and execution workflow.")
257
+ parser.add_argument("--debug", action="store_true", help="Enable debug level logging on the console.")
258
+ args = parser.parse_args()
259
+
260
+ setup_logging(debug=args.debug)
261
+
262
+ try:
263
+ asyncio.run(main())
264
+ except (KeyboardInterrupt, SystemExit) as e:
265
+ if isinstance(e, SystemExit) and e.code == 0:
266
+ logger.info("Script exited normally.")
267
+ else:
268
+ logger.info(f"Script interrupted ({type(e).__name__}). Exiting.")
269
+ except Exception as e:
270
+ logger.error(f"An unhandled error occurred at the top level: {e}", exc_info=True)
@@ -0,0 +1,189 @@
1
+ # file: autobyteus/examples/run_mcp_list_tools.py
2
+ """
3
+ This example script demonstrates how to use the McpToolRegistrar to connect
4
+ to a remote MCP server and list the available tools without registering or
5
+ executing them.
6
+
7
+ This is a "dry-run" or "preview" operation, useful for inspecting the
8
+ capabilities of a remote MCP server.
9
+ """
10
+ import asyncio
11
+ import logging
12
+ import sys
13
+ import os
14
+ import json
15
+ import argparse
16
+ from pathlib import Path
17
+ from typing import List
18
+
19
+ # --- Boilerplate to make the script runnable from the project root ---
20
+
21
+ # Ensure the autobyteus package is discoverable
22
+ SCRIPT_DIR = Path(__file__).resolve().parent
23
+ PACKAGE_ROOT = SCRIPT_DIR.parent
24
+ if str(PACKAGE_ROOT) not in sys.path:
25
+ sys.path.insert(0, str(PACKAGE_ROOT))
26
+
27
+ # Load environment variables from .env file in the project root
28
+ try:
29
+ from dotenv import load_dotenv
30
+ env_file_path = PACKAGE_ROOT / ".env"
31
+ if env_file_path.exists():
32
+ load_dotenv(env_file_path)
33
+ print(f"Loaded environment variables from: {env_file_path}")
34
+ else:
35
+ print(f"Info: No .env file found at: {env_file_path}. Relying on exported environment variables.")
36
+ except ImportError:
37
+ print("Warning: python-dotenv not installed. Cannot load .env file.")
38
+
39
+ # --- Imports for the MCP Client Example ---
40
+
41
+ try:
42
+ from autobyteus.tools.mcp import McpToolRegistrar
43
+ from autobyteus.tools.registry import ToolDefinition
44
+ except ImportError as e:
45
+ print(f"Error importing autobyteus components: {e}", file=sys.stderr)
46
+ print("Please ensure that the autobyteus library is installed and accessible in your PYTHONPATH.", file=sys.stderr)
47
+ sys.exit(1)
48
+
49
+ # --- Basic Logging Setup ---
50
+ logger = logging.getLogger("mcp_list_tools_example")
51
+
52
+ def setup_logging(debug: bool = False):
53
+ """Configures logging for the script."""
54
+ log_level = logging.DEBUG if debug else logging.INFO
55
+ root_logger = logging.getLogger()
56
+ if root_logger.hasHandlers():
57
+ for handler in root_logger.handlers[:]:
58
+ root_logger.removeHandler(handler)
59
+ logging.basicConfig(
60
+ level=log_level,
61
+ format='%(asctime)s - %(levelname)s - %(name)s - %(message)s',
62
+ stream=sys.stdout,
63
+ )
64
+ if debug:
65
+ logging.getLogger("autobyteus").setLevel(logging.DEBUG)
66
+ logger.info("Debug logging enabled.")
67
+ else:
68
+ logging.getLogger("autobyteus").setLevel(logging.INFO)
69
+
70
+ # --- Environment Variable Checks ---
71
+ def check_required_env_vars():
72
+ """Checks for environment variables required by the SQLite MCP server."""
73
+ required_vars = {
74
+ "script_path": "TEST_SQLITE_MCP_SCRIPT_PATH",
75
+ "db_path": "TEST_SQLITE_DB_PATH",
76
+ }
77
+ env_values = {}
78
+ missing_vars = []
79
+ for key, var_name in required_vars.items():
80
+ value = os.environ.get(var_name)
81
+ if not value:
82
+ missing_vars.append(var_name)
83
+ else:
84
+ env_values[key] = value
85
+ if missing_vars:
86
+ logger.error("This example requires the following environment variables to be set: %s", missing_vars)
87
+ logger.error("Example usage in your .env file:")
88
+ logger.error('TEST_SQLITE_MCP_SCRIPT_PATH="/path/to/mcp-database-server/dist/src/index.js"')
89
+ logger.error('TEST_SQLITE_DB_PATH="/path/to/your/database.db"')
90
+ sys.exit(1)
91
+
92
+ script_path_obj = Path(env_values["script_path"])
93
+ if not script_path_obj.exists():
94
+ logger.error(f"The script path specified by TEST_SQLITE_MCP_SCRIPT_PATH does not exist: {script_path_obj}")
95
+ sys.exit(1)
96
+
97
+ db_path_obj = Path(env_values["db_path"])
98
+ if not db_path_obj.exists():
99
+ logger.error(f"The database path specified by TEST_SQLITE_DB_PATH does not exist: {db_path_obj}")
100
+ logger.error("Please ensure the database file is created before running this script.")
101
+ sys.exit(1)
102
+
103
+ return env_values
104
+
105
+ def print_tool_definitions(tool_definitions: List[ToolDefinition]):
106
+ """Iterates through a list of tool definitions and prints their JSON schema."""
107
+ print("\n--- Discovered Remote Tool Schemas (from ToolDefinition) ---")
108
+ for tool_definition in sorted(tool_definitions, key=lambda d: d.name):
109
+ try:
110
+ # get_usage_json() provides a provider-agnostic JSON schema representation
111
+ tool_json_schema = tool_definition.get_usage_json()
112
+ print(f"\n# Tool: {tool_definition.name}")
113
+ print(f" Description: {tool_definition.description}")
114
+ print("# Schema (JSON):")
115
+ # Pretty-print the JSON schema
116
+ print(json.dumps(tool_json_schema, indent=2))
117
+ except Exception as e:
118
+ print(f"\n# Tool: {tool_definition.name}")
119
+ print(f" Error getting schema from definition: {e}")
120
+ print("\n--------------------------------------------------------\n")
121
+
122
+
123
+ async def main():
124
+ """
125
+ Main function to connect to the SQLite MCP server and list its tools.
126
+ """
127
+ logger.info("--- Starting MCP Remote Tool Listing Example ---")
128
+
129
+ env_vars = check_required_env_vars()
130
+
131
+ # 1. Instantiate the McpToolRegistrar.
132
+ registrar = McpToolRegistrar()
133
+
134
+ # 2. Define the configuration for the SQLite MCP server.
135
+ server_id = "sqlite-mcp"
136
+ sqlite_mcp_config_dict = {
137
+ server_id: {
138
+ "transport_type": "stdio",
139
+ "stdio_params": {
140
+ "command": "node",
141
+ "args": [
142
+ env_vars["script_path"],
143
+ env_vars["db_path"],
144
+ ],
145
+ "env": {}, # No specific env vars needed for the SQLite server itself
146
+ },
147
+ "enabled": True,
148
+ "tool_name_prefix": "sqlite",
149
+ }
150
+ }
151
+
152
+ try:
153
+ # 3. Use the registrar's `list_remote_tools` method for a preview.
154
+ # This connects to the server, lists tools, and disconnects without
155
+ # adding them to the main tool registry.
156
+ logger.info(f"Connecting to remote server '{server_id}' to preview available tools...")
157
+
158
+ tool_definitions = await registrar.list_remote_tools(mcp_config=sqlite_mcp_config_dict)
159
+
160
+ # 4. Print the results.
161
+ if tool_definitions:
162
+ print_tool_definitions(tool_definitions)
163
+ logger.info(f"Successfully listed {len(tool_definitions)} tools from the remote server.")
164
+ else:
165
+ logger.warning("No tools were found on the remote server.")
166
+
167
+ except Exception as e:
168
+ logger.error(f"An error occurred while trying to list remote tools: {e}", exc_info=True)
169
+
170
+ logger.info("--- MCP Remote Tool Listing Example Finished ---")
171
+
172
+
173
+ if __name__ == "__main__":
174
+ parser = argparse.ArgumentParser(description="List available tools from the remote SQLite MCP server.")
175
+ parser.add_argument("--debug", action="store_true", help="Enable debug level logging on the console.")
176
+ args = parser.parse_args()
177
+
178
+ setup_logging(debug=args.debug)
179
+
180
+ try:
181
+ asyncio.run(main())
182
+ except (KeyboardInterrupt, SystemExit) as e:
183
+ # Gracefully handle user interruption or normal exit.
184
+ if isinstance(e, SystemExit) and e.code == 0:
185
+ logger.info("Script exited normally.")
186
+ else:
187
+ logger.warning(f"Script interrupted ({type(e).__name__}). Exiting.")
188
+ except Exception as e:
189
+ logger.error(f"An unhandled error occurred at the top level: {e}", exc_info=True)