@Prompt
@Prompt declares an MCP prompt: a message template that a user picks in their client, often from a slash menu, and fills in with arguments. FrontMCP runs your execute() with those arguments and returns the messages it builds, which the client then puts in front of the model.
@Prompt(options)
class MyPrompt extends PromptContext {
async execute(args): Promise<GetPromptResult> { /* ... */ }
}
Reference
@Prompt(options)
Apply @Prompt to a class that extends PromptContext, and list the class in an app's prompts array.
import { Prompt, PromptContext, type GetPromptResult } from "@frontmcp/sdk";
@Prompt({
name: "triage_ticket",
title: "Triage a ticket",
description: "Suggest a priority and a next step for a support ticket",
arguments: [
{ name: "id", description: "Ticket id, like T-1", required: true },
{ name: "tone", description: "friendly or formal" },
],
})
class TriageTicket extends PromptContext {
async execute(args: Record<string, string>): Promise<GetPromptResult> {
return {
messages: [
{ role: "user", content: { type: "text", text: `Read ticket ${args.id}, then suggest a priority and a next step.` } },
],
};
}
}Options
Required:
| Option | Type | Description |
|---|---|---|
name | string | What clients list and ask for with prompts/get. Unique within the server. |
Optional:
| Option | Type | Description |
|---|---|---|
arguments | PromptArgument[] | The values the user fills in. Leave it out for a prompt without arguments. |
title | string | A readable name for menus, like "Triage a ticket". Clients fall back to name without it. |
description | string | What the prompt is for. Also becomes the result's description when execute() doesn't return one. |
icons | Icon[] | Icons clients can show next to the prompt. Each has a src, and optionally mimeType, sizes and theme ("light" or "dark"). |
availableWhen | EntryAvailability | Only offer the prompt in matching environments. Takes arrays of os, platform, runtime, deployment, provider, target, surface and env values. Elsewhere the prompt isn't listed, and prompts/get fails with -32003. See Environment awareness. |
Each entry in arguments:
| Field | Type | Description |
|---|---|---|
name | string | The key the value arrives under in args. |
description | string | Shown to the user filling it in. |
required | boolean | Defaults to false. When true, FrontMCP rejects a prompts/get that leaves it out, and execute() doesn't run. |
execute(args)
FrontMCP calls execute() when a client sends prompts/get, once every required argument is present.
args: the arguments the client sent, as strings. They're also available asthis.args.- Returns the prompt's messages. FrontMCP turns what
execute()returns into theprompts/getresult:
execute() returns | The client receives |
|---|---|
{ description?, messages } | Those messages. Each has a role, "user" or "assistant", and one content block of type text, image, audio, resource (an embedded resource) or resource_link. |
| A string | One user message with that text. |
An array of { role, content } messages | Those messages, as if you had returned { messages }. |
| Any other object | One user text message with the object as JSON. |
The result's description is the one you return, or else the prompt's description option. FrontMCP checks the result when execute() returns: a message whose role isn't "user" or "assistant" fails the request.
Inside execute(), this is the PromptContext, with the members every context class has: this.get() and this.tryGet() for providers, this.auth for who is asking, this.context for the request, this.fetch() and this.callTool(). It adds this.args for the arguments, and this.metadata for the options above. (Changed in 1.9.2: before, PromptContext had none of this.auth, this.context, this.fetch() and this.callTool(), and the caller was in this.authInfo, now deprecated.)
Caveats
- The class must extend
PromptContext. Using@Prompton any other class is a compile error. - Argument values are always strings, because that's all MCP allows. Convert them yourself.
- Arguments that aren't declared are passed through to
execute(), not dropped as they are for tools. - When
execute()throws aPublicMcpError(or a subclass, likeInvalidInputError), the client gets a JSON-RPC error, code-32602, whose message is your error's message. See rejecting bad arguments. - Any other error is an internal one: code
-32603, andPrompt execution failed:followed by its message in development. WithNODE_ENV=production, FrontMCP hides the message and sendsInternal FrontMCP error. Please contact support with error ID: …instead. this.fail(error)fails the request the same way asthrow errorfor aPublicMcpError. For any other error it's still-32603, but the message is your error's message without thePrompt execution failed:prefix, anddata.codeisSERVER_ERRORinstead ofPROMPT_EXECUTION_FAILED. Production hides it the same way.this.respond(value)endsexecute()asreturn valuewould. (Changed in 1.9.3: before, the request failed withPrompt output not found.)- Declaring
execute()as returningPromise<GetPromptResult>is optional. With it, TypeScript checks each message'sroleandcontentas you write them.
Usage
Declaring arguments
List every value the user should fill in. Clients read arguments from prompts/list to build the form, so describe each one. Open the Capabilities tab to see the prompt the way a client does, and the Tests tab to see what required does.
import { Prompt, PromptContext, type GetPromptResult } from "@frontmcp/sdk";
@Prompt({
name: "triage_ticket",
title: "Triage a ticket",
description: "Suggest a priority and a next step for a support ticket",
arguments: [
{ name: "id", description: "Ticket id, like T-1", required: true },
{ name: "tone", description: "friendly or formal (default: formal)" },
],
})
export class TriageTicket extends PromptContext {
async execute(args: Record<string, string>): Promise<GetPromptResult> {
const tone = args.tone ?? "formal";
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Read ticket ${args.id}. Suggest a priority (low, normal or high) and a next step, in a ${tone} tone.`,
},
},
],
};
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Writing several messages
A prompt can start a conversation with more than one turn. An assistant message puts words in the model's mouth, which is a reliable way to set the format of its answer.
import { Prompt, PromptContext, type GetPromptResult } from "@frontmcp/sdk";
@Prompt({
name: "draft_reply",
title: "Draft a reply",
description: "Draft a short reply to a customer",
arguments: [
{ name: "customer", description: "The customer's first name", required: true },
{ name: "problem", description: "What they reported", required: true },
],
})
export class DraftReply extends PromptContext {
async execute({ customer, problem }: Record<string, string>): Promise<GetPromptResult> {
return {
description: `Reply to ${customer}`,
messages: [
{ role: "user", content: { type: "text", text: `You're a support agent. Keep replies under 80 words and end with one clear next step.` } },
{ role: "assistant", content: { type: "text", text: "Understood. What did the customer report?" } },
{ role: "user", content: { type: "text", text: `${customer} wrote: "${problem}". Draft the reply.` } },
],
};
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
The result's description is the one you return. Without one, FrontMCP uses the prompt's description option.
Returning a string or an array
A prompt that's a single user message can return just its text. A prompt with several messages can return the array of messages, without the { messages } around it. Open the Tests tab to see what each one sends.
import { Prompt, PromptContext } from "@frontmcp/sdk";
@Prompt({
name: "escalation_note",
description: "Write an escalation note for the on-call engineer",
arguments: [{ name: "id", description: "Ticket id, like T-1", required: true }],
})
export class EscalationNote extends PromptContext {
async execute({ id }: Record<string, string>) {
return `Write a two-line escalation note for ticket ${id}: impact first, then what was tried.`;
}
}
@Prompt({ name: "reply_in_house_style", description: "Draft a reply in the help desk's style" })
export class ReplyInHouseStyle extends PromptContext {
async execute() {
return [
{ role: "user", content: { type: "text", text: "Draft replies in two sentences: what we did, then what happens next." } },
{ role: "assistant", content: { type: "text", text: "Understood. Paste the ticket." } },
];
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Including data in a message
A message can carry data as well as text. Embed it, or link to a resource the client can read.
Data in a message
Example 1 of 2
Embedded resource
Get the data from a provider with this.get(), and embed it as a resource block. The client sends the model the data itself, with its URI and MIME type.
import { Prompt, PromptContext, type GetPromptResult } from "@frontmcp/sdk";
import { TicketStore } from "./ticket-store";
@Prompt({
name: "summarize_ticket",
description: "Summarize a ticket and its history for a handover",
arguments: [{ name: "id", description: "Ticket id, like T-1", required: true }],
})
export class SummarizeTicket extends PromptContext {
async execute({ id }: Record<string, string>): Promise<GetPromptResult> {
const ticket = this.get(TicketStore).find(id);
return {
messages: [
{
role: "user",
content: {
type: "resource",
resource: { uri: `ticket://${id}`, mimeType: "application/json", text: JSON.stringify(ticket) },
},
},
{ role: "user", content: { type: "text", text: "Summarize this ticket in three bullet points for the next agent." } },
],
};
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Rejecting bad arguments
Arguments are free text, so a prompt can't declare a format the way a tool's schema can. Check the values in execute() and throw a PublicMcpError with a message the user can act on. The client gets a JSON-RPC error, code -32602, with your message.
import { Prompt, PromptContext, PublicMcpError, type GetPromptResult } from "@frontmcp/sdk";
const tickets = [{ id: "T-1", title: "Cannot log in", status: "open" }];
@Prompt({
name: "summarize_ticket",
description: "Summarize a ticket for a handover",
arguments: [{ name: "id", description: "Ticket id, like T-1", required: true }],
})
export class SummarizeTicket extends PromptContext {
async execute({ id }: Record<string, string>): Promise<GetPromptResult> {
const ticket = tickets.find((t) => t.id === id);
if (!ticket) throw new PublicMcpError(`There's no ticket ${id}. Ticket ids look like T-1.`);
return { messages: [{ role: "user", content: { type: "text", text: `Summarize: ${ticket.title} (${ticket.status})` } }] };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Writing a prompt as a function
For a prompt that doesn't need providers, the function form is shorter. It takes the same options, and the handler receives the arguments.
import { prompt } from "@frontmcp/sdk";
export const escalate = prompt({
name: "escalate",
description: "Write an escalation note for the on-call engineer",
arguments: [{ name: "id", description: "Ticket id", required: true }],
})((args) => ({
messages: [
{ role: "user", content: { type: "text", text: `Write a two-line escalation note for ticket ${args?.id}: impact first, then what was tried.` } },
],
}));Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Troubleshooting
Missing required argument: id
The client asked for the prompt without a value for an argument marked required: true, so execute() didn't run. The response is a JSON-RPC error with code -32602. If users often skip the argument, say more about it in its description, or make it optional and use a default in execute().
Prompt not found: triage_ticket
No prompt with that name is registered on the server, and the response is a JSON-RPC error with code -32602. Check, in order:
- The class is in its app's
promptsarray (nottools), and the app is in@FrontMcp({ apps }).@FrontMcphas nopromptsoption; one there is silently ignored. - The name matches exactly, including case.
- The prompt's
availableWhen.surface, if it has one, includes"mcp". MCP clients get this error for a prompt that leaves it out.
The plain name gets another app's prompt
Two apps declare a prompt with the same name. prompts/list shows them as <app id>:<name>, like desk:triage_ticket and billing:triage_ticket, and prompts/get finds each by that name. The plain name, triage_ticket, gets the first app's prompt. Use the names prompts/list gives:
import { App, FrontMcp, Prompt, PromptContext, type GetPromptResult } from "@frontmcp/sdk";
@Prompt({ name: "triage_ticket", arguments: [] })
class DeskTriage extends PromptContext {
async execute(): Promise<GetPromptResult> {
return { messages: [{ role: "user", content: { type: "text", text: "Triage the newest support ticket." } }] };
}
}
@Prompt({ name: "triage_ticket", arguments: [] })
class BillingTriage extends PromptContext {
async execute(): Promise<GetPromptResult> {
return { messages: [{ role: "user", content: { type: "text", text: "Triage the newest billing dispute." } }] };
}
}
@App({ id: "desk", name: "Help Desk", prompts: [DeskTriage] })
class DeskApp {}
@App({ id: "billing", name: "Billing", prompts: [BillingTriage] })
class BillingApp {}
@FrontMcp({ info: { name: "help-desk", version: "1.0.0" }, apps: [DeskApp, BillingApp] })
export default class Server {}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Give prompts names that are unique across the whole server, like triage_billing_dispute, so clients don't need the prefix. (Changed in 1.9.2: before, prompts/get answered Prompt not found: billing:triage_ticket, and the second app's prompt couldn't be reached.)
Prompt "…" is not available in the current environment
The prompt's availableWhen doesn't match where the server runs. It's left out of prompts/list, and prompts/get fails with a JSON-RPC error with code -32003, whose message says what the prompt requires and what the server is:
import { Prompt, PromptContext } from "@frontmcp/sdk";
@Prompt({ name: "deploy_checklist", description: "Walk through a Deno Deploy release", availableWhen: { runtime: ["deno"] } })
export class DeployChecklist extends PromptContext {
async execute() {
return "List what to check before running deployctl.";
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
The message lists the server's OS, runtime and deployment, in production too. If the prompt should be offered here, fix its availableWhen.
Prompt execution failed: Prompt output not found
execute() finished without returning anything, or returned undefined. Here a lookup misses for every ticket but T-1:
import { Prompt, PromptContext } from "@frontmcp/sdk";
const summaries: Record<string, string> = { "T-1": "Summarize ticket T-1, our oldest." };
@Prompt({ name: "summarize_ticket", arguments: [{ name: "id", required: true }] })
export class SummarizeTicket extends PromptContext {
async execute({ id }: Record<string, string>) {
return summaries[id]; // 🚩 undefined for T-2
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Return a result on every path:
// ✅ Every id gets a message
return summaries[id] ?? `Summarize ticket ${id}.`;
Before 1.9.3, a prompt that called this.respond() failed with this error too. It now ends the request with the value it's given.
Tool output validation failed, from a prompt
A message has a role other than "user" or "assistant", usually "system". MCP prompts have no system role, so FrontMCP rejects the result. The client gets error -32603, with INVALID_OUTPUT as the code in its data, and a message that says "Tool" even though this is a prompt. What failed validation is only in the server log:
import { Prompt, PromptContext } from "@frontmcp/sdk";
@Prompt({ name: "support_persona" })
export class SupportPersona extends PromptContext {
async execute() {
return { messages: [{ role: "system", content: { type: "text", text: "You are a patient support agent." } }] };
}
}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Put instructions in the first user message instead. Declaring execute() as returning Promise<GetPromptResult> catches this while you write it: TypeScript only accepts "user" and "assistant" there.
TypeScript says Property 'execute' in type '…' is not assignable to the same property in base type 'PromptContext'
execute() can finish without returning anything, usually because a code path has no return. It must return a string, an array or an object, or throw, on every path:
// 🚩 Promise<void> on the path where the ticket exists
async execute({ id }: Record<string, string>) {
if (!tickets.has(id)) throw new PublicMcpError(`There's no ticket ${id}.`);
}
// ✅ Every path returns or throws
async execute({ id }: Record<string, string>) {
if (!tickets.has(id)) throw new PublicMcpError(`There's no ticket ${id}.`);
return `Summarize ticket ${id}.`;
}