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 },
44 "required": ["command"]
45 }),
46 )
47 }
48
49 fn handle(
50 &self,
51 args: &Map<String, Value>,
52 ctx: &ToolContext,
53 ) -> Result<ToolOutput, ErrorData> {
54 let command = get_str(args, "command")
55 .ok_or_else(|| ErrorData::invalid_params("command is required", None))?;
56
57 if let Some(rejection) = crate::tools::ctx_shell::validate_command(&command) {
58 return Ok(ToolOutput::simple(rejection));
59 }
60
61 if let Err(msg) = crate::core::shell_allowlist::check_shell_allowlist(&command) {
62 return Ok(ToolOutput::simple(msg));
63 }
64
65 tokio::task::block_in_place(|| {
66 let cwd = get_str(args, "cwd");
67 let mut shell_args = Map::new();
68 shell_args.insert("command".to_string(), Value::String(command));
69 if let Some(dir) = cwd {
70 shell_args.insert("cwd".to_string(), Value::String(dir));
71 }
72 shell_args.insert("raw".to_string(), Value::Bool(false));
74
75 crate::tools::registered::ctx_shell::CtxShellTool.handle(&shell_args, ctx)
76 })
77 }
78}