mcesptool/PROJECT_SETUP.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

19 KiB
Raw Blame History

🚀 FastMCP ESPTool Server - Project Setup & Initialization

Quick Start

# 1. Clone and initialize project
git clone <repository-url> mcp-esptool-server
cd mcp-esptool-server

# 2. Set up Python environment with uv
uv venv
uv pip install -e ".[dev]"

# 3. Start development server
uv run mcp-esptool-server

# 4. Add to Claude Desktop
claude mcp add esptool "uv run mcp-esptool-server"

📁 Project Structure

mcp-esptool-server/
├── src/
│   └── mcp_esptool_server/
│       ├── __init__.py
│       ├── server.py                    # Main FastMCP server
│       ├── config.py                    # Configuration management
│       ├── middleware/                  # Middleware system
│       │   ├── __init__.py
│       │   ├── logger_interceptor.py    # Base middleware classes
│       │   ├── esptool_middleware.py    # ESPTool-specific middleware
│       │   └── idf_middleware.py        # ESP-IDF middleware
│       └── components/                  # Tool components
│           ├── __init__.py
│           ├── chip_control.py          # ESP chip operations
│           ├── flash_manager.py         # Flash memory management
│           ├── partition_manager.py     # Partition table tools
│           ├── security_manager.py      # Security & eFuse tools
│           ├── firmware_builder.py      # Binary processing
│           ├── ota_manager.py           # OTA update tools
│           ├── production_tools.py      # Factory programming
│           ├── diagnostics.py           # Debug & analysis
│           └── idf_host_apps.py         # ESP-IDF host applications
├── tests/
│   ├── unit/                           # Unit tests
│   ├── integration/                    # Integration tests
│   ├── end_to_end/                    # Full workflow tests
│   └── fixtures/                      # Test data
├── examples/                          # Usage examples
├── docs/                             # Additional documentation
├── scripts/                          # Development scripts
├── docker/                           # Docker configuration
├── .env.example                      # Environment template
├── .gitignore
├── Dockerfile
├── docker-compose.yml
├── Makefile
├── pyproject.toml
└── README.md

🔧 Configuration Setup

Environment Variables

# .env file configuration
# Copy from .env.example and customize

# Core paths
ESPTOOL_PATH=esptool                     # esptool binary path
ESP_IDF_PATH=/opt/esp-idf                # ESP-IDF installation
MCP_PROJECT_ROOTS=/workspace/projects    # Project directories

# Communication settings
ESP_DEFAULT_BAUD_RATE=460800            # Default baud rate
ESP_CONNECTION_TIMEOUT=30               # Connection timeout (seconds)
ESP_ENABLE_STUB_FLASHER=true           # Enable stub flasher

# Middleware options
MCP_ENABLE_PROGRESS=true                # Enable progress tracking
MCP_ENABLE_ELICITATION=true            # Enable user interaction
MCP_LOG_LEVEL=INFO                     # Logging level

# Development settings
DEV_ENABLE_HOT_RELOAD=true             # Hot reload in development
DEV_MOCK_HARDWARE=false                # Mock hardware for testing
DEV_ENABLE_TRACING=false               # Enable detailed tracing

# Production settings
PROD_ENABLE_SECURITY_AUDIT=true        # Security auditing
PROD_REQUIRE_CONFIRMATIONS=true        # User confirmations
PROD_MAX_CONCURRENT_OPERATIONS=5       # Concurrent operation limit

Python Configuration

# pyproject.toml
[project]
name = "mcp-esptool-server"
version = "2025.09.28.1"
description = "FastMCP server for ESP32/ESP8266 development with esptool integration"
readme = "README.md"
requires-python = ">=3.10"
license = { text = "MIT" }
authors = [
    { name = "Your Name", email = "your.email@example.com" }
]

keywords = [
    "mcp", "model-context-protocol", "esp32", "esp8266",
    "esptool", "esp-idf", "embedded", "iot", "fastmcp"
]

classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Topic :: Software Development :: Embedded Systems",
    "Topic :: Scientific/Engineering :: Artificial Intelligence",
]

dependencies = [
    "fastmcp>=2.12.4",              # FastMCP framework
    "esptool>=5.0.0",               # ESPTool Python API
    "pyserial>=3.5",                # Serial communication
    "pyserial-asyncio>=0.6",        # Async serial support
    "thefuzz[speedup]>=0.22.1",     # Fuzzy string matching
    "pydantic>=2.0.0",              # Data validation
    "click>=8.0.0",                 # CLI framework
    "rich>=13.0.0",                 # Rich console output
]

[project.optional-dependencies]
dev = [
    "pytest>=8.4.2",
    "pytest-asyncio>=0.21.0",
    "pytest-cov>=7.0.0",
    "pytest-mock>=3.12.0",
    "ruff>=0.13.2",
    "mypy>=1.5.0",
    "black>=23.0.0",
    "pre-commit>=3.0.0",
    "watchdog>=3.0.0",            # Hot reload support
]

idf = [
    "esp-idf-tools>=2.0.0",       # ESP-IDF integration
    "kconfiglib>=14.1.0",         # Kconfig parsing
]

testing = [
    "pytest-xdist>=3.0.0",       # Parallel testing
    "pytest-benchmark>=4.0.0",    # Performance testing
    "factory-boy>=3.3.0",        # Test data factories
]

docs = [
    "mkdocs>=1.5.0",
    "mkdocs-material>=9.0.0",
    "mkdocs-mermaid2-plugin>=1.0.0",
]

[project.scripts]
mcp-esptool-server = "mcp_esptool_server.server:main"
esptool-mcp = "mcp_esptool_server.cli:cli"

[project.urls]
Homepage = "https://github.com/yourusername/mcp-esptool-server"
Repository = "https://github.com/yourusername/mcp-esptool-server"
Issues = "https://github.com/yourusername/mcp-esptool-server/issues"
Documentation = "https://yourusername.github.io/mcp-esptool-server"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/mcp_esptool_server"]

[tool.ruff]
line-length = 100
target-version = "py310"
src = ["src", "tests"]

[tool.ruff.lint]
select = ["E", "F", "W", "B", "I", "N", "UP", "ANN", "S", "C4", "DTZ", "T20"]
ignore = ["E501", "ANN101", "ANN102", "S101"]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101", "ANN"]

[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
strict_optional = true

[[tool.mypy.overrides]]
module = "esptool.*"
ignore_missing_imports = true

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
addopts = [
    "--cov=src/mcp_esptool_server",
    "--cov-report=html",
    "--cov-report=term-missing",
    "--cov-fail-under=85"
]

[tool.coverage.run]
source = ["src"]
omit = ["tests/*", "*/test_*"]

[tool.coverage.report]
exclude_lines = [
    "pragma: no cover",
    "def __repr__",
    "raise AssertionError",
    "raise NotImplementedError",
]

🐳 Docker Development Environment

docker-compose.yml

# Development environment with hot reload
services:
  mcp-esptool-server:
    build:
      context: .
      dockerfile: Dockerfile
      target: ${MODE:-development}
    container_name: mcp-esptool-dev
    environment:
      - LOG_LEVEL=${LOG_LEVEL:-DEBUG}
      - MCP_PROJECT_ROOTS=/workspace/projects
      - ESPTOOL_PATH=/usr/local/bin/esptool
      - ESP_IDF_PATH=/opt/esp-idf
      - DEV_ENABLE_HOT_RELOAD=true
    volumes:
      # Development volumes for hot reload
      - ./src:/app/src:ro
      - ./tests:/app/tests:ro
      - ./examples:/app/examples:ro
      # Project workspace
      - esp-projects:/workspace/projects
      # ESP-IDF and tools
      - esp-idf:/opt/esp-idf
      - esp-tools:/opt/esp-tools
      # Device access (Linux)
      - /dev:/dev:ro
    ports:
      - "${SERVER_PORT:-8080}:8080"
    networks:
      - default
      - caddy
    labels:
      # Caddy reverse proxy
      caddy: ${CADDY_DOMAIN:-esp-tools.local}
      caddy.reverse_proxy: "{{upstreams 8080}}"
    restart: unless-stopped
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: pyproject.toml

  # ESP-IDF development environment
  esp-idf-dev:
    image: espressif/idf:latest
    container_name: esp-idf-tools
    volumes:
      - esp-idf:/opt/esp-idf
      - esp-tools:/opt/esp-tools
      - esp-projects:/workspace/projects
    command: tail -f /dev/null
    networks:
      - default

volumes:
  esp-projects:
    name: ${COMPOSE_PROJECT_NAME:-mcp-esptool}-projects
  esp-idf:
    name: ${COMPOSE_PROJECT_NAME:-mcp-esptool}-idf
  esp-tools:
    name: ${COMPOSE_PROJECT_NAME:-mcp-esptool}-tools

networks:
  default:
    name: ${COMPOSE_PROJECT_NAME:-mcp-esptool}
  caddy:
    external: true

Multi-stage Dockerfile

# Dockerfile
# Base image with Python and system dependencies
FROM python:3.11-slim-bookworm AS base

# Install system dependencies
RUN apt-get update && apt-get install -y \
    git \
    curl \
    wget \
    build-essential \
    cmake \
    ninja-build \
    libusb-1.0-0-dev \
    && rm -rf /var/lib/apt/lists/*

# Install uv for fast Python package management
RUN pip install uv

# Create app user
RUN useradd --create-home --shell /bin/bash app
USER app
WORKDIR /app

# Development stage
FROM base AS development

# Copy project files
COPY --chown=app:app pyproject.toml ./
COPY --chown=app:app src/ ./src/

# Install development dependencies
RUN uv venv && \
    uv pip install -e ".[dev,idf,testing]"

# Install esptool and ESP-IDF
RUN uv pip install esptool
ENV PATH="/home/app/.local/bin:$PATH"

# Configure ESP-IDF (development version)
RUN git clone --depth 1 --branch v5.1 https://github.com/espressif/esp-idf.git /opt/esp-idf
RUN cd /opt/esp-idf && ./install.sh esp32

# Set up environment
ENV ESP_IDF_PATH=/opt/esp-idf
ENV PATH="$ESP_IDF_PATH/tools:$PATH"

EXPOSE 8080
CMD ["uv", "run", "mcp-esptool-server"]

# Production stage
FROM base AS production

# Copy only necessary files
COPY --chown=app:app pyproject.toml ./
COPY --chown=app:app src/ ./src/

# Install production dependencies only
RUN uv venv && \
    uv pip install -e ".[idf]" --no-dev

# Install production tools
RUN uv pip install esptool
ENV PATH="/home/app/.local/bin:$PATH"

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

EXPOSE 8080
CMD ["uv", "run", "mcp-esptool-server", "--production"]

🛠️ Development Tools

Makefile

# Makefile for development tasks
.PHONY: help install dev test lint format clean docker-build docker-up docker-down

# Default target
help:
	@echo "Available targets:"
	@echo "  install     - Install dependencies"
	@echo "  dev         - Start development server"
	@echo "  test        - Run tests"
	@echo "  lint        - Run linting"
	@echo "  format      - Format code"
	@echo "  clean       - Clean build artifacts"
	@echo "  docker-up   - Start Docker development environment"
	@echo "  docker-down - Stop Docker environment"

# Installation
install:
	uv venv
	uv pip install -e ".[dev,idf,testing]"

# Development server
dev:
	uv run mcp-esptool-server --debug

# Testing
test:
	uv run pytest tests/ -v

test-cov:
	uv run pytest tests/ --cov=src --cov-report=html --cov-report=term

test-integration:
	uv run pytest tests/integration/ -v

# Code quality
lint:
	uv run ruff check src/ tests/
	uv run mypy src/

format:
	uv run ruff format src/ tests/
	uv run ruff check --fix src/ tests/

# Cleanup
clean:
	find . -type d -name "__pycache__" -exec rm -rf {} +
	find . -type f -name "*.pyc" -delete
	rm -rf dist/ build/ *.egg-info/
	rm -rf htmlcov/ .coverage
	rm -rf .pytest_cache/

# Docker operations
docker-build:
	docker compose build

docker-up:
	docker compose up -d

docker-down:
	docker compose down

docker-logs:
	docker compose logs -f mcp-esptool-server

# ESP-IDF specific
esp-idf-setup:
	docker compose exec esp-idf-dev /opt/esp-idf/install.sh

# Add to Claude Desktop
claude-add:
	claude mcp add esptool "uv run mcp-esptool-server"

# Remove from Claude Desktop
claude-remove:
	claude mcp remove esptool

🔧 Development Scripts

scripts/setup.py

#!/usr/bin/env python3
"""Setup script for MCP ESPTool Server development environment."""

import os
import subprocess
import sys
from pathlib import Path

def run_command(cmd: str, check: bool = True) -> subprocess.CompletedProcess:
    """Run command and handle errors."""
    print(f"Running: {cmd}")
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)

    if check and result.returncode != 0:
        print(f"Error running command: {cmd}")
        print(f"Stdout: {result.stdout}")
        print(f"Stderr: {result.stderr}")
        sys.exit(1)

    return result

def setup_python_environment():
    """Set up Python environment with uv."""
    print("🐍 Setting up Python environment...")

    # Check if uv is installed
    result = run_command("uv --version", check=False)
    if result.returncode != 0:
        print("Installing uv...")
        run_command("pip install uv")

    # Create virtual environment and install dependencies
    run_command("uv venv")
    run_command("uv pip install -e '.[dev,idf,testing]'")

    print("✅ Python environment ready!")

def setup_esptool():
    """Set up esptool."""
    print("🔧 Setting up esptool...")
    run_command("uv pip install esptool")

    # Verify installation
    result = run_command("uv run esptool version", check=False)
    if result.returncode == 0:
        print("✅ ESPTool ready!")
    else:
        print("⚠️ ESPTool installation may have issues")

def setup_esp_idf():
    """Set up ESP-IDF (optional)."""
    print("🏗️ Checking ESP-IDF installation...")

    esp_idf_path = os.environ.get('ESP_IDF_PATH')
    if esp_idf_path and Path(esp_idf_path).exists():
        print(f"✅ ESP-IDF found at {esp_idf_path}")
    else:
        print("⚠️ ESP-IDF not found. Install manually or use Docker environment.")
        print("   Docker: make docker-up")
        print("   Manual: https://docs.espressif.com/projects/esp-idf/en/latest/get-started/")

def setup_git_hooks():
    """Set up git pre-commit hooks."""
    print("🪝 Setting up git hooks...")

    if Path(".git").exists():
        run_command("uv run pre-commit install")
        print("✅ Git hooks installed!")
    else:
        print("⚠️ Not a git repository, skipping hooks")

def create_env_file():
    """Create .env file from template."""
    print("📝 Creating environment file...")

    env_file = Path(".env")
    env_example = Path(".env.example")

    if not env_file.exists() and env_example.exists():
        env_file.write_text(env_example.read_text())
        print("✅ Created .env file from template")
        print("   Please review and customize .env file")
    else:
        print(" .env file already exists or no template found")

def verify_installation():
    """Verify the installation."""
    print("🔍 Verifying installation...")

    # Test import
    result = run_command("uv run python -c 'import mcp_esptool_server; print(\"Import successful\")'", check=False)
    if result.returncode == 0:
        print("✅ Package import successful!")
    else:
        print("❌ Package import failed!")
        return False

    # Test server startup (dry run)
    result = run_command("uv run mcp-esptool-server --help", check=False)
    if result.returncode == 0:
        print("✅ Server startup test successful!")
    else:
        print("❌ Server startup test failed!")
        return False

    return True

def main():
    """Main setup function."""
    print("🚀 Setting up MCP ESPTool Server development environment...")

    # Change to project directory
    project_root = Path(__file__).parent.parent
    os.chdir(project_root)

    try:
        setup_python_environment()
        setup_esptool()
        setup_esp_idf()
        setup_git_hooks()
        create_env_file()

        if verify_installation():
            print("\n🎉 Setup completed successfully!")
            print("\nNext steps:")
            print("1. Review and customize .env file")
            print("2. Start development server: make dev")
            print("3. Add to Claude Desktop: make claude-add")
            print("4. Or use Docker: make docker-up")
        else:
            print("\n❌ Setup completed with errors. Check output above.")
            sys.exit(1)

    except KeyboardInterrupt:
        print("\n⚠️ Setup interrupted by user")
        sys.exit(1)
    except Exception as e:
        print(f"\n❌ Setup failed: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()

🚀 Getting Started Guide

1. Initial Setup

# Clone repository
git clone <repository-url> mcp-esptool-server
cd mcp-esptool-server

# Run automated setup
python scripts/setup.py

# Or manual setup
make install

2. Configuration

# Copy and customize environment
cp .env.example .env
# Edit .env with your settings

# Set up ESP-IDF (optional)
# Option A: Use Docker
make docker-up

# Option B: Install locally
# Follow: https://docs.espressif.com/projects/esp-idf/en/latest/get-started/

3. Development

# Start development server
make dev

# Run tests
make test

# Code formatting
make format

# Add to Claude Desktop
make claude-add

4. Verification

# Test basic functionality
uv run python -c "
import asyncio
from mcp_esptool_server.server import main
print('✅ Server imports successfully')
"

# Test with Claude
claude mcp list
# Should show 'esptool' server

This setup provides a complete development environment with modern Python tooling, Docker support, and comprehensive testing infrastructure.

★ Insight ───────────────────────────────────── Modern Python Project Structure: This setup follows current best practices with uv for dependency management, ruff for linting/formatting, and comprehensive testing infrastructure. The multi-stage Docker setup enables both development and production deployments.

Development Experience Focus: The Makefile and scripts prioritize developer productivity with hot reload, automated testing, and easy Claude Desktop integration. The setup script automates the entire development environment creation.

Production Readiness: The configuration separates development and production concerns while maintaining compatibility with your preferred infrastructure patterns (Caddy, Docker Compose, environment variables). ─────────────────────────────────────────────────

<function_calls> [{"content": "Create project initialization and setup documentation", "status": "completed", "activeForm": "Creating project initialization and setup documentation"}, {"content": "Design concrete implementation examples", "status": "in_progress", "activeForm": "Designing concrete implementation examples"}, {"content": "Create production deployment guide", "status": "pending", "activeForm": "Creating production deployment guide"}]