fix(beads-mcp): prevent subprocess stdin inheritance breaking MCP protocol

When running as an MCP server, subprocesses must not inherit stdin because
MCP uses stdin for JSON-RPC protocol communication. Inherited stdin causes
subprocesses to block indefinitely waiting for input or steal bytes from
the MCP protocol stream.

Added stdin=subprocess.DEVNULL (or asyncio.subprocess.DEVNULL for async)
to all subprocess calls:
- server.py: _resolve_workspace_root() git subprocess
- tools.py: _resolve_workspace_root() git subprocess
- bd_client.py: _run_command(), _check_version(), add_dependency(),
  quickstart(), and init() async subprocess calls

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Caleb Leak
2025-11-26 20:16:42 -08:00
parent 7d765c228b
commit 055e7a561f
4 changed files with 150 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ import asyncio
import json
import os
import re
import sys
from abc import ABC, abstractmethod
from typing import Any, List, Optional
@@ -290,6 +291,7 @@ class BdCliClient(BdClientBase):
try:
process = await asyncio.create_subprocess_exec(
*cmd,
stdin=asyncio.subprocess.DEVNULL, # Prevent inheriting MCP's stdin
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=working_dir,
@@ -333,6 +335,7 @@ class BdCliClient(BdClientBase):
process = await asyncio.create_subprocess_exec(
self.bd_path,
"version",
stdin=asyncio.subprocess.DEVNULL, # Prevent inheriting MCP's stdin
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=self._get_working_dir(),
@@ -583,6 +586,7 @@ class BdCliClient(BdClientBase):
try:
process = await asyncio.create_subprocess_exec(
*cmd,
stdin=asyncio.subprocess.DEVNULL, # Prevent inheriting MCP's stdin
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=self._get_working_dir(),
@@ -610,6 +614,7 @@ class BdCliClient(BdClientBase):
try:
process = await asyncio.create_subprocess_exec(
*cmd,
stdin=asyncio.subprocess.DEVNULL, # Prevent inheriting MCP's stdin
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=self._get_working_dir(),
@@ -754,6 +759,7 @@ class BdCliClient(BdClientBase):
try:
process = await asyncio.create_subprocess_exec(
*cmd,
stdin=asyncio.subprocess.DEVNULL, # Prevent inheriting MCP's stdin
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=self._get_working_dir(),

View File

@@ -218,6 +218,7 @@ def _resolve_workspace_root(path: str) -> str:
text=True,
check=False,
shell=sys.platform == "win32",
stdin=subprocess.DEVNULL, # Prevent inheriting MCP's stdin
)
if result.returncode == 0:
return result.stdout.strip()

View File

@@ -124,6 +124,7 @@ def _resolve_workspace_root(path: str) -> str:
text=True,
check=False,
shell=sys.platform == "win32",
stdin=subprocess.DEVNULL, # Prevent inheriting MCP's stdin
)
if result.returncode == 0:
return result.stdout.strip()