execute_shell_command
Execute shell commands directly from the Serena MCP server to automate tasks, run scripts, and retrieve command output for development workflows.
Instructions
Execute a shell command and return its output. If there is a memory about suggested commands, read that first. Never execute unsafe shell commands! IMPORTANT: Do not use this tool to start
long-running processes (e.g. servers) that are not intended to terminate quickly,
processes that require user interaction. Returns a JSON object containing the command's stdout and optionally stderr output.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| command | Yes | The shell command to execute. | |
| cwd | No | The working directory to execute the command in. If None, the project root will be used. | |
| capture_stderr | No | Whether to capture and return stderr output. | |
| max_answer_chars | No | If the output is longer than this number of characters, no content will be returned. -1 means using the default value, don't adjust unless there is no other way to get the content required for the task. |
Implementation Reference
- src/serena/tools/cmd_tools.py:11-52 (handler)ExecuteShellCommandTool class providing the handler logic (apply method) for the execute_shell_command tool. Calls the shell helper, handles cwd resolution, output processing, and truncation.class ExecuteShellCommandTool(Tool, ToolMarkerCanEdit): """ Executes a shell command. """ def apply( self, command: str, cwd: str | None = None, capture_stderr: bool = True, max_answer_chars: int = -1, ) -> str: """ Execute a shell command and return its output. If there is a memory about suggested commands, read that first. Never execute unsafe shell commands! IMPORTANT: Do not use this tool to start * long-running processes (e.g. servers) that are not intended to terminate quickly, * processes that require user interaction. :param command: the shell command to execute :param cwd: the working directory to execute the command in. If None, the project root will be used. :param capture_stderr: whether to capture and return stderr output :param max_answer_chars: if the output is longer than this number of characters, no content will be returned. -1 means using the default value, don't adjust unless there is no other way to get the content required for the task. :return: a JSON object containing the command's stdout and optionally stderr output """ if cwd is None: _cwd = self.get_project_root() else: if os.path.isabs(cwd): _cwd = cwd else: _cwd = os.path.join(self.get_project_root(), cwd) if not os.path.isdir(_cwd): raise FileNotFoundError( f"Specified a relative working directory ({cwd}), but the resulting path is not a directory: {_cwd}" ) result = execute_shell_command(command, cwd=_cwd, capture_stderr=capture_stderr) result = result.json() return self._limit_length(result, max_answer_chars)
- src/serena/util/shell.py:16-42 (helper)Core implementation executing the shell command using subprocess.Popen and returning a structured ShellCommandResult.def execute_shell_command(command: str, cwd: str | None = None, capture_stderr: bool = False) -> ShellCommandResult: """ Execute a shell command and return the output. :param command: The command to execute. :param cwd: The working directory to execute the command in. If None, the current working directory will be used. :param capture_stderr: Whether to capture the stderr output. :return: The output of the command. """ if cwd is None: cwd = os.getcwd() process = subprocess.Popen( command, shell=True, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE if capture_stderr else None, text=True, encoding="utf-8", errors="replace", cwd=cwd, **subprocess_kwargs(), ) stdout, stderr = process.communicate() return ShellCommandResult(stdout=stdout, stderr=stderr, return_code=process.returncode, cwd=cwd)
- src/serena/util/shell.py:9-13 (schema)Pydantic model defining the structure of the shell command output, used internally by the handler and serialized to JSON.class ShellCommandResult(BaseModel): stdout: str return_code: int cwd: str stderr: str | None = None
- src/serena/tools/tools_base.py:356-366 (registration)ToolRegistry singleton that automatically discovers all Tool subclasses (including ExecuteShellCommandTool) and registers them by derived name 'execute_shell_command'.class ToolRegistry: def __init__(self) -> None: self._tool_dict: dict[str, RegisteredTool] = {} for cls in iter_subclasses(Tool): if not any(cls.__module__.startswith(pkg) for pkg in tool_packages): continue is_optional = issubclass(cls, ToolMarkerOptional) name = cls.get_name_from_cls() if name in self._tool_dict: raise ValueError(f"Duplicate tool name found: {name}. Tool classes must have unique names.") self._tool_dict[name] = RegisteredTool(tool_class=cls, is_optional=is_optional, tool_name=name)
- src/serena/mcp.py:252-259 (registration)MCP-specific registration where instances from the tool registry are converted to FastMCP tools and added to the MCP server's tool manager using the tool name 'execute_shell_command'.def _set_mcp_tools(self, mcp: FastMCP, openai_tool_compatible: bool = False) -> None: """Update the tools in the MCP server""" if mcp is not None: mcp._tool_manager._tools = {} for tool in self._iter_tools(): mcp_tool = self.make_mcp_tool(tool, openai_tool_compatible=openai_tool_compatible) mcp._tool_manager._tools[tool.get_name()] = mcp_tool log.info(f"Starting MCP server with {len(mcp._tool_manager._tools)} tools: {list(mcp._tool_manager._tools.keys())}")