h3-mcp logoh3-mcp

Caching

Every cacheable modern result states its own freshness.

Six methods must carry ttlMs and cacheScope on the modern era:

server/discover · tools/list · prompts/list · resources/list · resources/templates/list · resources/read

The default is { ttlMs: 0, cacheScope: "private" } — immediately stale, never shared — so caching is always something you opt into deliberately.

defineMcpHandler({
  name: "my-server",
  version: "1.0.0",
  cache: {
    ttlMs: 60_000,
    cacheScope: "public",
    discover: { ttlMs: 3_600_000 },
    resourceRead: { ttlMs: 5_000, cacheScope: "private" },
  },
  resources: [
    defineMcpResource({
      name: "profile",
      uri: "app:///me",
      cache: { ttlMs: 1_000, cacheScope: "private" }, // per-definition override
      handler: async (uri) => ({ contents: [{ uri: uri.toString(), text: "..." }] }),
    }),
  ],
});

#Precedence

From most specific to least:

ttlMs / cacheScope returned by the handler itself
per-definition cache on a resource or resource template
per-method option — discover, tools, prompts, resources, resourceTemplates, resourceRead
the handler-level default

Interim (input_required) results never carry caching hints, and legacy results never carry them at all — the fields are stripped on the legacy path even if a shared handler set them.

#Scopes

ScopeMeaning
"private"Cacheable only for the authorization context that fetched it. The default.
"public"May be shared across clients and users.

Warning

cacheScope: "public" responses may be shared across authorization contexts, even on an authenticated endpoint. If a result depends on who asked — a tenant's records, a user's files, anything behind auth — it must stay "private". A single mislabeled result is a cross-tenant leak, not a performance regression.

The safe pattern is to widen the scope per method rather than globally: server/discover and tools/list are usually identical for everyone, while resources/read rarely is.

cache: {
  // safe: same for every caller
  discover: { ttlMs: 3_600_000, cacheScope: "public" },
  tools: { ttlMs: 600_000, cacheScope: "public" },
  // per-user: keep private, short
  resourceRead: { ttlMs: 5_000, cacheScope: "private" },
}

#Dynamic TTLs

A handler can decide freshness per result — useful when you know when the underlying data next changes:

defineMcpResource({
  name: "forecast",
  uri: "app:///forecast",
  handler: async (uri) => {
    const { data, expiresInMs } = await getForecast();
    return {
      contents: [{ uri: uri.toString(), text: JSON.stringify(data) }],
      ttlMs: expiresInMs,
      cacheScope: "public",
    };
  },
});
h3-mcp logo

h3-mcp  MCP servers, built on H3.