@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.

triage-ticket.prompt.ts
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.` } },
      ],
    };
  }
}

See more examples below.

Options

Required:

OptionTypeDescription
namestringWhat clients list and ask for with prompts/get. Unique within the server.

Optional:

OptionTypeDescription
argumentsPromptArgument[]The values the user fills in. Leave it out for a prompt without arguments.
titlestringA readable name for menus, like "Triage a ticket". Clients fall back to name without it.
descriptionstringWhat the prompt is for. Also becomes the result's description when execute() doesn't return one.
iconsIcon[]Icons clients can show next to the prompt. Each has a src, and optionally mimeType, sizes and theme ("light" or "dark").
availableWhenEntryAvailabilityOnly 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:

FieldTypeDescription
namestringThe key the value arrives under in args.
descriptionstringShown to the user filling it in.
requiredbooleanDefaults 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 as this.args.
  • Returns the prompt's messages. FrontMCP turns what execute() returns into the prompts/get result:
execute() returnsThe 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 stringOne user message with that text.
An array of { role, content } messagesThose messages, as if you had returned { messages }.
Any other objectOne 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 @Prompt on 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 a PublicMcpError (or a subclass, like InvalidInputError), 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, and Prompt execution failed: followed by its message in development. With NODE_ENV=production, FrontMCP hides the message and sends Internal FrontMCP error. Please contact support with error ID: … instead.
  • this.fail(error) fails the request the same way as throw error for a PublicMcpError. For any other error it's still -32603, but the message is your error's message without the Prompt execution failed: prefix, and data.code is SERVER_ERROR instead of PROMPT_EXECUTION_FAILED. Production hides it the same way.
  • this.respond(value) ends execute() as return value would. (Changed in 1.9.3: before, the request failed with Prompt output not found.)
  • Declaring execute() as returning Promise<GetPromptResult> is optional. With it, TypeScript checks each message's role and content as 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.

Open
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.

Open
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.

Open
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.

Open
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.

Open
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.

Open
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:

  1. The class is in its app's prompts array (not tools), and the app is in @FrontMcp({ apps }). @FrontMcp has no prompts option; one there is silently ignored.
  2. The name matches exactly, including case.
  3. 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:

Open
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:

Open
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:

Open
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:

Open
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}.`;
}