Skip to main content

lean_ctx/tools/registered/
ctx_session.rs

1use rmcp::model::Tool;
2use rmcp::ErrorData;
3use serde_json::{json, Map, Value};
4
5use crate::server::tool_trait::{get_bool, get_str, McpTool, ToolContext, ToolOutput};
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            "Cross-session memory: record task/finding/decision, restore previous session state.",
19            json!({
20                "type": "object",
21                "properties": {
22                    "action": {
23                        "type": "string",
24                        "description": "status|load|save|task|finding|decision|reset|list|cleanup|snapshot|restore|resume|profile|role|budget|slo|diff|verify|episodes|procedures"
25                    },
26                    "value": { "type": "string", "description": "Text for task/finding/decision" },
27                    "session_id": { "type": "string", "description": "For load (default: latest)" }
28                },
29                "required": ["action"]
30            }),
31        )
32    }
33
34    fn handle(
35        &self,
36        args: &Map<String, Value>,
37        ctx: &ToolContext,
38    ) -> Result<ToolOutput, ErrorData> {
39        let action = get_str(args, "action")
40            .ok_or_else(|| ErrorData::invalid_params("action is required", None))?;
41        let value = get_str(args, "value");
42        let sid = get_str(args, "session_id");
43        let format = get_str(args, "format");
44        let path = get_str(args, "path");
45        let write = get_bool(args, "write").unwrap_or(false);
46        let privacy = get_str(args, "privacy");
47        let terse = get_bool(args, "terse");
48
49        let tool_calls_handle = ctx
50            .tool_calls
51            .as_ref()
52            .ok_or_else(|| ErrorData::internal_error("tool_calls not available", None))?;
53        let call_durations: Vec<(String, u64)> = {
54            let tc = tool_calls_handle.blocking_read();
55            tc.iter().map(|c| (c.tool.clone(), c.duration_ms)).collect()
56        };
57
58        let session_handle = ctx
59            .session
60            .as_ref()
61            .ok_or_else(|| ErrorData::internal_error("session not available", None))?;
62        let mut session = session_handle.blocking_write();
63        let result = crate::tools::ctx_session::handle(
64            &mut session,
65            &call_durations,
66            &action,
67            value.as_deref(),
68            sid.as_deref(),
69            crate::tools::ctx_session::SessionToolOptions {
70                format: format.as_deref(),
71                path: path.as_deref(),
72                write,
73                privacy: privacy.as_deref(),
74                terse,
75            },
76        );
77        drop(session);
78
79        Ok(ToolOutput {
80            text: result,
81            original_tokens: 0,
82            saved_tokens: 0,
83            mode: Some(action),
84            path: None,
85            changed: false,
86            shell_outcome: None,
87        })
88    }
89}