diff --git a/molecule_runtime/a2a_client.py b/molecule_runtime/a2a_client.py index 0ae6dd2..5e0b057 100644 --- a/molecule_runtime/a2a_client.py +++ b/molecule_runtime/a2a_client.py @@ -10,7 +10,7 @@ import uuid import httpx -from platform_auth import auth_headers +from molecule_runtime.platform_auth import auth_headers logger = logging.getLogger(__name__) diff --git a/molecule_runtime/a2a_mcp_server.py b/molecule_runtime/a2a_mcp_server.py index 29ca254..3f25cb9 100644 --- a/molecule_runtime/a2a_mcp_server.py +++ b/molecule_runtime/a2a_mcp_server.py @@ -17,7 +17,23 @@ import json import logging import sys -from a2a_tools import ( +# Absolute imports so the installed-package location works too. Previously +# the script relied on `/app` being on sys.path (legacy template layout), +# which broke silently when the current template dropped that copy — +# claude-code then initialised with zero MCP tools and every agent +# reported "search_memory / commit_memory / list_peers / delegate_task +# not available" (second half of #507). The /app launch path is still +# supported via a sys.path shim below for anyone running the script +# with `python /app/a2a_mcp_server.py`. +import os as _os +if __package__ in (None, ""): + # Running as a script (python path/to/a2a_mcp_server.py) — put the + # package root on sys.path so the absolute imports below resolve. + _pkg_root = _os.path.dirname(_os.path.dirname(_os.path.abspath(__file__))) + if _pkg_root not in sys.path: + sys.path.insert(0, _pkg_root) + +from molecule_runtime.a2a_tools import ( tool_check_task_status, tool_commit_memory, tool_delegate_task, @@ -32,7 +48,7 @@ logger = logging.getLogger(__name__) # Re-export constants and client functions so existing imports # (e.g. tests that do `import a2a_mcp_server`) still work. -from a2a_client import ( # noqa: F401, E402 +from molecule_runtime.a2a_client import ( # noqa: F401, E402 PLATFORM_URL, WORKSPACE_ID, _A2A_ERROR_PREFIX, @@ -42,7 +58,7 @@ from a2a_client import ( # noqa: F401, E402 get_workspace_info, send_a2a_message, ) -from a2a_tools import report_activity # noqa: F401, E402 +from molecule_runtime.a2a_tools import report_activity # noqa: F401, E402 # --- Tool definitions (schemas) --- diff --git a/molecule_runtime/a2a_tools.py b/molecule_runtime/a2a_tools.py index 6ba37a0..b6cda40 100644 --- a/molecule_runtime/a2a_tools.py +++ b/molecule_runtime/a2a_tools.py @@ -8,7 +8,7 @@ import uuid import httpx -from a2a_client import ( +from molecule_runtime.a2a_client import ( PLATFORM_URL, WORKSPACE_ID, _A2A_ERROR_PREFIX, @@ -24,7 +24,7 @@ def _auth_headers_for_heartbeat() -> dict[str, str]: """Return Phase 30.1 auth headers; tolerate platform_auth being absent in older installs (e.g. during rolling upgrade).""" try: - from platform_auth import auth_headers + from molecule_runtime.platform_auth import auth_headers return auth_headers() except Exception: return {} diff --git a/molecule_runtime/consolidation.py b/molecule_runtime/consolidation.py index 38e4b58..ae792db 100644 --- a/molecule_runtime/consolidation.py +++ b/molecule_runtime/consolidation.py @@ -14,7 +14,7 @@ import os import httpx -from platform_auth import auth_headers +from molecule_runtime.platform_auth import auth_headers logger = logging.getLogger(__name__) diff --git a/molecule_runtime/executor_helpers.py b/molecule_runtime/executor_helpers.py index c435f84..19fb4a5 100644 --- a/molecule_runtime/executor_helpers.py +++ b/molecule_runtime/executor_helpers.py @@ -36,7 +36,10 @@ logger = logging.getLogger(__name__) WORKSPACE_MOUNT = "/workspace" CONFIG_MOUNT = "/configs" -DEFAULT_MCP_SERVER_PATH = "/app/a2a_mcp_server.py" +# Legacy template layout copied a2a_mcp_server.py into /app. Current +# templates don't — the script lives inside the installed runtime package. +# Kept as a last-resort fallback only. +LEGACY_MCP_SERVER_PATH = "/app/a2a_mcp_server.py" DEFAULT_DELEGATION_RESULTS_FILE = "/tmp/delegation_results.jsonl" PLATFORM_HTTP_TIMEOUT_S = 5.0 MEMORY_RECALL_LIMIT = 10 @@ -44,12 +47,38 @@ MEMORY_CONTENT_MAX_CHARS = 200 BRIEF_SUMMARY_MAX_LEN = 80 +def _default_mcp_server_path() -> str: + """Resolve the installed ``a2a_mcp_server.py`` path from the package. + + Fix for the secondary half of #507: when agents started producing text + again (after the CRLF hook fix), the a2a MCP server failed to start + because the hard-coded ``/app/a2a_mcp_server.py`` doesn't exist in the + current workspace-template image — the template's Dockerfile copies + ``adapter.py`` into /app but not the MCP server script. claude-code + then initialised with zero MCP tools, so every agent reported + "search_memory / commit_memory / list_peers / delegate_task not + available" on the first post-fix pulse. + + Resolve the path from the package itself so it always points at the + real installed script, regardless of which template layout imported + the runtime. Legacy /app/ path kept only as last-resort fallback. + """ + try: + from molecule_runtime import a2a_mcp_server as _mcp_mod + path = getattr(_mcp_mod, "__file__", None) + if path and os.path.isfile(path): + return path + except Exception: + pass + return LEGACY_MCP_SERVER_PATH + + def get_mcp_server_path() -> str: """Return the path to the stdio MCP server script. Overridable via A2A_MCP_SERVER_PATH for tests and non-default layouts. """ - return os.environ.get("A2A_MCP_SERVER_PATH", DEFAULT_MCP_SERVER_PATH) + return os.environ.get("A2A_MCP_SERVER_PATH", _default_mcp_server_path()) # ======================================================================== diff --git a/molecule_runtime/heartbeat.py b/molecule_runtime/heartbeat.py index a67bec7..194d52e 100644 --- a/molecule_runtime/heartbeat.py +++ b/molecule_runtime/heartbeat.py @@ -17,7 +17,7 @@ from pathlib import Path import httpx -from platform_auth import auth_headers +from molecule_runtime.platform_auth import auth_headers logger = logging.getLogger(__name__) diff --git a/molecule_runtime/main.py b/molecule_runtime/main.py index 07fbe86..df4e1d2 100644 --- a/molecule_runtime/main.py +++ b/molecule_runtime/main.py @@ -39,7 +39,7 @@ from initial_prompt import ( mark_initial_prompt_attempted, resolve_initial_prompt_marker, ) -from platform_auth import auth_headers +from molecule_runtime.platform_auth import auth_headers def get_machine_ip() -> str: # pragma: no cover