Resource Templates

+15 Mana ✨

Introduction

Static resource URIs work well when you know every resource in advance. But what about resources that depend on parameters — like a user profile identified by user ID, or a file identified by its path? Resource templates solve this by defining parameterized URIs that clients can fill in at runtime.

Key Concepts

  • Resource template: A parameterized URI pattern that defines a family of resources. Uses RFC 6570 URI Template syntax (e.g., user://{userId}/profile).
  • URI Template (RFC 6570): A standard for URI patterns with placeholders enclosed in curly braces. Parameters are expanded by the client before sending a request.
  • resources/templates/list: The JSON-RPC method to discover available resource templates on a server.
  • Autocompletion: Servers can provide suggested values for template parameters to help clients fill in the blanks.

Real World Context

A database MCP server cannot list every possible row as a separate resource — there could be millions. Instead, it exposes a template like db://users/{userId} that clients expand with a specific user ID. The LLM might say "I need the profile for user 42," and the client fills in the template to request db://users/42. Templates make infinite resource spaces practical.

Deep Dive

Template Syntax

Resource templates use RFC 6570 URI Template syntax. Parameters are enclosed in curly braces:

text
file:///{path}                    # Single parameter
db://users/{userId}/profile       # Parameter in path
github://{owner}/{repo}/issues    # Multiple parameters

The client replaces each parameter with a concrete value before sending a resources/read request. The expanded URI becomes a standard resource URI.

Discovering Templates

Clients discover available templates by sending a resources/templates/list request. Here is an example response:

json
{"resourceTemplates":[{"uriTemplate":"file:///{path}","name":"Project Files","title":"Project File Access","description":"Access files in the project directory","mimeType":"application/octet-stream"}]}

This response advertises a single template that accepts a file path parameter. Like static resources, templates can include an optional title field as a human-readable display name. The client can use this to request any file within the project directory.

Expanding Templates

When the client (or LLM) wants a specific resource, it expands the template by substituting parameter values:

text
Template:  file:///{path}
Expanded:  file:///src/index.ts

Template:  db://users/{userId}/profile
Expanded:  db://users/42/profile

The expanded URI is then sent as a normal resources/read request. The server handles it the same way it handles static resource reads.

Template Parameters and Autocompletion

Servers can help clients fill in template parameters by providing completion suggestions. When a user starts typing a parameter value, the client can request completions from the server:

typescript
server.resource(
  "user-profile",
  new ResourceTemplate("db://users/{userId}/profile", { list: undefined }),
  { description: "User profile by ID" },
  async (uri, { userId }) => {
    const user = await db.users.findUnique({ where: { id: userId } });
    return {
      contents: [{
        uri: uri.href,
        mimeType: "application/json",
        text: JSON.stringify(user)
      }]
    };
  }
);

This code shows a server registering a resource template that accepts a userId parameter and returns the user's profile as JSON.

When to Use Templates vs Static Resources

Use static resources when:

  • The set of resources is small and known in advance
  • Every resource should appear in the resources/list response
  • No parameters are needed

Use templates when:

  • Resources are parameterized (user IDs, file paths, dates)
  • The resource space is too large to enumerate
  • Resources are generated on demand from parameters

In practice, most servers use a combination. A project server might expose specific well-known files as static resources and a template for accessing any file by path.

Common Pitfalls

  1. Using templates for a small, fixed set — If you only have five resources, list them statically. Templates add complexity that is not needed when the resource set is small and stable.
  2. Not validating template parameters — When a client expands a template, the server must validate the parameter values. A template like file:///{path} without path validation could allow directory traversal attacks.
  3. Forgetting to expose both static and template lists — Some clients may only check resources/list and miss templates. Ensure your documentation tells clients to also check resources/templates/list.

Best Practices

  1. Use descriptive parameter names — Parameters like {userId} and {filePath} are self-documenting. Avoid cryptic names like {x} or {p1}.
  2. Provide descriptions for templates — The description field helps LLMs understand what parameters to provide and what the resulting resource will contain.
  3. Implement autocompletion when possible — For parameters with a finite set of valid values (like table names or user IDs), provide completion handlers so clients can offer suggestions.

Summary

  • Resource templates define parameterized URI patterns using RFC 6570 URI Template syntax.
  • Clients discover templates via resources/templates/list and expand them by substituting parameter values.
  • Templates are essential when the resource space is too large to enumerate statically.
  • Servers should validate all template parameters to prevent security issues.
  • Use static resources for small, known sets and templates for parameterized or dynamic resource spaces.

Code Examples

typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";

const server = new McpServer({
  name: "db-server",
  version: "1.0.0"
});

// Template for accessing user profiles by ID
server.resource(
  "user-profile",
  new ResourceTemplate("db://users/{userId}/profile", { list: undefined }),
  { description: "User profile by ID", mimeType: "application/json" },
  async (uri, { userId }) => {
    const user = await db.users.findUnique({ where: { id: userId } });
    return {
      contents: [{
        uri: uri.href,
        mimeType: "application/json",
        text: JSON.stringify(user, null, 2)
      }]
    };
  }
);

// Template for accessing any project file
server.resource(
  "project-file",
  new ResourceTemplate("file:///{path}", { list: undefined }),
  { description: "Access project files", mimeType: "application/octet-stream" },
  async (uri, { path }) => {
    const content = await fs.readFile(path, "utf-8");
    return {
      contents: [{ uri: uri.href, mimeType: "text/plain", text: content }]
    };
  }
);
✓ Completed