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 resourcename: Human-readable resource namedescription: Explanation of the resource's purposemime_type: Content type (e.g., "application/json", "text/plain")data_provider: Function that returns the resource's dataannotations: Optional metadata about the resourcetitle: Optional human-friendly display nameicons: Optional icons for UI display_meta: Optional metadata for protocol extensions, emitted verbatim inresources/list
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:
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
For verbatim text: return a
String— used as the text contents as-isFor explicit control over content (including binary):
- Return
TextResourceContentsfor text data - Return
BlobResourceContentsfor binary data (base64blobon the wire) - Return a
Vectorof them for multiple contents entries
- Return
Registering Resources
Resources can be registered with a server in two ways:
- During server creation:
server = mcp_server(
name = "my-server",
resources = my_resource # Single resource or vector of resources
)- 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.jlEach file must define exactly one MCPResource as the file's final expression — the loader registers the value of the last expression and ignores everything else defined in the file:
# 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
TextResourceContentsorBlobResourceContents(or a vector of them) is serialized directly — this is how binary resources are served (base64blobon the wire) and how you control the contentsuri/mimeTypeper entry. - A
Stringbecomes the text contents verbatim, with the resource'smime_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.
Completing Template Variables
A template's completions field supplies completion/complete suggestions for its placeholders, so clients can offer values as the user types a URI. It maps a variable name to either a Vector{String} (served filtered by prefix against the partial value) or a function — value -> values, or (value, context_args) -> values to also receive the arguments already resolved for the request:
artifact_template = ResourceTemplate(
name = "artifact",
uri_template = "app://artifact/{id}",
description = "Content-addressed result artifacts",
data_provider = (uri, vars) -> read_artifact(vars["id"]),
completions = Dict{String,Any}("id" => value -> recent_artifact_ids(value))
)Suggestions are capped at 100 values with total/hasMore reported to the client. The wire details are in The Modern Era; completion/complete is served in both eras and the completion capability is advertised by default.
Subscriptions and Change Notifications
Clients can follow resource changes instead of re-reading, through a different mechanism in each era:
- Legacy era:
resources/subscribe/resources/unsubscriberecord the client's interest per URI on its session (thesubscriberesource capability is advertised by default). Both are idempotent and answer with an empty result; a subscribed session then receivesnotifications/resources/updatedfor those URIs — on stdout for stdio, on the standalone GET SSE stream for Streamable HTTP. - Modern era:
subscriptions/listenreplaces both the legacy GET stream andresources/subscribe— a single long-lived request whose response stream carries only the notification types the client opted into. See The Modern Era.
Announce changes from server code with two exported functions, which deliver to the open subscriptions/listen streams that opted into them and to the subscribed legacy session:
# A resource's contents changed
notify_resource_updated(server, "app://logs/recent")
# A component list changed after registering or removing something at runtime
notify_list_changed(server, :resources) # or :tools, :promptsBoth return the number of clients notified (listen streams plus the legacy session when it was reached). There is no backlog — only clients connected at the time of the call are reached — and notify_list_changed reaches the legacy session only when the corresponding listChanged capability is declared.
Separately, subscribe!(server, uri, callback) and unsubscribe!(server, uri, callback) maintain an in-process registry of callbacks keyed by URI, for server-side code that wants to react to its own updates. notify_resource_updated invokes each matching callback as callback(uri) (errors are logged and swallowed); the callbacks themselves send nothing to clients.