fix(cloud_init): fix opensandbox-mcp binding, registration endpoint, and port check

- Write launcher.py to VM and mount into container instead of relying on
  FASTMCP_HOST env var (which FastMCP ignores — host= is passed explicitly
  at __init__ time, overriding env). Launcher monkey-patches FastMCP.__init__
  default to 0.0.0.0 before import so the server binds on all interfaces.
- Fix _osb_wait_mcp to run python3 socket check via docker exec inside the
  container (port 8000 is never mapped to host, so nc -z localhost 8000 always
  failed).
- Fix jq path: .id → .server.id (API response is {"server": {"id": "..."}}).
- Fix install endpoint: POST .../install/$agentId → POST .../install with
  body {"agentId": "$_AID"} (PUT path returned 404 in validation run).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Fasthei
2026-06-26 16:24:30 +08:00
co-authored by Claude Sonnet 4.6
parent 94a41356e2
commit ff4c1c14b3
@@ -172,16 +172,28 @@ chmod 644 "$SWARM_DIR/fluent-bit/fluent-bit.conf" "$SWARM_DIR/fluent-bit/parsers
for i in range(1, worker_count(record) + 1)
]
all_agent_ids = " ".join([lead_id] + worker_ids)
osb_domain, osb_protocol = osb
opensandbox_register_block = f"""
# ── opensandbox-mcp launcher: monkey-patch FastMCP host default to 0.0.0.0 ──
# FastMCP.__init__ hard-codes host='127.0.0.1'; FASTMCP_HOST env is ignored
# because the value is passed explicitly at construction time. We monkey-patch
# the default before import so the server binds on all interfaces inside the
# container and is reachable from peer containers in the compose network.
cat > "$SWARM_DIR/opensandbox-mcp-launch.py" <<'PYEOF'
import os, mcp.server.fastmcp as _fm
_orig = _fm.FastMCP.__init__
def _p(self, *a, host="0.0.0.0", **k): _orig(self, *a, host=host, **k)
_fm.FastMCP.__init__ = _p
from opensandbox.config import ConnectionConfig
from opensandbox_mcp.server import create_server
import anyio
mcp = create_server(connection_config=ConnectionConfig(
domain=os.environ["OSB_DOMAIN"], protocol=os.environ["OSB_PROTOCOL"]))
anyio.run(mcp.run_streamable_http_async)
PYEOF
chmod 644 "$SWARM_DIR/opensandbox-mcp-launch.py"
# ── Register opensandbox MCP server for all agents ──────────────────────────
_osb_wait_port() {{
# $1=host $2=port; poll up to 5 min (100 × 3s)
for _i in $(seq 1 100); do
nc -z "$1" "$2" 2>/dev/null && return 0
sleep 3
done
return 1
}}
_osb_wait_api() {{
for _i in $(seq 1 60); do
curl -sf "http://localhost:{s.swarm_api_port}/health" >/dev/null 2>&1 && return 0
@@ -189,18 +201,31 @@ _osb_wait_api() {{
done
return 1
}}
# Port check runs inside the opensandbox-mcp container (127.0.0.1 inside =
# 0.0.0.0 listener; host-side port is not mapped).
_osb_wait_mcp() {{
for _i in $(seq 1 100); do
docker exec swarm-opensandbox-mcp-1 python3 -c \
"import socket; s=socket.socket(); s.settimeout(1); s.connect(('localhost',8000)); s.close()" \
2>/dev/null && return 0
sleep 3
done
return 1
}}
echo "[swarm-bootstrap] waiting for swarm API and opensandbox-mcp …"
if _osb_wait_api && _osb_wait_port localhost 8000; then
if _osb_wait_api && _osb_wait_mcp; then
_OSB_SERVER_ID=$(curl -sf -X POST "http://localhost:{s.swarm_api_port}/api/mcp-servers" \\
-H "Authorization: Bearer {api_key}" \\
-H "Content-Type: application/json" \\
-d '{{"name":"opensandbox","description":"OpenSandbox isolated code execution","transport":"http","scope":"swarm","url":"http://opensandbox-mcp:8000/mcp"}}' \\
| jq -r '.id // empty')
| jq -r '.server.id // empty')
if [ -n "$_OSB_SERVER_ID" ]; then
for _AID in {all_agent_ids}; do
curl -sf -X POST "http://localhost:{s.swarm_api_port}/api/mcp-servers/$_OSB_SERVER_ID/install/$_AID" \\
-H "Authorization: Bearer {api_key}" >/dev/null 2>&1 || true
curl -sf -X POST "http://localhost:{s.swarm_api_port}/api/mcp-servers/$_OSB_SERVER_ID/install" \\
-H "Authorization: Bearer {api_key}" \\
-H "Content-Type: application/json" \\
-d "{{\\"agentId\\":\\"$_AID\\"}}" >/dev/null 2>&1 || true
done
echo "[swarm-bootstrap] opensandbox MCP server registered id=$_OSB_SERVER_ID agents={all_agent_ids}"
else
@@ -448,11 +473,11 @@ def _compose_yaml(settings: Settings, record: DeploymentRecord) -> str:
pull_policy: always
stop_grace_period: 10s
environment:
- FASTMCP_HOST=0.0.0.0
- FASTMCP_PORT=8000
command: >-
sh -c "pip install --quiet --no-cache-dir opensandbox-mcp &&
opensandbox-mcp --domain {osb_domain} --protocol {osb_protocol} --transport streamable-http"
- OSB_DOMAIN={osb_domain}
- OSB_PROTOCOL={osb_protocol}
volumes:
- /opt/swarm/opensandbox-mcp-launch.py:/launch.py:ro
command: sh -c "pip install --quiet --no-cache-dir opensandbox-mcp && python3 /launch.py"
restart: unless-stopped
"""