Entropic 2.3.8
Local-first agentic inference engine
Loading...
Searching...
No Matches
bash.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
12
13#include <nlohmann/json.hpp>
14
15#include <array>
16#include <cstdio>
17#include <filesystem>
18#include <stdexcept>
19#include <string>
20
21static auto logger = entropic::log::get("mcp.bash");
22
23namespace entropic {
24
25// ── working_dir validation ──────────────────────────────────────
26
43static bool is_safe_cwd(const std::string& cwd) {
44 static constexpr const char* unsafe_chars = ";&|`$<>\n\r\\\"'*?(){}[]";
45 if (cwd.find_first_of(unsafe_chars) != std::string::npos) {
46 return false;
47 }
48 std::error_code ec;
49 return std::filesystem::is_directory(cwd, ec);
50}
51
59static std::pair<std::string, int> run_popen(
60 const std::string& full_cmd) {
61
62 FILE* pipe = popen(full_cmd.c_str(), "r"); // NOLINT
63 if (pipe == nullptr) {
64 return {"Failed to open process", -1};
65 }
66
67 std::string output;
68 std::array<char, 4096> buf{};
69 while (fgets(buf.data(), buf.size(), pipe) != nullptr) {
70 output += buf.data();
71 }
72
73 int status = pclose(pipe); // NOLINT
74 int exit_code = WEXITSTATUS(status);
75 return {output, exit_code};
76}
77
78// ── ExecuteTool ─────────────────────────────────────────────────
79
85class ExecuteTool : public ToolBase {
86public:
95 : ToolBase(std::move(def)), server_(server) {}
96
104 ServerResponse execute(const std::string& args_json) override;
105
106private:
107 BashServer& server_;
108};
109
126ServerResponse ExecuteTool::execute(const std::string& args_json) {
127 auto args = nlohmann::json::parse(args_json);
128 std::string command = args.at("command").get<std::string>();
129
130 std::string cwd = args.value(
131 "working_dir", server_.working_dir().string());
132
133 logger->info("[bash.execute] cmd='{}' cwd='{}'", command, cwd);
134
135 if (!is_safe_cwd(cwd)) {
136 logger->warn("Rejected unsafe working_dir: '{}'", cwd);
137 return {"Error: working_dir is not an existing directory or "
138 "contains shell metacharacters", {}};
139 }
140
141 std::string full_cmd =
142 "cd " + cwd + " && " + command + " 2>&1";
143
144 auto [output, exit_code] = run_popen(full_cmd);
145 logger->info("Bash: exit={}, stdout={} chars, cmd='{}'",
146 exit_code, output.size(), command);
147
148 nlohmann::json result;
149 result["exit_code"] = exit_code;
150 result["output"] = output;
151 return {result.dump(), {}};
152}
153
154// ── BashServer ──────────────────────────────────────────────────
155
165 const std::filesystem::path& working_dir,
166 const std::string& data_dir,
167 int timeout)
168 : MCPServerBase("bash")
169 , working_dir_(working_dir)
170 , timeout_(timeout) {
171
172 auto def = load_tool_definition(
173 "execute", "bash", data_dir + "/tools");
174
175 execute_tool_ = std::make_unique<ExecuteTool>(
176 std::move(def), *this);
177
178 register_tool(execute_tool_.get());
179
180 logger->info("BashServer initialized: cwd='{}' timeout={}s",
181 working_dir_.string(), timeout_);
182}
183
189BashServer::~BashServer() = default;
190
199std::string
200BashServer::get_permission_pattern(const std::string& tool_name, const std::string& args_json) const {
201
202 std::string base_cmd = "unknown";
203 try {
204 auto args = nlohmann::json::parse(args_json);
205 std::string cmd = args.at("command").get<std::string>();
206 auto space = cmd.find(' ');
207 base_cmd = (space != std::string::npos)
208 ? cmd.substr(0, space) : cmd;
209 } catch (const std::exception& e) {
210 logger->warn("Failed to parse command for permission: {}",
211 e.what());
212 }
213 return tool_name + ":" + base_cmd + " *";
214}
215
223bool BashServer::set_working_dir(const std::string& path) {
224 working_dir_ = path;
225 logger->info("Working directory set to: {}", path);
226 return true;
227}
228
235const std::filesystem::path& BashServer::working_dir() const {
236 return working_dir_;
237}
238
246 return timeout_;
247}
248
249} // namespace entropic
Bash MCP server — shell command execution.
Bash MCP server for shell command execution.
Definition bash.h:28
int timeout() const
Get command timeout.
Definition bash.cpp:245
const std::filesystem::path & working_dir() const
Get the working directory.
Definition bash.cpp:235
bool set_working_dir(const std::string &path) override
Set working directory.
Definition bash.cpp:223
~BashServer() override
Destructor.
BashServer(const std::filesystem::path &working_dir, const std::string &data_dir, int timeout=30)
Construct with working directory and data dir.
Definition bash.cpp:164
std::string get_permission_pattern(const std::string &tool_name, const std::string &args_json) const override
Permission pattern: "execute:{base_cmd} *".
Definition bash.cpp:200
Tool for executing shell commands.
Definition bash.cpp:85
ServerResponse execute(const std::string &args_json) override
Execute a shell command.
Definition bash.cpp:126
ExecuteTool(ToolDefinition def, BashServer &server)
Construct from tool definition with server ref.
Definition bash.cpp:94
Concrete base class for MCP servers (80% logic).
Definition server_base.h:66
void register_tool(ToolBase *tool)
Register a tool with this server.
Abstract base class for individual MCP tools.
Definition tool_base.h:45
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Definition logging.cpp:211
Activate model on GPU (WARM → ACTIVE).
static bool is_safe_cwd(const std::string &cwd)
Reject working_dir values that would smuggle shell syntax.
Definition bash.cpp:43
ToolDefinition load_tool_definition(const std::string &tool_name, const std::string &server_prefix, const std::string &data_dir)
Load a tool definition from a JSON file.
Definition tool_base.cpp:81
static std::pair< std::string, int > run_popen(const std::string &full_cmd)
Run a shell command and capture output.
Definition bash.cpp:59
MCPServerBase concrete base class + ServerResponse.
Structured result from tool execution.
Definition server_base.h:33
Parsed tool definition from JSON schema file.
Definition tool_base.h:27
Abstract base class for individual MCP tools.