> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/block/goose/llms.txt
> Use this file to discover all available pages before exploring further.

# Tool Creation

> Design and implement tools for Goose extensions

Tools are the primary way extensions provide functionality to Goose. This guide covers best practices for designing and implementing effective tools.

## What is a Tool?

A tool is a function that:

* Has a well-defined interface (name, parameters, schema)
* Performs a specific operation
* Returns a result to the agent
* Can be called by the LLM during agent execution

Tools are exposed via MCP and called by the agent when needed.

## Tool Anatomy

Every tool consists of:

```rust theme={null}
Tool {
    name: "tool_name".into(),
    description: Some("What this tool does".to_string()),
    input_schema: serde_json::json!({
        "type": "object",
        "properties": {
            "param_name": {
                "type": "string",
                "description": "Parameter description"
            }
        },
        "required": ["param_name"]
    }),
}
```

### Name

* Use `snake_case`
* Be descriptive but concise
* Prefix with domain if needed (e.g., `file_read`, `db_query`)

Good:

* `read_file`
* `search_code`
* `execute_command`

Bad:

* `rf` (too short)
* `readTheContentsOfAFile` (too verbose)
* `do_stuff` (not descriptive)

### Description

Provide a clear description that explains:

* **What** the tool does
* **When** to use it
* **Important limitations** or requirements

```rust theme={null}
description: Some(
    "Read the contents of a file from the filesystem. \
    Returns the file contents as text. \
    Fails if the file doesn't exist or is not readable by the current user."
    .to_string()
)
```

### Input Schema

Define parameters using JSON Schema:

```json theme={null}
{
  "type": "object",
  "properties": {
    "path": {
      "type": "string",
      "description": "Path to the file to read"
    },
    "encoding": {
      "type": "string",
      "description": "File encoding (default: utf-8)",
      "enum": ["utf-8", "ascii", "latin1"]
    }
  },
  "required": ["path"]
}
```

## Implementing Tool Logic

### Basic Implementation

```rust theme={null}
async fn call_tool(
    &self,
    params: CallToolRequestParams,
) -> rmcp::Result<CallToolResult> {
    match params.name.as_str() {
        "read_file" => self.read_file_tool(params).await,
        "write_file" => self.write_file_tool(params).await,
        _ => Err(rmcp::Error::method_not_found(
            format!("Unknown tool: {}", params.name)
        )),
    }
}

async fn read_file_tool(
    &self,
    params: CallToolRequestParams,
) -> rmcp::Result<CallToolResult> {
    // 1. Extract and validate parameters
    let path = params.arguments
        .get("path")
        .and_then(|v| v.as_str())
        .ok_or_else(|| rmcp::Error::invalid_params(
            "Missing required parameter: path"
        ))?;

    // 2. Perform operation
    let contents = match std::fs::read_to_string(path) {
        Ok(contents) => contents,
        Err(e) => {
            return Ok(CallToolResult {
                content: vec![ToolResponseContent::text(
                    format!("Failed to read file: {}", e)
                )],
                is_error: Some(true),
            });
        }
    };

    // 3. Return result
    Ok(CallToolResult {
        content: vec![ToolResponseContent::text(contents)],
        is_error: Some(false),
    })
}
```

### Parameter Extraction Helpers

Create helper functions for common parameter types:

```rust theme={null}
fn get_string_param(
    params: &serde_json::Map<String, serde_json::Value>,
    name: &str,
) -> rmcp::Result<String> {
    params
        .get(name)
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
        .ok_or_else(|| rmcp::Error::invalid_params(
            format!("Missing or invalid parameter: {}", name)
        ))
}

fn get_optional_string_param(
    params: &serde_json::Map<String, serde_json::Value>,
    name: &str,
) -> Option<String> {
    params.get(name).and_then(|v| v.as_str()).map(|s| s.to_string())
}

fn get_bool_param(
    params: &serde_json::Map<String, serde_json::Value>,
    name: &str,
    default: bool,
) -> bool {
    params.get(name).and_then(|v| v.as_bool()).unwrap_or(default)
}
```

Usage:

```rust theme={null}
let path = get_string_param(&params.arguments, "path")?;
let encoding = get_optional_string_param(&params.arguments, "encoding")
    .unwrap_or_else(|| "utf-8".to_string());
let recursive = get_bool_param(&params.arguments, "recursive", false);
```

## Tool Design Patterns

### 1. Read-Only Tools

Tools that only read data, never modify:

```rust theme={null}
Tool {
    name: "list_files".into(),
    description: Some("List files in a directory".to_string()),
    input_schema: serde_json::json!({
        "type": "object",
        "properties": {
            "path": {"type": "string"},
            "pattern": {
                "type": "string",
                "description": "Glob pattern to filter files"
            }
        },
        "required": ["path"]
    }),
}
```

### 2. Write/Modify Tools

Tools that modify state:

```rust theme={null}
Tool {
    name: "write_file".into(),
    description: Some(
        "Write content to a file. Creates parent directories if needed. \
        WARNING: Overwrites existing files.".to_string()
    ),
    input_schema: serde_json::json!({
        "type": "object",
        "properties": {
            "path": {"type": "string"},
            "content": {"type": "string"},
            "append": {
                "type": "boolean",
                "description": "Append instead of overwrite",
                "default": false
            }
        },
        "required": ["path", "content"]
    }),
}
```

### 3. Query Tools

Tools that search or filter:

```rust theme={null}
Tool {
    name: "search_code".into(),
    description: Some(
        "Search for code patterns using regex. \
        Returns file paths and line numbers.".to_string()
    ),
    input_schema: serde_json::json!({
        "type": "object",
        "properties": {
            "pattern": {
                "type": "string",
                "description": "Regex pattern to search for"
            },
            "directory": {
                "type": "string",
                "description": "Directory to search in"
            },
            "file_pattern": {
                "type": "string",
                "description": "File glob pattern (e.g., '*.rs')"
            }
        },
        "required": ["pattern", "directory"]
    }),
}
```

### 4. Action Tools

Tools that perform actions:

```rust theme={null}
Tool {
    name: "execute_command".into(),
    description: Some(
        "Execute a shell command. \
        Returns stdout, stderr, and exit code. \
        WARNING: Commands run with current user permissions.".to_string()
    ),
    input_schema: serde_json::json!({
        "type": "object",
        "properties": {
            "command": {"type": "string"},
            "working_dir": {
                "type": "string",
                "description": "Working directory (default: current)"
            },
            "timeout_seconds": {
                "type": "integer",
                "description": "Max execution time",
                "default": 30
            }
        },
        "required": ["command"]
    }),
}
```

## Error Handling

### Informative Error Messages

Provide context in error messages:

```rust theme={null}
match std::fs::read_to_string(&path) {
    Ok(contents) => Ok(CallToolResult {
        content: vec![ToolResponseContent::text(contents)],
        is_error: Some(false),
    }),
    Err(e) => Ok(CallToolResult {
        content: vec![ToolResponseContent::text(
            format!("Failed to read file '{}': {}", path, e)
        )],
        is_error: Some(true),
    }),
}
```

### Validation Errors

```rust theme={null}
if !path.exists() {
    return Ok(CallToolResult {
        content: vec![ToolResponseContent::text(
            format!("File not found: {}", path.display())
        )],
        is_error: Some(true),
    });
}

if !path.is_file() {
    return Ok(CallToolResult {
        content: vec![ToolResponseContent::text(
            format!("Path is not a file: {}", path.display())
        )],
        is_error: Some(true),
    });
}
```

### Permission Errors

```rust theme={null}
match std::fs::metadata(&path) {
    Ok(metadata) => {
        if metadata.permissions().readonly() {
            return Ok(CallToolResult {
                content: vec![ToolResponseContent::text(
                    "Cannot write to read-only file".to_string()
                )],
                is_error: Some(true),
            });
        }
    }
    Err(e) => {
        return Ok(CallToolResult {
            content: vec![ToolResponseContent::text(
                format!("Permission denied: {}", e)
            )],
            is_error: Some(true),
        });
    }
}
```

## Return Types

### Text Results

Most common return type:

```rust theme={null}
Ok(CallToolResult {
    content: vec![ToolResponseContent::text(
        "Operation completed successfully".to_string()
    )],
    is_error: Some(false),
})
```

### Structured Data

Return JSON for structured data:

```rust theme={null}
let result = serde_json::json!({
    "files": [
        {"name": "file1.txt", "size": 1024},
        {"name": "file2.txt", "size": 2048}
    ],
    "total": 2
});

Ok(CallToolResult {
    content: vec![ToolResponseContent::text(
        serde_json::to_string_pretty(&result)?
    )],
    is_error: Some(false),
})
```

### Multiple Content Items

Return multiple pieces of content:

```rust theme={null}
Ok(CallToolResult {
    content: vec![
        ToolResponseContent::text("Summary: 5 files found".to_string()),
        ToolResponseContent::text(
            serde_json::to_string_pretty(&files)?
        ),
    ],
    is_error: Some(false),
})
```

## Best Practices

### 1. Single Responsibility

Each tool should do one thing well:

Good:

* `read_file` - reads a file
* `write_file` - writes a file
* `list_files` - lists files

Bad:

* `file_operations` - does everything

### 2. Idempotency

When possible, make tools idempotent:

```rust theme={null}
// Good: Creating directory is idempotent
if !path.exists() {
    std::fs::create_dir_all(&path)?;
}

// Returns success whether dir was created or already existed
```

### 3. Safe Defaults

Choose safe defaults for optional parameters:

```rust theme={null}
input_schema: serde_json::json!({
    "properties": {
        "overwrite": {
            "type": "boolean",
            "description": "Overwrite existing file",
            "default": false  // Safe default
        },
        "recursive": {
            "type": "boolean",
            "description": "Delete recursively",
            "default": false  // Safe default
        }
    }
})
```

### 4. Timeouts

Set reasonable timeouts for long operations:

```rust theme={null}
use tokio::time::timeout;
use std::time::Duration;

let result = timeout(
    Duration::from_secs(30),
    perform_operation()
).await;

match result {
    Ok(Ok(value)) => Ok(CallToolResult {
        content: vec![ToolResponseContent::text(value)],
        is_error: Some(false),
    }),
    Ok(Err(e)) => Ok(CallToolResult {
        content: vec![ToolResponseContent::text(
            format!("Operation failed: {}", e)
        )],
        is_error: Some(true),
    }),
    Err(_) => Ok(CallToolResult {
        content: vec![ToolResponseContent::text(
            "Operation timed out after 30 seconds".to_string()
        )],
        is_error: Some(true),
    }),
}
```

### 5. Input Validation

Validate inputs thoroughly:

```rust theme={null}
// Path validation
let path = PathBuf::from(path_str);
if path.is_absolute() {
    return Err(rmcp::Error::invalid_params(
        "Absolute paths not allowed"
    ));
}

// Pattern validation
let regex = regex::Regex::new(pattern)
    .map_err(|e| rmcp::Error::invalid_params(
        format!("Invalid regex pattern: {}", e)
    ))?;

// Range validation
if timeout_seconds > 300 {
    return Err(rmcp::Error::invalid_params(
        "Timeout cannot exceed 300 seconds"
    ));
}
```

### 6. Progress for Long Operations

For operations that may take time, provide feedback:

```rust theme={null}
let mut result = String::new();
result.push_str("Processing files...\n");

for (i, file) in files.iter().enumerate() {
    process_file(file)?;
    if i % 10 == 0 {
        result.push_str(&format!("Processed {}/{}\n", i, files.len()));
    }
}

result.push_str(&format!("Complete: {} files processed\n", files.len()));
```

## Testing Tools

### Unit Tests

```rust theme={null}
#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_read_file_tool() {
        let server = MyExtension::new();
        
        let params = CallToolRequestParams {
            name: "read_file".into(),
            arguments: serde_json::json!({
                "path": "test.txt"
            }).as_object().unwrap().clone(),
        };
        
        let result = server.call_tool(params).await.unwrap();
        assert!(!result.is_error.unwrap_or(false));
    }
    
    #[tokio::test]
    async fn test_read_file_not_found() {
        let server = MyExtension::new();
        
        let params = CallToolRequestParams {
            name: "read_file".into(),
            arguments: serde_json::json!({
                "path": "nonexistent.txt"
            }).as_object().unwrap().clone(),
        };
        
        let result = server.call_tool(params).await.unwrap();
        assert!(result.is_error.unwrap_or(false));
    }
}
```

### Integration Tests

Test tools through the extension manager:

```rust theme={null}
#[tokio::test]
async fn test_tool_integration() {
    let config = ExtensionConfig::builtin("myextension");
    let manager = ExtensionManager::new(
        vec![config],
        CancellationToken::new(),
    ).await.unwrap();
    
    let result = manager.call_tool(
        "read_file",
        serde_json::json!({"path": "test.txt"}),
    ).await.unwrap();
    
    assert!(!result.is_error.unwrap_or(false));
}
```

## Examples from Goose

See real tool implementations in:

* `crates/goose-mcp/src/memory/` - Memory storage tools
* `crates/goose-mcp/src/computercontroller/` - Desktop automation
* `crates/goose-mcp/src/autovisualiser/` - Visualization generation

## Next Steps

* Learn about [Extension Development](/development/extension-development)
* Understand the [MCP Protocol](/development/mcp-protocol)
* Build [Providers](/development/provider-interface)
