Skip to main content

lean_ctx/tools/registered/
ctx_session.rs

1use rmcp::ErrorData;
2use rmcp::model::Tool;
3use serde_json::{Map, Value, json};
4
5use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_bool, get_str};
6use crate::tool_defs::tool_def;
7
8pub struct CtxSessionTool;
9
10impl McpTool for CtxSessionTool {
11    fn name(&self) -> &'static str {
12        "ctx_session"
13    }
14
15    fn tool_def(&self) -> Tool {
16        tool_def(
17            "ctx_session",
18            "WORKFLOW: action=save at session end; action=load at session start.\n\
19             action=status (snapshot); task|finding|decision (progress).\n\
20             ANTIPATTERN: permanent project knowledge → ctx_knowledge.\n\
21             Also supports: profile|role|budget|slo|diff|verify|episodes|procedures.",
22            json!({
23                "type": "object",
24                "properties": {
25                    "action": {
26                        "type": "string",
27                        "description": "status|load|save|task|finding|decision|reset|list|cleanup|snapshot|restore|resume|profile|role|budget|slo|diff|verify|episodes|procedures"
28                    },
29                    "value": { "type": "string", "description": "Value for task/finding/decision actions" },
30                    "session_id": { "type": "string", "description": "Session ID (omit for latest)" }
31                },
32                "required": ["action"]
33            }),
34        )
35    }
36
37    fn handle(
38        &self,
39        args: &Map<String, Value>,
40        ctx: &ToolContext,
41    ) -> Result<ToolOutput, ErrorData> {
42        let action = get_str(args, "action")
43            .ok_or_else(|| ErrorData::invalid_params("action is required", None))?;
44        let value = get_str(args, "value");
45        let sid = get_str(args, "session_id");
46        let format = get_str(args, "format");
47        let path = get_str(args, "path");
48        let write = get_bool(args, "write").unwrap_or(false);
49        let privacy = get_str(args, "privacy");
50        let terse = get_bool(args, "terse");
51
52        let tool_calls_handle = ctx
53            .tool_calls
54            .as_ref()
55            .ok_or_else(|| ErrorData::internal_error("tool_calls not available", None))?;
56        let call_durations: Vec<(String, u64)> = {
57            let tc = tool_calls_handle.blocking_read();
58            tc.iter().map(|c| (c.tool.clone(), c.duration_ms)).collect()
59        };
60
61        let session_handle = ctx
62            .session
63            .as_ref()
64            .ok_or_else(|| ErrorData::internal_error("session not available", None))?;
65        let mut session = session_handle.blocking_write();
66        let result = crate::tools::ctx_session::handle(
67            &mut session,
68            &call_durations,
69            &action,
70            value.as_deref(),
71            sid.as_deref(),
72            crate::tools::ctx_session::SessionToolOptions {
73                format: format.as_deref(),
74                path: path.as_deref(),
75                write,
76                privacy: privacy.as_deref(),
77                terse,
78            },
79        );
80
81        Ok(ToolOutput {
82            text: result,
83            original_tokens: 0,
84            saved_tokens: 0,
85            mode: Some(action),
86            path: None,
87            changed: false,
88            shell_outcome: None,
89        })
90    }
91}