Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 7 additions & 151 deletions crates/goose-mcp/src/developer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,16 @@ impl DeveloperRouter {
If you need to run a long lived command, background it - e.g. `uvicorn main:app &` so that
this tool does not run indefinitely.

**Important**: Use ripgrep - `rg` - exclusively when you need to locate a file or a code reference,
other solutions may produce too large output because of hidden files! For example *do not* use `find` or `ls -r`
- List files by name: `rg --files | rg <filename>`
- List files that contain a regex: `rg '<regex>' -l`

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also update the windows instructions. or better, make them as much as possible the same

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me know which part of this you think is missing from windows! it actually had closer to this prompt than the one i'm updating here, it may not have been changed in the previous PR that introduced the new tools i'm removing

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, maybe to increase maintainability extract the commonalities so we will keep them in sync better going forward?

**Important**: Each shell command runs in its own process. Things like directory changes or
sourcing files do not persist between tool calls. So you may need to repeat them each time by
stringing together commands, e.g. `cd example && ls` or `source env/bin/activate && pip install numpy`

- Restrictions: Avoid find, grep, cat, head, tail, ls - use dedicated tools instead (Grep, Glob, Read, LS)
- Multiple commands: Use ; or && to chain commands, avoid newlines
- Pathnames: Use absolute paths and avoid cd unless explicitly requested
- Multiple commands: Use ; or && to chain commands, avoid newlines
- Pathnames: Use absolute paths and avoid cd unless explicitly requested
"#},
};

Expand All @@ -174,86 +177,6 @@ impl DeveloperRouter {
}),
);

let glob_tool = Tool::new(
"glob".to_string(),
indoc! {r#"
Search for files using glob patterns.

This tool provides fast file pattern matching using glob syntax.
Returns matching file paths sorted by modification time.
Examples:
- `*.rs` - Find all Rust files in current directory
- `src/**/*.py` - Find all Python files recursively in src directory
- `**/test*.js` - Find all JavaScript test files recursively

**Important**: Use this tool instead of shell commands like `find` or `ls -r` for file searching,
as it properly handles ignored files and is more efficient. This tool respects .gooseignore patterns.

Use this tool when you need to locate files by name patterns rather than content.
"#}.to_string(),
object!({
"type": "object",
"required": ["pattern"],
"properties": {
"pattern": {"type": "string", "description": "The glob pattern to search for"},
"path": {"type": "string", "description": "The directory to search in (defaults to current directory)"}
}
})
).annotate(ToolAnnotations {
title: Some("Search files by pattern".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(true),
open_world_hint: Some(false),
});

let grep_tool = Tool::new(
"grep".to_string(),
indoc! {r#"
Execute file content search commands using ripgrep, grep, or find.

Use this tool to run search commands that look for content within files. The tool
executes your command directly and filters results to respect .gooseignore patterns.

**Recommended tools and usage:**

**ripgrep (rg)** - Fast, recommended for most searches:
- List files containing pattern: `rg -l "pattern"`
- Case-insensitive search: `rg -i "pattern"`
- Search specific file types: `rg "pattern" --glob "*.js"`
- Show matches with context: `rg "pattern" -C 3`
- List files by name: `rg --files | rg <filename>`
- List files that contain a regex: `rg '<regex>' -l`
- Sort by modification time: `rg -l "pattern" --sort modified`

**grep** - Traditional Unix tool:
- Recursive search: `grep -r "pattern" .`
- List files only: `grep -rl "pattern" .`
- Include specific files: `grep -r "pattern" --include="*.py"`

**find + grep** - When you need complex file filtering:
- `find . -name "*.py" -exec grep -l "pattern" {} \;`
- `find . -type f -newer file.txt -exec grep "pattern" {} \;`

**Important**: Use this tool instead of the shell tool for search commands, as it
properly filters results to respect ignored files.
"#}
.to_string(),
object!({
"type": "object",
"required": ["command"],
"properties": {
"command": {"type": "string", "description": "The search command to execute (rg, grep, find, etc.)"}
}
})
).annotate(ToolAnnotations {
title: Some("Search file contents".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(true),
open_world_hint: Some(false),
});

// Create text editor tool with different descriptions based on editor API configuration
let (text_editor_desc, str_replace_command) = if let Some(ref editor) = editor_model {
(
Expand Down Expand Up @@ -574,8 +497,6 @@ impl DeveloperRouter {
Self {
tools: vec![
bash_tool,
glob_tool,
grep_tool,
text_editor_tool,
list_windows_tool,
screen_capture_tool,
Expand Down Expand Up @@ -815,69 +736,6 @@ impl DeveloperRouter {
])
}

async fn glob(&self, params: Value) -> Result<Vec<Content>, ToolError> {
let pattern =
params
.get("pattern")
.and_then(|v| v.as_str())
.ok_or(ToolError::InvalidParameters(
"The pattern string is required".to_string(),
))?;

let search_path = params.get("path").and_then(|v| v.as_str()).unwrap_or(".");

let full_pattern = if search_path == "." {
pattern.to_string()
} else {
format!("{}/{}", search_path.trim_end_matches('/'), pattern)
};

let glob_result = glob::glob(&full_pattern)
.map_err(|e| ToolError::InvalidParameters(format!("Invalid glob pattern: {}", e)))?;

let mut file_paths_with_metadata = Vec::new();

for entry in glob_result {
match entry {
Ok(path) => {
// Check if the path should be ignored
if !self.is_ignored(&path) {
// Get file metadata for sorting by modification time
if let Ok(metadata) = std::fs::metadata(&path) {
if metadata.is_file() {
let modified = metadata
.modified()
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
file_paths_with_metadata.push((path, modified));
}
}
}
}
Err(e) => {
tracing::warn!("Error reading glob entry: {}", e);
}
}
}

// Sort by modification time (newest first)
file_paths_with_metadata.sort_by(|a, b| b.1.cmp(&a.1));

// Extract just the file paths
let file_paths: Vec<String> = file_paths_with_metadata
.into_iter()
.map(|(path, _)| path.to_string_lossy().to_string())
.collect();

let result = file_paths.join("\n");

Ok(vec![
Content::text(result.clone()).with_audience(vec![Role::Assistant]),
Content::text(result)
.with_audience(vec![Role::User])
.with_priority(0.0),
])
}

async fn text_editor(&self, params: Value) -> Result<Vec<Content>, ToolError> {
let command = params
.get("command")
Expand Down Expand Up @@ -1644,8 +1502,6 @@ impl Router for DeveloperRouter {
Box::pin(async move {
match tool_name.as_str() {
"shell" => this.bash(arguments, notifier).await,
"glob" => this.glob(arguments).await,
"grep" => this.bash(arguments, notifier).await,
"text_editor" => this.text_editor(arguments).await,
"list_windows" => this.list_windows(arguments).await,
"screen_capture" => this.screen_capture(arguments).await,
Expand Down
Loading