# mcbluetooth-esp32 ESP32 Bluetooth test harness MCP server — UART-controlled peripheral for automated E2E Bluetooth testing. ## Overview This project turns an ESP32 into a programmable Bluetooth peripheral that can be controlled via MCP (Model Context Protocol). Combined with [mcbluetooth](https://github.com/supported-systems/mcbluetooth) (Linux BlueZ MCP server), it enables fully automated end-to-end Bluetooth testing orchestrated by an LLM. ``` ┌─────────────────────────────────────────────────────────────────┐ │ LLM (Claude, etc.) │ │ Orchestrates both MCP servers │ └─────────────────────────┬───────────────────────────────────────┘ │ ┌───────────────┴───────────────┐ │ │ ┌───────┴───────┐ ┌───────┴────────┐ │ mcbluetooth │ │mcbluetooth-esp32│ │ (bt_* tools)│ │ (esp32_* tools) │ └───────┬───────┘ └───────┬────────┘ │ │ D-Bus/BlueZ Serial/UART │ │ ┌───────┴───────┐ ┌───────┴────────┐ │ Linux Host │◄── Bluetooth ──►│ ESP32 │ │ (hci0/1) │ (over air) │ (peripheral) │ └───────────────┘ └────────────────┘ ``` ## ESP32 Capabilities The ESP32 can emulate various Bluetooth devices for testing: | Capability | Description | |------------|-------------| | **Classic BT Pairing** | All 4 SSP modes: Just Works, Numeric Comparison, Passkey Entry, Legacy PIN | | **SPP (Serial Port)** | Bidirectional data transfer over Classic Bluetooth virtual serial port | | **HID Device** | Keyboard/mouse combo emulation over Classic Bluetooth | | **BLE GATT Server** | Dynamic service/characteristic creation at runtime | | **Device Personas** | Presets for headset, speaker, keyboard, sensor, phone | | **IO Capabilities** | Configurable: `no_io`, `display_only`, `display_yesno`, `keyboard_only`, `keyboard_display` | | **Event Reporting** | Real-time events: pair_request, pair_complete, spp_data, hid_connect, gatt_read, gatt_write | ### Hardware Requirements - **Original ESP32** (ESP32-D0WD or similar) — must have Classic Bluetooth support - ESP32-S3, C3, H2, S2 will NOT work (no BR/EDR radio) - USB cable for serial communication ### Verified Hardware | Property | Value | |----------|-------| | Chip | ESP32-D0WD-V3 (rev 3.1) | | Features | Wi-Fi, BT Classic + BLE, Dual Core 240MHz | | Flash | 4MB | | Protocol | NDJSON over UART @ 115200 baud | ## Quick Start ### 1. Flash the firmware ```bash # Install ESP-IDF v5.x first (see docs/hardware-setup.md) cd firmware idf.py set-target esp32 idf.py -p /dev/ttyUSB0 flash ``` ### 2. Run the MCP server ```bash # Install and run uvx mcbluetooth-esp32 # Or with explicit port ESP32_SERIAL_PORT=/dev/ttyUSB0 uvx mcbluetooth-esp32 ``` ### 3. Add to Claude Code ```bash claude mcp add mcbluetooth-esp32 -- uvx mcbluetooth-esp32 ``` ### 4. Verify connection ```python esp32_connect() # → connected=true, ready=true esp32_ping() # → {pong: true} esp32_get_info() # → chip, fw_version, bt_mac ``` ## MCP Tools ### Connection & System | Tool | Description | |------|-------------| | `esp32_connect` | Open serial connection to ESP32 | | `esp32_disconnect` | Close serial connection | | `esp32_ping` | Verify UART link is alive | | `esp32_status` | Get current BT/BLE state | | `esp32_get_info` | Get chip model, firmware version, MAC | | `esp32_reset` | Reboot the ESP32 | ### Configuration | Tool | Description | |------|-------------| | `esp32_configure` | Set device name, IO capabilities, PIN | | `esp32_set_ssp_mode` | Configure SSP pairing mode with optional auto-accept | | `esp32_load_persona` | Load preset device profile (headset, speaker, etc.) | ### Classic Bluetooth | Tool | Description | |------|-------------| | `esp32_classic_enable` | Enable BR/EDR radio | | `esp32_classic_disable` | Disable BR/EDR radio | | `esp32_classic_set_discoverable` | Make device visible for pairing | | `esp32_classic_pair_respond` | Accept/reject incoming pairing | ### SPP (Serial Port Profile) | Tool | Description | |------|-------------| | `esp32_spp_send` | Send data (text or hex) over SPP connection | | `esp32_spp_disconnect` | Close the active SPP connection | | `esp32_spp_status` | Get SPP connection status and remote address | ### HID (Human Interface Device) | Tool | Description | |------|-------------| | `esp32_hid_enable` | Enable HID device (keyboard/mouse combo) | | `esp32_hid_disable` | Disable HID device | | `esp32_hid_connect` | Connect to a specific HID host | | `esp32_hid_disconnect` | Disconnect from current host | | `esp32_hid_send_keyboard` | Send keyboard report (keys + modifiers) | | `esp32_hid_send_mouse` | Send mouse report (movement + buttons) | | `esp32_hid_status` | Get HID connection status | ### BLE / GATT | Tool | Description | |------|-------------| | `esp32_ble_enable` | Enable BLE radio | | `esp32_ble_disable` | Disable BLE radio | | `esp32_ble_advertise` | Start/stop BLE advertising | | `esp32_ble_set_adv_data` | Configure advertisement data | | `esp32_gatt_add_service` | Create GATT service | | `esp32_gatt_add_characteristic` | Add characteristic to service | | `esp32_gatt_set_value` | Set characteristic value | | `esp32_gatt_notify` | Send notification to subscribers | | `esp32_gatt_clear` | Remove all GATT services | ### Events | Tool | Description | |------|-------------| | `esp32_get_events` | Retrieve event history | | `esp32_wait_event` | Block until specific event occurs | | `esp32_clear_events` | Clear event history | ## E2E Testing Run automated tests using Claude CLI in headless mode: ```bash # Setup test environment mkdir -p /tmp/bt-e2e-test && cd /tmp/bt-e2e-test git init # Create MCP config with both servers cat > .mcp.json << 'EOF' { "mcpServers": { "esp32": { "type": "stdio", "command": "uvx", "args": ["mcbluetooth-esp32"], "env": {"ESP32_SERIAL_PORT": "/dev/ttyUSB0"} }, "bluez": { "type": "stdio", "command": "uvx", "args": ["mcbluetooth"] } } } EOF # Run test suite PROMPT=$(cat tests/prompts/test-prompt-v5.md) claude -p "$PROMPT" \ --mcp-config .mcp.json \ --allowedTools "mcp__esp32__*,mcp__bluez__*" \ --output-format json ``` ### Test Coverage (v5 - 76 tests) | Phase | Tests | Coverage | |-------|-------|----------| | ESP32 Connection | 1-4 | connect, ping, info, status | | BlueZ Adapter | 5-8 | list, info, pairable, discoverable | | Classic BT + SSP | 9-24 | Full pairing workflow with auto-accept | | BLE GATT Setup | 30-42 | Battery Service, Environmental Sensing | | HCI Capture | 43-55 | btsnoop capture and analysis | | GATT Operations | 56-63 | read, write, notify, subscribe | | Cleanup | 64-76 | Adapter management, reset | ### Recent Test Results | Version | Tests | Result | Duration | |---------|-------|--------|----------| | v4 | 71 | 66 PASS, 5 PARTIAL | 8.6 min | | v5 | 76 | **76/76 PASS (100%)** | 7.6 min | ## Documentation | Document | Description | |----------|-------------| | [hardware-setup.md](docs/hardware-setup.md) | ESP-IDF installation, flashing, wiring | | [protocol-spec.md](docs/protocol-spec.md) | NDJSON UART protocol specification | | [test-scenarios.md](docs/test-scenarios.md) | Manual E2E test procedures | | [automated-e2e-testing.md](docs/automated-e2e-testing.md) | Claude CLI headless testing guide | ## Development ```bash # Install dev dependencies uv sync --all-extras # Run unit tests uv run pytest tests/ -v --ignore=tests/integration # Run integration tests (requires ESP32) ESP32_SERIAL_PORT=/dev/ttyUSB0 uv run pytest tests/integration/ -v # Lint uv run ruff check src/ ``` ## License MIT