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 (CCP). Actions: load (restore ~400 tok), save, status, task, \
19finding, decision, reset, list, cleanup, snapshot, restore, resume, profile (context profiles), \
20role (governance), budget (limits), slo (observability), diff (compare sessions), verify (output verification stats), \
21episodes (episodic memory), procedures (procedural memory).",
22            json!({
23                "type": "object",
24                "properties": {
25                    "action": {
26                        "type": "string",
27                        "enum": ["status", "load", "save", "task", "finding", "decision", "reset", "list", "cleanup", "snapshot", "restore", "resume", "profile", "role", "budget", "slo", "diff", "verify", "episodes", "procedures"],
28                        "description": "Session operation to perform"
29                    },
30                    "value": {
31                        "type": "string",
32                        "description": "Value for task/finding/decision/profile actions"
33                    },
34                    "session_id": {
35                        "type": "string",
36                        "description": "Session ID for load action (default: latest)"
37                    }
38                },
39                "required": ["action"]
40            }),
41        )
42    }
43
44    fn handle(
45        &self,
46        args: &Map<String, Value>,
47        ctx: &ToolContext,
48    ) -> Result<ToolOutput, ErrorData> {
49        let action = get_str(args, "action")
50            .ok_or_else(|| ErrorData::invalid_params("action is required", None))?;
51        let value = get_str(args, "value");
52        let sid = get_str(args, "session_id");
53        let format = get_str(args, "format");
54        let path = get_str(args, "path");
55        let write = get_bool(args, "write").unwrap_or(false);
56        let privacy = get_str(args, "privacy");
57        let terse = get_bool(args, "terse");
58
59        let tool_calls_handle = ctx
60            .tool_calls
61            .as_ref()
62            .ok_or_else(|| ErrorData::internal_error("tool_calls not available", None))?;
63        let call_durations: Vec<(String, u64)> = {
64            let tc = tool_calls_handle.blocking_read();
65            tc.iter().map(|c| (c.tool.clone(), c.duration_ms)).collect()
66        };
67
68        let session_handle = ctx
69            .session
70            .as_ref()
71            .ok_or_else(|| ErrorData::internal_error("session not available", None))?;
72        let mut session = session_handle.blocking_write();
73        let result = crate::tools::ctx_session::handle(
74            &mut session,
75            &call_durations,
76            &action,
77            value.as_deref(),
78            sid.as_deref(),
79            crate::tools::ctx_session::SessionToolOptions {
80                format: format.as_deref(),
81                path: path.as_deref(),
82                write,
83                privacy: privacy.as_deref(),
84                terse,
85            },
86        );
87        drop(session);
88
89        Ok(ToolOutput {
90            text: result,
91            original_tokens: 0,
92            saved_tokens: 0,
93            mode: Some(action),
94            path: None,
95            changed: false,
96        })
97    }
98}