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

# Tools

> Define tools that work well with LLMs and Gumloop

Tools are the functions your MCP server exposes to AI assistants. When an LLM decides it needs to perform an action—like searching documents, creating issues, or fetching data—it calls one of your tools.

You can use **FastMCP** (recommended) or the **low-level Server API**. Both are fully supported.

## Basic Tool

Here's a simple tool that searches documents. Notice the type annotations and docstring—these are essential for LLMs to understand and use your tool correctly.

```python theme={"dark"}
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field

mcp = FastMCP("my-server")

class SearchResult(BaseModel):
    title: str
    url: str
    snippet: str

@mcp.tool()
async def search(query: str = Field(description="Search query")) -> list[SearchResult]:
    """Search for documents matching the query."""
    # Implementation
    return results
```

## Requirements

Every tool needs a few things to work properly with Gumstack and LLMs. Missing any of these will cause issues.

### Declare in config.yaml

Every tool must be declared in `config.yaml`:

```yaml theme={"dark"}
tools:
  - name: "search"
    description: "Search for documents"
```

The `name` must match your function name exactly. Without this, Gumstack won't detect the tool.

### Return type annotation

Every tool must have a return type. This generates the output schema that Gumloop uses for node connections.

```python theme={"dark"}
# Good - typed return
@mcp.tool()
def get_user(id: str) -> User:
    ...

# Bad - no schema generated, breaks Gumloop
@mcp.tool()
def get_user(id: str):
    return {"name": "John"}
```

### Docstrings

LLMs use docstrings to decide when to call your tool. Be specific:

```python theme={"dark"}
# Good
@mcp.tool()
def create_issue(title: str, body: str) -> Issue:
    """Create a new issue in the project tracker.

    Use this when the user wants to report a bug or request a feature.
    """

# Bad - too vague
@mcp.tool()
def create_issue(title: str, body: str) -> Issue:
    """Create an issue."""
```

### Parameter descriptions

Use `Annotated` with a string or `Field(description=...)`:

```python theme={"dark"}
from typing import Annotated
from pydantic import Field

@mcp.tool()
def search(
    query: Annotated[str, "Keywords to search for"],
    limit: Annotated[int, Field(description="Max results", ge=1, le=100)] = 10
) -> list[SearchResult]:
    """Search documents."""
```

### Supported types

FastMCP supports all Pydantic types:

| Type            | Example                        |
| --------------- | ------------------------------ |
| Basic           | `int`, `float`, `str`, `bool`  |
| Collections     | `list[str]`, `dict[str, int]`  |
| Optional        | `str \| None`, `Optional[str]` |
| Constrained     | `Literal["A", "B"]`, `Enum`    |
| Pydantic models | `User`, `SearchResult`         |
| Date/time       | `datetime`, `date`             |
| Paths           | `Path`                         |

## Common Mistakes

These patterns cause problems in production. Avoid them to keep your tools reliable and debuggable.

<AccordionGroup>
  <Accordion title="Bare dict returns">
    ```python theme={"dark"}
    # Bad - no output schema
    @mcp.tool()
    def get_data() -> dict:
        return {"key": "value"}

    # Good - typed dict or BaseModel

    @mcp.tool()
    def get_data() -> dict[str, str]:
    return {"key": "value"}

    ```
  </Accordion>

  <Accordion title="Blocking I/O in async">
    ```python theme={"dark"}
    # Bad - blocks event loop
    @mcp.tool()
    async def fetch(url: str) -> str:
        return requests.get(url).text

    # Good - use async HTTP client
    @mcp.tool()
    async def fetch(url: str) -> str:
        async with httpx.AsyncClient() as client:
            resp = await client.get(url)
            return resp.text
    ```
  </Accordion>

  <Accordion title="Hardcoded credentials">
    ```python theme={"dark"}
    # Bad
    API_KEY = "sk-..."

    # Good

    creds = await get_credentials()
    api_key = creds["api_key"]

    ```
  </Accordion>

  <Accordion title="Swallowing exceptions">
    ```python theme={"dark"}
    # Bad - hides errors from observability
    try:
        result = do_thing()
    except Exception:
        return "Error occurred"

    # Good - let errors propagate
    result = do_thing()  # Gumstack logs the error
    ```
  </Accordion>
</AccordionGroup>

## One Tool, One Job

Resist the urge to create "swiss army knife" tools that do multiple things. Split complex operations into focused tools for better:

* **Observability** — See exactly which operation failed
* **Access control** — Admins can restrict specific actions
* **LLM accuracy** — Simpler tools are easier to use correctly

```python theme={"dark"}
# Instead of one mega-tool
@mcp.tool()
def manage_issues(action: str, issue_id: str = None, title: str = None):
    ...

# Split into focused tools
@mcp.tool()
def list_issues() -> list[Issue]: ...

@mcp.tool()
def create_issue(title: str, body: str) -> Issue: ...

@mcp.tool()
def close_issue(issue_id: str) -> Issue: ...
```

## Low-level Server API

FastMCP handles most use cases, but if you need fine-grained control over tool registration and responses, you can use the low-level `Server` class directly:

```python theme={"dark"}
from mcp.server.lowlevel.server import Server
import mcp.types as types

server = Server("my-server")

@server.list_tools()
async def list_tools() -> list[types.Tool]:
    return [
        types.Tool(
            name="greet",
            description="Say hello",
            inputSchema={"type": "object", "properties": {"name": {"type": "string"}}}
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "greet":
        return [types.TextContent(type="text", text=f"Hello, {arguments['name']}!")]
```

See the [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) and [FastMCP docs](https://gofastmcp.com/getting-started/welcome) for full API reference.
