A server that exposes a fixed list of resources — this file, that config, one specific report — can just hand the client a resources/list with every URI spelled out. That breaks down the moment the set of valid resources isn’t fixed at all: a filesystem server with an unbounded tree of paths, a ticket tracker with thousands of issues, a database server with a new row every minute. MCP’s answer is resource templates: a URI with placeholders in it, plus a declared list of what those placeholders mean, instead of an exhaustive enumeration of every resource that could ever match. It is a small piece of the tools, resources, and prompts model, and worth separating clearly from the plain resources/list most servers stop at.

What a resource template actually looks like

A template is a URI string using the syntax from RFC 6570 (URI Templates) — the same curly-brace placeholder convention used across plenty of web APIs. A filesystem server might declare file:///{path}; an issue tracker might declare issue:///{project}/{number}. Instead of (or alongside) a flat resources/list, the server responds to resources/templates/list with a set of these template objects, each carrying a uriTemplate, a human-readable name, an optional description, and an optional mimeType for what reading a matching URI will return.

A client doesn’t browse a template the way it browses a list — there’s nothing to page through. It fills in the placeholders (by hand, from context, or via completion if the server supports it) to produce a concrete URI, then reads that URI the normal way, through resources/read. The template is the server telling you the shape of a valid address, not the address itself.

Why this is different from just listing more resources

The honest reason templates exist is that enumeration doesn’t scale and sometimes doesn’t even make sense. Listing every file under a large repository, every ticket in a tracker with years of history, or every row a query could touch means either a massive response or a server that has to decide, unasked, which subset is worth returning. A template sidesteps that: the server states the pattern once, and the client (or the model, if it’s the one constructing the URI) supplies the specific value when it actually needs one resource, not the whole set.

It also means a server can expose access to resources that don’t exist yet at listing time. A ticket tracker doesn’t need to republish its resource list every time someone files a new issue — the template issue:///{project}/{number} already covers the issue that gets filed five minutes from now, with no notifications/resources/list_changed required just to keep up. That’s a narrower problem than the one resource subscriptions solve — subscriptions tell you a specific resource you already know about changed; templates tell you what addresses are even valid to ask about in the first place.

What the spec leaves to the server

MCP doesn’t define validation for the filled-in template — whether {number} has to be numeric, whether {path} can contain .., whether a project name has to exist. That’s entirely the server’s job, and it’s the same job it would have anyway for any user-supplied or model-supplied identifier: check it before you touch a filesystem or a database with it. A template is a hint about shape, not a guarantee about validity, the same distinction we’ve made for roots and tool annotations — all three are declarative metadata a well-behaved server honors and a careless one can still get wrong.

Why most servers skip it

Plenty of MCP servers never implement resources/templates/list at all, because plenty of servers don’t really expose resources in the richer sense — they expose tools, and a tool that takes a path or issueId argument does the same practical job without the client needing to understand URI templates at all. Resources and resource templates matter most for servers built around genuinely large or open-ended address spaces where a tool-shaped “get me this one” call is less natural than a URI a client can construct, bookmark, or pass around. That’s a smaller slice of the ecosystem than tool-first servers, which is why this primitive gets less airtime than tool calls even though it solves a real problem for the servers that need it.

Where a gateway fits: a resource template is still server-declared metadata the client has to trust before it starts filling in placeholders and reading whatever comes back — the same category of surface as a tool description or a completion suggestion. gate’s free MCP security scanner checks a server’s declared surface, templates included, before you connect it, and the per-tool allow, ask, or block rules in a gateway still apply to the resources/read call a filled-in template produces, regardless of how the URI it’s reading was constructed.

What this means if you’re building a server

If your resources have an open-ended or unbounded address space — paths, IDs, timestamps, anything you can’t or shouldn’t enumerate up front — a template is the right primitive, not a giant resources/list you try to paginate around. Validate every filled-in placeholder server-side as untrusted input, the same way you would a tool argument, and don’t assume the client only ever sends values that match the shape you described. If you’re generating a server from an existing API with gate’s MCP server builder, this is one more thing a straight endpoint-to-tool conversion won’t infer on its own — deciding which parameterized lookups deserve a resource template, versus staying a plain tool argument, is a judgment call covered in more depth in turning an API into an MCP server.

The bottom line

Resource templates let a server describe a pattern of valid addresses instead of listing every resource that pattern could match — useful for filesystems, trackers, and anything else with an address space too large or too fresh to enumerate. They carry no validation and no access control of their own; both still live entirely in the server that reads the filled-in URI. See gate’s MCP server directory for what today’s servers actually expose.