Resources
A resource is data the client can read and attach to a conversation: a file, a configuration, a database row, an API payload. Resources are dynamic by nature - they are data, not code - so unlike tools and prompts they are not mapped on JSON-RPC methods but provided at runtime by any bean implementing MCPResources:
@ApplicationScoped
public class MyResources implements MCPResources {
@Override
public List<Resource> resources() { (1)
return List.of(Resource.of("app://config", "config", "application/json", "The application configuration."));
}
@Override
public List<ResourceTemplate> resourceTemplates() { (2)
return List.of(ResourceTemplate.of("app://logs/{day}", "logs", "text/plain", "A daily log file."));
}
@Override
public Optional<ReadResourceResponse> read(final String uri) { (3)
if ("app://config".equals(uri)) {
return Optional.of(ReadResourceResponse.of(
ResourceContents.text(uri, "application/json", configuration())));
}
if (uri.startsWith("app://logs/")) {
return Optional.of(ReadResourceResponse.of(
ResourceContents.text(uri, "text/plain", log(uri.substring("app://logs/".length())))));
}
return Optional.empty(); (4)
}
}
-
resources/list: the resources with a stable uri. -
resources/templates/list: the parameterized ones, using a RFC 6570 uri template. -
resources/read: resolve a uri, whether it comes from the list or from a template expansion. -
Not ours - the next provider gets a chance, and if none answers the client gets
-32002.
All the implementations are used and visited in bean priority order (@Order), which is how a module can add its own resources without knowing the others. The resources capability - and the related methods - is only advertised when at least one implementation exists.
Descriptor versus contents
Two types, mirroring the specification:
| Type | Role |
|---|---|
|
|
the descriptor: uri, name, title, description, mime type, size. This is what |
|
|
the content: |
ResourceContents.text(uri, mimeType, text) and ResourceContents.blob(uri, mimeType, bytes) cover both cases, the latter encoding the bytes for you. A single uri can expand to several contents - a directory, a multi part document:
return Optional.of(new ReadResourceResponse(null, List.of(
ResourceContents.text(uri + "/README.adoc", "text/asciidoc", readme),
ResourceContents.blob(uri + "/logo.png", "image/png", logo))));
Subscriptions and change notifications
The server tracks subscriptions per session, so resources/subscribe and resources/unsubscribe need no code on your side. Emitting the change does:
@ApplicationScoped
public class MyResources implements MCPResources {
private final MCPNotifier notifier;
public MyResources(final MCPNotifier notifier) {
this.notifier = notifier;
}
public void onConfigurationReloaded() {
notifier.resourceUpdated("app://config"); (1)
}
public void onNewLogFile() {
notifier.resourceListChanged(); (2)
}
}
- Only sent to the sessions which subscribed to that exact uri.
-
Sent to every connected client, they are expected to call
resources/listagain.
|
NOTE
|
a client only receives these while it keeps a |
Linking a resource from a tool
A tool does not have to inline a big payload in its answer, it can point at a resource the client will read if it needs it:
return new ToolResponse(null, false, List.of(
Content.text("The report is ready."),
Content.resourceLink("app://reports/2025-01", "report", "text/markdown")), null);
Uri conventions
MCP does not impose a scheme. Use a stable, meaningful one - app://, file://
, db://table/id - because a uri is what a client stores when the user attaches a resource to a conversation. Templates should stay simple: one variable per path segment, no query string, so a client can offer completion on the variables (see Completing the arguments
, with ref.type() being ref/resource).