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

# Resources

> Expose data for AI agents to read with the @resource decorator

Resources are **read-only data** the client/LLM can pull in as context. Unlike tools (actions), resources are meant to provide information **without side effects**. Clients can also subscribe to resource URIs and receive change notifications.

Resources flow through the MCP protocol like this:

1. A client discovers resources via `resources/list` (each resource includes `uri`, optional `name`, optional `mimeType`, etc.).
2. A client reads a resource via `resources/read` with a `uri`.
3. The server executes your resource handler.
4. The server returns a `ReadResourceResult` containing `contents` per MCP spec.

Define resources with `@resource(...)` and register them with `server.collect(...)` (or inside `with server.binding(): ...`).

### Decorator signature

* **`resource(...)`**: `resource(uri: str, *, name=None, description=None, mime_type=None)`

Your handler is called like:

* **`fn()`** → returns `str` (text) or `bytes` (binary). (`async def` is also supported.)

***

## Basic resource

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from dedalus_mcp import MCPServer, resource

@resource("resource://config/app", name="config", description="Application config", mime_type="application/json")
def app_config() -> str:
    return '{"debug": true, "version": "1.2.0"}'

server = MCPServer("config-server")
server.collect(app_config)
```

The decorator defines the resource URI. `collect()` registers it. Clients list resources with `resources/list` and read them with `resources/read`.

***

## Text vs binary

Return `str` for text:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from dedalus_mcp import resource

@resource("resource://docs/readme", mime_type="text/markdown")
def readme() -> str:
    with open("README.md", "r", encoding="utf-8") as f:
        return f.read()
```

Return `bytes` for binary (Dedalus encodes it as base64 in the MCP response):

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from dedalus_mcp import resource

@resource("resource://assets/logo", mime_type="image/png")
def logo() -> bytes:
    with open("logo.png", "rb") as f:
        return f.read()
```

***

## Async resources

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import anyio
from dedalus_mcp import resource

@resource("resource://api/users", description="Active users", mime_type="application/json")
async def users() -> str:
    await anyio.sleep(0.1)  # simulate I/O
    return '{"users": ["ada", "grace"]}'
```

Prefer `async def` for I/O. (Like tools, sync handlers run inline.)

***

## MIME types

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import json
from dedalus_mcp import resource

@resource("resource://data/report", mime_type="application/json")
def report() -> str:
    return json.dumps({"users": 100, "active": 42})
```

Common defaults:

* `text/plain` (default when returning `str` and no `mime_type` is provided)
* `application/octet-stream` (default when returning `bytes` and no `mime_type` is provided)

***

## Subscriptions

Clients can subscribe to resource changes via `resources/subscribe` and unsubscribe via `resources/unsubscribe`.

When your underlying data changes, notify subscribers:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from dedalus_mcp import MCPServer

server = MCPServer("live-data")
server.collect(users)

# When the data behind this URI changes:
await server.notify_resource_updated("resource://api/users")
```

Subscribed clients receive `notifications/resources/updated`.

***

## Decorator options

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from dedalus_mcp import resource

@resource(
    "resource://config/app",
    name="app-config",           # Human-friendly name (shown in resources/list)
    description="App settings",  # Shown to clients
    mime_type="application/json",
)
def config() -> str:
    return '{"debug": false}'
```

***

## Error handling

If your resource handler raises, Dedalus returns a **text fallback** resource:

* `mimeType="text/plain"`
* `text="Resource error: <exception message>"`

If a URI is not registered, `resources/read` returns an empty `contents` list.

***

## Testing

Test resource handlers as normal functions:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import json

def test_config_resource():
    data = json.loads(app_config())
    assert data["version"] == "1.2.0"
```

Integration-style test via the server API (mirrors `resources/read`):

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import pytest
from dedalus_mcp import MCPServer

@pytest.mark.asyncio
async def test_resource_read():
    server = MCPServer("test")
    server.collect(app_config)

    result = await server.invoke_resource("resource://config/app")
    assert result.contents[0].text == '{"debug": true, "version": "1.2.0"}'
```

***

## Resource templates

Use `@resource_template(...)` to advertise **URI patterns** via `resources/templates/list` (for client discovery and completions). Dedalus MCP currently registers templates for listing, but **does not automatically route `resources/read` to a template function**—you still need to register concrete resource URIs with `@resource(...)`.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from dedalus_mcp import resource_template

@resource_template(
    "user-profile",
    uri_template="resource://users/{user_id}",
    description="User profile by ID",
)
async def user_profile(user_id: str) -> str:
    user = await fetch_user(user_id)
    return json.dumps(user)
```
