From eab36e217eb5c3d0cf7929ad7de55d6af4b6b951 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Tue, 5 May 2026 16:06:02 -0700 Subject: [PATCH] fix(external-connect): use molecule-mcp wrapper in Codex/OpenClaw templates (#2957) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The External Connect modal's Codex and OpenClaw tabs were rendering this MCP server config: command = "python3" args = ["-m", "molecule_runtime.a2a_mcp_server"] That spawns the bare MCP dispatcher with no presence wiring. The ``molecule-mcp`` console-script wrapper (mcp_cli.main) is what calls ``POST /registry/register`` at startup and runs the 20s heartbeat thread alongside the MCP stdio loop. Without the wrapper, the canvas flips the workspace back to ``awaiting_agent`` (OFFLINE) within 60-90s — even while tools work — because nothing is heartbeating. Operator-side this looks like: the workspace is registered and tools work fine when invoked, but the canvas shows "offline" / "Restart" CTA, peer agents see the workspace as awaiting_agent in list_peers output, and inbound A2A delivery silently fails the readiness check. A new external-Codex operator (#2957) hit this and spent debugging time on what should have been a copy-paste install. Fix: switch both Codex and OpenClaw templates to ``command = "molecule-mcp"`` / ``args = []``, matching the universal MCP template that already handles this correctly. Inline comment in each template explains the wrapper-vs-bare-module tradeoff so a future template author doesn't regress to the shorter form. Hermes-channel intentionally still spawns the bare module — the hermes plugin owns the platform plugin path and runs its own register_platform/heartbeat code in-process; double-heartbeating would race. Universal/Codex/OpenClaw all need the wrapper. Regression gate: TestExternalMcpTemplates_UseMoleculeMcpWrapper asserts the three templates that must use the wrapper actually do, and explicitly fails on the old ``-m molecule_runtime.a2a_mcp_server`` shape. Verified the test FAILS on pre-fix source by stashing only external_connection.go and re-running. Source: molecule-core#2957 issue 1 (item 4 of the report — the ``(codex returned empty output)`` / opaque-canvas-error / stale- session items live in codex-channel-molecule and are tracked separately). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../internal/handlers/external_connection.go | 26 +++++++++++-- .../handlers/external_connection_test.go | 37 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/workspace-server/internal/handlers/external_connection.go b/workspace-server/internal/handlers/external_connection.go index 4730d36b..320e761e 100644 --- a/workspace-server/internal/handlers/external_connection.go +++ b/workspace-server/internal/handlers/external_connection.go @@ -423,14 +423,23 @@ mkdir -p ~/.codex # (then open ~/.codex/config.toml in your editor and paste:) # # [mcp_servers.molecule] -# command = "python3" -# args = ["-m", "molecule_runtime.a2a_mcp_server"] +# command = "molecule-mcp" +# args = [] # startup_timeout_sec = 30 # # [mcp_servers.molecule.env] # WORKSPACE_ID = "{{WORKSPACE_ID}}" # PLATFORM_URL = "{{PLATFORM_URL}}" # MOLECULE_WORKSPACE_TOKEN = "" +# +# Use the "molecule-mcp" console-script wrapper (NOT +# "python3 -m molecule_runtime.a2a_mcp_server"). The wrapper is what +# keeps the workspace ALIVE on the canvas: it POSTs /registry/register +# at startup and runs a 20s heartbeat thread alongside the MCP stdio +# loop. The bare a2a_mcp_server module exposes tools but does NOT +# heartbeat — pointing codex at it leaves the canvas showing this +# workspace as awaiting_agent (OFFLINE) within 60-90s even while +# tools work. # 3. Run the bridge daemon as a durable background process — this # is the INBOUND path. Long-polls the platform inbox and runs @@ -507,11 +516,20 @@ pip install molecule-ai-workspace-runtime # 3. Wire the molecule MCP server. {{WORKSPACE_ID}} + {{PLATFORM_URL}} # are stamped server-side; paste the auth token before running. +# +# Use the "molecule-mcp" console-script wrapper (NOT +# "python3 -m molecule_runtime.a2a_mcp_server"). The wrapper is what +# keeps the workspace ALIVE on the canvas: it POSTs /registry/register +# at startup and runs a 20s heartbeat thread alongside the MCP stdio +# loop. The bare a2a_mcp_server module exposes tools but does NOT +# heartbeat — pointing openclaw at it leaves the canvas showing this +# workspace as awaiting_agent (OFFLINE) within 60-90s even while +# tools work. WORKSPACE_TOKEN="" openclaw mcp set molecule "$(cat <