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_editor_state.py when the first line read from the server does not contain the bytes 'WELCOME UNITY-MCP'. The Unity MCP bridge sends a greeting line on connect; its absence means either the connection was closed immediately (empty line) or the remote service is not the MCP bridge.
Source
Thrown at tools/stress_editor_state.py:80
async def read_frame(reader: asyncio.StreamReader) -> bytes:
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:
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_get_editor_state_frame() -> bytes:
payload = {"type": "get_editor_state", "params": {}}
return json.dumps(payload).encode("utf-8")
async def stress_loop(host: str, port: int, duration: float, interval: float, verbose: bool):
stop_time = time.time() + duration
stats = {"requests": 0, "errors": 0, "reconnects": 0}
print(f"Starting editor state stress test...")
print(f" Target: {host}:{port}")
print(f" Duration: {duration}s")
print(f" Interval: {interval}s ({1/interval:.1f} requests/sec)")
print(f" Press Ctrl+C to stop early")
print()
View on GitHub (pinned to c21bf496bc)
Solutions
- Confirm the port from ~/.unity-mcp/unity-mcp-status-*.json (unity_port field) and pass it via --port.
- Verify the Unity Editor is open with the MCP for Unity package active and the bridge started.
- If the greeting string changed, update the b'WELCOME UNITY-MCP' check in do_handshake.
Defensive patterns
Strategy: validation
Validate before calling
import json
from pathlib import Path
def discover_port() -> int | None:
status_dir = Path.home() / ".unity-mcp"
for f in sorted(status_dir.glob("unity-mcp-status-*.json"), key=lambda p: p.stat().st_mtime, reverse=True):
data = json.loads(f.read_text())
port = int(data.get("unity_port", 0) or 0)
if 0 < port < 65536:
return port
return None
port = discover_port()
if port is None:
raise SystemExit("No active Unity bridge found. Open Unity with MCP for Unity.") Try / catch
try:
await asyncio.wait_for(do_handshake(reader), timeout=TIMEOUT)
except ConnectionError as e:
print(f"Handshake failed: {e}. Is the bridge running on port {port}?")
raise Prevention
- Confirm the bridge port from the status JSON before connecting.
- Ensure Unity is fully loaded and the bridge started.
- If the greeting string changes in a bridge update, update the sentinel bytes.
When it happens
Trigger: Connecting to a port that is not the Unity bridge (e.g. the HTTP server, a different Unity tool, or nothing at all); the bridge accepted the connection but crashed before sending the greeting; the bridge protocol changed and the greeting string was altered.
Common situations: Wrong port discovered or hardcoded; the bridge is mid-startup and hasn't sent the greeting yet; connecting to the Python MCP server's port instead of the Unity-side TCP 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/fd3531a11b5fa195.
Report an issue: GitHub.