lean_ctx/tools/registered/
shell_alias.rs1use rmcp::ErrorData;
2use rmcp::model::Tool;
3use serde_json::{Map, Value, json};
4
5use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_str};
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 "Shell command with auto-compression (~95 patterns). Alias for ctx_shell.\n\
29 Output is compressed for token savings. For verbatim output pass raw=true.\n\
30 Use when your MCP client prefers shell/bash over ctx_shell — transparently\n\
31 delegates to ctx_shell internals.",
32 json!({
33 "type": "object",
34 "properties": {
35 "command": {
36 "type": "string",
37 "description": "Shell command"
38 },
39 "cwd": {
40 "type": "string",
41 "description": "Working dir"
42 },
43 "raw": {
44 "type": "boolean",
45 "description": "Return verbatim output (skip compression). Default false — pass true for the exact bytes."
46 }
47 },
48 "required": ["command"]
49 }),
50 )
51 }
52
53 fn handle(
54 &self,
55 args: &Map<String, Value>,
56 ctx: &ToolContext,
57 ) -> Result<ToolOutput, ErrorData> {
58 let command = get_str(args, "command")
59 .ok_or_else(|| ErrorData::invalid_params("command is required", None))?;
60
61 let write_allow_paths =
62 crate::core::config::Config::load().shell_write_allow_paths_effective();
63 let project_root = crate::core::config::Config::find_project_root();
64 if let Some(rejection) = crate::tools::ctx_shell::validate_command_with_write_allow_paths(
65 &command,
66 &write_allow_paths,
67 project_root.as_deref(),
68 ) {
69 return Ok(ToolOutput::simple(rejection));
70 }
71
72 if let Err(msg) = crate::core::shell_allowlist::check_shell_allowlist(&command) {
73 return Ok(ToolOutput::simple(msg.to_string()));
74 }
75
76 tokio::task::block_in_place(|| {
77 let cwd = get_str(args, "cwd");
78 let raw = args.get("raw").and_then(Value::as_bool).unwrap_or(false);
82 let mut shell_args = Map::new();
83 shell_args.insert("command".to_string(), Value::String(command));
84 if let Some(dir) = cwd {
85 shell_args.insert("cwd".to_string(), Value::String(dir));
86 }
87 shell_args.insert("raw".to_string(), Value::Bool(raw));
88
89 crate::tools::registered::ctx_shell::CtxShellTool.handle(&shell_args, ctx)
90 })
91 }
92}