Skip to main content
Glama
GongRzhe

Terminal Controller for MCP

by GongRzhe

execute_command

Executes terminal commands and returns output. Supports setting a timeout in seconds to control execution duration.

Instructions

Execute terminal command and return results

Args:
    command: Command line command to execute
    timeout: Command timeout in seconds, default is 30 seconds

Returns:
    Output of the command execution

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
commandYes
timeoutNo

Implementation Reference

  • The MCP tool handler for 'execute_command' — decorated with @mcp.tool(), it takes a command string and optional timeout, checks for dangerous commands, delegates execution to run_command(), and formats the result.
    @mcp.tool()
    async def execute_command(command: str, timeout: int = 30) -> str:
        """
        Execute terminal command and return results
        
        Args:
            command: Command line command to execute
            timeout: Command timeout in seconds, default is 30 seconds
        
        Returns:
            Output of the command execution
        """
        # Check for dangerous commands (can add more security checks)
        dangerous_commands = ["rm -rf /", "mkfs"]
        if any(dc in command.lower() for dc in dangerous_commands):
            return "For security reasons, this command is not allowed."
        
        result = await run_command(command, timeout)
        
        if result["success"]:
            output = f"Command executed successfully (duration: {result['duration']})\n\n"
            
            if result["stdout"]:
                output += f"Output:\n{result['stdout']}\n"
            else:
                output += "Command had no output.\n"
                
            if result["stderr"]:
                output += f"\nWarnings/Info:\n{result['stderr']}"
                
            return output
        else:
            output = f"Command execution failed (duration: {result['duration']})\n"
            
            if result["stdout"]:
                output += f"\nOutput:\n{result['stdout']}\n"
                
            if result["stderr"]:
                output += f"\nError:\n{result['stderr']}"
                
            output += f"\nReturn code: {result['return_code']}"
            return output
  • Helper function 'run_command' that asynchronously executes a shell command via subprocess, handling timeouts, error handling, and recording command history.
    async def run_command(cmd: str, timeout: int = 30) -> Dict:
        """
        Execute command and return results
        
        Args:
            cmd: Command to execute
            timeout: Command timeout in seconds
            
        Returns:
            Dictionary containing command execution results
        """
        start_time = datetime.now()
        
        try:
            # Create command appropriate for current OS
            if platform.system() == "Windows":
                process = await asyncio.create_subprocess_shell(
                    cmd,
                    stdout=asyncio.subprocess.PIPE,
                    stderr=asyncio.subprocess.PIPE,
                    shell=True
                )
            else:
                process = await asyncio.create_subprocess_shell(
                    cmd,
                    stdout=asyncio.subprocess.PIPE,
                    stderr=asyncio.subprocess.PIPE,
                    shell=True,
                    executable="/bin/bash"
                )
            
            try:
                stdout, stderr = await asyncio.wait_for(process.communicate(), timeout)
                stdout = stdout.decode('utf-8', errors='replace')
                stderr = stderr.decode('utf-8', errors='replace')
                return_code = process.returncode
            except asyncio.TimeoutError:
                try:
                    process.kill()
                except:
                    pass
                return {
                    "success": False,
                    "stdout": "",
                    "stderr": f"Command timed out after {timeout} seconds",
                    "return_code": -1,
                    "duration": str(datetime.now() - start_time),
                    "command": cmd
                }
        
            duration = datetime.now() - start_time
            result = {
                "success": return_code == 0,
                "stdout": stdout,
                "stderr": stderr,
                "return_code": return_code,
                "duration": str(duration),
                "command": cmd
            }
            
            # Add to history
            command_history.append({
                "timestamp": datetime.now().isoformat(),
                "command": cmd,
                "success": return_code == 0
            })
            
            # If history is too long, remove oldest record
            if len(command_history) > MAX_HISTORY_SIZE:
                command_history.pop(0)
                
            return result
        
        except Exception as e:
            return {
                "success": False,
                "stdout": "",
                "stderr": f"Error executing command: {str(e)}",
                "return_code": -1,
                "duration": str(datetime.now() - start_time),
                "command": cmd
            }
  • Registration via FastMCP — the MCP server is initialized and tools are registered using the @mcp.tool() decorator.
    from mcp.server.fastmcp import FastMCP
    
    # Initialize MCP server
    mcp = FastMCP("terminal-controller", log_level="INFO")
    
    # List to store command history
    command_history = []
    
    # Maximum history size
    MAX_HISTORY_SIZE = 50

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

B3.2/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided. The description lacks disclosure of safety, side effects, or return format. For a command execution tool, this is insufficient transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Very concise with a clear Args/Returns structure. Every sentence is meaningful and there is no wasted text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a potentially dangerous tool, the description is too minimal. It lacks information on error handling, return format, timeout behavior, and prerequisites.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, but the description adds basic meaning: 'command' is a command line string, 'timeout' is in seconds with default 30. This helps but doesn't specify constraints or format details.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: executing terminal commands and returning results. It distinguishes itself from sibling tools like file operations and directory navigation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives. Sibling tools are listed but the description gives no context for choosing execute_command over them.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.