lean_ctx/tools/registered/
ctx_intent.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 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}