Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion openhands/automation/presets/plugin/sdk_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,20 @@ def _normalize_mcp_config(raw_mcp_config):
return raw_mcp_config


def _get_workspace_mcp_config(workspace):
raw_mcp_config = workspace.get_mcp_config()
if raw_mcp_config:
return _normalize_mcp_config(raw_mcp_config)

# openhands-sdk 1.42.1 returns {} for ACPAgentSettings even though those
# settings contain mcp_config. Recover it until the SDK accepts ACP settings.
fetch_agent_settings = getattr(workspace, "_fetch_agent_settings", None)
if not callable(fetch_agent_settings):
return {}
settings = fetch_agent_settings()
return _normalize_mcp_config(getattr(settings, "mcp_config", {}))


def _build_conversation_title(event_context) -> str | None:
"""Build a descriptive conversation title from the automation event context.

Expand Down Expand Up @@ -384,7 +398,7 @@ def _build_conversation_title(event_context) -> str | None:
print("\n=== GET_MCP_CONFIG ===")
mcp_config = {}
try:
mcp_config = _normalize_mcp_config(workspace.get_mcp_config())
mcp_config = _get_workspace_mcp_config(workspace)
if mcp_config:
print(f" servers: {list(mcp_config.keys())}")
else:
Expand Down
16 changes: 15 additions & 1 deletion openhands/automation/presets/prompt/sdk_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,20 @@ def _normalize_mcp_config(raw_mcp_config):
return raw_mcp_config


def _get_workspace_mcp_config(workspace):
raw_mcp_config = workspace.get_mcp_config()
if raw_mcp_config:
return _normalize_mcp_config(raw_mcp_config)

# openhands-sdk 1.42.1 returns {} for ACPAgentSettings even though those
# settings contain mcp_config. Recover it until the SDK accepts ACP settings.
fetch_agent_settings = getattr(workspace, "_fetch_agent_settings", None)
if not callable(fetch_agent_settings):
return {}
settings = fetch_agent_settings()
return _normalize_mcp_config(getattr(settings, "mcp_config", {}))


def _build_conversation_title(event_context) -> str | None:
"""Build a descriptive conversation title from the automation event context.

Expand Down Expand Up @@ -353,7 +367,7 @@ def _build_conversation_title(event_context) -> str | None:
print("\n=== GET_MCP_CONFIG ===")
mcp_config = {}
try:
mcp_config = _normalize_mcp_config(workspace.get_mcp_config())
mcp_config = _get_workspace_mcp_config(workspace)
if mcp_config:
print(f" servers: {list(mcp_config.keys())}")
else:
Expand Down
44 changes: 44 additions & 0 deletions tests/test_preset_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,34 @@ def _load_preset_mcp_normalizer(
return cast(Callable[[Any], Any], namespace["_normalize_mcp_config"])


def _load_preset_workspace_mcp_loader(preset_name: str) -> Callable[[Any], Any]:
source_path = PRESETS_DIR / preset_name / "sdk_main.py"
module = ast.parse(source_path.read_text(), filename=str(source_path))
function_nodes = {
node.name: node
for node in module.body
if isinstance(node, ast.FunctionDef)
and node.name in {"_normalize_mcp_config", "_get_workspace_mcp_config"}
}
assert "_get_workspace_mcp_config" in function_nodes, (
f"{preset_name} preset must recover MCP config from ACP settings"
)
namespace: dict[str, Any] = {"_coerce_mcp_config": coerce_mcp_config}
selected_nodes: list[ast.stmt] = [
function_nodes["_normalize_mcp_config"],
function_nodes["_get_workspace_mcp_config"],
]
for node in selected_nodes:
ast.fix_missing_locations(node)
exec(
compile(
ast.Module(body=selected_nodes, type_ignores=[]), str(source_path), "exec"
),
namespace,
)
return cast(Callable[[Any], Any], namespace["_get_workspace_mcp_config"])


def _load_preset_title_builder(preset_name: str) -> Callable[[Any], str | None]:
source_path = PRESETS_DIR / preset_name / "sdk_main.py"
module = ast.parse(source_path.read_text(), filename=str(source_path))
Expand Down Expand Up @@ -226,6 +254,22 @@ def test_preset_mcp_normalizer_unwraps_without_sdk_coercer(preset_name):
assert normalize(None) == {}


@pytest.mark.parametrize("preset_name", ["prompt", "plugin"])
def test_preset_recovers_mcp_config_from_acp_settings(preset_name):
load_mcp_config = _load_preset_workspace_mcp_loader(preset_name)
workspace = MagicMock()
workspace.get_mcp_config.return_value = {}
workspace._fetch_agent_settings.return_value.mcp_config = {
"Plane": {"command": "uvx", "args": ["plane-mcp-server", "stdio"]}
}

mcp_config = load_mcp_config(workspace)

assert list(mcp_config) == ["Plane"]
workspace.get_mcp_config.assert_called_once_with()
workspace._fetch_agent_settings.assert_called_once_with()


_UTC_TIMESTAMP_RE = re.compile(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} UTC")


Expand Down
Loading