Skip to main content

lean_ctx/tools/registered/
ctx_index.rs

1use std::path::Path;
2
3use rmcp::ErrorData;
4use rmcp::model::Tool;
5use serde_json::{Map, Value, json};
6
7use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_str};
8use crate::tool_defs::tool_def;
9
10pub struct CtxIndexTool;
11
12impl McpTool for CtxIndexTool {
13    fn name(&self) -> &'static str {
14        "ctx_index"
15    }
16
17    fn tool_def(&self) -> Tool {
18        tool_def(
19            "ctx_index",
20            "Index orchestration — manage code graph index.\n\
21             WORKFLOW: status → build → build-full (escalate if stale).\n\
22             ANTI-PATTERN: build-full is expensive — use incremental build first.\n\
23             Actions: status (state), build (incremental), build-full (rebuild).",
24            json!({
25                "type": "object",
26                "properties": {
27                    "action": {
28                        "type": "string",
29                        "enum": ["status", "build", "build-full"],
30                        "description": "status|build|build-full"
31                    },
32                    "project_root": {
33                        "type": "string",
34                        "description": "Project root"
35                    }
36                },
37                "required": ["action"]
38            }),
39        )
40    }
41
42    fn handle(
43        &self,
44        args: &Map<String, Value>,
45        ctx: &ToolContext,
46    ) -> Result<ToolOutput, ErrorData> {
47        let action = get_str(args, "action")
48            .ok_or_else(|| ErrorData::invalid_params("action is required", None))?;
49        let root = if let Some(p) = ctx
50            .resolved_path("project_root")
51            .or(ctx.resolved_path("root"))
52        {
53            p
54        } else if let Some(err) = ctx.path_error("project_root").or(ctx.path_error("root")) {
55            return Err(ErrorData::invalid_params(
56                format!("project_root: {err}"),
57                None,
58            ));
59        } else {
60            &ctx.project_root
61        };
62
63        let result = crate::tools::ctx_index::handle(&action, Path::new(root));
64
65        // #420: `build-full` is an explicit "make everything fresh". The CLI path
66        // flushes the running daemon's read cache via `notify_cache_clear()`; the
67        // MCP tool runs in the process that owns this session's `SessionCache`, so
68        // clear it in-process here. Otherwise `ctx_read` map/signatures keep
69        // serving pre-rebuild output from the long-lived cache.
70        if action == "build-full"
71            && let Some(cache) = ctx.cache.as_ref()
72            && let Some(mut guard) = crate::server::bounded_lock::write(cache, "ctx_index")
73        {
74            guard.clear();
75        }
76
77        Ok(ToolOutput::simple(result))
78    }
79}