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

# Creating Extensions

> Build custom MCP servers to extend goose with new tools and capabilities

Extensions are how you add custom functionality to goose. By implementing a Model Context Protocol (MCP) server, you can provide tools, resources, and prompts that agents can discover and use.

## Extension Types

Goose supports several types of extensions:

<CardGroup cols={2}>
  <Card title="Tools" icon="wrench">
    Functions the agent can call (e.g., API integrations, file operations)
  </Card>

  <Card title="Resources" icon="database">
    Data sources the agent can read (e.g., databases, documentation)
  </Card>

  <Card title="Prompts" icon="message">
    Pre-defined prompt templates for common tasks
  </Card>

  <Card title="OAuth Apps" icon="key">
    Extensions with authentication flows for third-party services
  </Card>
</CardGroup>

## Quick Start: Python MCP Server

The fastest way to create an extension is using [FastMCP](https://github.com/jlowin/fastmcp), a Python framework for MCP servers.

### Installation

```bash theme={null}
pip install fastmcp
```

### Basic Server

Create `my_tools.py`:

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

# Initialize MCP server
mcp = FastMCP("My Custom Tools")

@mcp.tool()
def calculate_fibonacci(n: int) -> int:
    """Calculate the nth Fibonacci number"""
    if n <= 1:
        return n
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b

@mcp.tool()
def reverse_string(text: str) -> str:
    """Reverse a string"""
    return text[::-1]

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

### Register with Goose

Add to `~/.config/goose/config.yaml`:

```yaml theme={null}
extensions:
  - name: "my-tools"
    enabled: true
    transport:
      type: "stdio"
      command: "python"
      args: ["-m", "my_tools"]
```

### Test the Extension

```bash theme={null}
goose session
```

In the session:

```
you: Calculate the 10th Fibonacci number
goose: [Calling calculate_fibonacci(10)]
goose: The 10th Fibonacci number is 55.
```

## Tool Design Best Practices

### 1. Clear Descriptions

Provide detailed descriptions so the agent understands when to use your tool:

```python theme={null}
@mcp.tool()
def search_database(
    query: str,
    filters: dict = None,
    limit: int = 10
) -> list:
    """
    Search the product database using natural language.
    
    Use this tool when the user asks about products, inventory,
    or wants to find items matching certain criteria.
    
    Args:
        query: Natural language search query
        filters: Optional filters (e.g., {"category": "electronics", "price_max": 100})
        limit: Maximum number of results to return (default 10)
    
    Returns:
        List of matching products with name, price, and availability
    """
    # Implementation
```

<Tip>
  Good descriptions improve agent accuracy. Include:

  * What the tool does
  * When to use it
  * What parameters mean
  * What the return value represents
</Tip>

### 2. Type Safety

Use type hints for automatic schema generation:

```python theme={null}
from typing import Literal, Optional
from pydantic import BaseModel

class SearchResult(BaseModel):
    name: str
    price: float
    in_stock: bool

@mcp.tool()
def search_products(
    category: Literal["electronics", "clothing", "books"],
    max_price: Optional[float] = None
) -> list[SearchResult]:
    """Search products by category and price"""
    # Implementation
```

### 3. Error Handling

Provide clear error messages:

```python theme={null}
@mcp.tool()
def fetch_weather(city: str) -> dict:
    """Get current weather for a city"""
    try:
        response = requests.get(f"https://api.weather.com/v1/weather?city={city}")
        response.raise_for_status()
        return response.json()
    except requests.HTTPError as e:
        if e.response.status_code == 404:
            return {"error": f"City '{city}' not found. Check the spelling or try a nearby city."}
        elif e.response.status_code == 429:
            return {"error": "API rate limit exceeded. Please try again in a few minutes."}
        else:
            return {"error": f"Weather API error: {str(e)}"}
    except Exception as e:
        return {"error": f"Unexpected error fetching weather: {str(e)}"}
```

### 4. Idempotency

Make tools safe to retry:

```python theme={null}
@mcp.tool()
def create_user(email: str, name: str) -> dict:
    """Create a new user account (idempotent)"""
    # Check if user exists first
    existing = db.query(User).filter_by(email=email).first()
    if existing:
        return {"id": existing.id, "message": "User already exists"}
    
    # Create new user
    user = User(email=email, name=name)
    db.add(user)
    db.commit()
    return {"id": user.id, "message": "User created"}
```

## Resources

Resources provide read access to data:

```python theme={null}
@mcp.resource("docs://api-reference")
def get_api_docs() -> str:
    """API documentation for internal services"""
    with open("docs/api.md", "r") as f:
        return f.read()

@mcp.resource("database://users/{user_id}")
def get_user_profile(user_id: str) -> dict:
    """Get user profile data"""
    user = db.query(User).filter_by(id=user_id).first()
    if not user:
        raise ValueError(f"User {user_id} not found")
    return {
        "id": user.id,
        "name": user.name,
        "email": user.email,
        "created_at": user.created_at.isoformat()
    }
```

Resources use URI patterns for identification. The agent can discover and read them as needed.

## Prompts

Prompts are reusable templates:

````python theme={null}
@mcp.prompt()
def code_review_prompt(language: str, code: str) -> str:
    """Generate a code review prompt"""
    return f"""
Review this {language} code for:
- Code quality and style
- Potential bugs
- Performance issues
- Security vulnerabilities

Code:
```{language}
{code}
````

Provide specific, actionable feedback.
"""

````

Agents can use prompts to structure their requests to the LLM.

## TypeScript MCP Server

For Node.js/TypeScript:

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';

const server = new Server(
  {
    name: 'my-tools',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'calculate_sum',
      description: 'Add two numbers together',
      inputSchema: {
        type: 'object',
        properties: {
          a: { type: 'number', description: 'First number' },
          b: { type: 'number', description: 'Second number' },
        },
        required: ['a', 'b'],
      },
    },
  ],
}));

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'calculate_sum') {
    const { a, b } = request.params.arguments as { a: number; b: number };
    return {
      content: [
        {
          type: 'text',
          text: String(a + b),
        },
      ],
    };
  }
  throw new Error(`Unknown tool: ${request.params.name}`);
});

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

Save as `my-tools.ts` and register:

```yaml theme={null}
extensions:
  - name: "my-tools"
    transport:
      type: "stdio"
      command: "npx"
      args: ["tsx", "my-tools.ts"]
```

## Rust MCP Server

For performance-critical extensions in Rust:

```rust theme={null}
use goose_mcp::{mcp_server_runner::serve, Memory};
use rmcp::{
    model::{Content, TextContent, ToolCallContent, ToolInfo},
    ServerHandler,
};
use anyhow::Result;

struct MyTools;

#[async_trait]
impl ServerHandler for MyTools {
    async fn list_tools(&self) -> Result<Vec<ToolInfo>> {
        Ok(vec![
            ToolInfo {
                name: "reverse_string".to_string(),
                description: Some("Reverse a string".to_string()),
                input_schema: serde_json::json!({
                    "type": "object",
                    "properties": {
                        "text": {
                            "type": "string",
                            "description": "String to reverse"
                        }
                    },
                    "required": ["text"]
                }),
            },
        ])
    }

    async fn call_tool(&self, tool_call: ToolCallContent) -> Result<Vec<Content>> {
        match tool_call.name.as_str() {
            "reverse_string" => {
                let text = tool_call.arguments["text"]
                    .as_str()
                    .ok_or_else(|| anyhow::anyhow!("Missing 'text' parameter"))?;
                let reversed = text.chars().rev().collect::<String>();
                Ok(vec![Content::text(reversed)])
            }
            _ => Err(anyhow::anyhow!("Unknown tool: {}", tool_call.name)),
        }
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    serve(MyTools).await
}
```

See the [Tool Creation guide](/development/tool-creation) for more Rust examples.

## Testing Your Extension

### Manual Testing

Test your MCP server standalone:

```bash theme={null}
python -m my_tools
```

Then send JSON-RPC messages via stdin:

```json theme={null}
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
```

### MCP Inspector

Use the MCP Inspector for interactive testing:

```bash theme={null}
npm install -g @modelcontextprotocol/inspector
mcp-inspector python -m my_tools
```

This opens a web UI where you can:

* View available tools
* Test tool calls
* Inspect request/response formats

### Integration Testing

Test with goose:

```bash theme={null}
goose session
# Ask the agent to use your tool
```

Enable debug logging to see tool calls:

```bash theme={null}
goose session --log-level debug
```

## Distribution

### PyPI Package

Publish your Python extension:

```python theme={null}
# pyproject.toml
[project]
name = "my-mcp-tools"
version = "1.0.0"
dependencies = ["fastmcp"]

[project.scripts]
my-mcp-tools = "my_tools:main"
```

Users can install:

```bash theme={null}
pip install my-mcp-tools
```

And configure:

```yaml theme={null}
extensions:
  - name: "my-tools"
    transport:
      type: "stdio"
      command: "my-mcp-tools"
```

### npm Package

For TypeScript extensions:

```json theme={null}
{
  "name": "my-mcp-tools",
  "version": "1.0.0",
  "bin": {
    "my-mcp-tools": "./dist/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.5.0"
  }
}
```

Users install:

```bash theme={null}
npm install -g my-mcp-tools
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Extension Development" icon="code" href="/development/extension-development">
    In-depth extension development guide
  </Card>

  <Card title="MCP Protocol" icon="book" href="/development/mcp-protocol">
    Learn the Model Context Protocol specification
  </Card>

  <Card title="Tool Creation" icon="hammer" href="/development/tool-creation">
    Best practices for designing tools
  </Card>

  <Card title="Built-in Extensions" icon="box" href="/api/extensions/builtin">
    Study built-in extension implementations
  </Card>
</CardGroup>
