Resources
Resources are static or dynamic data provided by the MCP server to the LLM. Unlike tools, resources are read-only and allow the server to expose files, database records, or live telemetry data to the model.
In mcp-cpp-sdk, resources can be registered either directly or via templates.
Resources are identified by URIs, which should follow a hierarchical structure
appropriate for your application.
Static Resources
A static resource is a direct mapping from a fixed URI to a specific value or handler. These are ideal for configuration files, documentation, or fixed data sets that don’t change their identification scheme.
Register metadata and a typed read handler with add_resource:
#include <mcp/mcp.hpp>
mcp::Resource resource;
resource.uri = "mcp://logs/system";
resource.name = "System logs";
resource.mimeType = "text/plain";
server.add_resource<mcp::ReadResourceRequestParams, mcp::ReadResourceResult>(
resource, [](mcp::ReadResourceRequestParams request) {
mcp::TextResourceContents contents;
contents.uri = std::move(request.uri);
contents.mimeType = "text/plain";
contents.text = "System is running normally.";
mcp::ReadResourceResult result;
result.contents.emplace_back(std::move(contents));
return result;
});
For more complex resources, you can use the structured protocol types:
1 // Resource: config://app/settings
2 Resource config_resource;
3 config_resource.uri = "config://app/settings";
4 config_resource.name = "App Settings";
5 config_resource.description = "Current application settings";
6 config_resource.mimeType = "application/json";
7
8 server.add_resource<ReadResourceRequestParams, ReadResourceResult>(
9 config_resource, [](const ReadResourceRequestParams& params) -> ReadResourceResult {
10 TextResourceContents contents;
11 contents.uri = params.uri;
12 contents.text = R"({"theme":"dark","language":"en"})";
13 contents.mimeType = "application/json";
14 ReadResourceResult result;
15 result.contents.emplace_back(std::move(contents));
16 return result;
17 });
Resource Templates
Resource templates let one handler serve a family of URIs. The current matcher supports the common RFC 6570 expression forms for routing, but it is not a full RFC 6570 expansion or variable-extraction engine. The handler receives the full requested URI and can parse or validate application-specific segments itself.
mcp::ResourceTemplate logs;
logs.uriTemplate = "mcp://logs/{node}";
logs.name = "Node logs";
logs.mimeType = "text/plain";
server.add_resource_template<mcp::ReadResourceRequestParams,
mcp::ReadResourceResult>(
logs, [](mcp::ReadResourceRequestParams request) {
mcp::TextResourceContents contents;
contents.uri = std::move(request.uri);
contents.mimeType = "text/plain";
contents.text = "Logs for requested node";
mcp::ReadResourceResult result;
result.contents.emplace_back(std::move(contents));
return result;
});
Templates are particularly useful when you have a large or open-ended set of resources that share the same schema or purpose.
Dynamic Resources
Dynamic resources are those whose content or availability changes over time. While the registration might look similar to static resources, the handler is invoked every time the client reads the resource, allowing you to return fresh data.
If the list of available resources itself changes (e.g., new files are created), you should notify the client so they can refresh their cache:
// After adding or removing resources
co_await server.notify_resources_list_changed();
Resource Subscriptions
Clients can subscribe to resources to receive notifications when their content changes. This is essential for live data like logs, telemetry, or collaborative documents.
The server can track these subscriptions and perform actions (like starting a polling loop or opening a file watch) when a client expresses interest.
1 // ========== SUBSCRIPTION HANDLERS ==========
2 // Track subscriptions
3 std::cout << "[Server] Registering subscription callbacks\n";
4 server.on_subscribe([](const std::string& uri) {
5 std::cout << "[Server] Client subscribed to: " << uri << '\n';
6 });
7
8 server.on_unsubscribe([](const std::string& uri) {
9 std::cout << "[Server] Client unsubscribed from: " << uri << '\n';
10 });
For more details on how notifications work across the protocol, see mcp-cpp-sdk.
Resource Change Notifications
When a resource’s content changes, call notify_resource_updated on the
Server instance for the relevant session. The method sends only when that
session subscribed to the exact URI. In a multi-session deployment, the
application is responsible for invoking the notification on each relevant
per-session server instance.
// Notify subscribers that a specific resource has changed
co_await server.notify_resource_updated("mcp://status/counter");
The notification tells a subscribed client that it should read the resource again; delivery and refresh timing still depend on the connection and client.
1 std::cout << "[Server] Triggering notify_resource_updated\n";
2 co_await server.notify_resource_updated(resource_uri);
Resource Capabilities
To allow clients to discover your resources, ensure you enable the resources capability in your server configuration. You can also specify if the server supports sending notifications for resource changes.
mcp::ServerCapabilities caps;
caps.resources = mcp::ServerCapabilities::ResourcesCapability{
.subscribe = true // Enable support for subscriptions
};
mcp::Server server(info, std::move(caps));
Best Practices
Use Descriptive URIs: Follow a clear scheme like
myapp://records/{id}.MIME Types: Always provide accurate MIME types to help the client interpret the data.
Error Handling: Throwing an exception in a resource handler will automatically return an appropriate JSON-RPC error to the client.
Context Awareness: A resource handler can use
Contextfor logging and reverse requests. Resource reads do not currently propagate a requestprogressTokeninto the handler context, so do not rely on progress reporting for them. See Server Context for more information.