lean_ctx/tools/registered/
ctx_session.rs1use 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}