Skip to main content
Resources are data the LLM can read. Unlike tools (actions), resources provide context without side effects. Clients can subscribe to changes.

Basic resource

from dedalus_mcp import MCPServer, resource

@resource("resource://config/app", name="config", description="Application config")
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:
@resource("resource://docs/readme", mime_type="text/markdown")
def readme() -> str:
    return open("README.md").read()
Return bytes for binary (encoded as base64):
@resource("resource://assets/logo", mime_type="image/png")
def logo() -> bytes:
    return open("logo.png", "rb").read()

Async resources

@resource("resource://api/users", description="Active users")
async def users() -> str:
    async with httpx.AsyncClient() as client:
        resp = await client.get("https://api.example.com/users")
        return resp.text

MIME types

@resource("resource://data/report", mime_type="application/json")
def report() -> str:
    return json.dumps({"users": 100, "active": 42})
Common types:
  • text/plain (default for str)
  • application/json
  • text/markdown
  • application/octet-stream (default for bytes)

Subscriptions

Clients subscribe to resource changes. Notify them when data updates:
server = MCPServer("live-data")
server.collect(users)

# When data changes
await server.notify_resource_updated("resource://api/users")
Subscribed clients receive notifications/resources/updated.

Decorator options

@resource(
    "resource://config/app",
    name="app-config",           # Human-friendly name
    description="App settings",  # Shown to clients
    mime_type="application/json",
)
def config() -> str:
    ...

Error handling

Exceptions return an error wrapper:
@resource("resource://data/flaky")
def flaky() -> str:
    raise RuntimeError("Database unavailable")

# Result contains: {"error": "Resource error: Database unavailable"}
For graceful degradation, catch exceptions and return fallback content.

Testing

Test resources as functions:
def test_config_resource():
    result = app_config()
    data = json.loads(result)
    assert data["version"] == "1.2.0"
Integration test:
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

For dynamic URIs, use resource templates:
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)
Clients see the template in resources/templates/list. The {user_id} placeholder matches URI parameters.