1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! Param and response types for the `compress` MCP tool.
//!
//! `compress` is a code-aware token-reduction tool: for indexed source files it
//! returns the structural L1 outline (signatures + imports, no bodies); for prose
//! text it applies a lexical pass (whitespace collapsing, filler removal, paragraph
//! deduplication) that always runs, and optionally a kreuzberg prose-compression
//! pass when the `documents` feature is enabled.
//!
//! Split into its own file to keep `types.rs` under the 1000-line cap.
use rmcp::schemars;
use serde::{Deserialize, Serialize};
use crate::path::RelPath;
/// Parameters for the `compress` MCP tool.
///
/// Exactly one of `text` or `path` must be supplied; both or neither is an error.
#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
pub struct CompressParams {
/// Raw text to compress (prose path). Mutually exclusive with `path`.
pub text: Option<String>,
/// Repo-relative path of a source file to compress structurally (code path).
/// Mutually exclusive with `text`.
pub path: Option<RelPath>,
/// Reduction intensity: `off`, `light`, `moderate` (default), `aggressive`,
/// `maximum`. Only meaningful on the prose path; the code/structural path
/// always returns the L1 outline regardless of this setting.
#[serde(default)]
pub level: Option<String>,
/// When `true` (default), code blocks inside prose are left intact. Has no
/// effect on the structural (code file) path.
#[serde(default = "default_true")]
pub preserve_code: bool,
/// Soft token budget hint. Returned in the response but does not hard-cap
/// output in this version — accurate tokenizer is pending.
pub target_tokens: Option<u32>,
}
fn default_true() -> bool {
true
}
/// Parameters for the `expand` MCP tool.
///
/// Resolves one symbol by `name` (and optionally `kind`) in the file's L1 outline, then
/// reads the source file from disk and returns the raw bytes in `[start_byte..end_byte]`.
/// When more than one symbol matches `name` (e.g. overloads), the tool returns an error
/// listing the matches so the caller can disambiguate by supplying `kind`.
#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
pub struct ExpandParams {
/// Repo-relative path of the indexed source file.
pub path: RelPath,
/// Symbol name to expand. Matched exactly (case-sensitive) against the L1 outline.
/// Aliases: `symbol`, `needle`, `query`.
#[serde(alias = "symbol", alias = "needle", alias = "query")]
pub name: String,
/// Optional kind filter to disambiguate when `name` matches multiple symbols
/// (e.g. `"function"`, `"method"`, `"struct"`, …). Same values as `search_symbols`.
#[serde(default)]
pub kind: Option<String>,
}
/// Response from the `expand` MCP tool.
#[derive(Debug, Serialize, schemars::JsonSchema)]
pub(super) struct ExpandResponse {
/// Repo-relative path of the file that was read.
pub path: String,
/// Resolved symbol name.
pub name: String,
/// Resolved symbol kind (e.g. `"function"`, `"struct"`).
pub kind: String,
/// One-based start row of the symbol in the source file.
pub start_row: u32,
/// One-based end row of the symbol in the source file (computed from the byte slice).
pub end_row: u32,
/// Raw source text of the symbol body (`file_bytes[start_byte..end_byte]`).
pub body: String,
/// Byte length of the returned body (before any truncation).
pub bytes: usize,
/// `true` when the body was truncated to the `EXPAND_BODY_CAP`.
pub truncated: bool,
}
/// Response from the `compress` MCP tool.
#[derive(Debug, Serialize, schemars::JsonSchema)]
pub(super) struct CompressResponse {
/// Byte length of the original input (file contents or text).
pub original_bytes: usize,
/// Token count of the original input — a real tokenizer count when built with
/// `documents`, else a `bytes/4` estimate.
pub original_tokens: u64,
/// Byte length of the compressed output.
pub compressed_bytes: usize,
/// Token count of the compressed input — a real tokenizer count when built with
/// `documents`, else a `bytes/4` estimate.
pub compressed_tokens: u64,
/// Tokens actually removed by compression: `original_tokens - compressed_tokens`
/// (saturating, never negative).
pub tokens_reduced: u64,
/// `true` when [`original_tokens`] / [`compressed_tokens`] were produced by a
/// real tokenizer (the `documents` feature), `false` for the `bytes/4` estimate.
pub tokens_counted: bool,
/// Compression ratio: `compressed_bytes as f32 / original_bytes as f32`.
/// Values below 1.0 indicate a reduction; 1.0 means no change.
pub ratio: f32,
/// The strategy that was applied: `"structural"` for indexed code files,
/// `"lexical"` for prose-only compression (no kreuzberg), or
/// `"lexical+prose"` when kreuzberg prose reduction ran.
pub strategy: String,
/// The compressed output text.
pub output: String,
/// Disclosure note about token counting accuracy.
pub tokens_note: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn expand_accepts_symbol_alias_for_name() {
let params: ExpandParams =
serde_json::from_value(serde_json::json!({ "path": "src/lib.rs", "symbol": "scan" }))
.unwrap();
assert_eq!(params.name, "scan");
}
#[test]
fn expand_accepts_query_alias_for_name() {
let params: ExpandParams =
serde_json::from_value(serde_json::json!({ "path": "src/lib.rs", "query": "scan" }))
.unwrap();
assert_eq!(params.name, "scan");
}
#[test]
fn expand_canonical_name_still_binds() {
let params: ExpandParams =
serde_json::from_value(serde_json::json!({ "path": "src/lib.rs", "name": "scan" }))
.unwrap();
assert_eq!(params.name, "scan");
}
}