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            "Session memory. save at session end, load at start, status = snapshot;\n\
19             task|finding|decision record progress (value=text).\n\
20             ANTIPATTERN: permanent project knowledge → ctx_knowledge.",
21            json!({
22                "type": "object",
23                "properties": {
24                    "action": {
25                        "type": "string",
26                        "description": "status|load|save|task|finding|decision|list|… (invalid action lists all)"
27                    },
28                    "value": { "type": "string" },
29                    "session_id": { "type": "string", "description": "Omit for latest" }
30                },
31                "required": ["action"]
32            }),
33        )
34    }
35
36    fn handle(
37        &self,
38        args: &Map<String, Value>,
39        ctx: &ToolContext,
40    ) -> Result<ToolOutput, ErrorData> {
41        let action = get_str(args, "action")
42            .ok_or_else(|| ErrorData::invalid_params("action is required", None))?;
43        let value = get_str(args, "value");
44        let sid = get_str(args, "session_id");
45        let format = get_str(args, "format");
46        let path = get_str(args, "path");
47        let write = get_bool(args, "write").unwrap_or(false);
48        let privacy = get_str(args, "privacy");
49        let terse = get_bool(args, "terse");
50
51        let tool_calls_handle = ctx
52            .tool_calls
53            .as_ref()
54            .ok_or_else(|| ErrorData::internal_error("tool_calls not available", None))?;
55        let call_durations: Vec<(String, u64)> = {
56            let tc = tool_calls_handle.blocking_read();
57            tc.iter().map(|c| (c.tool.clone(), c.duration_ms)).collect()
58        };
59        let agent_id = ctx
60            .agent_id
61            .as_ref()
62            .and_then(|agent_id| agent_id.blocking_read().clone());
63
64        let session_handle = ctx
65            .session
66            .as_ref()
67            .ok_or_else(|| ErrorData::internal_error("session not available", None))?;
68        let mut session = session_handle.blocking_write();
69        let result = crate::tools::ctx_session::handle(
70            &mut session,
71            &call_durations,
72            &action,
73            value.as_deref(),
74            sid.as_deref(),
75            crate::tools::ctx_session::SessionToolOptions {
76                format: format.as_deref(),
77                path: path.as_deref(),
78                write,
79                privacy: privacy.as_deref(),
80                terse,
81                agent_id: agent_id.as_deref(),
82            },
83        );
84
85        Ok(ToolOutput {
86            text: result,
87            original_tokens: 0,
88            saved_tokens: 0,
89            mode: Some(action),
90            path: None,
91            changed: false,
92            shell_outcome: None,
93            content_blocks: None,
94        })
95    }
96}