myrmidon / python /src /mcp_server /features /developer /execution_tools.py
tek Atrust
chore(deploy): build monolithic server for Hugging Face
d5ef46f
Raw
History Blame Contribute Delete
3.74 kB
# python/src/mcp_server/features/developer/execution_tools.py
import logging
from pydantic import BaseModel, Field
# Using the same dependency injector pattern
from .file_operation_tools import ToolDependencies
logger = logging.getLogger(__name__)
# A whitelist of commands that are considered safe to be proposed.
# The approval UI should strongly enforce this.
SAFE_COMMAND_WHITELIST = [
"make test-be",
"make test",
"make lint-be",
"make lint",
# Specific pytest commands could be allowed if necessary
# "uv run pytest python/tests/server/api_routes/test_changes_api.py"
]
class ProposeShellCommandTool(BaseModel):
"""
Proposes running a shell command from a pre-approved whitelist.
This is a high-risk operation that requires human approval. Only commands
from a known safe list (like 'make test-be') should ever be approved.
"""
command: str = Field(
..., description=f"The shell command to propose. Must be one of: {', '.join(SAFE_COMMAND_WHITELIST)}"
)
async def execute(self) -> str:
"""Submits the shell command execution proposal."""
logger.info(f"Proposing to execute a shell command: {self.command}")
# --- Crucial Security Check ---
# The tool itself should validate against the whitelist before even proposing.
if self.command not in SAFE_COMMAND_WHITELIST:
logger.warning(f"Blocked attempt to propose a non-whitelisted command: {self.command}")
return (
f"Error: The command '{self.command}' is not in the list of approved safe commands. Proposal rejected."
)
try:
service = ToolDependencies.get_propose_change_service()
payload = {"command": self.command, "description": f"Propose to run the shell command: `{self.command}`."}
proposal = await service.create_proposal(change_type="shell", payload=payload)
return (
f"Successfully proposed to run the command: `{self.command}`. "
f"Proposal ID: {proposal['id']}. Please await human approval."
)
except Exception as e:
logger.error(f"Failed to propose shell command '{self.command}': {e}", exc_info=True)
return f"Error: Could not propose shell command execution. Reason: {e}"
class ExecuteShellCommandTool(BaseModel):
"""
Physically executes a shell command on the system.
WARNING: High-risk operation. Use for verification, linting, and system tasks.
Used for autonomous self-healing (Phase 4.6.43).
"""
tool_name: str = "execute_shell_command"
command: str = Field(..., description="The exact shell command to execute.")
async def execute(self) -> str:
"""Executes the shell command directly."""
import asyncio
logger.info(f"🐚 MCP Tool: Physically executing command: {self.command}")
try:
process = await asyncio.create_subprocess_shell(
self.command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await process.communicate()
output = stdout.decode().strip()
error = stderr.decode().strip()
if process.returncode == 0:
return f"βœ… Command succeeded:\n{output}"
else:
return f"❌ Command failed (Code {process.returncode}):\n{error}\nOutput:\n{output}"
except Exception as e:
logger.error(f"Command execution failed: {e}")
return f"❌ Execution error: {str(e)}"
# To be added to the MCP's tool registry
developer_execution_tools = [
ProposeShellCommandTool,
ExecuteShellCommandTool,
]