Source code for ralph.mcp.transport.opencode

"""OpenCode-specific MCP transport helpers."""

from __future__ import annotations

import json
from typing import cast

from ralph.mcp.tools.names import (
    ALL_RALPH_TOOLS,
    OPENCODE_NATIVE_TOOLS_TO_DISABLE,
    claude_tool_name,
)
from ralph.mcp.upstream.config import UpstreamMcpServer, normalize_upstream_mcp_servers


[docs] def merge_opencode_config_content(existing: str | None, endpoint: str) -> str: """Merge Ralph MCP endpoint into an existing OpenCode config and return JSON.""" config_text, _upstreams = build_opencode_provider_config(existing, endpoint) return config_text
[docs] def build_opencode_provider_config( existing: str | None, endpoint: str ) -> tuple[str, tuple[UpstreamMcpServer, ...]]: """Build a full OpenCode config JSON with Ralph MCP and return it with upstream servers.""" config_obj = _parse_opencode_config_content(existing) existing_mcp = config_obj.get("mcp") upstreams = ( normalize_upstream_mcp_servers(cast("dict[str, object]", existing_mcp)) if isinstance(existing_mcp, dict) else () ) config_obj["mcp"] = { "ralph": { "type": "remote", "url": endpoint, "enabled": True, "timeout": 30000, } } permission_section_obj = config_obj.setdefault("permission", {}) if not isinstance(permission_section_obj, dict): permission_section_obj = {} config_obj["permission"] = permission_section_obj permission_section = cast("dict[str, object]", permission_section_obj) permission_section["ralph_*"] = "allow" permission_section["mcp__ralph__*"] = "allow" for tool_name in ALL_RALPH_TOOLS: bare_name = str(tool_name) permission_section[bare_name] = "allow" permission_section[claude_tool_name(bare_name)] = "allow" existing_tools = config_obj.get("tools", {}) if not isinstance(existing_tools, dict): existing_tools = {} disable_overrides = dict.fromkeys(OPENCODE_NATIVE_TOOLS_TO_DISABLE, False) config_obj["tools"] = {**cast("dict[str, object]", existing_tools), **disable_overrides} config_obj.setdefault("$schema", "https://opencode.ai/config.json") return json.dumps(config_obj, sort_keys=True), upstreams
def _parse_opencode_config_content(existing: str | None) -> dict[str, object]: if not existing: return {} try: decoded: object = json.loads(existing) except json.JSONDecodeError: return {} if not isinstance(decoded, dict): return {} return cast("dict[str, object]", decoded) __all__ = [ "build_opencode_provider_config", "merge_opencode_config_content", ]