MCP Resources

Resources provide data that language models can access. Each resource has a URI, name, MIME type, and a data provider function.

Resource Structure

Every resource in ModelContextProtocol.jl is represented by the MCPResource struct:

  • uri: Unique URI identifier for the resource
  • name: Human-readable resource name
  • description: Explanation of the resource's purpose
  • mime_type: Content type (e.g., "application/json", "text/plain")
  • data_provider: Function that returns the resource's data
  • annotations: Optional metadata about the resource

Creating Resources

Here's how to create a basic resource:

using URIs

weather_resource = MCPResource(
    uri = "weather://current",
    name = "Current Weather",
    description = "Current weather conditions",
    mime_type = "application/json",
    data_provider = () -> Dict(
        "temperature" => 22.5,
        "conditions" => "Partly Cloudy",
        "updated" => Dates.format(now(), "yyyy-mm-dd HH:MM:SS")
    )
)

Note: The uri field accepts both strings and URI objects. Strings are automatically converted to URIs.

Data Providers

The data_provider is a zero-argument function called on every resources/read. It can return different types of data:

  1. For simple data (automatically serialized to JSON):

    • Return Julia objects (Dict, Array, etc.) that can be JSON-serialized
    • These become a text contents entry with the resource's mime_type
  2. For verbatim text: return a String — used as the text contents as-is

  3. For explicit control over content (including binary):

    • Return TextResourceContents for text data
    • Return BlobResourceContents for binary data (base64 blob on the wire)
    • Return a Vector of them for multiple contents entries

Registering Resources

Resources can be registered with a server in two ways:

  1. During server creation:
server = mcp_server(
    name = "my-server",
    resources = my_resource  # Single resource or vector of resources
)
  1. After server creation:
register!(server, my_resource)

Directory-Based Organization

Resources can be organized in directory structures and auto-registered:

my_server/
└── resources/
    ├── weather.jl
    └── stock_data.jl

Each file should export one or more MCPResource instances:

# weather.jl
using ModelContextProtocol
using Dates

weather_resource = MCPResource(
    uri = "weather://current",
    name = "Current Weather",
    description = "Current weather conditions",
    mime_type = "application/json",
    data_provider = () -> Dict(
        "temperature" => 22.5,
        "conditions" => "Partly Cloudy",
        "updated" => Dates.format(now(), "yyyy-mm-dd HH:MM:SS")
    )
)

Then auto-register from the directory:

server = mcp_server(
    name = "my-server",
    auto_register_dir = "my_server"
)

Advanced Examples

Resource with Dynamic Data

The data_provider is called with no arguments on every resources/read. What it returns determines the wire contents:

  • A TextResourceContents or BlobResourceContents (or a vector of them) is serialized directly — this is how binary resources are served (base64 blob on the wire) and how you control the contents uri/mimeType per entry.
  • A String becomes the text contents verbatim, with the resource's mime_type.
  • Anything else is JSON-encoded into a text contents entry.
# JSON data (encoded automatically)
log_resource = MCPResource(
    uri = "app://logs/recent",
    name = "Recent Log Entries",
    description = "The most recent application log entries",
    mime_type = "application/json",
    data_provider = function ()
        entries = isfile("app.log") ?
            collect(Iterators.take(eachline("app.log"), 50)) : String[]
        return Dict("count" => length(entries), "entries" => entries)
    end
)

Resource with Binary Data

Return a BlobResourceContents to serve binary data (base64-encoded on the wire):

image_resource = MCPResource(
    uri = "images://logo",
    name = "Logo Image",
    description = "Company logo",
    mime_type = "image/png",
    data_provider = () -> BlobResourceContents(
        uri = "images://logo",
        mime_type = "image/png",
        blob = read("logo.png")   # Vector{UInt8}
    )
)

This pairs naturally with ResourceLink tool results: a tool can return a link to a large artifact, and the client then reads the binary via resources/read.

URI Templates

For a parameterized family of resources — content-addressed artifacts, per-id results, file trees — register a ResourceTemplate instead of one resource per URI. Templates use RFC 6570 level-1 placeholders ({var}, matching one path segment) and are advertised to clients via resources/templates/list:

artifact_template = ResourceTemplate(
    name = "artifact",
    uri_template = "app://artifact/{id}",
    description = "Content-addressed result artifacts",
    mime_type = "image/png",
    data_provider = (uri, vars) -> BlobResourceContents(
        uri = uri,
        mime_type = "image/png",
        blob = read(artifact_path(vars["id"]))
    )
)

server = mcp_server(
    name = "my-server",
    resource_templates = [artifact_template]   # or register!(server, artifact_template)
)

A resources/read whose URI matches no exact resource is routed through the templates: the first match with a provider serves it. The provider receives the requested URI — provider(uri) — or opts into provider(uri, vars) to also get the extracted placeholder values. Return values follow the same contract as resource providers (ResourceContents/vector, String verbatim, JSON fallback). Exact-URI resources always take precedence over templates.