resource-vs-tool.md

reference

← Back to skill

Content hash: 30fb0f6b081b815b047adb3980679cd8710de4056b25fa6be359dea680471284
## Resources vs Tools: The Critical Distinction

### The rule
| If the model should... | Expose as... | Example |
|---|---|---|
| **Read** data | Resource | Document contents, DB rows, config |
| **Act / mutate** | Tool | Create, update, delete, send, call API |
| **Follow a conversation template** | Prompt | Summary request, review checklist |

### Why this matters
Tools and resources have different semantics in MCP. A tool implies an action
that may have side effects. A resource implies read-only data. Exposing
read-only data as tools confuses the model and bloats the tool list.

### When a resource is better
```python
# BAD: read-only data as a tool
@mcp.tool()
def get_all_todos() -> str: ...

# GOOD: same data as a resource
@mcp.resource("todos://all")
def get_all_todos() -> str: ...
```

### When a prompt is the right fit
Prompts are reusable message templates that a client can pull in:
```python
@mcp.prompt()
def summarize_todos() -> str:
    return "Here are the user's todos. Summarize key action items."
```

### URL-like resource identifiers
```python
# Static
@mcp.resource("todos://all")

# Parameterized (model can request specific one)
@mcp.resource("todos://{todo_id}")
```

### Tool granularity guideline
| Wrong (monolithic) | Right (focused) |
|---|---|
| `manage_todo(action="add|delete|list|complete")` | `add_todo`, `complete_todo`, `list_todos` |
| `api_call(endpoint=..., body=...)` | `search_products`, `get_order` |

### Docstring is the prompt
The model reads your docstring as the tool description. Every word matters:
- **What** the tool does
- **When** to use it
- **What** each parameter means (with format hints)
- **What** the return value looks like (error format too)