Skip to main content

lean_ctx_server/tools/registered/
ctx_proof.rs

1// SPDX-License-Identifier: Apache-2.0
2use rmcp::ErrorData;
3use rmcp::model::Tool;
4use serde_json::{Map, Value, json};
5
6use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_bool, get_str, get_usize};
7use lean_ctx::tool_defs::tool_def;
8
9pub struct CtxProofTool;
10
11impl McpTool for CtxProofTool {
12    fn name(&self) -> &'static str {
13        "ctx_proof"
14    }
15
16    fn tool_def(&self) -> Tool {
17        tool_def(
18            "ctx_proof",
19            "Export machine-readable ContextProofV1 (Verifier, Pipeline, Provenance).\n\
20             WORKFLOW: call after completing a task to generate audit trail.\n\
21             ANTIPATTERN: not for budget analysis — use ctx_radar/ctx_metrics instead.\n\
22             action=export (only valid); format=json|summary|both; write=true|false;\n\
23             max_evidence=max tool receipts (default 50). Writes to .lean-ctx/proofs/.",
24            json!({
25                "type": "object",
26                "properties": {
27                    "action": { "type": "string", "description": "export" },
28                    "project_root": { "type": "string", "description": "Project root" },
29                    "format": { "type": "string", "description": "json|summary|both" },
30                    "write": { "type": "boolean", "description": "Write to .lean-ctx/proofs/" },
31                    "filename": { "type": "string", "description": "Optional output filename" },
32                    "max_evidence": { "type": "integer", "description": "Max tool receipts" },
33                    "max_ledger_files": { "type": "integer", "description": "Max ledger files" }
34                },
35                "required": ["action"]
36            }),
37        )
38    }
39
40    fn handle(
41        &self,
42        args: &Map<String, Value>,
43        ctx: &ToolContext,
44    ) -> Result<ToolOutput, ErrorData> {
45        let action = get_str(args, "action")
46            .ok_or_else(|| ErrorData::invalid_params("action is required", None))?;
47        if action != "export" {
48            return Err(ErrorData::invalid_params(
49                "unsupported action (expected: export)",
50                None,
51            ));
52        }
53
54        let root = if let Some(p) = ctx.resolved_path("project_root") {
55            p.to_string()
56        } else if let Some(err) = ctx.path_error("project_root") {
57            return Err(ErrorData::invalid_params(
58                format!("project_root: {err}"),
59                None,
60            ));
61        } else {
62            ctx.project_root.clone()
63        };
64        let format = get_str(args, "format");
65        let write = get_bool(args, "write").unwrap_or(true);
66        let filename = get_str(args, "filename");
67        let max_evidence = get_usize(args, "max_evidence").map(|v| v.min(100_000));
68        let max_ledger_files = get_usize(args, "max_ledger_files").map(|v| v.min(100_000));
69
70        let session_data = ctx
71            .session
72            .as_ref()
73            .map(|s| tokio::task::block_in_place(|| s.blocking_read()).clone());
74        let pipeline_data = ctx
75            .pipeline_stats
76            .as_ref()
77            .map(|p| tokio::task::block_in_place(|| p.blocking_read()).clone());
78        let ledger_data = ctx
79            .ledger
80            .as_ref()
81            .map(|l| tokio::task::block_in_place(|| l.blocking_read()).clone());
82
83        let sources = lean_ctx::core::context_proof::ProofSources {
84            project_root: Some(root.clone()),
85            session: session_data,
86            pipeline: pipeline_data,
87            ledger: ledger_data,
88        };
89
90        let out = crate::tools::ctx_proof::handle_export(
91            &root,
92            format.as_deref(),
93            write,
94            filename.as_deref(),
95            max_evidence,
96            max_ledger_files,
97            sources,
98        )
99        .map_err(|e| ErrorData::invalid_params(e, None))?;
100
101        Ok(ToolOutput {
102            text: out,
103            original_tokens: 0,
104            saved_tokens: 0,
105            mode: Some(action),
106            path: Some(root),
107            changed: false,
108            shell_outcome: None,
109            content_blocks: None,
110        })
111    }
112}