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
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.