AttestoMCP.Server.CachePolicy (attesto_mcp_server v2.8.0)

Copy Markdown View Source

Cache-hint policy for cacheable MCP results.

The server emits ttlMs and cacheScope hints on modern complete results. It does not cache responses itself. Without a :cache_policy option or a definition's :cache policy the 2.3 behaviour is kept: :cache_ttl_ms (default 30_000) and a private scope, with "public" only when cache_scope: :public, allow_public_cache: true, and the dispatch context's trusted :public_catalog flag are all present.

Granular policy

cache_policy: [
  methods: %{
    "server/discover" => [ttl_ms: 300_000],
    "tools/list" => [ttl_ms: 60_000, scope: :public],
    "resources/read" => [ttl_ms: 5_000]
  },
  resolver: {MyApp.MCPCache, :policy, []},
  max_ttl_ms: 600_000
]

:methods accepts the standard cacheable operations: server/discover, tools/list, prompts/list, resources/list, resources/templates/list, and resources/read. A resource or resource-template definition may carry its own cache: [ttl_ms: ..., scope: ...] policy for resources/read, even without a server-level :cache_policy option. Each policy may set :ttl_ms (a non-negative integer), :scope (:private or :public), or both; an unset field inherits the lower-precedence value.

The optional :resolver callback receives %{method: method, definition: nil | %{type: type, identity: identity}, policy: policy} and the trusted request context. It returns {:ok, policy} to override the fields it sets or :default to keep the static result. It runs in the request worker under the request deadline. An exception, exit, throw, or invalid return produces a zero-TTL private hint for that response; it never falls back to a public or longer-lived policy.

Resolution order is: global default, method policy, definition policy, resolver output, then valid handler hints on resources/read (which may shorten ttlMs or select "private" but cannot lengthen or promote), and finally the mandatory constraints below.

Mandatory constraints

These apply with or without a granular policy:

  • Interim input_required results carry no hints, and complete results from a multi-round retry use ttlMs: 0 and "private".
  • "public" requires allow_public_cache: true and an explicit host choice (a granular :public policy, or the 2.3 options above), and the complete response must be caller-independent. A catalog is caller-independent only when every registered definition of that type has no required scopes, alternative scope sets, or authorize callback, no HTTP definition policy applies, and no presentation callback is configured. Paginated catalogs stay private because their cursors bind the caller. A resource read additionally requires the selected definition to be unrestricted and its metadata to use only the configured constant implementation identity. Otherwise the scope is "private". Method-level scope requirements do not make a result caller-dependent, so a public hint lets shared caches serve it to callers without those scopes.
  • Output from :instructions_provider or :tool_presentation is private, and its TTL is 0 unless a method policy, definition policy, or resolver explicitly supplies one.
  • A list page carrying nextCursor is never fresher than the cursor's lifetime, and a private hint is never fresher than the remaining lifetime of the verified access token in :attesto_mcp_claims.
  • TTLs never exceed :max_ttl_ms or the largest JSON-safe integer.

All pages of one list request receive the same scope; continuation cursors are bound to it and are rejected if the effective scope changes.

MCP cache hints do not change HTTP caching. Protected HTTP responses keep Cache-Control: private, no-store.

Summary

Types

A normalized policy; nil fields inherit lower-precedence values.

Functions

Validates a static or resolver-supplied policy.

Types

policy()

@type policy() :: %{ttl_ms: non_neg_integer() | nil, scope: String.t() | nil}

A normalized policy; nil fields inherit lower-precedence values.

Functions

normalize_policy(policy)

@spec normalize_policy(term()) :: {:ok, policy()} | {:error, :invalid_cache_policy}

Validates a static or resolver-supplied policy.