Skip to main content

lean_ctx/tools/registered/
ctx_intent.rs

1use 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 CtxIntentTool;
9
10impl McpTool for CtxIntentTool {
11    fn name(&self) -> &'static str {
12        "ctx_intent"
13    }
14
15    fn tool_def(&self) -> Tool {
16        tool_def(
17            "ctx_intent",
18            "Submit task goals as JSON or short text — server infers from tool calls.\n\
19             ANTI-PATTERN: not needed for simple tasks.\n\
20             query=task|JSON; format=json for JSON output; project_root=scope.",
21            json!({
22                "type": "object",
23                "properties": {
24                    "query": { "type": "string", "description": "Compact JSON intent or short text" },
25                    "project_root": { "type": "string", "description": "Project root" },
26                    "format": { "type": "string", "description": "Output format (omit for default, \"json\" for JSON route)" }
27                },
28                "required": ["query"]
29            }),
30        )
31    }
32
33    fn handle(
34        &self,
35        args: &Map<String, Value>,
36        ctx: &ToolContext,
37    ) -> Result<ToolOutput, ErrorData> {
38        let query = get_str(args, "query")
39            .ok_or_else(|| ErrorData::invalid_params("query is required", None))?;
40        let root = if let Some(p) = ctx.resolved_path("project_root") {
41            p.to_string()
42        } else if let Some(err) = ctx.path_error("project_root") {
43            return Err(ErrorData::invalid_params(
44                format!("project_root: {err}"),
45                None,
46            ));
47        } else {
48            ".".to_string()
49        };
50        let format = get_str(args, "format");
51
52        let cache = ctx
53            .cache
54            .as_ref()
55            .ok_or_else(|| ErrorData::internal_error("cache not available", None))?;
56        let Some(mut cache_guard) = crate::server::bounded_lock::write(cache, "ctx_intent:cache")
57        else {
58            return Ok(ToolOutput::simple(
59                "[intent unavailable — cache busy, retry]".to_string(),
60            ));
61        };
62        let output = crate::tools::ctx_intent::handle(
63            &mut cache_guard,
64            &query,
65            &root,
66            ctx.crp_mode,
67            format.as_deref(),
68        );
69        drop(cache_guard);
70
71        if let Some(ref session) = ctx.session
72            && let Some(mut session_guard) =
73                crate::server::bounded_lock::write(session, "ctx_intent:session")
74        {
75            session_guard.set_task(&query, Some("intent"));
76        }
77
78        Ok(ToolOutput {
79            text: output,
80            original_tokens: 0,
81            saved_tokens: 0,
82            mode: Some("semantic".to_string()),
83            path: None,
84            changed: false,
85            shell_outcome: None,
86        })
87    }
88}