Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 55 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,72 @@ This project is currently a **Proof of Concept (PoC)** for an MCP server tailore

**Current Capabilities:**

**Core Features:**
* `run_esp_idf_install`: Install ESP-IDF dependencies and toolchain via `install.sh`.
**Core Features (30 tools available):**

**Project Management:**
* `create_esp_project`: Create a new ESP-IDF project.
* `setup_project_esp_target`: Set target chip for ESP-IDF projects (esp32, esp32c3, esp32s3, etc.).
* `get_project_info`: Get detailed information about an ESP-IDF project.
* `list_components`: List all components in an ESP-IDF project.

**Build & Flash:**
* `build_esp_project`: Build ESP-IDF projects with incremental build support.
* `list_esp_serial_ports`: List available serial ports for ESP devices.
* `clean_esp_project`: Clean build files from an ESP-IDF project.
* `flash_esp_project`: Flash built firmware to connected ESP devices.
* `erase_flash_esp`: Erase flash memory on ESP device.
* `flash_and_monitor_esp`: Flash firmware and immediately monitor serial output.

**Device Operations:**
* `list_esp_serial_ports`: List available serial ports for ESP devices.
* `monitor_esp`: Monitor serial output from ESP device.

**Configuration:**
* `menuconfig_esp`: Run menuconfig to configure ESP-IDF project.
* `get_project_config`: Get project configuration information (sdkconfig).
* `set_esp_partition`: Set partition table for ESP-IDF project.
* `get_esp_idf_version`: Get ESP-IDF version information.
* `check_esp_idf_env`: Check ESP-IDF environment status and configuration.
* `run_esp_idf_install`: Run install.sh script in ESP-IDF directory.

**Debugging:**
* `gdb_attach`: Attach GDB debugger to ESP device.
* `get_core_dump`: Get core dump information from ESP device.

**Runtime Analysis:**
* `get_heap_info`: Get heap memory information from ESP device.
* `get_task_stats`: Get FreeRTOS task statistics from ESP device.

**Testing:**
* `run_pytest`: Run pytest tests with pytest-embedded support for ESP-IDF projects.

**File Operations:**
* `read_file`: Read contents of a file in the project.
* `write_file`: Write content to a file in the project.
* `list_files`: List files and directories in a project path.

**Analysis Tools:**
* `parse_build_log`: Parse and analyze build log with structured output for AI analysis.
* `analyze_memory_map`: Analyze memory usage from .map file.
* `compare_sdkconfig`: Compare two sdkconfig files and output structured differences.
* `analyze_dependencies`: Analyze component dependencies from CMakeLists.txt files.
* `format_device_log`: Parse and format device serial logs with structured output.

**Additional Features:**
* Flexible ESP-IDF path management: supports per-project ESP-IDF versions via `idf_path` parameter.
* Dynamic ESP-IDF path detection: automatically reads IDF_PATH from MCP configuration.
* SDK config management: supports custom `sdkconfig_defaults` files for build configuration (multiple files can be specified separated by semicolons).
* Build time tracking for performance monitoring.
* Optional port specification for flashing operations.
* Includes experimental support for automatic issue fixing based on build logs.
* Windows Git Bash path compatibility: automatic path conversion for cross-platform support.

**Recent Bug Fixes & Improvements:**
* Fixed event loop nesting errors by implementing synchronous server communication
* Fixed JSON-RPC protocol errors with proper empty line handling
* Improved environment variable handling with better error messages
* Fixed subprocess encoding issues (UTF-8 with error replacement)
* Added Git Bash path conversion for Windows (E:/path → /e/path)
* Implemented dynamic ESP-IDF path detection from MCP configuration
* Added comprehensive error handling and logging

**Vision & Future Work:**
The long-term vision is to expand this MCP into a comprehensive toolkit for interacting with embedded devices, potentially integrating with home assistant platforms, and streamlining documentation access for ESP-IDF and related technologies.
Expand Down
Binary file added __pycache__/esp_utils.cpython-311.pyc
Binary file not shown.
Binary file added __pycache__/main.cpython-311.pyc
Binary file not shown.
215 changes: 215 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
"""
Configuration module for ESP MCP Server
Centralized configuration management to avoid hardcoded values
"""
import os
import logging
from typing import List, Dict, Any

logger = logging.getLogger(__name__)


class MCPConfig:
"""Configuration class for ESP MCP Server"""

# MCP Server Information
PROTOCOL_VERSION = "2024-11-05"
SERVER_VERSION = "1.0.0"
SERVER_NAME = "esp-mcp"

# Command Execution Settings
DEFAULT_COMMAND_TIMEOUT = 300 # seconds (5 minutes)
SERIAL_PORT_TIMEOUT = 10 # seconds
GDB_TIMEOUT = 600 # seconds (10 minutes for GDB)

# Serial Port Settings
DEFAULT_FLASH_BAUD = 460800
DEFAULT_MONITOR_BAUD = 115200

# Common Serial Ports (fallback list)
COMMON_SERIAL_PORTS = [
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6",
"/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyACM0", "/dev/ttyACM1",
"/dev/cu.usbserial-*", "/dev/cu.SLAB_USBtoUART"
]

# Windows Fallback Paths
DEFAULT_SYSTEM_ROOT = r"C:\Windows"
MODE_COM_PATH = os.path.join("System32", "mode.com")

# Result Limits (for analysis tools)
MAX_ERRORS = 20
MAX_WARNINGS = 20
MAX_SYMBOLS = 50
MAX_ADDED_CONFIGS = 50
MAX_REMOVED_CONFIGS = 50
MAX_MODIFIED_CONFIGS = 100
MAX_LOG_ENTRIES = 200
MAX_CRASHES = 10
MAX_ERRORS_LOG = 20

# Display Settings
LOG_DEBUG_LENGTH = 200 # Characters to show in debug logs
TIME_FORMAT_MINUTES = "{minutes}m {seconds}s"
TIME_FORMAT_SECONDS = "{seconds}s"

# Encoding Settings
DEFAULT_ENCODING = 'utf-8'
ENCODING_ERRORS = 'replace'

# File Operation Limits
DEFAULT_READ_LINES = 100

@classmethod
def load_from_env(cls) -> 'MCPConfig':
"""Load configuration from environment variables

Environment variables:
- ESP_MCP_TIMEOUT: Default command timeout in seconds
- ESP_MCP_SERIAL_TIMEOUT: Serial port timeout in seconds
- ESP_MCP_FLASH_BAUD: Default flash baud rate
- ESP_MCP_MONITOR_BAUD: Default monitor baud rate
- ESP_MCP_MAX_ERRORS: Maximum errors to return in analysis
- ESP_MCP_MAX_WARNINGS: Maximum warnings to return in analysis

Returns:
MCPConfig: Configuration instance with environment overrides
"""
config = cls()

# Load timeout settings
if 'ESP_MCP_TIMEOUT' in os.environ:
try:
value = int(os.environ['ESP_MCP_TIMEOUT'])
if value > 0:
config.DEFAULT_COMMAND_TIMEOUT = value
logger.debug(f"Loaded timeout from env: {config.DEFAULT_COMMAND_TIMEOUT}s")
else:
logger.warning("Invalid ESP_MCP_TIMEOUT value (must be > 0), using default")
except ValueError:
logger.warning("Invalid ESP_MCP_TIMEOUT value, using default")

if 'ESP_MCP_SERIAL_TIMEOUT' in os.environ:
try:
value = int(os.environ['ESP_MCP_SERIAL_TIMEOUT'])
if value > 0:
config.SERIAL_PORT_TIMEOUT = value
logger.debug(f"Loaded serial timeout from env: {config.SERIAL_PORT_TIMEOUT}s")
else:
logger.warning("Invalid ESP_MCP_SERIAL_TIMEOUT value (must be > 0), using default")
except ValueError:
logger.warning("Invalid ESP_MCP_SERIAL_TIMEOUT value, using default")

# Load baud rate settings
if 'ESP_MCP_FLASH_BAUD' in os.environ:
try:
value = int(os.environ['ESP_MCP_FLASH_BAUD'])
if value > 0:
config.DEFAULT_FLASH_BAUD = value
logger.debug(f"Loaded flash baud from env: {config.DEFAULT_FLASH_BAUD}")
else:
logger.warning("Invalid ESP_MCP_FLASH_BAUD value (must be > 0), using default")
except ValueError:
logger.warning("Invalid ESP_MCP_FLASH_BAUD value, using default")

if 'ESP_MCP_MONITOR_BAUD' in os.environ:
try:
value = int(os.environ['ESP_MCP_MONITOR_BAUD'])
if value > 0:
config.DEFAULT_MONITOR_BAUD = value
logger.debug(f"Loaded monitor baud from env: {config.DEFAULT_MONITOR_BAUD}")
else:
logger.warning("Invalid ESP_MCP_MONITOR_BAUD value (must be > 0), using default")
except ValueError:
logger.warning("Invalid ESP_MCP_MONITOR_BAUD value, using default")

# Load limit settings
if 'ESP_MCP_MAX_ERRORS' in os.environ:
try:
value = int(os.environ['ESP_MCP_MAX_ERRORS'])
if value > 0:
config.MAX_ERRORS = value
logger.debug(f"Loaded max errors from env: {config.MAX_ERRORS}")
else:
logger.warning("Invalid ESP_MCP_MAX_ERRORS value (must be > 0), using default")
except ValueError:
logger.warning("Invalid ESP_MCP_MAX_ERRORS value, using default")

if 'ESP_MCP_MAX_WARNINGS' in os.environ:
try:
value = int(os.environ['ESP_MCP_MAX_WARNINGS'])
if value > 0:
config.MAX_WARNINGS = value
logger.debug(f"Loaded max warnings from env: {config.MAX_WARNINGS}")
else:
logger.warning("Invalid ESP_MCP_MAX_WARNINGS value (must be > 0), using default")
except ValueError:
logger.warning("Invalid ESP_MCP_MAX_WARNINGS value, using default")

return config

@classmethod
def get_system_root(cls) -> str:
"""Get Windows SystemRoot path

Returns:
str: SystemRoot path from environment or default
"""
return os.environ.get("SystemRoot", cls.DEFAULT_SYSTEM_ROOT)

def to_dict(self) -> Dict[str, Any]:
"""Convert configuration to dictionary

Returns:
Dict[str, Any]: Configuration as dictionary
"""
return {
'server': {
'name': self.SERVER_NAME,
'version': self.SERVER_VERSION,
'protocol_version': self.PROTOCOL_VERSION
},
'timeouts': {
'command': self.DEFAULT_COMMAND_TIMEOUT,
'serial_port': self.SERIAL_PORT_TIMEOUT,
'gdb': self.GDB_TIMEOUT
},
'serial': {
'flash_baud': self.DEFAULT_FLASH_BAUD,
'monitor_baud': self.DEFAULT_MONITOR_BAUD,
'common_ports': self.COMMON_SERIAL_PORTS
},
'limits': {
'max_errors': self.MAX_ERRORS,
'max_warnings': self.MAX_WARNINGS,
'max_symbols': self.MAX_SYMBOLS,
'max_added_configs': self.MAX_ADDED_CONFIGS,
'max_removed_configs': self.MAX_REMOVED_CONFIGS,
'max_modified_configs': self.MAX_MODIFIED_CONFIGS,
'max_log_entries': self.MAX_LOG_ENTRIES,
'max_crashes': self.MAX_CRASHES,
'max_errors_log': self.MAX_ERRORS_LOG
},
'display': {
'log_debug_length': self.LOG_DEBUG_LENGTH,
'time_format_minutes': self.TIME_FORMAT_MINUTES,
'time_format_seconds': self.TIME_FORMAT_SECONDS
},
'encoding': {
'default': self.DEFAULT_ENCODING,
'errors': self.ENCODING_ERRORS
}
}


# Global configuration instance
config = MCPConfig.load_from_env()


def get_config() -> MCPConfig:
"""Get the global configuration instance

Returns:
MCPConfig: Global configuration instance
"""
return config
Loading