Skip to main content

lean_ctx_server/tools/registered/
ctx_control.rs

1// SPDX-License-Identifier: Apache-2.0
2use rmcp::ErrorData;
3use rmcp::model::Tool;
4use serde_json::{Map, Value, json};
5
6use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput};
7use lean_ctx::tool_defs::tool_def;
8
9pub struct CtxControlTool;
10
11impl McpTool for CtxControlTool {
12    fn name(&self) -> &'static str {
13        "ctx_control"
14    }
15
16    fn tool_def(&self) -> Tool {
17        tool_def(
18            "ctx_control",
19            "Fine-tune context — exclude, include, pin, unpin, evict, set_view, set_priority, mark_outdated, reset, list, history.\n\
20            Overlay-based, reversible, scoped to call/session/project.\n\
21            WORKFLOW: after ctx_compose, exclude low-relevance files.\n\
22            ANTIPATTERN: not for initial context building — use ctx_compose/ctx_read first.",
23            json!({
24                "type": "object",
25                "properties": {
26                    "action": {
27                        "type": "string",
28                        "description": "exclude|include|pin|unpin|evict|set_view|set_priority|mark_outdated|reset|list|history"
29                    },
30                    "target": { "type": "string", "description": "@F1 handle, file path or item ID" },
31                    "targets": { "type": "string", "description": "evict: comma-separated file paths" },
32                    "value": { "type": "string", "description": "New content, view name, or priority" },
33                    "scope": { "type": "string", "description": "call (this turn only), session (rest of this session), project (persists)" },
34                    "reason": { "type": "string", "description": "Reason for the action" }
35                },
36                "required": ["action"]
37            }),
38        )
39    }
40
41    fn handle(
42        &self,
43        args: &Map<String, Value>,
44        ctx: &ToolContext,
45    ) -> Result<ToolOutput, ErrorData> {
46        let root = if let Some(ref session_lock) = ctx.session {
47            crate::server::bounded_lock::read(session_lock, "ctx_control:session")
48                .as_ref()
49                .and_then(|s| s.project_root.clone())
50                .unwrap_or_else(|| ctx.project_root.clone())
51        } else {
52            ctx.project_root.clone()
53        };
54
55        let mut overlays = lean_ctx::core::context_overlay::OverlayStore::load_project(
56            &std::path::PathBuf::from(&root),
57        );
58
59        let result = if let Some(ref ledger_lock) = ctx.ledger {
60            let Some(mut ledger) =
61                crate::server::bounded_lock::write(ledger_lock, "ctx_control:ledger")
62            else {
63                return Ok(ToolOutput::simple(
64                    "[control unavailable — ledger busy, retry]".to_string(),
65                ));
66            };
67            let r = control(args, &mut ledger, &mut overlays, &root);
68            ledger.save();
69            r
70        } else {
71            let mut ledger = lean_ctx::core::context_ledger::ContextLedger::load();
72            let r = control(args, &mut ledger, &mut overlays, &root);
73            ledger.save();
74            r
75        };
76        let _ = overlays.save_project(&std::path::PathBuf::from(&root));
77
78        Ok(ToolOutput::simple(result))
79    }
80}
81
82/// `evict` needs the project root to resolve partial paths; every other
83/// action is the shared overlay handler (CLI and dashboard use it too).
84fn control(
85    args: &Map<String, Value>,
86    ledger: &mut lean_ctx::core::context_ledger::ContextLedger,
87    overlays: &mut lean_ctx::core::context_overlay::OverlayStore,
88    root: &str,
89) -> String {
90    if args.get("action").and_then(Value::as_str) == Some("evict") {
91        let targets = args
92            .get("targets")
93            .or_else(|| args.get("target"))
94            .and_then(Value::as_str)
95            .unwrap_or_default();
96        return crate::tools::ctx_control::evict(targets, ledger, overlays, root);
97    }
98    crate::tools::ctx_control::handle(Some(args), ledger, overlays)
99}