mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
Merge branch 'feat/mcp_server_with_eim_v6.0' into 'release/v6.0'
feat(tools): Fixed mcp-server functionality + EIM integration (v6.0) See merge request espressif/esp-idf!46481
This commit is contained in:
@@ -267,44 +267,96 @@ Read Otadata Partition: ``read-otadata``
|
||||
|
||||
This command prints the contents of the ``otadata`` partition which stores the information about the currently selected OTA app slot. Refer to :doc:`/api-reference/system/ota` for more about the ``otadata`` partition.
|
||||
|
||||
Start MCP Server: ``mcp-server``
|
||||
---------------------------------
|
||||
ESP-IDF MCP Server
|
||||
-------------------
|
||||
|
||||
The ESP-IDF MCP (Model Context Protocol) server enables AI integration with ESP-IDF projects. The MCP server provides tools and resources that allow AI assistants to interact with your ESP-IDF project through a standardized protocol. Using natural language, you can tell the AI assistant commands like "set target to esp32" or "build this project".
|
||||
|
||||
Starting the MCP Server
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To use the MCP server with an AI assistant, configure your agent or IDE to start the server. You can do that in two ways:
|
||||
|
||||
1. Using ``eim run`` (recommended): Use the ESP-IDF Installation Manager (EIM) to start a new process with an active ESP-IDF environment. This feature is available from EIM 0.8.1 and **ESP-IDF must be installed via the EIM installer**. This is the easiest option and does not require you to activate ESP-IDF in your shell first.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
eim run "idf.py mcp-server"
|
||||
|
||||
2. Using ``idf.py`` directly: Run the MCP server with ``idf.py mcp-server`` from a shell where the ESP-IDF environment is already activated. The command must be executed from a valid ESP-IDF project directory, or use ``idf.py -C <project_dir> mcp-server`` to specify the project.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
idf.py mcp-server
|
||||
|
||||
This command starts an MCP (Model Context Protocol) server that enables AI integration with ESP-IDF projects. The MCP server provides tools and resources that allow AI assistants to interact with your ESP-IDF project through a standardized protocol.
|
||||
.. note::
|
||||
|
||||
The MCP server provides the following tools:
|
||||
The MCP server requires the ``mcp`` feature to be installed. Install it using the EIM installer. See `EIM documentation > CLI Configuration - Global features <https://docs.espressif.com/projects/idf-im-ui/en/latest/cli_configuration.html#global-features-all-versions>`_ for how to install specific features.
|
||||
|
||||
- ``build_project``: Build the ESP-IDF project with specified target
|
||||
- ``set_target``: Set the ESP-IDF target (esp32, esp32s3, esp32c6, etc.)
|
||||
- ``flash_project``: Flash the built project to a connected device
|
||||
- ``monitor_serial``: Start serial monitor (runs in background)
|
||||
- ``clean_project``: Clean build artifacts
|
||||
- ``menuconfig``: Open menuconfig interface (terminal-based)
|
||||
Available Tools and Resources
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The MCP server provides the following commands you can use:
|
||||
|
||||
- ``set target``: Set the ESP-IDF target (esp32, esp32s3, esp32c6, etc.)
|
||||
- ``build project``: Build the ESP-IDF project with the current target
|
||||
- ``flash project``: Flash the built project to a connected device. Specify it by port name.
|
||||
- ``clean project``: Clean build artifacts
|
||||
|
||||
The MCP server also provides these resources:
|
||||
|
||||
- ``project://config``: Get current project configuration
|
||||
- ``project://status``: Get current project build status
|
||||
- ``project://devices``: Get list of connected ESP devices
|
||||
- ``project://status``: Get current project build status and artifacts
|
||||
- ``project://devices``: Get list of connected devices
|
||||
|
||||
.. note::
|
||||
Adding ESP-IDF MCP Server to IDEs and AI agents
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The MCP server requires the ``mcp`` Python package to be installed. Install it with: ``./install.sh --enable-mcp``.
|
||||
Cursor IDE
|
||||
~~~~~~~~~~
|
||||
|
||||
Adding ESP-IDF MCP Server to IDEs
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Add the ESP-IDF MCP server configuration to your Cursor ``mcp.json`` file:
|
||||
|
||||
**Claude Desktop:**
|
||||
.. code-block:: json
|
||||
|
||||
Use the Claude CLI to add the ESP-IDF MCP server:
|
||||
{
|
||||
"mcpServers": {
|
||||
"esp-idf-eim": {
|
||||
"command": "eim",
|
||||
"args": [
|
||||
"run",
|
||||
"idf.py mcp-server"
|
||||
],
|
||||
"env": {
|
||||
"IDF_MCP_WORKSPACE_FOLDER": "${workspaceFolder}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
To use the ESP-IDF MCP server with Cursor IDE, open your ESP-IDF project folder and use the AI chat window. The AI assistant will have access to ESP-IDF specific tools and can help you build, flash, and manage your project.
|
||||
|
||||
The ``IDF_MCP_WORKSPACE_FOLDER`` environment variable tells the MCP server which directory contains your ESP-IDF project. This ensures the server operates in the correct project context, allowing it to access your project's configuration, build files, and perform operations like building and flashing in the right location.
|
||||
|
||||
Claude Desktop
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Use the Claude CLI to add the ESP-IDF MCP server.
|
||||
|
||||
With ``eim`` (no need to activate ESP-IDF first):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
claude mcp add esp-idf python /path/to/esp-idf/tools/idf.py mcp-server --env IDF_PATH=/path/to/esp-idf
|
||||
claude mcp add --transport stdio esp-idf-eim -- eim run "idf.py mcp-server"
|
||||
|
||||
With ``idf.py`` (must be executed from an activated ESP-IDF environment):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
claude mcp add --transport stdio esp-idf -- idf.py mcp-server
|
||||
|
||||
Navigate to your ESP-IDF project directory and run the ``claude`` command to chat with the AI assistant.
|
||||
|
||||
|
||||
Configuration Presets: ``--preset``
|
||||
====================================
|
||||
|
||||
@@ -267,44 +267,96 @@ uf2 二进制文件也可以通过 :ref:`idf.py uf2 <generate-uf2-binary>` 生
|
||||
|
||||
此命令将打印 ``otadata`` 分区的内容,该分区存储当前所选 OTA 应用程序分区的信息。有关 ``otadata`` 分区的更多信息,请参阅 :doc:`/api-reference/system/ota`。
|
||||
|
||||
启动 MCP 服务器:``mcp-server``
|
||||
---------------------------------
|
||||
ESP-IDF MCP 服务器
|
||||
------------------
|
||||
|
||||
ESP-IDF 的 MCP(Model Context Protocol,模型上下文协议)服务器可实现 AI 与 ESP-IDF 项目的集成。该 MCP 服务器提供工具和资源,使 AI 助手能够通过标准化协议与 ESP-IDF 项目交互。通过自然语言,即可向 AI 助手下达诸如“设定目标芯片为 esp32”或“构建项目”之类的命令。
|
||||
|
||||
启动 MCP 服务器
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
要配合 AI 助手使用 MCP 服务器,可配置智能体或 IDE 启动该服务器。具体来说,有两种实现方式:
|
||||
|
||||
1. 使用 ``eim run`` (推荐):通过 ESP-IDF 安装管理器 (ESP-IDF Installation Manager, EIM) 在已激活的 ESP-IDF 环境中启动一个新进程。该功能需要 EIM 0.8.1 或更高版本,且 **ESP-IDF 必须通过 EIM 安装程序安装**。该方案是最简单的选项,且无需先在 shell 中激活 ESP-IDF 环境。
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
eim run "idf.py mcp-server"
|
||||
|
||||
2. 直接使用 ``idf.py``:在已激活 ESP-IDF 环境的 shell 中运行 ``idf.py mcp-server`` 命令启动 MCP 服务器。必须在有效的 ESP-IDF 项目目录中执行该命令,或者使用 ``idf.py -C <project_dir> mcp-server`` 指定项目路径。
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
idf.py mcp-server
|
||||
|
||||
此命令将启动 MCP(模型上下文协议)服务器,实现 AI 与 ESP-IDF 项目的集成。该服务器通过标准化协议提供工具和资源,使 AI 助手能够与 ESP-IDF 项目进行交互。
|
||||
.. note::
|
||||
|
||||
MCP 服务器提供以下工具:
|
||||
MCP 服务器需要通过 EIM 安装器来安装 ``mcp`` 功能。有关如何安装特定功能,请参见 `EIM 文档 > CLI 配置 - 全局功能 <https://docs.espressif.com/projects/idf-im-ui/en/latest/cli_configuration.html#global-features-all-versions>`_。
|
||||
|
||||
- ``build_project``:使用指定目标芯片构建 ESP-IDF 项目
|
||||
- ``set_target``:设置 ESP-IDF 目标芯片(esp32、esp32s3、esp32c6 等)
|
||||
- ``flash_project``:将构建好的项目烧录至已连接设备
|
||||
- ``monitor_serial``:启动串行监视器(在后台运行)
|
||||
- ``clean_project``:清理构建产物
|
||||
- ``menuconfig``:打开 menuconfig 界面(基于终端)
|
||||
可用工具与资源
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
MCP 服务器提供以下可用的命令:
|
||||
|
||||
- ``set target``:设置 ESP-IDF 的目标芯片(esp32,esp32s3,esp32c6 等)
|
||||
- ``build project``:使用当前目标构建 ESP-IDF 项目
|
||||
- ``flash project``:将已构建的项目烧录到已连接的设备,通过端口名称进行指定。
|
||||
- ``clean project``:清理构建产物
|
||||
|
||||
同时提供以下资源:
|
||||
|
||||
- ``project://config``:获取当前项目配置
|
||||
- ``project://status``:获取当前项目构建状态
|
||||
- ``project://devices``:获取已连接的 ESP 设备列表
|
||||
- ``project://status``:获取当前项目的构建状态和构建产物
|
||||
- ``project://devices``:获取已连接的设备列表
|
||||
|
||||
.. note::
|
||||
将 ESP-IDF MCP 服务器添加至 IDE 和 AI 智能体
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
运行 MCP 服务器需提前安装 ``mcp`` Python 包。可通过以下命令安装:``./install.sh --enable-mcp``。
|
||||
Cursor IDE
|
||||
~~~~~~~~~~
|
||||
|
||||
将 ESP-IDF MCP 服务器添加到 IDE
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
将 ESP-IDF MCP 服务器配置添加至 Cursor 的 ``mcp.json`` 文件中:
|
||||
|
||||
**Claude Desktop:**
|
||||
.. code-block:: json
|
||||
|
||||
使用 Claude CLI 添加 ESP-IDF MCP 服务器:
|
||||
{
|
||||
"mcpServers": {
|
||||
"esp-idf-eim": {
|
||||
"command": "eim",
|
||||
"args": [
|
||||
"run",
|
||||
"idf.py mcp-server"
|
||||
],
|
||||
"env": {
|
||||
"IDF_MCP_WORKSPACE_FOLDER": "${workspaceFolder}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
若想在 Cursor IDE 中使用 ESP-IDF MCP 服务器,请打开 ESP-IDF 项目文件夹并使用 AI 聊天窗口,AI 助手能够访问 ESP-IDF 专用工具,并帮助构建、烧录和管理项目。
|
||||
|
||||
MCP 服务器能够通过 ``IDF_MCP_WORKSPACE_FOLDER`` 环境变量确认哪个目录包含 ESP-IDF 项目,确保服务器在正确的项目上下文中运行,使其能够访问项目的配置和构建文件,并在正确的位置执行诸如构建和烧录之类的操作。
|
||||
|
||||
Claude 桌面版
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
使用 Claude CLI 添加 ESP-IDF MCP 服务器。
|
||||
|
||||
使用 ``eim`` (无需事先激活 ESP-IDF):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
claude mcp add esp-idf python /path/to/esp-idf/tools/idf.py mcp-server --env IDF_PATH=/path/to/esp-idf
|
||||
claude mcp add --transport stdio esp-idf-eim -- eim run "idf.py mcp-server"
|
||||
|
||||
使用 ``idf.py`` (必须在已激活的 ESP-IDF 环境中执行):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
claude mcp add --transport stdio esp-idf -- idf.py mcp-server
|
||||
|
||||
前往 ESP-IDF 项目目录,并运行 ``claude`` 命令,与 AI 助手聊天。
|
||||
|
||||
|
||||
配置预设:``--preset``
|
||||
========================
|
||||
|
||||
+122
-116
@@ -1,20 +1,29 @@
|
||||
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from click.core import Context
|
||||
|
||||
from idf_py_actions.errors import FatalError
|
||||
from idf_py_actions.tools import PropertyDict
|
||||
from idf_py_actions.tools import get_build_context
|
||||
from idf_py_actions.tools import get_target
|
||||
from idf_py_actions.tools import idf_version
|
||||
|
||||
try:
|
||||
from idf_component_tools.build_system_tools import CMAKE_PROJECT_LINE
|
||||
except ImportError:
|
||||
CMAKE_PROJECT_LINE = [
|
||||
r'include($ENV{IDF_PATH}/tools/cmake/project.cmake)',
|
||||
r'include($ENV{IDF_PATH}/tools/cmakev2/idf.cmake)',
|
||||
]
|
||||
|
||||
|
||||
try:
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
@@ -23,51 +32,98 @@ except ImportError:
|
||||
MCP_AVAILABLE = False
|
||||
|
||||
|
||||
def is_valid_project_dir(directory: str) -> bool:
|
||||
"""
|
||||
Determine if the given directory is a valid ESP-IDF project directory.
|
||||
- Must be a directory.
|
||||
- Must contain a CMakeLists.txt.
|
||||
- CMakeLists.txt must include CMAKE_PROJECT_LINE.
|
||||
"""
|
||||
root = Path(directory)
|
||||
|
||||
if not root.is_dir():
|
||||
return False
|
||||
cmakelists_path = root / 'CMakeLists.txt'
|
||||
if not cmakelists_path.is_file():
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(str(cmakelists_path), encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if any(proj_line in line for proj_line in CMAKE_PROJECT_LINE):
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def action_extensions(base_actions: dict, project_path: str) -> dict:
|
||||
"""ESP-IDF MCP Server Extension"""
|
||||
|
||||
def start_mcp_server(action_name: str, ctx: Context, args: PropertyDict, **kwargs: Any) -> None:
|
||||
"""Start MCP server for ESP-IDF project integration"""
|
||||
|
||||
if not MCP_AVAILABLE:
|
||||
raise FatalError('MCP dependencies not available. Install with: ./install.sh --enable-mcp')
|
||||
raise FatalError(
|
||||
'MCP dependencies not available. '
|
||||
'Install ESP-IDF using the EIM installer and select the "mcp" feature to be included. '
|
||||
'For more information, refer to the official Espressif EIM Installer documentation '
|
||||
'or use "idf.py docs" and search for EIM configuration instructions.'
|
||||
)
|
||||
|
||||
current_project = None
|
||||
# Use current working directory if available, fallback to project_path
|
||||
for project_dir in [os.getcwd(), project_path]:
|
||||
if project_dir and os.path.exists(os.path.join(project_dir, 'CMakeLists.txt')):
|
||||
current_project = project_dir
|
||||
break
|
||||
|
||||
if not current_project:
|
||||
raise FatalError('Open the MCP server in a valid ESP-IDF project directory.')
|
||||
|
||||
print(f'Starting ESP-IDF MCP Server for project: {current_project}')
|
||||
print(f'Target: {get_target(current_project)}')
|
||||
print(f'ESP-IDF Version: {idf_version()}')
|
||||
print(f'Working Directory: {os.getcwd()}')
|
||||
# Verify that mcp-server was executed from a valid ESP-IDF project directory.
|
||||
# This is necessary to obtain the correct context such as args, project path, etc.
|
||||
if not is_valid_project_dir(project_path):
|
||||
current_project = None
|
||||
for candidate in [os.getcwd(), os.environ.get('IDF_MCP_WORKSPACE_FOLDER', '')]:
|
||||
if is_valid_project_dir(candidate):
|
||||
current_project = candidate
|
||||
break
|
||||
if not current_project:
|
||||
raise FatalError('Open the MCP server in a valid ESP-IDF project directory.')
|
||||
try:
|
||||
cmd = [
|
||||
sys.executable,
|
||||
os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'),
|
||||
'-C',
|
||||
current_project,
|
||||
'mcp-server',
|
||||
]
|
||||
print(
|
||||
f'Starting ESP-IDF MCP Server with command: {" ".join(cmd)} in project path: {current_project}',
|
||||
file=sys.stderr,
|
||||
)
|
||||
subprocess.run(cmd, cwd=current_project, check=True)
|
||||
return
|
||||
except Exception as e:
|
||||
print(f'ERROR: Failed to start ESP-IDF MCP Server: {str(e)}', file=sys.stderr)
|
||||
raise FatalError(f'Failed to start ESP-IDF MCP Server: {str(e)}') from e
|
||||
|
||||
# Initialize MCP server
|
||||
mcp = FastMCP('ESP-IDF')
|
||||
|
||||
# === TOOLS (Actions) ===
|
||||
@mcp.tool()
|
||||
def build_project(target: str = 'all') -> str:
|
||||
"""Build ESP-IDF project with specified target"""
|
||||
def build_project() -> str:
|
||||
"""Build ESP-IDF project"""
|
||||
try:
|
||||
# Use the current project directory
|
||||
cmd = [
|
||||
sys.executable,
|
||||
os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'),
|
||||
'-C',
|
||||
current_project,
|
||||
'build',
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, cwd=current_project)
|
||||
# Information logs are shown in some mcp clients using stderr
|
||||
print(f'INFO: Building project with command: {" ".join(cmd)} in path: {project_path}', file=sys.stderr)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, cwd=project_path)
|
||||
if result.returncode == 0:
|
||||
return f'Successfully built target: {target}'
|
||||
print('INFO: Build successful', file=sys.stderr)
|
||||
return 'Successfully built project'
|
||||
else:
|
||||
print(f'ERROR: Build failed: {result.stderr}', file=sys.stderr)
|
||||
return f'Build failed: {result.stderr}'
|
||||
except Exception as e:
|
||||
print(f'ERROR: Build failed: {str(e)}', file=sys.stderr)
|
||||
return f'Build failed: {str(e)}'
|
||||
|
||||
@mcp.tool()
|
||||
@@ -77,17 +133,19 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
||||
cmd = [
|
||||
sys.executable,
|
||||
os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'),
|
||||
'-C',
|
||||
current_project,
|
||||
'set-target',
|
||||
target,
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, cwd=current_project)
|
||||
print(f'INFO: Setting target with command: {" ".join(cmd)} in path: {project_path}', file=sys.stderr)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, cwd=project_path)
|
||||
if result.returncode == 0:
|
||||
print(f'INFO: Target set to: {target}', file=sys.stderr)
|
||||
return f'Target set to: {target}'
|
||||
else:
|
||||
print(f'ERROR: Failed to set target: {result.stderr}', file=sys.stderr)
|
||||
return f'Failed to set target: {result.stderr}'
|
||||
except Exception as e:
|
||||
print(f'ERROR: Failed to set target: {str(e)}', file=sys.stderr)
|
||||
return f'Error setting target: {str(e)}'
|
||||
|
||||
@mcp.tool()
|
||||
@@ -102,40 +160,20 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
||||
cmd = [
|
||||
sys.executable,
|
||||
os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'),
|
||||
'-C',
|
||||
current_project,
|
||||
] + flash_args
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, cwd=current_project)
|
||||
print(f'INFO: Flashing project with command: {" ".join(cmd)} in path: {project_path}', file=sys.stderr)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, cwd=project_path)
|
||||
|
||||
if result.returncode == 0:
|
||||
print('INFO: Flash successful', file=sys.stderr)
|
||||
return f'Successfully flashed project{" to port " + port if port else ""}'
|
||||
else:
|
||||
print(f'ERROR: Flash failed: {result.stderr}', file=sys.stderr)
|
||||
return f'Flash failed: {result.stderr}'
|
||||
except Exception as e:
|
||||
print(f'ERROR: Flash failed: {str(e)}', file=sys.stderr)
|
||||
return f'Error flashing: {str(e)}'
|
||||
|
||||
@mcp.tool()
|
||||
def monitor_serial(port: str | None = None) -> str:
|
||||
"""Start serial monitor (returns immediately, monitor runs in background)"""
|
||||
try:
|
||||
monitor_args = []
|
||||
if port:
|
||||
monitor_args.extend(['-p', port])
|
||||
monitor_args.append('monitor')
|
||||
|
||||
cmd = [
|
||||
sys.executable,
|
||||
os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'),
|
||||
'-C',
|
||||
current_project,
|
||||
] + monitor_args
|
||||
|
||||
# Start monitor in background
|
||||
subprocess.Popen(cmd, cwd=current_project)
|
||||
return f'Serial monitor started{" on port " + port if port else ""}'
|
||||
except Exception as e:
|
||||
return f'Error starting monitor: {str(e)}'
|
||||
|
||||
@mcp.tool()
|
||||
def clean_project() -> str:
|
||||
"""Clean build artifacts"""
|
||||
@@ -143,90 +181,64 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
||||
cmd = [
|
||||
sys.executable,
|
||||
os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'),
|
||||
'-C',
|
||||
current_project,
|
||||
'clean',
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, cwd=current_project)
|
||||
print(f'INFO: Cleaning project with command: {" ".join(cmd)} in path: {project_path}', file=sys.stderr)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, cwd=project_path)
|
||||
if result.returncode == 0:
|
||||
print('INFO: Project cleaned successfully', file=sys.stderr)
|
||||
return 'Project cleaned successfully'
|
||||
else:
|
||||
print(f'ERROR: Clean failed: {result.stderr}', file=sys.stderr)
|
||||
return f'Clean failed: {result.stderr}'
|
||||
except Exception as e:
|
||||
print(f'ERROR: Error cleaning: {str(e)}', file=sys.stderr)
|
||||
return f'Error cleaning: {str(e)}'
|
||||
|
||||
@mcp.tool()
|
||||
def menuconfig() -> str:
|
||||
"""Open menuconfig interface (terminal-based)"""
|
||||
try:
|
||||
cmd = [
|
||||
sys.executable,
|
||||
os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'),
|
||||
'-C',
|
||||
current_project,
|
||||
'menuconfig',
|
||||
]
|
||||
subprocess.run(cmd, cwd=current_project)
|
||||
return 'Menuconfig completed'
|
||||
except Exception as e:
|
||||
return f'Error opening menuconfig: {str(e)}'
|
||||
|
||||
# === RESOURCES (Data Access) ===
|
||||
|
||||
@mcp.resource('project://config')
|
||||
def get_project_config() -> str:
|
||||
"""Get current project configuration"""
|
||||
build_dir = args.get('build_dir', '')
|
||||
config: dict[str, Any] = {}
|
||||
|
||||
if not os.path.exists(build_dir):
|
||||
config['build_dir_exists'] = False
|
||||
return json.dumps(config, indent=2)
|
||||
|
||||
config['build_dir'] = build_dir
|
||||
proj_desc_fn = f'{build_dir}/project_description.json'
|
||||
config['project_description'] = 'Project description does not exist'
|
||||
|
||||
try:
|
||||
config_data = {}
|
||||
with open(proj_desc_fn, encoding='utf-8') as f:
|
||||
config['project_description'] = json.load(f)
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
|
||||
# Get target
|
||||
config_data['target'] = get_target(current_project)
|
||||
|
||||
# Get build directory info
|
||||
build_dir = os.path.join(current_project, 'build')
|
||||
config_data['build_dir'] = build_dir
|
||||
config_data['build_exists'] = os.path.exists(build_dir)
|
||||
|
||||
# Get project description if available
|
||||
try:
|
||||
ctx = get_build_context()
|
||||
if 'proj_desc' in ctx:
|
||||
config_data['project_description'] = ctx['proj_desc']
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Get sdkconfig info
|
||||
sdkconfig_path = os.path.join(current_project, 'sdkconfig')
|
||||
config_data['sdkconfig_exists'] = os.path.exists(sdkconfig_path)
|
||||
|
||||
return json.dumps(config_data, indent=2)
|
||||
except Exception as e:
|
||||
return f'Error getting config: {str(e)}'
|
||||
return json.dumps(config, indent=2)
|
||||
|
||||
@mcp.resource('project://status')
|
||||
def get_project_status() -> str:
|
||||
"""Get current project build status"""
|
||||
try:
|
||||
status = {
|
||||
'project_path': current_project,
|
||||
'target': get_target(current_project),
|
||||
'project_path': project_path,
|
||||
'target': get_target(project_path),
|
||||
'idf_version': idf_version(),
|
||||
'build_dir': os.path.join(current_project, 'build'),
|
||||
}
|
||||
|
||||
# Check if built
|
||||
build_dir = os.path.join(current_project, 'build')
|
||||
build_dir = args.build_dir
|
||||
if os.path.exists(build_dir):
|
||||
status['built'] = True
|
||||
|
||||
# Check for common build artifacts
|
||||
status['build_dir'] = build_dir
|
||||
artifacts = ['bootloader', 'partition_table', 'app-flash', 'flash_args']
|
||||
status['artifacts'] = {}
|
||||
for artifact in artifacts:
|
||||
artifact_path = os.path.join(build_dir, artifact)
|
||||
status['artifacts'][artifact] = os.path.exists(artifact_path)
|
||||
else:
|
||||
status['built'] = False
|
||||
status['build_dir_exists'] = False
|
||||
|
||||
return json.dumps(status, indent=2)
|
||||
except Exception as e:
|
||||
@@ -234,25 +246,19 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
||||
|
||||
@mcp.resource('project://devices')
|
||||
def get_connected_devices() -> str:
|
||||
"""Get list of connected ESP devices"""
|
||||
"""Get list of connected devices"""
|
||||
try:
|
||||
# Use esptool to list ports
|
||||
cmd = [sys.executable, '-m', 'esptool', '--list-ports']
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
devices = {
|
||||
'available_ports': result.stdout.strip().split('\n') if result.stdout else [],
|
||||
'esptool_available': result.returncode == 0,
|
||||
}
|
||||
import serial.tools.list_ports
|
||||
|
||||
devices_on_ports = [p.device.strip() for p in serial.tools.list_ports.comports()]
|
||||
print(f'Devices: {devices_on_ports}', file=sys.stderr)
|
||||
devices = {'available_ports': devices_on_ports if devices_on_ports else []}
|
||||
return json.dumps(devices, indent=2)
|
||||
except Exception as e:
|
||||
return f'Error getting devices: {str(e)}'
|
||||
|
||||
# Start the MCP server
|
||||
print('MCP Server running on stdio...')
|
||||
print('Available tools: build_project, set_target, flash_project, monitor_serial, clean_project, menuconfig')
|
||||
print('Available resources: project://config, project://status, project://devices')
|
||||
|
||||
try:
|
||||
mcp.run()
|
||||
@@ -268,6 +274,6 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
||||
'callback': start_mcp_server,
|
||||
'help': 'Start MCP (Model Context Protocol) server for AI integration',
|
||||
'options': [],
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user