CoplayDev/unity-mcp · error · ConnectionError
Unexpected handshake from server: {line!r}
Error message
Unexpected handshake from server: {line!r} What it means
Raised by do_handshake() in stress_mcp.py when the first line from the server does not contain 'WELCOME UNITY-MCP'. The bridge sends 'WELCOME UNITY-MCP 1 FRAMING=1\n' on connect; a mismatch means the connection was closed before the greeting (empty line) or the remote service is not the Unity bridge.
Source
Thrown at tools/stress_mcp.py:82
header = await read_exact(reader, 8)
(length,) = struct.unpack(">Q", header)
if length <= 0 or length > (64 * 1024 * 1024):
raise ValueError(f"Invalid frame length: {length}")
return await read_exact(reader, length)
async def write_frame(writer: asyncio.StreamWriter, payload: bytes) -> None:
header = struct.pack(">Q", len(payload))
writer.write(header)
writer.write(payload)
await asyncio.wait_for(writer.drain(), timeout=TIMEOUT)
async def do_handshake(reader: asyncio.StreamReader) -> None:
# Server sends a single line handshake: "WELCOME UNITY-MCP 1 FRAMING=1\n"
line = await reader.readline()
if not line or b"WELCOME UNITY-MCP" not in line:
raise ConnectionError(f"Unexpected handshake from server: {line!r}")
def make_ping_frame() -> bytes:
return b"ping"
def make_execute_menu_item(menu_path: str) -> bytes:
# Retained for manual debugging; not used in normal stress runs
payload = {"type": "execute_menu_item", "params": {
"action": "execute", "menu_path": menu_path}}
return json.dumps(payload).encode("utf-8")
async def client_loop(idx: int, host: str, port: int, stop_time: float, stats: dict):
reconnect_delay = 0.2
while time.time() < stop_time:
writer = None
try:View on GitHub (pinned to c21bf496bc)
Solutions
- Check ~/.unity-mcp/unity-mcp-status-*.json for the current unity_port and pass it explicitly.
- Ensure Unity is fully loaded with the MCP for Unity package before starting the stress test.
- If the greeting changed in a bridge update, update the b'WELCOME UNITY-MCP' sentinel in do_handshake.
Defensive patterns
Strategy: validation
Validate before calling
import json, socket
from pathlib import Path
def bridge_ready(host: str, port: int) -> bool:
if not (0 < port < 65536):
return False
try:
with socket.create_connection((host, port), timeout=2.0) as s:
greeting = s.recv(128)
return b"WELCOME UNITY-MCP" in greeting
except OSError:
return False Try / catch
try:
await asyncio.wait_for(do_handshake(reader), timeout=TIMEOUT)
except ConnectionError:
stats["disconnects"] += 1
await asyncio.sleep(reconnect_delay)
continue Prevention
- Verify the bridge port and greeting via the status JSON before launching clients.
- Ensure Unity is fully initialized before starting the stress test.
- Update the sentinel string if the bridge greeting protocol changes.
When it happens
Trigger: Connecting to the wrong port (HTTP server, a different tool, or nothing); the bridge accepted then dropped the connection before sending the greeting; the bridge is still starting up and the connection was reset; the greeting protocol string was changed in a newer bridge version.
Common situations: discover_port returned the default 6400 but the bridge is on a different port; the editor hasn't finished initializing the bridge; connecting to the MCP Python server port instead of the Unity-side bridge.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Unexpected handshake from server: {line!r}
- Invalid frame length: {length}
- Invalid frame length: {length}
- Connection closed while reading
- Connection closed while reading
AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13).
Data as JSON: /api/errors/6ee42153d0a31445.
Report an issue: GitHub.