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