@ResourceTemplate
@ResourceTemplate declares a family of MCP resources that share a URI pattern, like ticket://{id}. Clients fill in the pattern to read one member, such as ticket://T-1, and FrontMCP passes the parts it matched to your code. For a single resource at a fixed URI, use @Resource.
@ResourceTemplate(options)
class MyTemplate extends ResourceContext<Params> {
async execute(uri, params) { /* ... */ }
}
Reference
@ResourceTemplate(options)
Apply @ResourceTemplate to a class that extends ResourceContext, and list the class in an app's resources array, next to static resources.
import { ResourceContext, ResourceNotFoundError, ResourceTemplate } from "@frontmcp/sdk";
@ResourceTemplate({
name: "ticket",
uriTemplate: "ticket://{id}",
description: "One support ticket by id, like ticket://T-1",
mimeType: "application/json",
})
class TicketResource extends ResourceContext<{ id: string }> {
async execute(uri: string, params: { id: string }) {
const ticket = await findTicket(params.id);
if (!ticket) throw new ResourceNotFoundError(uri);
return ticket;
}
}Options
Required:
| Option | Type | Description |
|---|---|---|
name | string | Identifies the template. Unique within the server. |
uriTemplate | string | The URI pattern, with parameters in braces, like ticket://{id}. It must start with a scheme and ://. See URI template syntax. |
Optional:
| Option | Type | Description |
|---|---|---|
title | string | A display name for people. Clients show it instead of name. |
description | string | What each resource is, and what the parameters mean. Clients and models read it in resources/templates/list. |
mimeType | string | The content type of every matching resource. Listed in resources/templates/list, and used for content blocks FrontMCP builds from what execute() returns. |
icons | Icon[] | Images clients can show: src, and optionally mimeType, sizes and theme. |
annotations | ResourceAnnotations | audience, priority and lastModified, as for @Resource. FrontMCP 1.8 accepts them but leaves them out of resources/templates/list. |
_meta | Record<string, unknown> | Extra metadata. Like annotations, FrontMCP 1.8 doesn't send it in resources/templates/list. |
availableWhen | EntryAvailability | Only offer the template on certain platforms, runtimes or deployments. Elsewhere it isn't in resources/templates/list, and reading a URI it matches fails with -32003. See Environment awareness. |
URI template syntax
| Pattern | Matches | Example |
|---|---|---|
{name} | One path segment: anything up to the next / | ticket://{id} matches ticket://T-1 with id = "T-1" |
{+name} | One or more segments, slashes included | kb://{+path} matches kb://billing/refunds.md with path = "billing/refunds.md" |
| Other text | Itself, exactly | customer://{customer}/tickets needs the literal /tickets |
Parameter values are percent-decoded, so reading note://a%2Fb from note://{id} gives id = "a/b".
execute(uri, params)
FrontMCP calls execute() for each read whose URI matches the template.
uri: the URI that was read, liketicket://T-1.params: the matched parameters, like{ id: "T-1" }. Values are always strings. Type them withResourceContext<{ id: string }>.- Returns the content, converted as for
@Resource. Content FrontMCP builds for you is labelled with the URI that was read, liketicket://T-1, and a string's MIME type can come from that URI's extension. Blocks you return in{ contents }are sent as they are.
this.uri and this.params hold the same values as the arguments. The rest of this is the same ResourceContext as for @Resource.
Completing parameters
Add a method named after a parameter with Completer appended, like idCompleter for {id}. When a client sends completion/complete for the template, FrontMCP calls it with what the user has typed so far.
async idCompleter(partial: string) {
return { values: ["T-1", "T-12"], total: 2, hasMore: false };
}
It returns values, and optionally total and hasMore. Completers can use this.get(). To choose a completer at run time instead, override getArgumentCompleter(name) and return a function, or null.
resourceTemplate(options)(handler)
The function form takes the same options. The handler receives (uri, params) and returns the { contents: [...] } shape. Function-form templates can't have completers.
Caveats
- The class must extend
ResourceContext. Using@ResourceTemplateon any other class is a compile error. - Parameters are strings, and they aren't validated.
ticket://anythingmatchesticket://{id}. Check the value inexecute(). - A
{name}parameter doesn't match/. Use{+name}for paths. - If a
@Resourcehas exactly the URI that was read, it wins over any template. - Templates are listed in
resources/templates/list, notresources/list. - To answer "not found" for parameters that don't point at anything, throw
ResourceNotFoundError(uri). The client gets-32602, the same error as for a URI no template matches. Other errors fail the read as they do for@Resource.
Usage
Reading one item by id
Put the id in the URI, and return the item. FrontMCP sends it as JSON, labelled with the URI that was read.
import { ResourceContext, ResourceTemplate } from "@frontmcp/sdk";
import { tickets } from "./tickets";
@ResourceTemplate({
name: "ticket",
uriTemplate: "ticket://{id}",
description: "One support ticket by id, like ticket://T-1",
mimeType: "application/json",
})
export class TicketResource extends ResourceContext<{ id: string }> {
async execute(uri: string, { id }: { id: string }) {
return tickets.find((t) => t.id === id);
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
In the Call tab, pick ticket://{id} from the Request menu and read any id. The content's uri is the one you read.
Handling an id that doesn't exist
Throw ResourceNotFoundError with the URI. The client gets a -32602 error that names it, the same as for a URI that matches no template at all.
import { ResourceContext, ResourceNotFoundError, ResourceTemplate } from "@frontmcp/sdk";
import { tickets } from "./tickets";
@ResourceTemplate({ name: "ticket", uriTemplate: "ticket://{id}", mimeType: "application/json" })
export class TicketResource extends ResourceContext<{ id: string }> {
async execute(uri: string, { id }: { id: string }) {
const ticket = tickets.find((t) => t.id === id);
if (!ticket) throw new ResourceNotFoundError(uri);
return ticket;
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
To send a message of your own instead, like There's no ticket T-9. Ticket ids look like T-1., throw a PublicMcpError: the read fails with -32602 and exactly that message. A plain Error is a server failure, -32603, and production hides its message. See @Resource.
Using several parameters
Each {name} becomes a key in params. Literal text between them has to match exactly.
import { ResourceContext, ResourceTemplate } from "@frontmcp/sdk";
import { tickets } from "./tickets";
type Params = { customer: string; status: string };
@ResourceTemplate({
name: "customer-tickets",
uriTemplate: "customer://{customer}/tickets/{status}",
description: "A customer's tickets with one status, open or closed",
mimeType: "application/json",
})
export class CustomerTickets extends ResourceContext<Params> {
async execute(uri: string, { customer, status }: Params) {
const found = tickets.filter((t) => t.customer === customer && t.status === status);
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ customer, status, tickets: found }) }] };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Matching paths with slashes
{name} stops at the first /. Use {+name} when a parameter is a path.
Path parameters
Example 1 of 3
{+path} matches the whole path
import { ResourceContext, ResourceTemplate } from "@frontmcp/sdk";
@ResourceTemplate({ name: "kb-article", uriTemplate: "kb://{+path}", description: "A knowledge-base article by path" })
export class KbArticle extends ResourceContext<{ path: string }> {
async execute(uri: string, { path }: { path: string }) {
return { contents: [{ uri, mimeType: "text/markdown", text: `# ${path}\n\nRefunds take 5 business days.` }] };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Suggesting parameter values
Clients can offer suggestions as the user types an id. Add an idCompleter() method, and FrontMCP answers completion/complete requests for the {id} parameter with it. The test sends the request a client would.
import { ResourceContext, ResourceTemplate } from "@frontmcp/sdk";
import { tickets } from "./tickets";
@ResourceTemplate({ name: "ticket", uriTemplate: "ticket://{id}", mimeType: "application/json" })
export class TicketResource extends ResourceContext<{ id: string }> {
async execute(uri: string, { id }: { id: string }) {
return { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(tickets.find((t) => t.id === id)) }] };
}
async idCompleter(partial: string) {
const values = tickets.map((t) => t.id).filter((id) => id.startsWith(partial));
return { values, total: values.length };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Writing a template as a function
For a template that doesn't need context, the function form is shorter. Its handler gets the same (uri, params) and returns the full { contents } shape.
import { resourceTemplate } from "@frontmcp/sdk";
export const agentQueue = resourceTemplate({
name: "agent-queue",
uriTemplate: "agent://{agent}/queue",
description: "The tickets assigned to one support agent",
mimeType: "application/json",
})((uri, { agent }) => ({
contents: [{ uri, mimeType: "application/json", text: JSON.stringify({ agent, tickets: ["T-1", "T-3"] }) }],
}));Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Troubleshooting
The server doesn't start: "URI template must have a valid scheme"
uriTemplate needs a scheme followed by ://, like ticket://{id}. {id} or ticket:{id} are rejected.
Reads fail with -32602 "Resource not found"
No template matched the URI. Check, in order:
- The literal parts match exactly, including case and slashes.
- No
{name}value contains a/. Use{+name}, or have the client percent-encode the value withencodeURIComponent(). - The template is in its app's
resourcesarray. execute()threwResourceNotFoundError, which gives the same error. Check that the item exists.- The template's
availableWhen.surface, if it has one, includes"mcp". MCP clients can't read a template that leaves it out.
Reads fail with -32003 "ResourceTemplate "…" is not available in the current environment"
The template's availableWhen doesn't match where the server runs. It's left out of resources/templates/list, and reading a URI it matches fails with this error, which names the URI, what the template requires and what the server is:
import { ResourceContext, ResourceTemplate } from "@frontmcp/sdk";
@ResourceTemplate({ name: "deploy-notes", uriTemplate: "deploy://{id}/notes", mimeType: "text/plain", availableWhen: { runtime: ["deno"] } })
export class DeployNotes extends ResourceContext<{ id: string }> {
async execute(uri: string, { id }: { id: string }) {
return `Deploy notes for ${id}`;
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
My template isn't in resources/list
That's expected. Clients find templates with resources/templates/list, and mcp.resources.listTemplates() in tests.
Completions come back empty
Check that the method is named after the parameter, idCompleter for {id}, and that the template is a class. If the completer throws, FrontMCP logs a warning and answers with no values.