run_bash
Run bash commands within the workspace root. Specify command, optional timeout, and working directory to automate system tasks.
Instructions
Run a bash command within the workspace root.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| command | Yes | ||
| timeout | No | ||
| working_dir | No | . |
Implementation Reference
- The core handler function for the 'run_bash' tool. It creates an async subprocess shell running /bin/bash with the given command, enforces a timeout (default from config), and returns a dict with command, cwd, exit_code, timed_out, stdout, and stderr.
@mcp.tool() async def run_bash( command: str, timeout: int | None = None, working_dir: str = ".", ) -> dict: """Run a bash command within the workspace root.""" cwd = _resolve_path(working_dir) if not cwd.is_dir(): raise ValueError(f"'{working_dir}' is not a directory.") proc = await asyncio.create_subprocess_shell( command, cwd=str(cwd), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, executable="/bin/bash", ) timeout = timeout or config.default_command_timeout try: stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=timeout) timed_out = False except asyncio.TimeoutError: proc.kill() stdout, stderr = await proc.communicate() timed_out = True return { "command": command, "cwd": str(cwd), "exit_code": proc.returncode if not timed_out else -1, "timed_out": timed_out, "stdout": stdout.decode(errors="replace"), "stderr": stderr.decode(errors="replace"), } - The write_file tool - included for context as a sibling tool defined in the same file.
@mcp.tool() def write_file(path: str, content: str, overwrite: bool = True) -> dict: """Write a file within the workspace.""" target = _resolve_path(path) if target.exists() and not overwrite: raise ValueError(f"'{path}' already exists and overwrite is disabled.") target.parent.mkdir(parents=True, exist_ok=True) target.write_text(content, encoding="utf-8") return { "path": str(target), "bytes_written": len(content.encode("utf-8")), } - The _resolve_path helper used by run_bash to resolve and validate working_dir against the workspace root, enforcing the allow_external_paths config.
def register(mcp, *, config) -> None: def _resolve_path(path: str) -> Path: raw_path = Path(path).expanduser() resolved = ( raw_path.resolve() if raw_path.is_absolute() else (config.workspace_root / raw_path).resolve() ) if config.allow_external_paths: return resolved if resolved != config.workspace_root and config.workspace_root not in resolved.parents: raise ValueError( f"Path '{path}' is outside the workspace root {config.workspace_root}." ) return resolved - src/friday_mcp_server/tools/__init__.py:1-11 (registration)Registration orchestration: register_all_tools calls workspace.register(mcp, config=config) which registers the run_bash tool via the @mcp.tool() decorator pattern.
"""Tool registry for Friday MCP Server.""" from friday_mcp_server.tools import skills, system, utils, web, workspace def register_all_tools(mcp, *, config, skill_store) -> None: system.register(mcp, config=config) utils.register(mcp) web.register(mcp, config=config) workspace.register(mcp, config=config) skills.register(mcp, skill_store=skill_store) - Config dataclass containing default_command_timeout (line 29) and allow_external_paths (line 28) used by run_bash for timeout and path validation.
@dataclass(frozen=True) class Config: server_name: str transport: str workspace_root: Path skills_root: Path allow_external_paths: bool default_command_timeout: int max_fetch_chars: int