mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
fix(tools): detach MCP tool subprocess stdin from JSON-RPC pipe
idf.py mcp-server tool handlers were spawning idf.py without redirecting stdin, so the child inherited the long-lived MCP JSON-RPC transport and could hang indefinitely on tools/call. Pass stdin=subprocess.DEVNULL on every spawn of idf.py. Closes https://github.com/espressif/esp-idf/issues/18961
This commit is contained in:
@@ -146,6 +146,9 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
|||||||
mcp = FastMCP('ESP-IDF')
|
mcp = FastMCP('ESP-IDF')
|
||||||
|
|
||||||
# === TOOLS (Actions) ===
|
# === TOOLS (Actions) ===
|
||||||
|
# Tool handlers spawn idf.py with stdin=subprocess.DEVNULL. The MCP server's
|
||||||
|
# stdin is the long-lived JSON-RPC transport pipe; a child that inherits it
|
||||||
|
# can hang instead of exiting.
|
||||||
@mcp.tool(description=f'Build the ESP-IDF project (runs `idf.py build`). {bound_hint}')
|
@mcp.tool(description=f'Build the ESP-IDF project (runs `idf.py build`). {bound_hint}')
|
||||||
def build_project(project_dir: str | None = None) -> str:
|
def build_project(project_dir: str | None = None) -> str:
|
||||||
"""Build the ESP-IDF project.
|
"""Build the ESP-IDF project.
|
||||||
@@ -169,7 +172,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
|||||||
'build',
|
'build',
|
||||||
]
|
]
|
||||||
print(f'INFO: Building project with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr)
|
print(f'INFO: Building project with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr)
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
result = subprocess.run(cmd, capture_output=True, text=True, stdin=subprocess.DEVNULL)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
print('INFO: Build successful', file=sys.stderr)
|
print('INFO: Build successful', file=sys.stderr)
|
||||||
return 'Successfully built project'
|
return 'Successfully built project'
|
||||||
@@ -210,7 +213,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
|||||||
target,
|
target,
|
||||||
]
|
]
|
||||||
print(f'INFO: Setting target with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr)
|
print(f'INFO: Setting target with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr)
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
result = subprocess.run(cmd, capture_output=True, text=True, stdin=subprocess.DEVNULL)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
print(f'INFO: Target set to: {target}', file=sys.stderr)
|
print(f'INFO: Target set to: {target}', file=sys.stderr)
|
||||||
return f'Target set to: {target}'
|
return f'Target set to: {target}'
|
||||||
@@ -252,7 +255,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
|||||||
effective_dir,
|
effective_dir,
|
||||||
] + flash_args
|
] + flash_args
|
||||||
print(f'INFO: Flashing project with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr)
|
print(f'INFO: Flashing project with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr)
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
result = subprocess.run(cmd, capture_output=True, text=True, stdin=subprocess.DEVNULL)
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
print('INFO: Flash successful', file=sys.stderr)
|
print('INFO: Flash successful', file=sys.stderr)
|
||||||
@@ -293,7 +296,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
|||||||
name,
|
name,
|
||||||
]
|
]
|
||||||
print(f'INFO: Creating project "{name}" in {parent_dir}', file=sys.stderr)
|
print(f'INFO: Creating project "{name}" in {parent_dir}', file=sys.stderr)
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
result = subprocess.run(cmd, capture_output=True, text=True, stdin=subprocess.DEVNULL)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
project_path_new = os.path.join(parent_dir, name)
|
project_path_new = os.path.join(parent_dir, name)
|
||||||
print(f'INFO: Project "{name}" created at {project_path_new}', file=sys.stderr)
|
print(f'INFO: Project "{name}" created at {project_path_new}', file=sys.stderr)
|
||||||
@@ -329,7 +332,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
|||||||
'clean',
|
'clean',
|
||||||
]
|
]
|
||||||
print(f'INFO: Cleaning project with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr)
|
print(f'INFO: Cleaning project with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr)
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
result = subprocess.run(cmd, capture_output=True, text=True, stdin=subprocess.DEVNULL)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
print('INFO: Project cleaned successfully', file=sys.stderr)
|
print('INFO: Project cleaned successfully', file=sys.stderr)
|
||||||
return 'Project cleaned successfully'
|
return 'Project cleaned successfully'
|
||||||
|
|||||||
Reference in New Issue
Block a user