Chapter 12

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.

TypeScript
  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.

TypeScript
  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.

Links