hud-python 0.4.45__py3-none-any.whl → 0.5.1__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.
- hud/__init__.py +27 -7
- hud/agents/__init__.py +11 -5
- hud/agents/base.py +220 -500
- hud/agents/claude.py +200 -240
- hud/agents/gemini.py +275 -0
- hud/agents/gemini_cua.py +335 -0
- hud/agents/grounded_openai.py +98 -100
- hud/agents/misc/integration_test_agent.py +51 -20
- hud/agents/misc/response_agent.py +41 -36
- hud/agents/openai.py +291 -292
- hud/agents/{openai_chat_generic.py → openai_chat.py} +80 -34
- hud/agents/operator.py +211 -0
- hud/agents/tests/conftest.py +133 -0
- hud/agents/tests/test_base.py +300 -622
- hud/agents/tests/test_base_runtime.py +233 -0
- hud/agents/tests/test_claude.py +379 -210
- hud/agents/tests/test_client.py +9 -10
- hud/agents/tests/test_gemini.py +369 -0
- hud/agents/tests/test_grounded_openai_agent.py +65 -50
- hud/agents/tests/test_openai.py +376 -140
- hud/agents/tests/test_operator.py +362 -0
- hud/agents/tests/test_run_eval.py +179 -0
- hud/cli/__init__.py +461 -545
- hud/cli/analyze.py +43 -5
- hud/cli/build.py +664 -110
- hud/cli/debug.py +8 -5
- hud/cli/dev.py +882 -734
- hud/cli/eval.py +782 -668
- hud/cli/flows/dev.py +167 -0
- hud/cli/flows/init.py +191 -0
- hud/cli/flows/tasks.py +153 -56
- hud/cli/flows/templates.py +151 -0
- hud/cli/flows/tests/__init__.py +1 -0
- hud/cli/flows/tests/test_dev.py +126 -0
- hud/cli/init.py +60 -58
- hud/cli/push.py +29 -11
- hud/cli/rft.py +311 -0
- hud/cli/rft_status.py +145 -0
- hud/cli/tests/test_analyze.py +5 -5
- hud/cli/tests/test_analyze_metadata.py +3 -2
- hud/cli/tests/test_analyze_module.py +120 -0
- hud/cli/tests/test_build.py +108 -6
- hud/cli/tests/test_build_failure.py +41 -0
- hud/cli/tests/test_build_module.py +50 -0
- hud/cli/tests/test_cli_init.py +6 -1
- hud/cli/tests/test_cli_more_wrappers.py +30 -0
- hud/cli/tests/test_cli_root.py +140 -0
- hud/cli/tests/test_convert.py +361 -0
- hud/cli/tests/test_debug.py +12 -10
- hud/cli/tests/test_dev.py +197 -0
- hud/cli/tests/test_eval.py +251 -0
- hud/cli/tests/test_eval_bedrock.py +51 -0
- hud/cli/tests/test_init.py +124 -0
- hud/cli/tests/test_main_module.py +11 -5
- hud/cli/tests/test_mcp_server.py +12 -100
- hud/cli/tests/test_push_happy.py +74 -0
- hud/cli/tests/test_push_wrapper.py +23 -0
- hud/cli/tests/test_registry.py +1 -1
- hud/cli/tests/test_utils.py +1 -1
- hud/cli/{rl → utils}/celebrate.py +14 -12
- hud/cli/utils/config.py +18 -1
- hud/cli/utils/docker.py +130 -4
- hud/cli/utils/env_check.py +9 -9
- hud/cli/utils/git.py +136 -0
- hud/cli/utils/interactive.py +39 -5
- hud/cli/utils/metadata.py +69 -0
- hud/cli/utils/runner.py +1 -1
- hud/cli/utils/server.py +2 -2
- hud/cli/utils/source_hash.py +3 -3
- hud/cli/utils/tasks.py +4 -1
- hud/cli/utils/tests/__init__.py +0 -0
- hud/cli/utils/tests/test_config.py +58 -0
- hud/cli/utils/tests/test_docker.py +93 -0
- hud/cli/utils/tests/test_docker_hints.py +71 -0
- hud/cli/utils/tests/test_env_check.py +74 -0
- hud/cli/utils/tests/test_environment.py +42 -0
- hud/cli/utils/tests/test_git.py +142 -0
- hud/cli/utils/tests/test_interactive_module.py +60 -0
- hud/cli/utils/tests/test_local_runner.py +50 -0
- hud/cli/utils/tests/test_logging_utils.py +23 -0
- hud/cli/utils/tests/test_metadata.py +49 -0
- hud/cli/utils/tests/test_package_runner.py +35 -0
- hud/cli/utils/tests/test_registry_utils.py +49 -0
- hud/cli/utils/tests/test_remote_runner.py +25 -0
- hud/cli/utils/tests/test_runner_modules.py +52 -0
- hud/cli/utils/tests/test_source_hash.py +36 -0
- hud/cli/utils/tests/test_tasks.py +80 -0
- hud/cli/utils/version_check.py +258 -0
- hud/cli/{rl → utils}/viewer.py +2 -2
- hud/clients/README.md +12 -11
- hud/clients/__init__.py +4 -3
- hud/clients/base.py +166 -26
- hud/clients/environment.py +51 -0
- hud/clients/fastmcp.py +13 -6
- hud/clients/mcp_use.py +40 -15
- hud/clients/tests/test_analyze_scenarios.py +206 -0
- hud/clients/tests/test_protocol.py +9 -3
- hud/datasets/__init__.py +23 -20
- hud/datasets/loader.py +327 -0
- hud/datasets/runner.py +192 -105
- hud/datasets/tests/__init__.py +0 -0
- hud/datasets/tests/test_loader.py +221 -0
- hud/datasets/tests/test_utils.py +315 -0
- hud/datasets/utils.py +270 -90
- hud/environment/__init__.py +50 -0
- hud/environment/connection.py +206 -0
- hud/environment/connectors/__init__.py +33 -0
- hud/environment/connectors/base.py +68 -0
- hud/environment/connectors/local.py +177 -0
- hud/environment/connectors/mcp_config.py +109 -0
- hud/environment/connectors/openai.py +101 -0
- hud/environment/connectors/remote.py +172 -0
- hud/environment/environment.py +694 -0
- hud/environment/integrations/__init__.py +45 -0
- hud/environment/integrations/adk.py +67 -0
- hud/environment/integrations/anthropic.py +196 -0
- hud/environment/integrations/gemini.py +92 -0
- hud/environment/integrations/langchain.py +82 -0
- hud/environment/integrations/llamaindex.py +68 -0
- hud/environment/integrations/openai.py +238 -0
- hud/environment/mock.py +306 -0
- hud/environment/router.py +112 -0
- hud/environment/scenarios.py +493 -0
- hud/environment/tests/__init__.py +1 -0
- hud/environment/tests/test_connection.py +317 -0
- hud/environment/tests/test_connectors.py +218 -0
- hud/environment/tests/test_environment.py +161 -0
- hud/environment/tests/test_integrations.py +257 -0
- hud/environment/tests/test_local_connectors.py +201 -0
- hud/environment/tests/test_scenarios.py +280 -0
- hud/environment/tests/test_tools.py +208 -0
- hud/environment/types.py +23 -0
- hud/environment/utils/__init__.py +35 -0
- hud/environment/utils/formats.py +215 -0
- hud/environment/utils/schema.py +171 -0
- hud/environment/utils/tool_wrappers.py +113 -0
- hud/eval/__init__.py +67 -0
- hud/eval/context.py +674 -0
- hud/eval/display.py +299 -0
- hud/eval/instrument.py +185 -0
- hud/eval/manager.py +466 -0
- hud/eval/parallel.py +268 -0
- hud/eval/task.py +340 -0
- hud/eval/tests/__init__.py +1 -0
- hud/eval/tests/test_context.py +178 -0
- hud/eval/tests/test_eval.py +210 -0
- hud/eval/tests/test_manager.py +152 -0
- hud/eval/tests/test_parallel.py +168 -0
- hud/eval/tests/test_task.py +145 -0
- hud/eval/types.py +63 -0
- hud/eval/utils.py +183 -0
- hud/patches/__init__.py +19 -0
- hud/patches/mcp_patches.py +151 -0
- hud/patches/warnings.py +54 -0
- hud/samples/browser.py +4 -4
- hud/server/__init__.py +2 -1
- hud/server/low_level.py +2 -1
- hud/server/router.py +164 -0
- hud/server/server.py +567 -80
- hud/server/tests/test_mcp_server_integration.py +11 -11
- hud/server/tests/test_mcp_server_more.py +1 -1
- hud/server/tests/test_server_extra.py +2 -0
- hud/settings.py +45 -3
- hud/shared/exceptions.py +36 -10
- hud/shared/hints.py +26 -1
- hud/shared/requests.py +15 -3
- hud/shared/tests/test_exceptions.py +40 -31
- hud/shared/tests/test_hints.py +167 -0
- hud/telemetry/__init__.py +20 -19
- hud/telemetry/exporter.py +201 -0
- hud/telemetry/instrument.py +158 -253
- hud/telemetry/tests/test_eval_telemetry.py +356 -0
- hud/telemetry/tests/test_exporter.py +258 -0
- hud/telemetry/tests/test_instrument.py +401 -0
- hud/tools/__init__.py +16 -2
- hud/tools/apply_patch.py +639 -0
- hud/tools/base.py +54 -4
- hud/tools/bash.py +2 -2
- hud/tools/computer/__init__.py +4 -0
- hud/tools/computer/anthropic.py +2 -2
- hud/tools/computer/gemini.py +385 -0
- hud/tools/computer/hud.py +23 -6
- hud/tools/computer/openai.py +20 -21
- hud/tools/computer/qwen.py +434 -0
- hud/tools/computer/settings.py +37 -0
- hud/tools/edit.py +3 -7
- hud/tools/executors/base.py +4 -2
- hud/tools/executors/pyautogui.py +1 -1
- hud/tools/grounding/grounded_tool.py +13 -18
- hud/tools/grounding/grounder.py +10 -31
- hud/tools/grounding/tests/test_grounded_tool.py +26 -44
- hud/tools/jupyter.py +330 -0
- hud/tools/playwright.py +18 -3
- hud/tools/shell.py +308 -0
- hud/tools/tests/test_apply_patch.py +718 -0
- hud/tools/tests/test_computer.py +4 -9
- hud/tools/tests/test_computer_actions.py +24 -2
- hud/tools/tests/test_jupyter_tool.py +181 -0
- hud/tools/tests/test_shell.py +596 -0
- hud/tools/tests/test_submit.py +85 -0
- hud/tools/tests/test_types.py +193 -0
- hud/tools/types.py +21 -1
- hud/types.py +167 -57
- hud/utils/__init__.py +2 -0
- hud/utils/env.py +67 -0
- hud/utils/hud_console.py +61 -3
- hud/utils/mcp.py +15 -58
- hud/utils/strict_schema.py +162 -0
- hud/utils/tests/test_init.py +1 -2
- hud/utils/tests/test_mcp.py +1 -28
- hud/utils/tests/test_pretty_errors.py +186 -0
- hud/utils/tests/test_tool_shorthand.py +154 -0
- hud/utils/tests/test_version.py +1 -1
- hud/utils/types.py +20 -0
- hud/version.py +1 -1
- hud_python-0.5.1.dist-info/METADATA +264 -0
- hud_python-0.5.1.dist-info/RECORD +299 -0
- {hud_python-0.4.45.dist-info → hud_python-0.5.1.dist-info}/WHEEL +1 -1
- hud/agents/langchain.py +0 -261
- hud/agents/lite_llm.py +0 -72
- hud/cli/rl/__init__.py +0 -180
- hud/cli/rl/config.py +0 -101
- hud/cli/rl/display.py +0 -133
- hud/cli/rl/gpu.py +0 -63
- hud/cli/rl/gpu_utils.py +0 -321
- hud/cli/rl/local_runner.py +0 -595
- hud/cli/rl/presets.py +0 -96
- hud/cli/rl/remote_runner.py +0 -463
- hud/cli/rl/rl_api.py +0 -150
- hud/cli/rl/vllm.py +0 -177
- hud/cli/rl/wait_utils.py +0 -89
- hud/datasets/parallel.py +0 -687
- hud/misc/__init__.py +0 -1
- hud/misc/claude_plays_pokemon.py +0 -292
- hud/otel/__init__.py +0 -35
- hud/otel/collector.py +0 -142
- hud/otel/config.py +0 -181
- hud/otel/context.py +0 -570
- hud/otel/exporters.py +0 -369
- hud/otel/instrumentation.py +0 -135
- hud/otel/processors.py +0 -121
- hud/otel/tests/__init__.py +0 -1
- hud/otel/tests/test_processors.py +0 -197
- hud/rl/README.md +0 -30
- hud/rl/__init__.py +0 -1
- hud/rl/actor.py +0 -176
- hud/rl/buffer.py +0 -405
- hud/rl/chat_template.jinja +0 -101
- hud/rl/config.py +0 -192
- hud/rl/distributed.py +0 -132
- hud/rl/learner.py +0 -637
- hud/rl/tests/__init__.py +0 -1
- hud/rl/tests/test_learner.py +0 -186
- hud/rl/train.py +0 -382
- hud/rl/types.py +0 -101
- hud/rl/utils/start_vllm_server.sh +0 -30
- hud/rl/utils.py +0 -524
- hud/rl/vllm_adapter.py +0 -143
- hud/telemetry/job.py +0 -352
- hud/telemetry/replay.py +0 -74
- hud/telemetry/tests/test_replay.py +0 -40
- hud/telemetry/tests/test_trace.py +0 -63
- hud/telemetry/trace.py +0 -158
- hud/utils/agent_factories.py +0 -86
- hud/utils/async_utils.py +0 -65
- hud/utils/group_eval.py +0 -223
- hud/utils/progress.py +0 -149
- hud/utils/tasks.py +0 -127
- hud/utils/tests/test_async_utils.py +0 -173
- hud/utils/tests/test_progress.py +0 -261
- hud_python-0.4.45.dist-info/METADATA +0 -552
- hud_python-0.4.45.dist-info/RECORD +0 -228
- {hud_python-0.4.45.dist-info → hud_python-0.5.1.dist-info}/entry_points.txt +0 -0
- {hud_python-0.4.45.dist-info → hud_python-0.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Runtime patches for the standard mcp package.
|
|
3
|
+
|
|
4
|
+
These patches apply fixes from the HUD fork without requiring a separate package.
|
|
5
|
+
Import this module early (e.g., in hud/__init__.py) to apply patches.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def patch_streamable_http_error_handling() -> None:
|
|
17
|
+
"""
|
|
18
|
+
Patch StreamableHTTPTransport.post_writer to handle request errors properly.
|
|
19
|
+
|
|
20
|
+
The original implementation doesn't catch errors in handle_request_async,
|
|
21
|
+
which can cause silent failures. This patch wraps the handler to send
|
|
22
|
+
errors to the read stream so clients know the request failed.
|
|
23
|
+
"""
|
|
24
|
+
try:
|
|
25
|
+
from mcp.client.streamable_http import StreamableHTTPTransport
|
|
26
|
+
|
|
27
|
+
async def patched_post_writer(
|
|
28
|
+
self: Any,
|
|
29
|
+
client: Any,
|
|
30
|
+
write_stream_reader: Any,
|
|
31
|
+
read_stream_writer: Any,
|
|
32
|
+
write_stream: Any,
|
|
33
|
+
start_get_stream: Any,
|
|
34
|
+
tg: Any,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Patched post_writer with error handling for handle_request_async."""
|
|
37
|
+
from mcp.client.streamable_http import RequestContext
|
|
38
|
+
from mcp.shared.message import ClientMessageMetadata
|
|
39
|
+
from mcp.types import JSONRPCRequest
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
async with write_stream_reader:
|
|
43
|
+
async for session_message in write_stream_reader:
|
|
44
|
+
message = session_message.message
|
|
45
|
+
metadata = (
|
|
46
|
+
session_message.metadata
|
|
47
|
+
if isinstance(session_message.metadata, ClientMessageMetadata)
|
|
48
|
+
else None
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
is_resumption = bool(metadata and metadata.resumption_token)
|
|
52
|
+
|
|
53
|
+
logger.debug("Sending client message: %s", message)
|
|
54
|
+
|
|
55
|
+
if self._is_initialized_notification(message):
|
|
56
|
+
start_get_stream()
|
|
57
|
+
|
|
58
|
+
ctx = RequestContext(
|
|
59
|
+
client=client,
|
|
60
|
+
headers=self.request_headers,
|
|
61
|
+
session_id=self.session_id,
|
|
62
|
+
session_message=session_message,
|
|
63
|
+
metadata=metadata,
|
|
64
|
+
read_stream_writer=read_stream_writer,
|
|
65
|
+
sse_read_timeout=self.sse_read_timeout,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Patched: Accept ctx and is_resumption as params, add error handling
|
|
69
|
+
async def handle_request_async(
|
|
70
|
+
ctx: RequestContext = ctx,
|
|
71
|
+
is_resumption: bool = is_resumption,
|
|
72
|
+
) -> None:
|
|
73
|
+
try:
|
|
74
|
+
if is_resumption:
|
|
75
|
+
await self._handle_resumption_request(ctx)
|
|
76
|
+
else:
|
|
77
|
+
await self._handle_post_request(ctx)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
# Send error to read stream so client knows request failed
|
|
80
|
+
logger.error("Request handler error: %s", e)
|
|
81
|
+
await ctx.read_stream_writer.send(e)
|
|
82
|
+
|
|
83
|
+
if isinstance(message.root, JSONRPCRequest):
|
|
84
|
+
tg.start_soon(handle_request_async, ctx, is_resumption)
|
|
85
|
+
else:
|
|
86
|
+
await handle_request_async(ctx, is_resumption)
|
|
87
|
+
|
|
88
|
+
except Exception:
|
|
89
|
+
logger.exception("Error in post_writer")
|
|
90
|
+
finally:
|
|
91
|
+
await read_stream_writer.aclose()
|
|
92
|
+
await write_stream.aclose()
|
|
93
|
+
|
|
94
|
+
StreamableHTTPTransport.post_writer = patched_post_writer
|
|
95
|
+
logger.debug("Patched StreamableHTTPTransport.post_writer")
|
|
96
|
+
|
|
97
|
+
except ImportError:
|
|
98
|
+
logger.debug("mcp.client.streamable_http not available, skipping patch")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.warning("Failed to patch streamable_http: %s", e)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def patch_client_session_validation() -> None:
|
|
104
|
+
"""
|
|
105
|
+
Patch ClientSession to skip structured output validation.
|
|
106
|
+
|
|
107
|
+
The original validation is strict and raises errors for non-conforming
|
|
108
|
+
but usable responses. We replace it with a no-op.
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
from mcp.client.session import ClientSession
|
|
112
|
+
|
|
113
|
+
async def noop_validate(self: Any, name: str, result: Any) -> None:
|
|
114
|
+
"""Skip structured output validation entirely."""
|
|
115
|
+
|
|
116
|
+
ClientSession._validate_tool_result = noop_validate
|
|
117
|
+
logger.debug("Patched ClientSession._validate_tool_result to skip validation")
|
|
118
|
+
|
|
119
|
+
except ImportError:
|
|
120
|
+
logger.debug("mcp.client.session not available, skipping patch")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.warning("Failed to patch client session: %s", e)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def suppress_fastmcp_logging(level: int = logging.WARNING) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Suppress verbose fastmcp logging.
|
|
128
|
+
|
|
129
|
+
FastMCP logs a lot of INFO-level messages that clutter output.
|
|
130
|
+
This sets all fastmcp loggers to the specified level.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
level: Logging level to set (default: WARNING)
|
|
134
|
+
"""
|
|
135
|
+
loggers_to_suppress = [
|
|
136
|
+
"fastmcp",
|
|
137
|
+
"fastmcp.server.server",
|
|
138
|
+
"fastmcp.server.openapi",
|
|
139
|
+
"fastmcp.tools.tool_manager",
|
|
140
|
+
]
|
|
141
|
+
for logger_name in loggers_to_suppress:
|
|
142
|
+
logging.getLogger(logger_name).setLevel(level)
|
|
143
|
+
logger.debug("Suppressed fastmcp logging to level %s", level)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def apply_all_patches() -> None:
|
|
147
|
+
"""Apply all MCP patches."""
|
|
148
|
+
patch_streamable_http_error_handling()
|
|
149
|
+
patch_client_session_validation()
|
|
150
|
+
suppress_fastmcp_logging()
|
|
151
|
+
logger.debug("All MCP patches applied")
|
hud/patches/warnings.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centralized warning filters for noisy third-party dependencies.
|
|
3
|
+
|
|
4
|
+
Keep these helpers here so the rest of the codebase can stay clean and avoid
|
|
5
|
+
scattering warning filters across unrelated modules.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import warnings
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Iterator
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def apply_default_warning_filters(*, verbose: bool) -> None:
|
|
19
|
+
"""Apply our default warning filters for non-verbose CLI/server modes."""
|
|
20
|
+
if verbose:
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
24
|
+
|
|
25
|
+
# Pydantic v2 emits PydanticDeprecatedSince20 for v1-style config usage in deps.
|
|
26
|
+
try:
|
|
27
|
+
from pydantic.warnings import PydanticDeprecatedSince20
|
|
28
|
+
except Exception:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
warnings.filterwarnings("ignore", category=PydanticDeprecatedSince20)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@contextmanager
|
|
35
|
+
def suppress_mcp_use_import_warnings() -> Iterator[None]:
|
|
36
|
+
"""Suppress known noisy warnings emitted during `mcp_use` imports."""
|
|
37
|
+
try:
|
|
38
|
+
from pydantic.warnings import PydanticDeprecatedSince20
|
|
39
|
+
except Exception: # pragma: no cover
|
|
40
|
+
PydanticDeprecatedSince20 = None # type: ignore[assignment]
|
|
41
|
+
|
|
42
|
+
with warnings.catch_warnings():
|
|
43
|
+
# mcp_use currently emits DeprecationWarning from its package __init__.py.
|
|
44
|
+
warnings.filterwarnings("ignore", category=DeprecationWarning, module=r"mcp_use(\..*)?$")
|
|
45
|
+
|
|
46
|
+
# mcp_use currently defines Pydantic v1-style `class Config` in oauth models.
|
|
47
|
+
if PydanticDeprecatedSince20 is not None:
|
|
48
|
+
warnings.filterwarnings(
|
|
49
|
+
"ignore",
|
|
50
|
+
category=PydanticDeprecatedSince20,
|
|
51
|
+
module=r"mcp_use\.client\.auth\.oauth$",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
yield
|
hud/samples/browser.py
CHANGED
|
@@ -7,17 +7,17 @@ from typing import Any
|
|
|
7
7
|
from pydantic import Field
|
|
8
8
|
|
|
9
9
|
from hud.settings import settings
|
|
10
|
-
from hud.types import
|
|
10
|
+
from hud.types import LegacyTask, MCPToolCall
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class BrowserTask(
|
|
14
|
-
"""
|
|
13
|
+
class BrowserTask(LegacyTask):
|
|
14
|
+
"""LegacyTask subclass with browser defaults for BrowserTask(prompt=...)."""
|
|
15
15
|
|
|
16
16
|
prompt: str = "Open Google and be ready to search."
|
|
17
17
|
mcp_config: dict[str, Any] = Field(
|
|
18
18
|
default_factory=lambda: {
|
|
19
19
|
"browser": {
|
|
20
|
-
"url":
|
|
20
|
+
"url": settings.hud_mcp_url,
|
|
21
21
|
"headers": {
|
|
22
22
|
"Authorization": f"Bearer {settings.api_key}",
|
|
23
23
|
"Mcp-Image": "hudevals/hud-remote-browser:0.1.1",
|
hud/server/__init__.py
CHANGED
hud/server/low_level.py
CHANGED
|
@@ -89,11 +89,12 @@ class LowLevelServerWithInit(_BaseLL):
|
|
|
89
89
|
|
|
90
90
|
def __init__(
|
|
91
91
|
self,
|
|
92
|
+
fastmcp: Any,
|
|
92
93
|
*args: Any,
|
|
93
94
|
init_fn: Callable[[RequestContext], Awaitable[None]] | None = None,
|
|
94
95
|
**kwargs: Any,
|
|
95
96
|
) -> None:
|
|
96
|
-
super().__init__(*args, **kwargs)
|
|
97
|
+
super().__init__(fastmcp, *args, **kwargs)
|
|
97
98
|
self._init_fn = init_fn
|
|
98
99
|
|
|
99
100
|
async def run(
|
hud/server/router.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""MCP Router utilities for FastAPI-like composition patterns."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from hud.server.server import MCPServer
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
|
|
13
|
+
from fastmcp import FastMCP
|
|
14
|
+
from fastmcp.tools import Tool
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# MCPRouter is just an alias to FastMCP for FastAPI-like patterns
|
|
19
|
+
MCPRouter = MCPServer
|
|
20
|
+
|
|
21
|
+
# Prefix for internal tool names
|
|
22
|
+
_INTERNAL_PREFIX = "int_"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class HiddenRouter(MCPRouter):
|
|
26
|
+
"""Wraps a FastMCP router to provide a single dispatcher tool for its sub-tools.
|
|
27
|
+
|
|
28
|
+
Instead of exposing all tools at the top level, this creates a single tool
|
|
29
|
+
(named after the router) that dispatches to the router's tools internally.
|
|
30
|
+
|
|
31
|
+
Useful for setup/evaluate patterns where you want:
|
|
32
|
+
- A single 'setup' tool that can call setup_basic(), setup_advanced(), etc.
|
|
33
|
+
- A single 'evaluate' tool that can call evaluate_score(), evaluate_complete(), etc.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
# Create a router with multiple setup functions
|
|
37
|
+
setup_router = MCPRouter(name="setup")
|
|
38
|
+
|
|
39
|
+
@setup_router.tool
|
|
40
|
+
async def reset():
|
|
41
|
+
return "Environment reset"
|
|
42
|
+
|
|
43
|
+
@setup_router.tool
|
|
44
|
+
async def seed_data():
|
|
45
|
+
return "Data seeded"
|
|
46
|
+
|
|
47
|
+
# Wrap in HiddenRouter
|
|
48
|
+
hidden_setup = HiddenRouter(setup_router)
|
|
49
|
+
|
|
50
|
+
# Now you have one 'setup' tool that dispatches to reset/seed_data
|
|
51
|
+
mcp.include_router(hidden_setup)
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
router: FastMCP,
|
|
57
|
+
*,
|
|
58
|
+
title: str | None = None,
|
|
59
|
+
description: str | None = None,
|
|
60
|
+
meta: dict[str, Any] | None = None,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Wrap an existing router with a dispatcher pattern.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
router: The FastMCP router to wrap
|
|
66
|
+
title: Optional title for the dispatcher tool (defaults to "{name} Dispatcher")
|
|
67
|
+
description: Optional description for the dispatcher tool
|
|
68
|
+
meta: Optional metadata for the dispatcher tool
|
|
69
|
+
"""
|
|
70
|
+
name = router.name or "router"
|
|
71
|
+
|
|
72
|
+
# Naming scheme for hidden/internal tools
|
|
73
|
+
self._prefix_fn: Callable[[str], str] = lambda n: f"{_INTERNAL_PREFIX}{n}"
|
|
74
|
+
|
|
75
|
+
super().__init__(name=name)
|
|
76
|
+
|
|
77
|
+
# Set up dispatcher tool
|
|
78
|
+
dispatcher_title = title or f"{name.title()} Dispatcher"
|
|
79
|
+
dispatcher_desc = description or f"Call internal '{name}' functions"
|
|
80
|
+
|
|
81
|
+
# Register dispatcher that routes to hidden tools
|
|
82
|
+
async def _dispatch(
|
|
83
|
+
name: str,
|
|
84
|
+
arguments: dict | str | None = None,
|
|
85
|
+
ctx: Any | None = None,
|
|
86
|
+
) -> Any:
|
|
87
|
+
"""Gateway to hidden tools.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
name: Internal function name (without prefix)
|
|
91
|
+
arguments: Arguments to forward to the internal tool (dict or JSON string)
|
|
92
|
+
ctx: Request context injected by FastMCP
|
|
93
|
+
"""
|
|
94
|
+
# Handle JSON string inputs
|
|
95
|
+
if isinstance(arguments, str):
|
|
96
|
+
import json
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
arguments = json.loads(arguments)
|
|
100
|
+
except json.JSONDecodeError:
|
|
101
|
+
arguments = {}
|
|
102
|
+
|
|
103
|
+
# Call the internal tool
|
|
104
|
+
return await self._tool_manager.call_tool(self._prefix_fn(name), arguments or {}) # type: ignore
|
|
105
|
+
|
|
106
|
+
from fastmcp.tools.tool import FunctionTool
|
|
107
|
+
|
|
108
|
+
dispatcher_tool = FunctionTool.from_function(
|
|
109
|
+
_dispatch,
|
|
110
|
+
name=name,
|
|
111
|
+
title=dispatcher_title,
|
|
112
|
+
description=dispatcher_desc,
|
|
113
|
+
tags=set(),
|
|
114
|
+
meta=meta,
|
|
115
|
+
)
|
|
116
|
+
self._tool_manager.add_tool(dispatcher_tool)
|
|
117
|
+
|
|
118
|
+
# Copy all tools from source router as hidden tools
|
|
119
|
+
for tool in router._tool_manager._tools.values():
|
|
120
|
+
tool._key = self._prefix_fn(tool.name)
|
|
121
|
+
self._tool_manager.add_tool(tool)
|
|
122
|
+
|
|
123
|
+
# Expose list of available functions via resource
|
|
124
|
+
async def _functions_catalogue() -> list[str]:
|
|
125
|
+
"""List all internal function names without prefix."""
|
|
126
|
+
return [
|
|
127
|
+
key.removeprefix(_INTERNAL_PREFIX)
|
|
128
|
+
for key in self._tool_manager._tools
|
|
129
|
+
if key.startswith(_INTERNAL_PREFIX)
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
from fastmcp.resources import Resource
|
|
133
|
+
|
|
134
|
+
catalogue_resource = Resource.from_function(
|
|
135
|
+
_functions_catalogue,
|
|
136
|
+
uri=f"{name}://functions",
|
|
137
|
+
name=f"{name.title()} Functions",
|
|
138
|
+
description=f"List of available {name} functions",
|
|
139
|
+
)
|
|
140
|
+
self._resource_manager.add_resource(catalogue_resource)
|
|
141
|
+
|
|
142
|
+
# Override _list_tools to hide internal tools when mounted
|
|
143
|
+
async def _list_tools(self, context: Any = None) -> list[Tool]:
|
|
144
|
+
"""Override _list_tools to hide internal tools when mounted.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
context: MiddlewareContext passed by FastMCP (optional for backwards compat)
|
|
148
|
+
"""
|
|
149
|
+
return [
|
|
150
|
+
tool
|
|
151
|
+
for key, tool in self._tool_manager._tools.items()
|
|
152
|
+
if not key.startswith(_INTERNAL_PREFIX)
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
def _sync_list_tools(self) -> dict[str, Tool]:
|
|
156
|
+
"""Override _list_tools to hide internal tools when mounted."""
|
|
157
|
+
return {
|
|
158
|
+
key: tool
|
|
159
|
+
for key, tool in self._tool_manager._tools.items()
|
|
160
|
+
if not key.startswith(_INTERNAL_PREFIX)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
__all__ = ["HiddenRouter", "MCPRouter"]
|