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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ReadInput {
/// The absolute path to the file to read
pub file_path: String,
/// The line number to start reading from. Only provide if the file is too large to read at once
pub offset: Option<usize>,
/// The number of lines to read. Only provide if the file is too large to read at once.
pub limit: Option<usize>,
/// Page range for PDF files (e.g., "1-5", "3", "10-20"). Only applicable to PDF files. Maximum 20 pages per request.
pub pages: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct WriteInput {
/// The absolute path to the file to write (must be absolute, not relative)
pub file_path: String,
/// The content to write to the file
pub content: String,
/// Decode Unicode escape sequences like \u4F60 before writing (default false)
#[serde(default)]
pub decode_unicode_escapes: bool,
}
#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct EditInput {
/// The absolute path to the file to modify
pub file_path: String,
/// The text to replace
pub old_string: String,
/// The text to replace it with (must be different from old_string)
pub new_string: String,
/// Replace all occurrences of old_string (default false)
#[serde(default)]
pub replace_all: bool,
/// Decode Unicode escape sequences like \u4F60 in old_string and new_string (default false)
#[serde(default)]
pub decode_unicode_escapes: bool,
}
#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GlobInput {
/// The glob pattern to match files against
pub pattern: String,
/// The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.
pub path: Option<String>,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum GrepOutputMode {
Content,
FilesWithMatches,
Count,
}
impl Default for GrepOutputMode {
fn default() -> Self {
Self::FilesWithMatches
}
}
#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GrepInput {
/// The regular expression pattern to search for in file contents
pub pattern: String,
/// File or directory to search in (rg PATH). Defaults to current working directory.
pub path: Option<String>,
/// Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}") - maps to rg --glob
pub glob: Option<String>,
/// Output mode: "content" shows matching lines (supports -A/-B/-C context, -n line numbers, head_limit), "files_with_matches" shows file paths (supports head_limit), "count" shows match counts (supports head_limit). Defaults to "files_with_matches".
#[serde(default)]
pub output_mode: GrepOutputMode,
/// Number of lines to show before each match (rg -B). Requires output_mode: "content", ignored otherwise.
#[serde(default, rename = "-B")]
pub before: Option<usize>,
/// Number of lines to show after each match (rg -A). Requires output_mode: "content", ignored otherwise.
#[serde(default, rename = "-A")]
pub after: Option<usize>,
/// Alias for context.
#[serde(default, rename = "-C")]
pub context_alias: Option<usize>,
/// Number of lines to show before and after each match (rg -C). Requires output_mode: "content", ignored otherwise.
pub context: Option<usize>,
/// Show line numbers in output (rg -n). Requires output_mode: "content", ignored otherwise. Defaults to true.
#[serde(default, rename = "-n")]
pub line_numbers: Option<bool>,
/// Case insensitive search (rg -i)
#[serde(default, rename = "-i")]
pub case_insensitive: Option<bool>,
/// File type to search (rg --type). Common types: js, py, rust, go, java, etc. More efficient than include for standard file types.
#[serde(rename = "type")]
pub file_type: Option<String>,
/// Limit output to first N lines/entries, equivalent to "| head -N". Works across all output modes: content (limits output lines), files_with_matches (limits file paths), count (limits count entries). Defaults to 0 (unlimited).
pub head_limit: Option<usize>,
/// Skip first N lines/entries before applying head_limit, equivalent to "| tail -n +N | head -N". Works across all output modes. Defaults to 0.
pub offset: Option<usize>,
/// Enable multiline mode where . matches newlines and patterns can span lines (rg -U --multiline-dotall). Default: false.
#[serde(default)]
pub multiline: bool,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct WriteOutput {
/// Whether the write operation succeeded
pub success: bool,
/// Summary of what was written
pub message: String,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct EditOutput {
/// Whether the edit operation succeeded
pub success: bool,
/// Summary of what was edited
pub message: String,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GlobOutput {
/// Total number of files found
#[serde(rename = "numFiles")]
pub num_files: usize,
/// Array of file paths that match the pattern
pub filenames: Vec<String>,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GrepOutput {
/// Total number of files found
#[serde(rename = "numFiles")]
pub num_files: usize,
/// Array of file paths that match the pattern
pub filenames: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
/// Total number of matches found
#[serde(rename = "numMatches", skip_serializing_if = "Option::is_none")]
pub num_matches: Option<usize>,
}
#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct BashInput {
/// The command to execute
pub command: String,
/// Optional timeout in milliseconds (max 600000)
pub timeout: Option<u64>,
/// Clear, concise description of what this command does in active voice. Never use words like "complex" or "risk" in the description - just describe what it does.
///
/// For simple commands (git, npm, standard CLI tools), keep it brief (5-10 words):
/// - ls → "List files in current directory"
/// - git status → "Show working tree status"
/// - npm install → "Install package dependencies"
///
/// For commands that are harder to parse at a glance (piped commands, obscure flags, etc.), add enough context to clarify what it does:
/// - find . -name "*.tmp" -exec rm {} \; → "Find and delete all .tmp files recursively"
/// - git reset --hard origin/main → "Discard all local changes and match remote main"
/// - curl -s url | jq '.data[]' → "Fetch JSON from URL and extract data array elements"
pub description: Option<String>,
/// Set to true to run this command in the background. Use TaskOutput to read the output later.
pub run_in_background: Option<bool>,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct BashOutput {
/// The standard output of the command
#[serde(skip_serializing_if = "Option::is_none")]
pub stdout: Option<String>,
/// The standard error output of the command
#[serde(skip_serializing_if = "Option::is_none")]
pub stderr: Option<String>,
/// Whether the command was interrupted
#[serde(skip_serializing_if = "Option::is_none")]
pub interrupted: Option<bool>,
/// ID of the background task if command is running in background
#[serde(rename = "backgroundTaskId", skip_serializing_if = "Option::is_none")]
pub background_task_id: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct TaskOutputInput {
/// The task ID to get output from
pub task_id: String,
/// Whether to wait for completion
#[serde(default = "default_true")]
#[schemars(required)]
pub block: bool,
/// Max wait time in ms
#[serde(default = "default_task_timeout")]
#[schemars(range(min = 0, max = 600000), required)]
pub timeout: u64,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct TaskOutputResponse {
pub status: String,
pub stdout: String,
pub stderr: String,
}
#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct TaskStopInput {
/// The ID of the background task to stop
pub task_id: Option<String>,
/// Deprecated: use task_id instead
pub shell_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct TaskStopOutput {
/// Status message about the operation
pub message: String,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum NotebookCellType {
Code,
Markdown,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum NotebookEditMode {
Replace,
Insert,
Delete,
}
impl Default for NotebookEditMode {
fn default() -> Self {
Self::Replace
}
}
#[derive(Debug, Clone, Default, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct NotebookEditInput {
/// The absolute path to the Jupyter notebook file to edit (must be absolute, not relative)
pub notebook_path: String,
/// The ID of the target cell. Use this or cell_number. If both are provided, they must refer to the same cell, or the same insertion point in insert mode.
pub cell_id: Option<String>,
/// The 0-indexed number of the target cell. Use this or cell_id. In insert mode, the new cell is inserted at this index.
pub cell_number: Option<usize>,
/// The new source for the cell
pub new_source: String,
/// The type of the cell (code or markdown). If not specified, it defaults to the current cell type. If using edit_mode=insert, this is required.
pub cell_type: Option<NotebookCellType>,
/// The type of edit to make (replace, insert, delete). Defaults to replace.
#[serde(default)]
pub edit_mode: NotebookEditMode,
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct NotebookEditOutput {
/// Whether the notebook edit operation succeeded
pub success: bool,
/// The ID of a newly inserted cell when one is created
#[serde(rename = "cellId", skip_serializing_if = "Option::is_none")]
pub cell_id: Option<String>,
}
const fn default_true() -> bool {
true
}
const fn default_task_timeout() -> u64 {
30_000
}