Skip to main content

lean_ctx/tools/registered/
ctx_execute.rs

1use rmcp::model::Tool;
2use rmcp::ErrorData;
3use serde_json::{json, Map, Value};
4
5use crate::server::tool_trait::{get_int, get_str, McpTool, ToolContext, ToolOutput};
6use crate::tool_defs::tool_def;
7
8pub struct CtxExecuteTool;
9
10impl McpTool for CtxExecuteTool {
11    fn name(&self) -> &'static str {
12        "ctx_execute"
13    }
14
15    fn tool_def(&self) -> Tool {
16        tool_def(
17            "ctx_execute",
18            "Run code in sandbox (11 languages). Only stdout enters context. Raw data never leaves subprocess. Languages: javascript, typescript, python, shell, ruby, go, rust, php, perl, r, elixir.",
19            json!({
20                "type": "object",
21                "properties": {
22                    "language": {
23                        "type": "string",
24                        "description": "Language: javascript|typescript|python|shell|ruby|go|rust|php|perl|r|elixir"
25                    },
26                    "code": {
27                        "type": "string",
28                        "description": "Code to execute in sandbox"
29                    },
30                    "intent": {
31                        "type": "string",
32                        "description": "What you want from the output (triggers intent-driven filtering for large results)"
33                    },
34                    "timeout": {
35                        "type": "integer",
36                        "description": "Timeout in seconds (default: 30)"
37                    },
38                    "action": {
39                        "type": "string",
40                        "description": "batch — execute multiple scripts. Provide items as JSON array [{language, code}]"
41                    },
42                    "items": {
43                        "type": "string",
44                        "description": "JSON array of [{\"language\": \"...\", \"code\": \"...\"}] for batch execution"
45                    },
46                    "path": {
47                        "type": "string",
48                        "description": "File path for action=file"
49                    }
50                }
51            }),
52        )
53    }
54
55    fn handle(
56        &self,
57        args: &Map<String, Value>,
58        ctx: &ToolContext,
59    ) -> Result<ToolOutput, ErrorData> {
60        let action = get_str(args, "action").unwrap_or_default();
61
62        let result = if action == "batch" {
63            let items_str = get_str(args, "items")
64                .ok_or_else(|| ErrorData::invalid_params("items is required for batch", None))?;
65            let items: Vec<serde_json::Value> = serde_json::from_str(&items_str)
66                .map_err(|e| ErrorData::invalid_params(format!("Invalid items JSON: {e}"), None))?;
67            let batch: Vec<(String, String)> = items
68                .iter()
69                .filter_map(|item| {
70                    let lang = item.get("language")?.as_str()?.to_string();
71                    let code = item.get("code")?.as_str()?.to_string();
72                    Some((lang, code))
73                })
74                .collect();
75            crate::tools::ctx_execute::handle_batch(&batch)
76        } else if action == "file" {
77            let path = ctx
78                .resolved_path("path")
79                .ok_or_else(|| ErrorData::invalid_params("path is required for action=file", None))?
80                .to_string();
81            let project_root = if ctx.project_root.is_empty() {
82                None
83            } else {
84                Some(ctx.project_root.as_str())
85            };
86            let intent = get_str(args, "intent");
87            crate::tools::ctx_execute::handle_file(&path, intent.as_deref(), project_root)
88        } else {
89            let language = get_str(args, "language")
90                .ok_or_else(|| ErrorData::invalid_params("language is required", None))?;
91            let code = get_str(args, "code")
92                .ok_or_else(|| ErrorData::invalid_params("code is required", None))?;
93            let intent = get_str(args, "intent");
94            let timeout = get_int(args, "timeout").map(|t| t as u64);
95            crate::tools::ctx_execute::handle(&language, &code, intent.as_deref(), timeout)
96        };
97
98        let result = crate::core::redaction::redact_text_if_enabled(&result);
99        Ok(ToolOutput {
100            text: result,
101            original_tokens: 0,
102            saved_tokens: 0,
103            mode: Some(action),
104            path: None,
105            changed: false,
106        })
107    }
108}