Skip to main content

lean_host_mcp/tools/
mod.rs

1//! Tool implementations.
2//!
3//! Split by what plumbing they share rather than one file per tool:
4//!
5//! - [`declaration`]: `inspect_declaration`, the bounded single-declaration
6//!   proof-work inspection tool.
7//! - [`proof_search`]: `search_for_proof`, the proof-agent retrieval tool
8//!   built from bounded proof-state and declaration-search calls.
9//! - [`proof_action`]: `try_proof_step` and `verify_declaration`, the
10//!   non-mutating proof action tools.
11//! - [`position`]: `proof_state` and `find_references`, backed by bounded
12//!   module queries from `lean-rs-worker`.
13
14use std::sync::Arc;
15
16pub mod declaration;
17pub mod position;
18pub mod proof_action;
19pub mod proof_search;
20pub(crate) mod source_input;
21
22use crate::broker::ProjectBroker;
23
24/// Which field of the MCP tool result carries the serialized envelope.
25///
26/// The model reads `content` text; `structuredContent` serves code-mode /
27/// validating clients. `Text` (the default) emits the envelope once, as a
28/// `content` text block — the leanest shape for the proof-agent audience and
29/// the only one Claude Code reads. `Both` duplicates onto both fields for
30/// clients that want each.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
32pub enum ResponseCarrier {
33    #[default]
34    Text,
35    Structured,
36    Both,
37}
38
39impl ResponseCarrier {
40    /// Parse the config/env spelling. Case-insensitive; returns `None` for an
41    /// unknown value so the caller can report it.
42    pub fn parse(value: &str) -> Option<Self> {
43        match value.trim().to_ascii_lowercase().as_str() {
44            "text" => Some(Self::Text),
45            "structured" => Some(Self::Structured),
46            "both" => Some(Self::Both),
47            _ => None,
48        }
49    }
50}
51
52/// How much operational telemetry the model-facing envelope carries.
53///
54/// `Quiet` (the default) keeps only proof-relevant content: it drops the
55/// `runtime` block (unless a restart/pressure signal is actionable), the
56/// manifest hash and full import list from `freshness`, and per-tool search /
57/// cache instrumentation. `Full` reproduces every field for debugging and
58/// observability tooling. Correctness and truncation signals are never gated.
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub enum TelemetryVerbosity {
61    #[default]
62    Quiet,
63    Full,
64}
65
66impl TelemetryVerbosity {
67    /// Parse the config/env spelling. Case-insensitive; returns `None` for an
68    /// unknown value so the caller can report it.
69    pub fn parse(value: &str) -> Option<Self> {
70        match value.trim().to_ascii_lowercase().as_str() {
71            "quiet" => Some(Self::Quiet),
72            "full" => Some(Self::Full),
73            _ => None,
74        }
75    }
76
77    /// Whether the demoted telemetry fields should be serialized.
78    #[must_use]
79    pub fn is_full(self) -> bool {
80        matches!(self, Self::Full)
81    }
82}
83
84/// Server-wide output budget overrides.
85///
86/// When unset, each tool keeps its own built-in byte caps (inspection allows a
87/// larger per-field cap than the proof actions). When set, the value overrides
88/// every tool's default before clamping, replacing what used to be a per-call
89/// request argument.
90#[derive(Debug, Clone, Copy, Default)]
91pub struct OutputBudgetOverrides {
92    pub max_field_bytes: Option<u32>,
93    pub max_total_bytes: Option<u32>,
94    pub heartbeat_limit: Option<u64>,
95}
96
97/// Presentation knobs resolved once at startup and shared by every tool call.
98///
99/// They decide where the envelope rides, how much telemetry it carries, and the
100/// output byte/heartbeat budgets that were once per-call request arguments.
101#[derive(Debug, Clone, Copy, Default)]
102pub struct ToolConfig {
103    pub carrier: ResponseCarrier,
104    pub verbosity: TelemetryVerbosity,
105    pub output: OutputBudgetOverrides,
106}
107
108/// Shared state every tool handler reads.
109///
110/// Holds the broker and the resolved presentation [`ToolConfig`]; each tool
111/// calls a narrow broker operation for its semantic work instead of receiving
112/// a raw project actor handle.
113#[derive(Debug, Clone)]
114pub struct ToolContext {
115    pub broker: Arc<ProjectBroker>,
116    pub config: ToolConfig,
117}
118
119/// Imports passed to worker sessions: `Init` is the internal base
120/// environment, while the public freshness envelope records only the
121/// caller-supplied vector.
122pub(crate) fn session_imports(imports: Vec<String>) -> Vec<String> {
123    if imports.iter().any(|import| import == "Init") {
124        imports
125    } else {
126        let mut out = Vec::with_capacity(imports.len().saturating_add(1));
127        out.push("Init".to_owned());
128        out.extend(imports);
129        out
130    }
131}