lean_ctx/tools/registered/
shell_alias.rs1use rmcp::model::Tool;
2use rmcp::ErrorData;
3use serde_json::{json, Map, Value};
4
5use crate::server::tool_trait::{get_str, McpTool, ToolContext, ToolOutput};
6use crate::tool_defs::tool_def;
7
8pub struct ShellAliasTool;
19
20impl McpTool for ShellAliasTool {
21 fn name(&self) -> &'static str {
22 "shell"
23 }
24
25 fn tool_def(&self) -> Tool {
26 tool_def(
27 "shell",
28 "Execute a shell command. Returns token-optimized compressed output (95+ patterns for git, npm, cargo, docker, tsc, etc). Equivalent to running the command in a terminal but with automatic output compression for efficiency.",
29 json!({
30 "type": "object",
31 "properties": {
32 "command": {
33 "type": "string",
34 "description": "The shell command to execute"
35 },
36 "cwd": {
37 "type": "string",
38 "description": "Working directory (optional, defaults to project root)"
39 }
40 },
41 "required": ["command"]
42 }),
43 )
44 }
45
46 fn handle(
47 &self,
48 args: &Map<String, Value>,
49 ctx: &ToolContext,
50 ) -> Result<ToolOutput, ErrorData> {
51 let command = get_str(args, "command")
52 .ok_or_else(|| ErrorData::invalid_params("command is required", None))?;
53
54 if let Some(rejection) = crate::tools::ctx_shell::validate_command(&command) {
55 return Ok(ToolOutput::simple(rejection));
56 }
57
58 if let Err(msg) = crate::core::shell_allowlist::check_shell_allowlist(&command) {
59 return Ok(ToolOutput::simple(msg));
60 }
61
62 tokio::task::block_in_place(|| {
63 let cwd = get_str(args, "cwd");
64 let mut shell_args = Map::new();
65 shell_args.insert("command".to_string(), Value::String(command));
66 if let Some(dir) = cwd {
67 shell_args.insert("cwd".to_string(), Value::String(dir));
68 }
69 shell_args.insert("raw".to_string(), Value::Bool(false));
71
72 crate::tools::registered::ctx_shell::CtxShellTool.handle(&shell_args, ctx)
73 })
74 }
75}