> ## 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.

# Extension Development

> Build custom extensions for Goose using the Model Context Protocol

Extensions add capabilities to Goose through tools, resources, and prompts. They are implemented as MCP (Model Context Protocol) servers.

## What are Extensions?

Extensions are MCP servers that provide:

* **Tools** - Functions the agent can call (e.g., read files, search web, query databases)
* **Resources** - Data sources the agent can access
* **Prompts** - Reusable prompt templates

Goose includes several builtin extensions and supports custom external extensions.

## Builtin Extensions

Builtin extensions are implemented in `crates/goose-mcp/src/`:

* **autovisualiser** - Automatic visualization generation
* **computercontroller** - Desktop automation (keyboard, mouse)
* **memory** - Long-term memory storage
* **tutorial** - Interactive tutorials

These run as in-process MCP servers.

## Extension Architecture

Extensions communicate with Goose using the MCP protocol over stdio. Each extension:

1. Implements the MCP server specification
2. Exposes tools/resources/prompts via MCP methods
3. Receives requests from the agent
4. Returns results in MCP format

```
Goose Agent
    ↓ MCP Protocol (stdio)
Extension Manager
    ↓
Extension (MCP Server)
    ↓
Your Extension Logic
```

## Creating a Builtin Extension

### 1. Define Your Extension

Create a new module in `crates/goose-mcp/src/`:

```rust theme={null}
// crates/goose-mcp/src/myextension/mod.rs
use rmcp::{ServerHandler, model::*};
use async_trait::async_trait;

pub struct MyExtensionServer;

impl MyExtensionServer {
    pub fn new() -> Self {
        Self
    }
}

#[async_trait]
impl ServerHandler for MyExtensionServer {
    async fn list_tools(
        &self,
        _params: ListToolsRequestParams,
    ) -> rmcp::Result<ListToolsResult> {
        Ok(ListToolsResult {
            tools: vec![
                Tool {
                    name: "my_tool".into(),
                    description: Some("Does something useful".to_string()),
                    input_schema: serde_json::json!({
                        "type": "object",
                        "properties": {
                            "input": {
                                "type": "string",
                                "description": "Input text"
                            }
                        },
                        "required": ["input"]
                    }),
                },
            ],
        })
    }
    
    async fn call_tool(
        &self,
        params: CallToolRequestParams,
    ) -> rmcp::Result<CallToolResult> {
        match params.name.as_str() {
            "my_tool" => {
                let input = params.arguments
                    .get("input")
                    .and_then(|v| v.as_str())
                    .unwrap_or("");
                    
                let result = format!("Processed: {}", input);
                
                Ok(CallToolResult {
                    content: vec![ToolResponseContent::text(result)],
                    is_error: Some(false),
                })
            }
            _ => Err(rmcp::Error::method_not_found("Unknown tool")),
        }
    }
}
```

### 2. Register the Extension

Add to `crates/goose-mcp/src/lib.rs`:

```rust theme={null}
pub mod myextension;
pub use myextension::MyExtensionServer;

pub static BUILTIN_EXTENSIONS: Lazy<HashMap<&'static str, SpawnServerFn>> = 
    Lazy::new(|| {
        HashMap::from([
            builtin!(autovisualiser, AutoVisualiserRouter),
            builtin!(computercontroller, ComputerControllerServer),
            builtin!(memory, MemoryServer),
            builtin!(tutorial, TutorialServer),
            builtin!(myextension, MyExtensionServer),  // Add this
        ])
    });
```

### 3. Use the Extension

In your goose configuration or code:

```rust theme={null}
use goose::agents::extension::ExtensionConfig;

let config = ExtensionConfig::builtin("myextension");
```

## Creating an External Extension

External extensions are standalone executables that implement the MCP server protocol.

### Using Python (FastMCP)

FastMCP makes it easy to build MCP servers in Python:

```python theme={null}
from fastmcp import FastMCP

mcp = FastMCP("My Extension")

@mcp.tool()
def process_text(text: str) -> str:
    """Process input text.
    
    Args:
        text: The text to process
        
    Returns:
        Processed text
    """
    return f"Processed: {text}"

if __name__ == "__main__":
    mcp.run()
```

Save as `my_extension.py` and configure in Goose:

```json theme={null}
{
  "name": "myextension",
  "module": "my_extension",
  "type": "stdio"
}
```

### Using TypeScript (MCP SDK)

```typescript theme={null}
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new Server(
  {
    name: "myextension",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "process_text",
      description: "Process input text",
      inputSchema: {
        type: "object",
        properties: {
          text: {
            type: "string",
            description: "Text to process",
          },
        },
        required: ["text"],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "process_text") {
    return {
      content: [
        {
          type: "text",
          text: `Processed: ${request.params.arguments.text}`,
        },
      ],
    };
  }
  throw new Error("Unknown tool");
});

const transport = new StdioServerTransport();
await server.connect(transport);
```

### Using Rust (RMCP)

Create a standalone Rust binary:

```rust theme={null}
use rmcp::{ServerHandler, ServiceExt};
use async_trait::async_trait;

struct MyExtension;

#[async_trait]
impl ServerHandler for MyExtension {
    // Implement list_tools, call_tool, etc.
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let server = MyExtension;
    let stdin = tokio::io::stdin();
    let stdout = tokio::io::stdout();
    
    server.serve((stdin, stdout)).await?.waiting().await;
    Ok(())
}
```

## Tool Design Best Practices

### 1. Clear Descriptions

Provide clear, concise descriptions:

```rust theme={null}
Tool {
    name: "search_files".into(),
    description: Some(
        "Search for files matching a pattern in a directory"
        .to_string()
    ),
    // ...
}
```

### 2. Well-Defined Schemas

Use JSON Schema to define parameters:

```rust theme={null}
input_schema: serde_json::json!({
    "type": "object",
    "properties": {
        "pattern": {
            "type": "string",
            "description": "Glob pattern to match"
        },
        "directory": {
            "type": "string",
            "description": "Directory to search in"
        }
    },
    "required": ["pattern", "directory"]
})
```

### 3. Error Handling

Return clear error messages:

```rust theme={null}
match perform_operation() {
    Ok(result) => Ok(CallToolResult {
        content: vec![ToolResponseContent::text(result)],
        is_error: Some(false),
    }),
    Err(e) => Ok(CallToolResult {
        content: vec![ToolResponseContent::text(
            format!("Error: {}", e)
        )],
        is_error: Some(true),
    }),
}
```

### 4. Atomic Operations

Each tool should do one thing well. Split complex operations into multiple tools.

### 5. Idempotency

When possible, make tools idempotent - they can be called multiple times with the same result.

## Testing Extensions

### Using MCP Inspector

Test your extension with the MCP Inspector:

```bash theme={null}
npx @modelcontextprotocol/inspector cargo run -p goose-mcp --example mcp
```

Or for Python:

```bash theme={null}
npx @modelcontextprotocol/inspector python my_extension.py
```

### Integration Tests

Write integration tests:

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

## Extension Configuration

Extensions can be configured via:

### Environment Variables

```rust theme={null}
let api_key = std::env::var("MY_EXTENSION_API_KEY")?;
```

### Extension Config

```rust theme={null}
pub struct MyExtensionServer {
    config: HashMap<String, String>,
}

impl MyExtensionServer {
    pub fn new_with_config(config: HashMap<String, String>) -> Self {
        Self { config }
    }
}
```

## Resources and Prompts

### Implementing Resources

```rust theme={null}
async fn list_resources(
    &self,
    _params: ListResourcesRequestParams,
) -> rmcp::Result<ListResourcesResult> {
    Ok(ListResourcesResult {
        resources: vec![
            Resource {
                uri: "myext://data/users".to_string(),
                name: "User Data".to_string(),
                description: Some("List of users".to_string()),
                mime_type: Some("application/json".to_string()),
            },
        ],
    })
}

async fn read_resource(
    &self,
    params: ReadResourceRequestParams,
) -> rmcp::Result<ReadResourceResult> {
    match params.uri.as_str() {
        "myext://data/users" => {
            let data = get_user_data();
            Ok(ReadResourceResult {
                contents: vec![ResourceContents {
                    uri: params.uri,
                    mime_type: Some("application/json".to_string()),
                    text: Some(serde_json::to_string(&data)?),
                    blob: None,
                }],
            })
        }
        _ => Err(rmcp::Error::invalid_params("Unknown resource")),
    }
}
```

### Implementing Prompts

```rust theme={null}
async fn list_prompts(
    &self,
    _params: ListPromptsRequestParams,
) -> rmcp::Result<ListPromptsResult> {
    Ok(ListPromptsResult {
        prompts: vec![
            Prompt {
                name: "analyze_code".to_string(),
                description: Some("Analyze code quality".to_string()),
                arguments: Some(vec![
                    PromptArgument {
                        name: "language".to_string(),
                        description: Some("Programming language".to_string()),
                        required: Some(true),
                    },
                ]),
            },
        ],
    })
}
```

## Next Steps

* Learn about the [MCP Protocol](/development/mcp-protocol)
* Understand [Tool Creation](/development/tool-creation)
* See [Provider Development](/development/provider-interface)
