Skip to main content
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

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:
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):
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

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

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:
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

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:
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):
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(...).
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)