- Make get_client() sync (was async but did no async work). Callers
that omitted await silently got a coroutine object instead of the
SerialClient, causing "'coroutine' object has no attribute 'connect'"
errors on every tool call.
- Fix esp32_connect: use get_client_or_none() for init check and
client.event_queue.wait_for() for boot event (wait_event() didn't
exist on SerialClient).
- Normalise Response.data to dict at parse time — firmware returns
bare strings on some error paths, which broke .get() calls in tool
error handlers.
- Remove stale await from ble.py (9 calls) and classic.py (4 calls).
Tested with dual-MCP headless claude session: 26/27 PASS.
The firmware uses UART0 (via USB bridge) with ESP-IDF console disabled,
not UART1 on GPIO4/GPIO5 as originally documented. Updated both docs to
reflect the actual hardware-verified configuration:
- protocol-spec.md: UART peripheral description
- hardware-setup.md: wiring section, monitor section, sdkconfig table,
troubleshooting steps
Rewrite test-scenarios.md with detailed per-step instructions
including exact tool calls, expected responses, negative test
cases, teardown procedures, and an environment variable reference.