Use Resources for Addressable Context
A resource is read by URI and represents data, not an action. This is a fundamentally different surface from a tool: a resource answers "give me the content of the object at this address", not "perform an operation". Static resources fit known documents, templates fit parameterized objects. Both are clearer than a universal tool like get_everything.
A static resource is a fixed URI and content, like our release policy.
server.registerResource(
"release-policy",
"runbook://release-policy",
{
title: "Release policy",
description: "Release admission criteria",
mimeType: "text/markdown",
cacheHint: { ttlMs: 60_000, cacheScope: "public" }
},
async (uri) => {
const runbook = getRunbook("release-policy");
if (!runbook) {
throw new Error("Runbook release-policy is missing");
}
return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: runbook.body }] };
}
);A template describes a whole family of addresses - for example, a snapshot of any release by service and version.
server.registerResource(
"release-snapshot",
new ResourceTemplate("release://{service}/{version}", {
list: async () => ({
resources: listReleaseSnapshots().map((snapshot) => ({
uri: `release://${snapshot.service}/${snapshot.version}`,
name: `${snapshot.service}@${snapshot.version}`,
title: `Release snapshot ${snapshot.service}@${snapshot.version}`,
mimeType: "application/json"
}))
})
}),
{
title: "Release snapshot",
description: "Facts on CI, tests, security, and rollback",
mimeType: "application/json",
cacheHint: { ttlMs: 10_000, cacheScope: "private" }
},
async (uri, variables) => {
const service = singleVariable(variables.service, "service");
const version = singleVariable(variables.version, "version");
const snapshot = getReleaseSnapshot(service, version);
if (!snapshot) {
throw new Error(`Release ${service}@${version} not found`);
}
return {
contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(snapshot, null, 2) }]
};
}
);When to choose which is convenient to decide across a few axes at once.
Question · Resource · Tool
- Semantics - "Give the object's content by URI"; "Perform an operation with args"
- Discovery - List + URI templates; Tool list
- Cache - Natural, by URI and version; Depends on the operation
- Side effect - Not expected; Possible and must be declared
- Example -
release://web-portal/2.4.0;review_release
The point of the split is the semantics of access. A resource has a natural cache - by URI and version; no side effect is expected; discovery goes through a list and URI templates. A tool's cache depends on the operation, and a side effect is possible and must be declared. Confusing them means losing both cache predictability and clarity of intent.
The URI itself, meanwhile, is a contract. Choose a stable scheme, normalize variables, and don't let a template bypass a tenant boundary. In the example the server accepts only one non-empty string per variable, and the lookup is done by the domain repository - not the protocol layer.
Resource content is untrusted too. A document may contain a prompt injection. The host must mark provenance, bound the size, and not turn resource text into system instructions - otherwise addressable context becomes an attack channel.
Tools and resources cover actions and data. The third MCP surface remains - interaction templates, and it has its own thin boundary of power.