hud-python 0.4.45__py3-none-any.whl → 0.5.13__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 +70 -5
- hud/agents/base.py +238 -500
- hud/agents/claude.py +236 -247
- hud/agents/gateway.py +42 -0
- hud/agents/gemini.py +264 -0
- hud/agents/gemini_cua.py +324 -0
- hud/agents/grounded_openai.py +98 -100
- hud/agents/misc/integration_test_agent.py +51 -20
- hud/agents/misc/response_agent.py +48 -36
- hud/agents/openai.py +282 -296
- hud/agents/{openai_chat_generic.py → openai_chat.py} +63 -33
- hud/agents/operator.py +199 -0
- hud/agents/resolver.py +70 -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 +381 -214
- 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 +377 -140
- hud/agents/tests/test_operator.py +362 -0
- hud/agents/tests/test_resolver.py +192 -0
- hud/agents/tests/test_run_eval.py +179 -0
- hud/agents/types.py +148 -0
- hud/cli/__init__.py +493 -546
- hud/cli/analyze.py +43 -5
- hud/cli/build.py +699 -113
- hud/cli/debug.py +8 -5
- hud/cli/dev.py +889 -732
- hud/cli/eval.py +793 -667
- 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/pull.py +1 -1
- hud/cli/push.py +38 -13
- 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 +110 -8
- 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.py +1 -1
- 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 +70 -1
- 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 +45 -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 +326 -0
- hud/datasets/runner.py +198 -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 +52 -0
- hud/environment/connection.py +258 -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 +137 -0
- hud/environment/connectors/openai.py +101 -0
- hud/environment/connectors/remote.py +172 -0
- hud/environment/environment.py +835 -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 +263 -0
- hud/environment/scenarios.py +620 -0
- hud/environment/tests/__init__.py +1 -0
- hud/environment/tests/test_connection.py +317 -0
- hud/environment/tests/test_connectors.py +205 -0
- hud/environment/tests/test_environment.py +593 -0
- hud/environment/tests/test_integrations.py +257 -0
- hud/environment/tests/test_local_connectors.py +242 -0
- hud/environment/tests/test_scenarios.py +1086 -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 +727 -0
- hud/eval/display.py +299 -0
- hud/eval/instrument.py +187 -0
- hud/eval/manager.py +533 -0
- hud/eval/parallel.py +268 -0
- hud/eval/task.py +372 -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 +291 -0
- hud/eval/types.py +65 -0
- hud/eval/utils.py +194 -0
- hud/patches/__init__.py +19 -0
- hud/patches/mcp_patches.py +308 -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 +165 -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 +18 -2
- hud/tools/agent.py +223 -0
- hud/tools/apply_patch.py +639 -0
- hud/tools/base.py +54 -4
- hud/tools/bash.py +2 -2
- hud/tools/computer/__init__.py +36 -3
- 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_agent_tool.py +355 -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 +194 -56
- hud/utils/__init__.py +2 -0
- hud/utils/env.py +67 -0
- hud/utils/hud_console.py +89 -18
- 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.13.dist-info/METADATA +264 -0
- hud_python-0.5.13.dist-info/RECORD +305 -0
- {hud_python-0.4.45.dist-info → hud_python-0.5.13.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.13.dist-info}/entry_points.txt +0 -0
- {hud_python-0.4.45.dist-info → hud_python-0.5.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""OpenAI Agents SDK connectors - import tools from OpenAI agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
__all__ = ["OpenAIConnectorMixin"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OpenAIConnectorMixin:
|
|
12
|
+
"""Mixin providing OpenAI Agents SDK connector methods."""
|
|
13
|
+
|
|
14
|
+
# These are defined on Environment/MCPServer
|
|
15
|
+
_tool_manager: Any
|
|
16
|
+
|
|
17
|
+
def connect_function_tools(
|
|
18
|
+
self,
|
|
19
|
+
tools: list[Any],
|
|
20
|
+
*,
|
|
21
|
+
prefix: str | None = None,
|
|
22
|
+
) -> Any:
|
|
23
|
+
"""Import FunctionTools from the OpenAI Agents SDK.
|
|
24
|
+
|
|
25
|
+
Wraps each tool so calls go through HUD with telemetry.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
```python
|
|
29
|
+
from agents import function_tool
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@function_tool
|
|
33
|
+
def search(query: str) -> str:
|
|
34
|
+
'''Search for information.'''
|
|
35
|
+
return f"Results for {query}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@function_tool
|
|
39
|
+
def calculate(expression: str) -> float:
|
|
40
|
+
'''Evaluate a math expression.'''
|
|
41
|
+
return eval(expression)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
env = Environment("my-env")
|
|
45
|
+
env.connect_function_tools([search, calculate])
|
|
46
|
+
|
|
47
|
+
async with env:
|
|
48
|
+
result = await env.call_tool("search", query="MCP protocol")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Note:
|
|
52
|
+
Requires `openai-agents`: pip install openai-agents
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
from agents import FunctionTool
|
|
56
|
+
except ImportError as e:
|
|
57
|
+
raise ImportError(
|
|
58
|
+
"openai-agents is required for connect_function_tools. "
|
|
59
|
+
"Install with: pip install openai-agents"
|
|
60
|
+
) from e
|
|
61
|
+
|
|
62
|
+
for tool in tools:
|
|
63
|
+
if isinstance(tool, FunctionTool):
|
|
64
|
+
self._add_openai_function_tool(tool, prefix)
|
|
65
|
+
|
|
66
|
+
return self
|
|
67
|
+
|
|
68
|
+
def _add_openai_function_tool(self, tool: Any, prefix: str | None) -> None:
|
|
69
|
+
"""Convert OpenAI FunctionTool to local MCP tool."""
|
|
70
|
+
name = f"{prefix}_{tool.name}" if prefix else tool.name
|
|
71
|
+
|
|
72
|
+
# Get the original invoke function
|
|
73
|
+
original_invoke = tool.on_invoke_tool
|
|
74
|
+
|
|
75
|
+
# Create wrapper that calls the original
|
|
76
|
+
async def invoke(**arguments: Any) -> Any:
|
|
77
|
+
# OpenAI's on_invoke_tool expects (ToolContext, str_json_args)
|
|
78
|
+
# We need to create a minimal context
|
|
79
|
+
from agents.tool_context import ToolContext
|
|
80
|
+
|
|
81
|
+
ctx = ToolContext(context=None)
|
|
82
|
+
result = await original_invoke(ctx, json.dumps(arguments))
|
|
83
|
+
return result
|
|
84
|
+
|
|
85
|
+
# Set function metadata for FastMCP
|
|
86
|
+
invoke.__name__ = name
|
|
87
|
+
invoke.__doc__ = tool.description
|
|
88
|
+
|
|
89
|
+
# Register using FastMCP's tool decorator mechanism
|
|
90
|
+
# We access the internal _tool_manager from MCPServer
|
|
91
|
+
from fastmcp.tools import Tool as FastMCPTool
|
|
92
|
+
|
|
93
|
+
fastmcp_tool = FastMCPTool.from_function(
|
|
94
|
+
fn=invoke,
|
|
95
|
+
name=name,
|
|
96
|
+
description=tool.description,
|
|
97
|
+
)
|
|
98
|
+
# Override the schema with OpenAI's (more accurate)
|
|
99
|
+
fastmcp_tool.parameters = tool.params_json_schema
|
|
100
|
+
|
|
101
|
+
self._tool_manager.add_tool(fastmcp_tool)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Remote connection connectors - HUD Hub, URL, OpenAPI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
7
|
+
|
|
8
|
+
from hud.environment.connectors.mcp_config import MCPConfigConnectorMixin
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
|
|
13
|
+
from fastmcp.tools.tool import Tool
|
|
14
|
+
|
|
15
|
+
__all__ = ["RemoteConnectorMixin"]
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RemoteConnectorMixin(MCPConfigConnectorMixin):
|
|
21
|
+
"""Mixin providing remote connection methods.
|
|
22
|
+
|
|
23
|
+
Note: include_router() is inherited from MCPServer (via FastMCP).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def connect_hub(
|
|
27
|
+
self,
|
|
28
|
+
slug: str,
|
|
29
|
+
*,
|
|
30
|
+
alias: str | None = None,
|
|
31
|
+
prefix: str | None = None,
|
|
32
|
+
include: list[str] | None = None,
|
|
33
|
+
exclude: list[str] | None = None,
|
|
34
|
+
transform: Callable[[Tool], Tool | None] | None = None,
|
|
35
|
+
) -> Any:
|
|
36
|
+
"""Connect to a HUD Hub environment.
|
|
37
|
+
|
|
38
|
+
Creates an MCP connection to the HUD API with the hub slug in headers.
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
```python
|
|
42
|
+
env = Environment("my-env")
|
|
43
|
+
env.connect_hub("browser")
|
|
44
|
+
|
|
45
|
+
async with env:
|
|
46
|
+
await env.call_tool("navigate", url="https://google.com")
|
|
47
|
+
```
|
|
48
|
+
"""
|
|
49
|
+
from hud.settings import settings
|
|
50
|
+
|
|
51
|
+
logger.info("Connecting to hub environment: %s", slug)
|
|
52
|
+
|
|
53
|
+
# Store hub config for serialization (v5 format)
|
|
54
|
+
# Note: Only first hub is stored for serialization (task configs use single hub)
|
|
55
|
+
if not hasattr(self, "_hub_config") or self._hub_config is None:
|
|
56
|
+
hub_config: dict[str, Any] = {"name": slug}
|
|
57
|
+
if include:
|
|
58
|
+
hub_config["include"] = include
|
|
59
|
+
if exclude:
|
|
60
|
+
hub_config["exclude"] = exclude
|
|
61
|
+
self._hub_config = hub_config
|
|
62
|
+
|
|
63
|
+
# Create mcp_config with standard MCP URL and hub slug in headers
|
|
64
|
+
# Note: Authorization is injected at request time by httpx/aiohttp hooks
|
|
65
|
+
# in hud.eval.instrument (uses contextvar for api_key).
|
|
66
|
+
mcp_config = {
|
|
67
|
+
"hud": {
|
|
68
|
+
"url": settings.hud_mcp_url,
|
|
69
|
+
"headers": {"Environment-Name": slug},
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
self.connect_mcp_config(
|
|
74
|
+
mcp_config, prefix=prefix, include=include, exclude=exclude, transform=transform
|
|
75
|
+
)
|
|
76
|
+
logger.info("Hub connected: %s", slug)
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
def connect_url(
|
|
80
|
+
self,
|
|
81
|
+
url: str,
|
|
82
|
+
*,
|
|
83
|
+
headers: dict[str, str] | None = None,
|
|
84
|
+
alias: str | None = None,
|
|
85
|
+
prefix: str | None = None,
|
|
86
|
+
include: list[str] | None = None,
|
|
87
|
+
exclude: list[str] | None = None,
|
|
88
|
+
transform: Callable[[Tool], Tool | None] | None = None,
|
|
89
|
+
) -> Any:
|
|
90
|
+
"""Connect to an MCP server via URL.
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
```python
|
|
94
|
+
env = Environment("my-env")
|
|
95
|
+
env.connect_url(
|
|
96
|
+
"https://mcp.example.com",
|
|
97
|
+
headers={"Authorization": "Bearer token"},
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
async with env:
|
|
101
|
+
await env.call_tool("search", query="hello")
|
|
102
|
+
```
|
|
103
|
+
"""
|
|
104
|
+
from hud.environment.connection import ConnectionType
|
|
105
|
+
|
|
106
|
+
auth = headers.get("Authorization") if headers else None
|
|
107
|
+
return self._add_connection(
|
|
108
|
+
alias or url,
|
|
109
|
+
url,
|
|
110
|
+
connection_type=ConnectionType.REMOTE,
|
|
111
|
+
auth=auth,
|
|
112
|
+
prefix=prefix,
|
|
113
|
+
include=include,
|
|
114
|
+
exclude=exclude,
|
|
115
|
+
transform=transform,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def connect_openapi(
|
|
119
|
+
self,
|
|
120
|
+
openapi_spec: dict[str, Any] | str,
|
|
121
|
+
*,
|
|
122
|
+
base_url: str | None = None,
|
|
123
|
+
headers: dict[str, str] | None = None,
|
|
124
|
+
name: str | None = None,
|
|
125
|
+
prefix: str | None = None,
|
|
126
|
+
timeout: float = 30.0,
|
|
127
|
+
) -> Any:
|
|
128
|
+
"""Mount an OpenAPI specification as an MCP server.
|
|
129
|
+
|
|
130
|
+
Converts REST API endpoints to MCP tools. Base URL is auto-inferred
|
|
131
|
+
from the spec URL when possible.
|
|
132
|
+
|
|
133
|
+
Example:
|
|
134
|
+
```python
|
|
135
|
+
env = Environment("my-env")
|
|
136
|
+
env.connect_openapi("https://petstore.swagger.io/v2/swagger.json")
|
|
137
|
+
|
|
138
|
+
async with env:
|
|
139
|
+
result = await env.call_tool("getPetById", petId=1)
|
|
140
|
+
```
|
|
141
|
+
"""
|
|
142
|
+
from urllib.parse import urlparse
|
|
143
|
+
|
|
144
|
+
import httpx
|
|
145
|
+
from fastmcp import FastMCP
|
|
146
|
+
|
|
147
|
+
if isinstance(openapi_spec, str):
|
|
148
|
+
if openapi_spec.startswith(("http://", "https://")):
|
|
149
|
+
if base_url is None:
|
|
150
|
+
parsed = urlparse(openapi_spec)
|
|
151
|
+
base_url = f"{parsed.scheme}://{parsed.netloc}"
|
|
152
|
+
|
|
153
|
+
resp = httpx.get(openapi_spec, headers=headers)
|
|
154
|
+
resp.raise_for_status()
|
|
155
|
+
openapi_spec = resp.json()
|
|
156
|
+
else:
|
|
157
|
+
import json
|
|
158
|
+
|
|
159
|
+
with open(openapi_spec) as f:
|
|
160
|
+
openapi_spec = json.load(f)
|
|
161
|
+
|
|
162
|
+
if base_url is None:
|
|
163
|
+
raise ValueError("base_url is required when openapi_spec is a dict or file")
|
|
164
|
+
|
|
165
|
+
client = httpx.AsyncClient(base_url=base_url, headers=headers or {}, timeout=timeout)
|
|
166
|
+
mcp_server = FastMCP.from_openapi(
|
|
167
|
+
openapi_spec=cast("dict[str, Any]", openapi_spec),
|
|
168
|
+
client=client,
|
|
169
|
+
name=name or "openapi",
|
|
170
|
+
)
|
|
171
|
+
self.include_router(mcp_server, prefix=prefix) # type: ignore
|
|
172
|
+
return self
|