From ba36cfd8fa3c30a01b6cdbfc1403a8107874fee6 Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Mon, 3 Aug 2026 13:10:23 -0700 Subject: [PATCH] fix(demos): wait for the gateway to bind instead of sleeping Demos 1-5 and the web console started their processes and then slept a fixed 1-2 seconds. cMCP Runtime 0.3.0 does attestation and audit setup before it binds, so on a cold start the first tool call hit a refused connection and every cMCP demo failed. Poll the port instead, with a 60s ceiling and a message pointing at the logs. Also fixes demo-05's docstring, which was copied from demo-04. Co-Authored-By: Claude Opus 5 (1M context) --- demo-01-cmcp-in-action/run.py | 23 +++++++++++++++++++++-- demo-02-policy-swap/run.py | 23 +++++++++++++++++++++-- demo-04-context-enforcement/run.py | 23 +++++++++++++++++++++-- demo-05-compliance-domain/run.py | 25 ++++++++++++++++++++++--- web-console/run.py | 25 ++++++++++++++++++++++--- 5 files changed, 107 insertions(+), 12 deletions(-) diff --git a/demo-01-cmcp-in-action/run.py b/demo-01-cmcp-in-action/run.py index 2cdd6ce..9d38267 100644 --- a/demo-01-cmcp-in-action/run.py +++ b/demo-01-cmcp-in-action/run.py @@ -7,6 +7,7 @@ import os import pathlib import shutil +import socket import subprocess import sys import time @@ -35,6 +36,24 @@ def _find_cmcp() -> str: sys.exit("cmcp not found. Run: pip install cmcp-runtime") +def _wait_for_port(port: int, what: str, timeout: float = 60.0) -> None: + """Block until something is listening on 127.0.0.1:port. + + A fixed sleep used to be enough; cMCP Runtime now does attestation and audit + setup before it binds, so the demo raced startup and failed on a refused + connection. + """ + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + try: + with socket.create_connection(("127.0.0.1", port), timeout=1): + return + except OSError: + time.sleep(0.25) + sys.exit(f"{what} did not start listening on :{port} within {timeout:.0f}s. " + "See the *.log files in this demo folder.") + + def main() -> None: token = os.environ.setdefault("CMCP_BEARER_TOKEN", "demo-token") # noqa: F841 @@ -49,7 +68,7 @@ def main() -> None: [sys.executable, str(REPO_ROOT / "server" / "server.py")], stdout=server_log, stderr=server_log, ) - time.sleep(1) + _wait_for_port(9001, "MCP filesystem server") print("-- Starting cMCP Runtime (CMCP_DEV_MODE=1) on :8443 --", flush=True) env = os.environ.copy() @@ -59,7 +78,7 @@ def main() -> None: stdout=cmcp_log, stderr=cmcp_log, cwd=SCRIPT_DIR, env=env, ) - time.sleep(2) + _wait_for_port(8443, "cMCP Runtime") print() subprocess.run([sys.executable, str(SCRIPT_DIR / "call.py")], check=True) diff --git a/demo-02-policy-swap/run.py b/demo-02-policy-swap/run.py index 4ba7884..9bc0993 100644 --- a/demo-02-policy-swap/run.py +++ b/demo-02-policy-swap/run.py @@ -8,6 +8,7 @@ import os import pathlib import shutil +import socket import subprocess import sys import time @@ -41,6 +42,24 @@ def _find_cmcp() -> str: sys.exit("cmcp not found. Run: pip install cmcp-runtime") +def _wait_for_port(port: int, what: str, timeout: float = 60.0) -> None: + """Block until something is listening on 127.0.0.1:port. + + A fixed sleep used to be enough; cMCP Runtime now does attestation and audit + setup before it binds, so the demo raced startup and failed on a refused + connection. + """ + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + try: + with socket.create_connection(("127.0.0.1", port), timeout=1): + return + except OSError: + time.sleep(0.25) + sys.exit(f"{what} did not start listening on :{port} within {timeout:.0f}s. " + "See the *.log files in this demo folder.") + + def _post(url: str, payload: dict, token: str) -> dict: data = json.dumps(payload).encode() req = urllib.request.Request( @@ -99,7 +118,7 @@ def main() -> None: [sys.executable, str(REPO_ROOT / "server" / "server.py")], stdout=server_log, stderr=server_log, ) - time.sleep(1) + _wait_for_port(9001, "MCP filesystem server") env = os.environ.copy() env["CMCP_DEV_MODE"] = "1" @@ -108,7 +127,7 @@ def main() -> None: stdout=cmcp_log, stderr=cmcp_log, cwd=SCRIPT_DIR, env=env, ) - time.sleep(2) + _wait_for_port(8443, "cMCP Runtime") # Step 3: write_file denied print() diff --git a/demo-04-context-enforcement/run.py b/demo-04-context-enforcement/run.py index 0b483dd..c19db3e 100644 --- a/demo-04-context-enforcement/run.py +++ b/demo-04-context-enforcement/run.py @@ -7,6 +7,7 @@ import os import pathlib import shutil +import socket import subprocess import sys import time @@ -35,6 +36,24 @@ def _find_cmcp() -> str: sys.exit("cmcp not found. Run: pip install cmcp-runtime") +def _wait_for_port(port: int, what: str, timeout: float = 60.0) -> None: + """Block until something is listening on 127.0.0.1:port. + + A fixed sleep used to be enough; cMCP Runtime now does attestation and audit + setup before it binds, so the demo raced startup and failed on a refused + connection. + """ + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + try: + with socket.create_connection(("127.0.0.1", port), timeout=1): + return + except OSError: + time.sleep(0.25) + sys.exit(f"{what} did not start listening on :{port} within {timeout:.0f}s. " + "See the *.log files in this demo folder.") + + def main() -> None: os.environ.setdefault("CMCP_BEARER_TOKEN", "demo-token") @@ -49,7 +68,7 @@ def main() -> None: [sys.executable, str(REPO_ROOT / "server" / "server.py")], stdout=server_log, stderr=server_log, ) - time.sleep(1) + _wait_for_port(9001, "MCP filesystem server") print("-- Starting cMCP Runtime (CMCP_DEV_MODE=1) on :8443 --", flush=True) env = os.environ.copy() @@ -59,7 +78,7 @@ def main() -> None: stdout=cmcp_log, stderr=cmcp_log, cwd=SCRIPT_DIR, env=env, ) - time.sleep(2) + _wait_for_port(8443, "cMCP Runtime") print() subprocess.run([sys.executable, str(SCRIPT_DIR / "call.py")], check=True) diff --git a/demo-05-compliance-domain/run.py b/demo-05-compliance-domain/run.py index 004ec2f..7262bee 100644 --- a/demo-05-compliance-domain/run.py +++ b/demo-05-compliance-domain/run.py @@ -1,4 +1,4 @@ -"""Demo 4: context-aware enforcement — cross-platform launcher (replaces run.sh). +"""Demo 5: attribute-based enforcement — cross-platform launcher (replaces run.sh). Usage: python demo-05-compliance-domain/run.py # from repo root @@ -7,6 +7,7 @@ import os import pathlib import shutil +import socket import subprocess import sys import time @@ -35,6 +36,24 @@ def _find_cmcp() -> str: sys.exit("cmcp not found. Run: pip install cmcp-runtime") +def _wait_for_port(port: int, what: str, timeout: float = 60.0) -> None: + """Block until something is listening on 127.0.0.1:port. + + A fixed sleep used to be enough; cMCP Runtime now does attestation and audit + setup before it binds, so the demo raced startup and failed on a refused + connection. + """ + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + try: + with socket.create_connection(("127.0.0.1", port), timeout=1): + return + except OSError: + time.sleep(0.25) + sys.exit(f"{what} did not start listening on :{port} within {timeout:.0f}s. " + "See the *.log files in this demo folder.") + + def main() -> None: os.environ.setdefault("CMCP_BEARER_TOKEN", "demo-token") @@ -49,7 +68,7 @@ def main() -> None: [sys.executable, str(REPO_ROOT / "server" / "server.py")], stdout=server_log, stderr=server_log, ) - time.sleep(1) + _wait_for_port(9001, "MCP filesystem server") print("-- Starting cMCP Runtime (CMCP_DEV_MODE=1) on :8443 --", flush=True) env = os.environ.copy() @@ -59,7 +78,7 @@ def main() -> None: stdout=cmcp_log, stderr=cmcp_log, cwd=SCRIPT_DIR, env=env, ) - time.sleep(2) + _wait_for_port(8443, "cMCP Runtime") print() subprocess.run([sys.executable, str(SCRIPT_DIR / "call.py")], check=True) diff --git a/web-console/run.py b/web-console/run.py index 9c85421..2151cc8 100644 --- a/web-console/run.py +++ b/web-console/run.py @@ -18,6 +18,7 @@ import os import pathlib import shutil +import socket import subprocess import sys import time @@ -46,6 +47,24 @@ def _find_cmcp() -> str: sys.exit("cmcp not found. Run: pip install cmcp-runtime httpx") +def _wait_for_port(port: int, what: str, timeout: float = 60.0) -> None: + """Block until something is listening on 127.0.0.1:port. + + A fixed sleep used to be enough; cMCP Runtime now does attestation and audit + setup before it binds, so the console raced startup and the first assessment + failed on a refused connection. + """ + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + try: + with socket.create_connection(("127.0.0.1", port), timeout=1): + return + except OSError: + time.sleep(0.25) + sys.exit(f"{what} did not start listening on :{port} within {timeout:.0f}s. " + "See server.log and cmcp.log in this folder.") + + def _ensure_submodule() -> None: if (EXAMPLE / "agent" / "credit_risk_agent.py").exists(): return @@ -83,7 +102,7 @@ def main() -> None: procs.append(subprocess.Popen( [sys.executable, str(EXAMPLE / "server" / "mock_mcp_server.py")], stdout=server_log, stderr=server_log)) - time.sleep(1) + _wait_for_port(8080, "EU credit-risk MCP server") print("-- cMCP gateway on :8443 (CMCP_DEV_MODE=1)", flush=True) env = os.environ.copy() @@ -91,12 +110,12 @@ def main() -> None: procs.append(subprocess.Popen( [_find_cmcp(), "start", "--config", str(GATEWAY_CFG)], stdout=cmcp_log, stderr=cmcp_log, env=env)) - time.sleep(2) + _wait_for_port(8443, "cMCP gateway") print(f"-- web console on http://localhost:{PORT}", flush=True) web = subprocess.Popen([sys.executable, str(HERE / "webserver.py")], env=os.environ.copy()) procs.append(web) - time.sleep(1) + _wait_for_port(int(PORT), "web console") url = f"http://localhost:{PORT}" print(f"\nOpen {url} (opening it for you now). Ctrl+C to stop.\n", flush=True)