Skip to main content
Glama
jsebgiraldo

OpenWRT SSH MCP Server

by jsebgiraldo

openwrt_execute_command

Execute validated shell commands on OpenWRT routers via SSH, using a security whitelist for safe remote management when specialized tools are unavailable.

Instructions

Execute a validated shell command on the OpenWRT router. Commands are validated against a security whitelist. Use this for commands not covered by other specialized tools.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
commandYesShell command to execute (must be in whitelist)

Implementation Reference

  • Main handler function OpenWRTTools.execute_command that validates the command, ensures SSH connection, executes via ssh_client, and returns the execution result with success status, output, error, exit code, and execution time.
    @staticmethod
    async def execute_command(command: str) -> dict[str, Any]:
        """
        Execute a validated command on the OpenWRT router.
        
        Args:
            command: Shell command to execute
            
        Returns:
            dict: Execution result
        """
        # Validate command
        is_valid, error_msg = SecurityValidator.validate_command(command)
        if not is_valid:
            return {
                "success": False,
                "error": error_msg,
                "output": "",
            }
    
        # Execute
        await ssh_client.ensure_connected()
        result = await ssh_client.execute(command)
    
        return {
            "success": result["success"],
            "output": result["stdout"],
            "error": result["stderr"],
            "exit_code": result["exit_code"],
            "execution_time": result["execution_time"],
        }
  • Tool schema definition for openwrt_execute_command registering it with the MCP server, including the command parameter (string, required) and description indicating command validation against a security whitelist.
    Tool(
        name="openwrt_execute_command",
        description=(
            "Execute a validated shell command on the OpenWRT router. "
            "Commands are validated against a security whitelist. "
            "Use this for commands not covered by other specialized tools."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "command": {
                    "type": "string",
                    "description": "Shell command to execute (must be in whitelist)",
                },
            },
            "required": ["command"],
        },
    ),
  • Tool registration routing in the call_tool handler that extracts the command argument and calls OpenWRTTools.execute_command with validation for missing arguments.
    elif name == "openwrt_execute_command":
        command = arguments.get("command")
        if not command:
            raise ValueError("Missing required argument: command")
        result = await OpenWRTTools.execute_command(command)
  • Security validation function SecurityValidator.validate_command that checks commands against dangerous blocked patterns and an allowed whitelist, returning validation status and error message.
    @classmethod
    def validate_command(cls, command: str) -> tuple[bool, Optional[str]]:
        """
        Validate if a command is safe to execute.
        
        Returns:
            tuple[bool, Optional[str]]: (is_valid, error_message)
        """
        if not settings.enable_command_validation:
            logger.warning("Command validation is DISABLED - executing without checks")
            return True, None
    
        # Check for blocked patterns first
        for pattern in cls.BLOCKED_PATTERNS:
            if re.search(pattern, command, re.IGNORECASE):
                error = f"Command blocked by security policy: matches dangerous pattern '{pattern}'"
                logger.warning(f"SECURITY: Blocked command: {command}")
                return False, error
    
        # Check against whitelist
        for pattern in cls.ALLOWED_PATTERNS:
            if re.match(pattern, command.strip()):
                logger.debug(f"Command validated: {command}")
                return True, None
    
        # Command not in whitelist
        error = f"Command not in whitelist: {command}"
        logger.warning(f"SECURITY: Command rejected (not whitelisted): {command}")
        return False, error
  • SSH execution function SSHClient.execute that runs commands on the OpenWRT router via asyncssh, handles timeouts, logs execution, and returns structured results with stdout, stderr, exit code, and execution time.
    async def execute(self, command: str, timeout: Optional[int] = None) -> dict:
        """
        Execute a command on the OpenWRT router.
        
        Args:
            command: Command to execute
            timeout: Execution timeout in seconds (defaults to SSH_TIMEOUT)
            
        Returns:
            dict: Execution result with keys:
                - success: bool
                - stdout: str
                - stderr: str
                - exit_code: int
                - execution_time: float
        """
        if not self.is_connected or not self.connection:
            raise ConnectionError("SSH connection not established. Call connect() first.")
    
        if timeout is None:
            timeout = settings.ssh_timeout
    
        start_time = datetime.now()
        
        try:
            logger.debug(f"Executing command: {command}")
            
            # Execute command
            result = await asyncio.wait_for(
                self.connection.run(command, check=False),
                timeout=timeout
            )
            
            execution_time = (datetime.now() - start_time).total_seconds()
            
            # Parse result
            response = {
                "success": result.exit_status == 0,
                "stdout": result.stdout.strip() if result.stdout else "",
                "stderr": result.stderr.strip() if result.stderr else "",
                "exit_code": result.exit_status,
                "execution_time": execution_time,
            }
    
            # Log execution
            audit_logger.log_command(
                command=command,
                success=response["success"],
                output=response["stdout"],
                error=response["stderr"] if not response["success"] else None,
                execution_time=execution_time,
            )
    
            if response["success"]:
                logger.debug(f"Command succeeded in {execution_time:.2f}s")
            else:
                logger.warning(
                    f"Command failed with exit code {response['exit_code']}: {response['stderr']}"
                )
    
            return response
    
        except asyncio.TimeoutError:
            execution_time = (datetime.now() - start_time).total_seconds()
            error = f"Command execution timed out after {timeout}s"
            logger.error(error)
            audit_logger.log_command(
                command=command,
                success=False,
                error=error,
                execution_time=execution_time,
            )
            return {
                "success": False,
                "stdout": "",
                "stderr": error,
                "exit_code": -1,
                "execution_time": execution_time,
            }
    
        except Exception as e:
            execution_time = (datetime.now() - start_time).total_seconds()
            error = f"Command execution error: {str(e)}"
            logger.error(error)
            audit_logger.log_command(
                command=command,
                success=False,
                error=error,
                execution_time=execution_time,
            )
            return {
                "success": False,
                "stdout": "",
                "stderr": error,
                "exit_code": -1,
                "execution_time": execution_time,
            }
Behavior4/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively communicates critical behavioral traits: the command validation against a security whitelist (important safety context), the execution nature (implies mutation/action), and the general-purpose nature. However, it doesn't mention potential side effects, error handling, or output format details.

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?

The description is perfectly concise with two sentences that each earn their place: the first establishes core functionality and security constraints, the second provides crucial usage guidance. No wasted words, and the most important information (what it does and when to use it) is front-loaded.

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

Completeness4/5

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

For a single-parameter tool with no annotations and no output schema, the description does well by covering purpose, security constraints, and usage guidelines. However, it lacks information about what the tool returns (output format) and doesn't mention potential risks or limitations of command execution, which would be valuable given the tool's power and potential for destructive operations.

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 description coverage is 100%, with the single parameter 'command' well-documented in the schema. The description adds only minimal value beyond the schema by reinforcing the whitelist requirement ('must be in whitelist' vs 'must be in whitelist' in schema). This meets the baseline expectation when schema coverage is complete.

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 specific action ('execute a validated shell command') and resource ('on the OpenWRT router'), distinguishing it from sibling tools that perform specialized operations like getting firewall rules or system info. It explicitly mentions the security validation aspect, which adds important context.

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

Usage Guidelines5/5

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

The description provides explicit guidance on when to use this tool ('for commands not covered by other specialized tools'), creating a clear boundary with sibling tools that handle specific functions like opkg operations, thread management, or status retrieval. This helps the agent understand this is a general-purpose tool when specialized alternatives aren't available.

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

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jsebgiraldo/openwrt_ssh_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server