hud-python 0.3.4__py3-none-any.whl → 0.4.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.

Potentially problematic release.


This version of hud-python might be problematic. Click here for more details.

Files changed (192) hide show
  1. hud/__init__.py +22 -89
  2. hud/agents/__init__.py +17 -0
  3. hud/agents/art.py +101 -0
  4. hud/agents/base.py +599 -0
  5. hud/{mcp → agents}/claude.py +373 -321
  6. hud/{mcp → agents}/langchain.py +250 -250
  7. hud/agents/misc/__init__.py +7 -0
  8. hud/{agent → agents}/misc/response_agent.py +80 -80
  9. hud/{mcp → agents}/openai.py +352 -334
  10. hud/agents/openai_chat_generic.py +154 -0
  11. hud/{mcp → agents}/tests/__init__.py +1 -1
  12. hud/agents/tests/test_base.py +742 -0
  13. hud/agents/tests/test_claude.py +324 -0
  14. hud/{mcp → agents}/tests/test_client.py +363 -324
  15. hud/{mcp → agents}/tests/test_openai.py +237 -238
  16. hud/cli/__init__.py +617 -0
  17. hud/cli/__main__.py +8 -0
  18. hud/cli/analyze.py +371 -0
  19. hud/cli/analyze_metadata.py +230 -0
  20. hud/cli/build.py +427 -0
  21. hud/cli/clone.py +185 -0
  22. hud/cli/cursor.py +92 -0
  23. hud/cli/debug.py +392 -0
  24. hud/cli/docker_utils.py +83 -0
  25. hud/cli/init.py +281 -0
  26. hud/cli/interactive.py +353 -0
  27. hud/cli/mcp_server.py +756 -0
  28. hud/cli/pull.py +336 -0
  29. hud/cli/push.py +379 -0
  30. hud/cli/remote_runner.py +311 -0
  31. hud/cli/runner.py +160 -0
  32. hud/cli/tests/__init__.py +3 -0
  33. hud/cli/tests/test_analyze.py +284 -0
  34. hud/cli/tests/test_cli_init.py +265 -0
  35. hud/cli/tests/test_cli_main.py +27 -0
  36. hud/cli/tests/test_clone.py +142 -0
  37. hud/cli/tests/test_cursor.py +253 -0
  38. hud/cli/tests/test_debug.py +453 -0
  39. hud/cli/tests/test_mcp_server.py +139 -0
  40. hud/cli/tests/test_utils.py +388 -0
  41. hud/cli/utils.py +263 -0
  42. hud/clients/README.md +143 -0
  43. hud/clients/__init__.py +16 -0
  44. hud/clients/base.py +354 -0
  45. hud/clients/fastmcp.py +202 -0
  46. hud/clients/mcp_use.py +278 -0
  47. hud/clients/tests/__init__.py +1 -0
  48. hud/clients/tests/test_client_integration.py +111 -0
  49. hud/clients/tests/test_fastmcp.py +342 -0
  50. hud/clients/tests/test_protocol.py +188 -0
  51. hud/clients/utils/__init__.py +1 -0
  52. hud/clients/utils/retry_transport.py +160 -0
  53. hud/datasets.py +322 -192
  54. hud/misc/__init__.py +1 -0
  55. hud/{agent → misc}/claude_plays_pokemon.py +292 -283
  56. hud/otel/__init__.py +35 -0
  57. hud/otel/collector.py +142 -0
  58. hud/otel/config.py +164 -0
  59. hud/otel/context.py +536 -0
  60. hud/otel/exporters.py +366 -0
  61. hud/otel/instrumentation.py +97 -0
  62. hud/otel/processors.py +118 -0
  63. hud/otel/tests/__init__.py +1 -0
  64. hud/otel/tests/test_processors.py +197 -0
  65. hud/server/__init__.py +5 -5
  66. hud/server/context.py +114 -0
  67. hud/server/helper/__init__.py +5 -0
  68. hud/server/low_level.py +132 -0
  69. hud/server/server.py +166 -0
  70. hud/server/tests/__init__.py +3 -0
  71. hud/settings.py +73 -79
  72. hud/shared/__init__.py +5 -0
  73. hud/{exceptions.py → shared/exceptions.py} +180 -180
  74. hud/{server → shared}/requests.py +264 -264
  75. hud/shared/tests/test_exceptions.py +157 -0
  76. hud/{server → shared}/tests/test_requests.py +275 -275
  77. hud/telemetry/__init__.py +25 -30
  78. hud/telemetry/instrument.py +379 -0
  79. hud/telemetry/job.py +309 -141
  80. hud/telemetry/replay.py +74 -0
  81. hud/telemetry/trace.py +83 -0
  82. hud/tools/__init__.py +33 -34
  83. hud/tools/base.py +365 -65
  84. hud/tools/bash.py +161 -137
  85. hud/tools/computer/__init__.py +15 -13
  86. hud/tools/computer/anthropic.py +437 -414
  87. hud/tools/computer/hud.py +376 -328
  88. hud/tools/computer/openai.py +295 -286
  89. hud/tools/computer/settings.py +82 -0
  90. hud/tools/edit.py +314 -290
  91. hud/tools/executors/__init__.py +30 -30
  92. hud/tools/executors/base.py +539 -532
  93. hud/tools/executors/pyautogui.py +621 -619
  94. hud/tools/executors/tests/__init__.py +1 -1
  95. hud/tools/executors/tests/test_base_executor.py +338 -338
  96. hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
  97. hud/tools/executors/xdo.py +511 -503
  98. hud/tools/{playwright_tool.py → playwright.py} +412 -379
  99. hud/tools/tests/__init__.py +3 -3
  100. hud/tools/tests/test_base.py +282 -0
  101. hud/tools/tests/test_bash.py +158 -152
  102. hud/tools/tests/test_bash_extended.py +197 -0
  103. hud/tools/tests/test_computer.py +425 -52
  104. hud/tools/tests/test_computer_actions.py +34 -34
  105. hud/tools/tests/test_edit.py +259 -240
  106. hud/tools/tests/test_init.py +27 -27
  107. hud/tools/tests/test_playwright_tool.py +183 -183
  108. hud/tools/tests/test_tools.py +145 -157
  109. hud/tools/tests/test_utils.py +156 -156
  110. hud/tools/types.py +72 -0
  111. hud/tools/utils.py +50 -50
  112. hud/types.py +136 -89
  113. hud/utils/__init__.py +10 -16
  114. hud/utils/async_utils.py +65 -0
  115. hud/utils/design.py +168 -0
  116. hud/utils/mcp.py +55 -0
  117. hud/utils/progress.py +149 -149
  118. hud/utils/telemetry.py +66 -66
  119. hud/utils/tests/test_async_utils.py +173 -0
  120. hud/utils/tests/test_init.py +17 -21
  121. hud/utils/tests/test_progress.py +261 -225
  122. hud/utils/tests/test_telemetry.py +82 -37
  123. hud/utils/tests/test_version.py +8 -8
  124. hud/version.py +7 -7
  125. hud_python-0.4.0.dist-info/METADATA +474 -0
  126. hud_python-0.4.0.dist-info/RECORD +132 -0
  127. hud_python-0.4.0.dist-info/entry_points.txt +3 -0
  128. {hud_python-0.3.4.dist-info → hud_python-0.4.0.dist-info}/licenses/LICENSE +21 -21
  129. hud/adapters/__init__.py +0 -8
  130. hud/adapters/claude/__init__.py +0 -5
  131. hud/adapters/claude/adapter.py +0 -180
  132. hud/adapters/claude/tests/__init__.py +0 -1
  133. hud/adapters/claude/tests/test_adapter.py +0 -519
  134. hud/adapters/common/__init__.py +0 -6
  135. hud/adapters/common/adapter.py +0 -178
  136. hud/adapters/common/tests/test_adapter.py +0 -289
  137. hud/adapters/common/types.py +0 -446
  138. hud/adapters/operator/__init__.py +0 -5
  139. hud/adapters/operator/adapter.py +0 -108
  140. hud/adapters/operator/tests/__init__.py +0 -1
  141. hud/adapters/operator/tests/test_adapter.py +0 -370
  142. hud/agent/__init__.py +0 -19
  143. hud/agent/base.py +0 -126
  144. hud/agent/claude.py +0 -271
  145. hud/agent/langchain.py +0 -215
  146. hud/agent/misc/__init__.py +0 -3
  147. hud/agent/operator.py +0 -268
  148. hud/agent/tests/__init__.py +0 -1
  149. hud/agent/tests/test_base.py +0 -202
  150. hud/env/__init__.py +0 -11
  151. hud/env/client.py +0 -35
  152. hud/env/docker_client.py +0 -349
  153. hud/env/environment.py +0 -446
  154. hud/env/local_docker_client.py +0 -358
  155. hud/env/remote_client.py +0 -212
  156. hud/env/remote_docker_client.py +0 -292
  157. hud/gym.py +0 -130
  158. hud/job.py +0 -773
  159. hud/mcp/__init__.py +0 -17
  160. hud/mcp/base.py +0 -631
  161. hud/mcp/client.py +0 -312
  162. hud/mcp/tests/test_base.py +0 -512
  163. hud/mcp/tests/test_claude.py +0 -294
  164. hud/task.py +0 -149
  165. hud/taskset.py +0 -237
  166. hud/telemetry/_trace.py +0 -347
  167. hud/telemetry/context.py +0 -230
  168. hud/telemetry/exporter.py +0 -575
  169. hud/telemetry/instrumentation/__init__.py +0 -3
  170. hud/telemetry/instrumentation/mcp.py +0 -259
  171. hud/telemetry/instrumentation/registry.py +0 -59
  172. hud/telemetry/mcp_models.py +0 -270
  173. hud/telemetry/tests/__init__.py +0 -1
  174. hud/telemetry/tests/test_context.py +0 -210
  175. hud/telemetry/tests/test_trace.py +0 -312
  176. hud/tools/helper/README.md +0 -56
  177. hud/tools/helper/__init__.py +0 -9
  178. hud/tools/helper/mcp_server.py +0 -78
  179. hud/tools/helper/server_initialization.py +0 -115
  180. hud/tools/helper/utils.py +0 -58
  181. hud/trajectory.py +0 -94
  182. hud/utils/agent.py +0 -37
  183. hud/utils/common.py +0 -256
  184. hud/utils/config.py +0 -120
  185. hud/utils/deprecation.py +0 -115
  186. hud/utils/misc.py +0 -53
  187. hud/utils/tests/test_common.py +0 -277
  188. hud/utils/tests/test_config.py +0 -129
  189. hud_python-0.3.4.dist-info/METADATA +0 -284
  190. hud_python-0.3.4.dist-info/RECORD +0 -120
  191. /hud/{adapters/common → shared}/tests/__init__.py +0 -0
  192. {hud_python-0.3.4.dist-info → hud_python-0.4.0.dist-info}/WHEEL +0 -0
hud/clients/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # HUD MCP Client Architecture
2
+
3
+ This directory contains the MCP client implementations for HUD SDK. The architecture is designed to be flexible and extensible, allowing different client implementations while maintaining a consistent interface for agents.
4
+
5
+ ## Architecture Overview
6
+
7
+ ```
8
+ hud/clients/
9
+ ├── base.py # Protocol definition and base class
10
+ ├── mcp_use.py # MCP-use based implementation (legacy)
11
+ ├── fastmcp.py # FastMCP based implementation (modern)
12
+ └── __init__.py # Exports and default client
13
+ ```
14
+
15
+ ## Protocol Definition
16
+
17
+ All clients must implement the `AgentMCPClient` protocol:
18
+
19
+ ```python
20
+ class AgentMCPClient(Protocol):
21
+ async def initialize(self) -> None:
22
+ """Initialize the client - connect and fetch telemetry."""
23
+
24
+ async def list_tools(self) -> list[types.Tool]:
25
+ """List all available tools."""
26
+
27
+ async def call_tool(name: str, arguments: dict | None = None) -> types.CallToolResult:
28
+ """Execute a tool by name."""
29
+ ```
30
+
31
+ ## Available Implementations
32
+
33
+ ### 1. MCPUseHUDClient
34
+ - Based on the `mcp_use` library
35
+ - Supports multiple concurrent server connections
36
+ - Battle-tested and stable
37
+ - Good for complex multi-server setups
38
+
39
+ ### 2. FastMCPHUDClient (Default)
40
+ - Based on the `fastmcp` library
41
+ - Modern, clean API with better error handling
42
+ - Supports various transports (HTTP, WebSocket, stdio, in-memory)
43
+ - Better type safety and structured data support
44
+
45
+ ## Usage Examples
46
+
47
+ ### Basic Usage
48
+
49
+ ```python
50
+ from hud.clients import MCPUseHUDClient, FastMCPHUDClient
51
+
52
+ # Configuration works for both clients
53
+ mcp_config = {
54
+ "server_name": {
55
+ "command": "python",
56
+ "args": ["server.py"]
57
+ }
58
+ }
59
+
60
+ # Option 1: MCP-use client
61
+ client = MCPUseHUDClient(mcp_config)
62
+
63
+ # Option 2: FastMCP client
64
+ client = FastMCPHUDClient(mcp_config)
65
+
66
+ # Both use the same API
67
+ async with client:
68
+ tools = await client.list_tools()
69
+ result = await client.call_tool("tool_name", {"arg": "value"})
70
+ ```
71
+
72
+ ### With Agents
73
+
74
+ ```python
75
+ from hud.agents import ClaudeAgent
76
+
77
+ # Either client works with agents
78
+ client = FastMCPHUDClient(mcp_config)
79
+
80
+ agent = ClaudeAgent(
81
+ mcp_client=client,
82
+ model="claude-3-7-sonnet-20250219"
83
+ )
84
+
85
+ # Agent works identically with either client
86
+ result = await agent.run("Your task here")
87
+ ```
88
+
89
+
90
+ ## Adding New Clients
91
+
92
+ To add a new client implementation:
93
+
94
+ 1. Inherit from `BaseHUDClient`
95
+ 2. Implement the required methods:
96
+ - `_connect()` - Establish connection
97
+ - `list_tools()` - List available tools
98
+ - `call_tool()` - Execute tools
99
+ - `_read_resource_internal()` - Read resources
100
+
101
+ 3. The base class handles:
102
+ - Initialization flow
103
+ - Telemetry fetching
104
+ - Verbose logging
105
+ - Common HUD features
106
+
107
+ ## Tool Output Validation
108
+
109
+ Both client implementations support tool output validation through the MCP protocol:
110
+
111
+ ```python
112
+ # Enable strict validation
113
+ client = MCPClient(mcp_config, strict_validation=True)
114
+
115
+ # With strict validation:
116
+ # - Tools must return structured content if they define an output schema
117
+ # - Content must match the schema or an error is raised
118
+ # - Helps catch tool implementation issues early
119
+
120
+ # Default behavior (lenient validation):
121
+ client = MCPClient(mcp_config, strict_validation=False) # Default
122
+
123
+ # With lenient validation:
124
+ # - Schema violations are logged as warnings
125
+ # - Execution continues even if output doesn't match schema
126
+ # - Better for development and debugging
127
+ ```
128
+
129
+ ### Example with Validation
130
+
131
+ ```python
132
+ from hud.clients import MCPClient
133
+
134
+ # Create client with strict validation
135
+ client = MCPClient(mcp_config, strict_validation=True)
136
+
137
+ try:
138
+ # If tool has output schema but returns invalid data,
139
+ # this will raise a RuntimeError
140
+ result = await client.call_tool("some_tool", {"arg": "value"})
141
+ except RuntimeError as e:
142
+ print(f"Validation error: {e}")
143
+ ```
@@ -0,0 +1,16 @@
1
+ """HUD MCP client implementations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .base import AgentMCPClient, BaseHUDClient
6
+ from .fastmcp import FastMCPHUDClient
7
+
8
+ # Default to FastMCP for new features
9
+ MCPClient = FastMCPHUDClient
10
+
11
+ __all__ = [
12
+ "AgentMCPClient",
13
+ "BaseHUDClient",
14
+ "FastMCPHUDClient",
15
+ "MCPClient",
16
+ ]
hud/clients/base.py ADDED
@@ -0,0 +1,354 @@
1
+ """Base protocol and implementation for HUD MCP clients."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from abc import abstractmethod
8
+ from typing import TYPE_CHECKING, Any, Protocol, overload, runtime_checkable
9
+
10
+ from mcp.types import Implementation
11
+
12
+ from hud.types import MCPToolCall, MCPToolResult
13
+ from hud.utils.mcp import setup_hud_telemetry
14
+ from hud.version import __version__ as hud_version
15
+
16
+ if TYPE_CHECKING:
17
+ import mcp.types as types
18
+
19
+ else:
20
+ pass
21
+
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ @runtime_checkable
27
+ class AgentMCPClient(Protocol):
28
+ """Minimal interface for MCP clients used by agents.
29
+
30
+ Any custom client must implement this interface.
31
+
32
+ Any custom agent can assume that this will be the interaction protocol.
33
+ """
34
+
35
+ _initialized: bool
36
+
37
+ @property
38
+ def mcp_config(self) -> dict[str, dict[str, Any]]:
39
+ """Get the MCP config."""
40
+ ...
41
+
42
+ @property
43
+ def is_connected(self) -> bool:
44
+ """Check if client is connected and initialized."""
45
+ ...
46
+
47
+ async def initialize(self, mcp_config: dict[str, dict[str, Any]] | None = None) -> None:
48
+ """Initialize the client."""
49
+ ...
50
+
51
+ async def list_tools(self) -> list[types.Tool]:
52
+ """List all available tools."""
53
+ ...
54
+
55
+ async def call_tool(self, tool_call: MCPToolCall) -> MCPToolResult:
56
+ """Execute a tool by name."""
57
+ ...
58
+
59
+ async def shutdown(self) -> None:
60
+ """Shutdown the client."""
61
+ ...
62
+
63
+
64
+ class BaseHUDClient(AgentMCPClient):
65
+ """Base class with common HUD functionality that adds:
66
+ - Connection management
67
+ - Tool discovery
68
+ - Telemetry fetching (hud environment-specific)
69
+ - Logging
70
+ - Strict tool output validation (optional)
71
+ - Environment analysis (optional)
72
+
73
+ Any custom client should inherit from this class, and implement:
74
+ - _connect: Connect to the MCP server
75
+ - list_tools: List all available tools
76
+ - list_resources: List all available resources
77
+ - call_tool: Execute a tool by name
78
+ - read_resource: Read a resource by URI
79
+ - _disconnect: Disconnect from the MCP server
80
+ - any other MCP client methods
81
+ """
82
+
83
+ client_info = Implementation(name="hud-mcp", title="hud MCP Client", version=hud_version)
84
+
85
+ def __init__(
86
+ self,
87
+ mcp_config: dict[str, dict[str, Any]] | None = None,
88
+ verbose: bool = False,
89
+ strict_validation: bool = False,
90
+ auto_trace: bool = True,
91
+ ) -> None:
92
+ """
93
+ Initialize base client.
94
+
95
+ Args:
96
+ mcp_config: MCP server configuration dict
97
+ verbose: Enable verbose logging
98
+ strict_validation: Enable strict tool output validation
99
+ """
100
+ self.verbose = verbose
101
+ self._mcp_config = mcp_config
102
+ self._strict_validation = strict_validation
103
+ self._auto_trace = auto_trace
104
+
105
+ self._initialized = False
106
+ self._telemetry_data = {} # Initialize telemetry data
107
+
108
+ if self.verbose:
109
+ self._setup_verbose_logging()
110
+
111
+ async def initialize(self, mcp_config: dict[str, dict[str, Any]] | None = None) -> None:
112
+ """Initialize connection and fetch tools."""
113
+ if self._initialized:
114
+ logger.warning(
115
+ "Client already connected, if you want to reconnect or change the configuration, "
116
+ "call shutdown() first. This is especially important if you are using an agent."
117
+ )
118
+ return
119
+
120
+ self._mcp_config = mcp_config or self._mcp_config
121
+ if self._mcp_config is None:
122
+ raise ValueError(
123
+ "An MCP server configuration is required"
124
+ "Either pass it to the constructor or call initialize with a configuration"
125
+ )
126
+
127
+ setup_hud_telemetry(self._mcp_config, auto_trace=self._auto_trace)
128
+
129
+ logger.debug("Initializing MCP client...")
130
+
131
+ # Subclasses implement connection
132
+ await self._connect(self._mcp_config)
133
+
134
+ # Common hud behavior - fetch telemetry
135
+ await self._fetch_telemetry()
136
+
137
+ self._initialized = True
138
+ logger.info("Client initialized")
139
+
140
+ async def shutdown(self) -> None:
141
+ """Disconnect from the MCP server."""
142
+ if self._initialized:
143
+ await self._disconnect()
144
+ self._initialized = False
145
+ logger.info("Client disconnected")
146
+ else:
147
+ logger.warning("Client is not running, cannot disconnect")
148
+
149
+ @overload
150
+ async def call_tool(self, tool_call: MCPToolCall, /) -> MCPToolResult: ...
151
+ @overload
152
+ async def call_tool(
153
+ self,
154
+ *,
155
+ name: str,
156
+ arguments: dict[str, Any] | None = None,
157
+ ) -> MCPToolResult: ...
158
+
159
+ async def call_tool(
160
+ self,
161
+ tool_call: MCPToolCall | None = None,
162
+ *,
163
+ name: str | None = None,
164
+ arguments: dict[str, Any] | None = None,
165
+ ) -> MCPToolResult:
166
+ if tool_call is not None:
167
+ return await self._call_tool(tool_call)
168
+ elif name is not None:
169
+ return await self._call_tool(MCPToolCall(name=name, arguments=arguments))
170
+ else:
171
+ raise TypeError(
172
+ "call_tool() requires either an MCPToolCall positional arg "
173
+ "or keyword 'name' (and optional 'arguments')."
174
+ )
175
+
176
+ @abstractmethod
177
+ async def _connect(self, mcp_config: dict[str, dict[str, Any]]) -> None:
178
+ """Subclasses implement their connection logic."""
179
+ raise NotImplementedError
180
+
181
+ @abstractmethod
182
+ async def list_tools(self) -> list[types.Tool]:
183
+ """List all available tools."""
184
+ raise NotImplementedError
185
+
186
+ @abstractmethod
187
+ async def list_resources(self) -> list[types.Resource]:
188
+ """List all available resources."""
189
+ raise NotImplementedError
190
+
191
+ @abstractmethod
192
+ async def _call_tool(self, tool_call: MCPToolCall) -> MCPToolResult:
193
+ """Execute a tool by name."""
194
+ raise NotImplementedError
195
+
196
+ @abstractmethod
197
+ async def read_resource(self, uri: str) -> types.ReadResourceResult | None:
198
+ """Read a resource by URI."""
199
+ raise NotImplementedError
200
+
201
+ @abstractmethod
202
+ async def _disconnect(self) -> None:
203
+ """Subclasses implement their disconnection logic."""
204
+ raise NotImplementedError
205
+
206
+ @property
207
+ def is_connected(self) -> bool:
208
+ """Check if client is connected and initialized."""
209
+ return self._initialized
210
+
211
+ @property
212
+ def mcp_config(self) -> dict[str, dict[str, Any]]:
213
+ """Get the MCP config."""
214
+ if self._mcp_config is None:
215
+ raise ValueError("Please initialize the client with a valid MCP config")
216
+ return self._mcp_config
217
+
218
+ async def __aenter__(self: Any) -> Any:
219
+ """Async context manager entry."""
220
+ await self.initialize()
221
+ return self
222
+
223
+ async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
224
+ """Async context manager exit."""
225
+ await self.shutdown()
226
+
227
+ def _setup_verbose_logging(self) -> None:
228
+ """Configure verbose logging for debugging."""
229
+ logging.getLogger("mcp").setLevel(logging.DEBUG)
230
+ logging.getLogger("fastmcp").setLevel(logging.DEBUG)
231
+
232
+ if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
233
+ handler = logging.StreamHandler()
234
+ handler.setFormatter(
235
+ logging.Formatter("[%(levelname)s] %(asctime)s - %(name)s - %(message)s")
236
+ )
237
+ logger.addHandler(handler)
238
+ logger.setLevel(logging.DEBUG)
239
+
240
+ async def _fetch_telemetry(self) -> None:
241
+ """Common telemetry fetching for all hud clients."""
242
+ try:
243
+ # Try to read telemetry resource directly
244
+ result = await self.read_resource("telemetry://live")
245
+ if result and result.contents:
246
+ # Parse telemetry data
247
+ telemetry_data = json.loads(result.contents[0].text) # type: ignore
248
+ self._telemetry_data = telemetry_data
249
+
250
+ logger.info("📡 Telemetry data fetched:")
251
+ if "live_url" in telemetry_data:
252
+ logger.info(" 🖥️ Live URL: %s", telemetry_data["live_url"])
253
+ if "cdp_url" in telemetry_data:
254
+ logger.info(" 🦾 CDP URL: %s", telemetry_data["cdp_url"])
255
+ if "status" in telemetry_data:
256
+ logger.info(" 📊 Status: %s", telemetry_data["status"])
257
+ if "services" in telemetry_data:
258
+ logger.debug(" 📋 Services:")
259
+ for service, status in telemetry_data["services"].items():
260
+ status_icon = "✅" if status == "running" else "❌"
261
+ logger.debug(" %s %s: %s", status_icon, service, status)
262
+
263
+ if self.verbose:
264
+ logger.debug("Full telemetry data:\n%s", json.dumps(telemetry_data, indent=2))
265
+ except Exception as e:
266
+ # Telemetry is optional
267
+ if self.verbose:
268
+ logger.debug("No telemetry available: %s", e)
269
+
270
+ async def analyze_environment(self) -> dict[str, Any]:
271
+ """Complete analysis of the MCP environment.
272
+
273
+ Returns:
274
+ Dictionary containing:
275
+ - tools: All tools with full schemas
276
+ - hub_tools: Hub structures with subtools
277
+ - telemetry: Telemetry resources and data
278
+ - resources: All available resources
279
+ - metadata: Environment metadata
280
+ """
281
+ if not self._initialized:
282
+ raise ValueError("Client must be initialized before analyzing the environment")
283
+
284
+ analysis: dict[str, Any] = {
285
+ "tools": [],
286
+ "hub_tools": {},
287
+ "telemetry": self._telemetry_data,
288
+ "resources": [],
289
+ "metadata": {
290
+ "servers": list(self._mcp_config.keys()), # type: ignore
291
+ "initialized": self._initialized,
292
+ },
293
+ }
294
+
295
+ # Get all tools with schemas
296
+ tools = await self.list_tools()
297
+ for tool in tools:
298
+ tool_info = {
299
+ "name": tool.name,
300
+ "description": tool.description,
301
+ "input_schema": tool.inputSchema,
302
+ }
303
+ analysis["tools"].append(tool_info)
304
+
305
+ # Check if this is a hub tool (like setup, evaluate)
306
+ if (
307
+ tool.description
308
+ and "internal" in tool.description.lower()
309
+ and "functions" in tool.description.lower()
310
+ ):
311
+ # This is likely a hub dispatcher tool
312
+ hub_functions = await self.get_hub_tools(tool.name)
313
+ if hub_functions:
314
+ analysis["hub_tools"][tool.name] = hub_functions
315
+
316
+ # Get all resources
317
+ try:
318
+ resources = await self.list_resources()
319
+ for resource in resources:
320
+ resource_info = {
321
+ "uri": str(resource.uri),
322
+ "name": resource.name,
323
+ "description": resource.description,
324
+ "mime_type": getattr(resource, "mimeType", None),
325
+ }
326
+ analysis["resources"].append(resource_info)
327
+ except Exception as e:
328
+ if self.verbose:
329
+ logger.debug("Could not list resources: %s", e)
330
+
331
+ return analysis
332
+
333
+ async def get_hub_tools(self, hub_name: str) -> list[str]:
334
+ """Get all subtools for a specific hub (setup/evaluate).
335
+
336
+ Args:
337
+ hub_name: Name of the hub (e.g., "setup", "evaluate")
338
+
339
+ Returns:
340
+ List of available function names for the hub
341
+ """
342
+ try:
343
+ # Read the hub's functions catalogue resource
344
+ result = await self.read_resource(f"file:///{hub_name}/functions")
345
+ if result and result.contents:
346
+ # Parse the JSON list of function names
347
+ import json
348
+
349
+ functions = json.loads(result.contents[0].text) # type: ignore
350
+ return functions
351
+ except Exception as e:
352
+ if self.verbose:
353
+ logger.debug("Could not read hub functions for '%s': %s", hub_name, e)
354
+ return []