mcesptool/INTEGRATION_PATTERNS.md
Ryan Malloy 64c1505a00 Add QEMU ESP32 emulation support
Integrate Espressif's QEMU fork for virtual ESP device management:

- QemuManager component with 5 MCP tools (start/stop/list/status/flash)
- Config auto-detects QEMU binaries from ~/.espressif/tools/
- Supports esp32, esp32s2, esp32s3, esp32c3 chip emulation
- Virtual serial over TCP (socket://localhost:PORT) transparent to esptool
- Scan integration: QEMU instances appear in esp_scan_ports results
- Blank flash images initialized to 0xFF (erased NOR flash state)
- 38 unit tests covering lifecycle, port allocation, flash writes
2026-01-28 15:35:22 -07:00

14 KiB

🔗 Integration Patterns: Arduino MCP ↔ ESPTool MCP

Overview

The ESPTool MCP server is designed to complement and enhance the existing Arduino MCP server, creating a unified ESP development ecosystem. Rather than replacing Arduino workflows, it provides specialized ESP capabilities that work seamlessly together.

🎯 Integration Philosophy

Complementary Architecture

┌─────────────────────┐    ┌─────────────────────┐
│   Arduino MCP       │    │   ESPTool MCP       │
│   Server            │    │   Server            │
├─────────────────────┤    ├─────────────────────┤
│ • Sketch Management │    │ • Direct ESP Control│
│ • Library System    │    │ • Advanced Flashing │
│ • Compilation       │    │ • Security Features │
│ • Basic Upload      │    │ • Production Tools  │
│ • Serial Monitor    │    │ • OTA Management    │
│ • Cross-Platform    │    │ • eFuse Programming │
└─────────────────────┘    └─────────────────────┘
         │                           │
         └─────────┬─────────────────┘
                   │
         ┌─────────▼─────────┐
         │  Unified ESP      │
         │  Development      │
         │  Workflow         │
         └───────────────────┘

🏗️ Integration Patterns

1. Workflow Handoff Pattern

The Arduino MCP server handles high-level development, while ESPTool MCP handles low-level chip operations:

# Example: Advanced ESP32 Development Workflow
class UnifiedESPWorkflow:
    """Orchestrates between Arduino and ESPTool MCP servers"""

    async def complete_esp_development_cycle(self, project_config: dict):
        """End-to-end ESP development using both servers"""

        # Phase 1: Arduino MCP Server - High-level development
        arduino_tasks = [
            "arduino_create_sketch",           # Create project structure
            "arduino_install_library",         # Install ESP32 libraries
            "arduino_write_sketch",            # Write application code
            "arduino_compile_sketch"           # Compile to binary
        ]

        # Phase 2: ESPTool MCP Server - Low-level optimization
        esptool_tasks = [
            "esp_detect_chip",                 # Identify ESP variant
            "esp_firmware_analyze",            # Optimize binary
            "esp_partition_create_ota",        # Setup OTA partitions
            "esp_flash_firmware",              # Advanced flashing
            "esp_security_audit"               # Security validation
        ]

        return await self._execute_unified_workflow(arduino_tasks, esptool_tasks)

2. Shared Configuration Pattern

Both servers share common configuration for seamless operation:

# config/unified_esp_config.py
from pathlib import Path
from dataclasses import dataclass
from typing import List, Dict

@dataclass
class UnifiedESPConfig:
    """Shared configuration between Arduino and ESPTool MCP servers"""

    # Shared paths
    project_roots: List[Path]
    sketch_directory: Path
    libraries_directory: Path
    tools_directory: Path

    # Arduino MCP specific
    arduino_cli_path: str
    arduino_cores: List[str]

    # ESPTool MCP specific
    esptool_path: str
    esp_idf_path: Path
    partition_templates: Dict[str, Path]

    # Shared ESP settings
    default_baud_rate: int = 460800
    connection_timeout: int = 30
    enable_stub_flasher: bool = True

    @classmethod
    def from_environment(cls) -> 'UnifiedESPConfig':
        """Initialize from environment variables and MCP roots"""
        return cls(
            project_roots=cls._get_mcp_roots(),
            sketch_directory=Path(os.getenv('MCP_SKETCH_DIR', '~/Arduino')).expanduser(),
            arduino_cli_path=os.getenv('ARDUINO_CLI_PATH', 'arduino-cli'),
            esptool_path=os.getenv('ESPTOOL_PATH', 'esptool'),
            # ... other config
        )

3. Cross-Server Communication Pattern

Enable servers to coordinate operations and share information:

# coordination/server_bridge.py
class MCPServerBridge:
    """Facilitates communication between Arduino and ESPTool MCP servers"""

    def __init__(self, arduino_server_url: str, esptool_server_url: str):
        self.arduino_client = MCPClient(arduino_server_url)
        self.esptool_client = MCPClient(esptool_server_url)

    async def compile_and_flash_esp(self, sketch_path: str, port: str) -> dict:
        """Coordinate compilation via Arduino MCP and flashing via ESPTool MCP"""

        # Step 1: Use Arduino MCP to compile
        compile_result = await self.arduino_client.call_tool(
            "arduino_compile_sketch",
            {"sketch_path": sketch_path, "board": "esp32"}
        )

        if not compile_result["success"]:
            return {"error": "Compilation failed", "details": compile_result}

        # Step 2: Use ESPTool MCP for advanced flashing
        flash_result = await self.esptool_client.call_tool(
            "esp_flash_firmware",
            {
                "port": port,
                "firmware_files": [{"address": 0x1000, "file": compile_result["binary_path"]}],
                "verify": True,
                "optimize": True
            }
        )

        return {
            "compile_result": compile_result,
            "flash_result": flash_result,
            "success": flash_result.get("success", False)
        }

🎨 Natural Language Workflows

Unified Development Commands

Claude can seamlessly use both servers to provide comprehensive ESP development:

User: "Create an ESP32 weather station with OTA updates"

Claude: I'll create a comprehensive ESP32 weather station with OTA capability using both Arduino and ESP-specific tools.

        # Arduino MCP Server - Application Development
        → arduino_create_sketch("WeatherStation")
        → arduino_install_library("DHT sensor library")
        → arduino_install_library("WiFiManager")
        → arduino_write_sketch(weather_station_code)
        → arduino_compile_sketch()

        # ESPTool MCP Server - Production Setup
        → esp_detect_chip(port="/dev/ttyUSB0")
        → esp_partition_create_ota(app_size="1MB")
        → esp_flash_firmware(with_ota_partitions=True)
        → esp_security_audit()

        ✓ Weather station deployed with OTA updates enabled
        ✓ Secure boot configured for production
        ✓ Ready for remote firmware updates

Development vs Production Workflows

User: "I'm developing an ESP32 project - I want fast iteration"

Claude: I'll set up a development-optimized workflow for rapid testing.

        # Development Mode - Fast Iteration
        → arduino_compile_sketch(board="esp32", debug=True)
        → esp_load_test_firmware(to_ram=True)  # No flash wear
        → serial_monitor(detailed_logging=True)

        # When ready for testing:
        → esp_flash_firmware(development_mode=True)

User: "Now I'm ready for production deployment"

Claude: I'll configure this for production with security and optimization.

        # Production Mode - Security & Optimization
        → esp_firmware_analyze(optimize=True)
        → esp_enable_flash_encryption()
        → esp_enable_secure_boot()
        → esp_flash_firmware(production_mode=True)
        → esp_factory_test_suite()

        ✓ Production deployment complete with security enabled

🔄 Shared Resource Management

Unified Device Detection

# resources/unified_device_manager.py
class UnifiedDeviceManager:
    """Manages ESP devices across both MCP servers"""

    async def get_all_esp_devices(self) -> List[Dict]:
        """Combine device information from both servers"""

        # Get basic device info from Arduino MCP
        arduino_devices = await self._get_arduino_devices()

        # Get detailed ESP info from ESPTool MCP
        esptool_devices = await self._get_esptool_devices()

        # Merge information
        unified_devices = []
        for device in arduino_devices:
            if device['board_type'].startswith('esp'):
                esp_details = self._find_esptool_device(device['port'], esptool_devices)
                unified_devices.append({
                    **device,
                    "esp_details": esp_details,
                    "capabilities": self._determine_capabilities(device, esp_details)
                })

        return unified_devices

    def _determine_capabilities(self, arduino_info: dict, esp_info: dict) -> List[str]:
        """Determine what operations are available for this device"""
        capabilities = ["compile", "upload", "monitor"]  # Arduino basics

        if esp_info:
            capabilities.extend([
                "direct_flash", "partition_management", "security_config",
                "ota_support", "factory_programming"
            ])

        return capabilities

Resource Synchronization

# Both servers expose unified resources
@arduino_mcp.resource("unified://esp_devices")
@esptool_mcp.resource("unified://esp_devices")
async def synchronized_esp_devices() -> str:
    """Synchronized device list across both servers"""
    device_manager = UnifiedDeviceManager()
    devices = await device_manager.get_all_esp_devices()
    return json.dumps(devices, indent=2)

🚀 Advanced Integration Scenarios

1. CI/CD Pipeline Integration

# .github/workflows/esp32-deploy.yml
name: ESP32 Production Deployment

on:
  push:
    tags: ['v*']

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup ESP Development Environment
        run: |
          # Install both MCP servers
          uvx mcp-arduino
          uvx mcp-esptool-server

      - name: Compile Firmware
        run: |
          claude mcp call arduino arduino_compile_sketch \
            --sketch-path ./WeatherStation \
            --board esp32 \
            --optimize-size

      - name: Security Configuration
        run: |
          claude mcp call esptool esp_firmware_analyze \
            --input ./WeatherStation/build/WeatherStation.bin \
            --security-check

      - name: Factory Programming
        run: |
          claude mcp call esptool esp_factory_program \
            --firmware ./WeatherStation/build/WeatherStation.bin \
            --security-keys ./keys/ \
            --partition-table ./partitions/production.csv

2. Development Environment Setup

# setup/unified_development_environment.py
class UnifiedESPDevEnvironment:
    """Sets up complete ESP development environment"""

    async def setup_project(self, project_name: str, project_type: str):
        """Initialize new ESP project with both Arduino and ESP tooling"""

        setup_tasks = {
            "arduino_tasks": [
                ("arduino_create_sketch", {"name": project_name}),
                ("arduino_install_core", {"core": "esp32"}),
                ("arduino_config_init", {})
            ],
            "esptool_tasks": [
                ("esp_config_init", {}),
                ("esp_partition_create_ota", {"project": project_name}),
                ("esp_security_template_create", {"level": "development"})
            ]
        }

        # Execute tasks in parallel where possible
        arduino_results = await self._execute_arduino_tasks(setup_tasks["arduino_tasks"])
        esptool_results = await self._execute_esptool_tasks(setup_tasks["esptool_tasks"])

        return {
            "project_name": project_name,
            "arduino_setup": arduino_results,
            "esptool_setup": esptool_results,
            "ready_for_development": all([arduino_results["success"], esptool_results["success"]])
        }

3. Error Recovery and Diagnostics

# diagnostics/unified_troubleshooting.py
class UnifiedTroubleshooting:
    """Cross-server diagnostic and recovery tools"""

    async def diagnose_esp_issue(self, port: str, symptoms: List[str]) -> dict:
        """Use both servers to diagnose ESP development issues"""

        diagnosis = {
            "port": port,
            "symptoms": symptoms,
            "tests_performed": [],
            "recommendations": []
        }

        # Arduino MCP diagnostics
        arduino_tests = [
            ("serial_check_port", {"port": port}),
            ("arduino_list_boards", {}),
            ("serial_test_connection", {"port": port})
        ]

        # ESPTool MCP diagnostics
        esptool_tests = [
            ("esp_detect_chip", {"port": port}),
            ("esp_security_audit", {"port": port}),
            ("esp_flash_analyze", {"port": port})
        ]

        # Run diagnostics and generate recommendations
        arduino_results = await self._run_arduino_diagnostics(arduino_tests)
        esptool_results = await self._run_esptool_diagnostics(esptool_tests)

        diagnosis.update({
            "arduino_diagnostics": arduino_results,
            "esptool_diagnostics": esptool_results,
            "recommendations": self._generate_recommendations(arduino_results, esptool_results)
        })

        return diagnosis

This integration design ensures that both MCP servers work together seamlessly, providing Claude with a comprehensive toolkit for ESP development that spans from high-level Arduino sketch development to low-level chip programming and production deployment.