mctxdocs
Build Your MCP Server

Prompts

Reusable conversation templates the MCP client can hydrate with variables. Learn handler signatures, the conversation() builder, and how to inject user context.

Need help? Connect help.mctx.ai for instant answers.

A prompt is a reusable conversation template the MCP client can hydrate with variables and use to start a conversation. Users invoke prompts explicitly — through slash commands or menu options in their client — making them a good fit for recurring workflows like code reviews, debugging sessions, or report generation.

Register a prompt with server.prompt(name, handler):

import { createServer, T } from "@mctx-ai/mcp";

const server = createServer();

const codeReview = (args) =>
  `Review this ${args.language} code for bugs, performance issues, and style:\n\n${args.code}`;

codeReview.description = "Review code for quality issues";
codeReview.input = {
  code: T.string({ required: true, description: "Code to review" }),
  language: T.string({ default: "JavaScript", description: "Programming language" }),
};

server.prompt("code-review", codeReview);

export default { fetch: server.fetch };

Handler signature

handler(args, ask?, ctx?) => string | ConversationResult | Message[] | Promise<...>
ParameterTypeNotes
argsRecord<string, any>Validated input from the client. Shape matches your .input definition.
askAskFunction | nullLLM sampling function. null when the client doesn't support sampling. Always guard before calling.
ctxMcpContextRequest context. Contains ctx.userId — the authenticated subscriber identity injected by the mctx platform.

The handler returns one of:

  • string — becomes a single user message
  • Message[] — an array of MCP message objects with explicit roles
  • ConversationResult — the output of conversation(), which wraps a Message[]

Handler properties

PropertyTypeNotes
.descriptionstringWhat this prompt does — shown to the client
.inputRecord<string, SchemaDefinition>Input schema built with T. Same system as tools.

The conversation() builder

For multi-message prompts, use the conversation() builder. It gives you user and ai role helpers to construct a structured dialogue:

import { conversation } from "@mctx-ai/mcp";

const debugSession = (args) =>
  conversation(({ user, ai }) => [
    user.say(`I am seeing this error:\n\n${args.error}`),
    ...(args.stackTrace ? [user.say(`Stack trace:\n${args.stackTrace}`)] : []),
    ai.say("I will analyze the error and provide step-by-step debugging guidance."),
  ]);

debugSession.description = "Start a guided debugging session";
debugSession.input = {
  error: T.string({ required: true, description: "Error message or description" }),
  stackTrace: T.string({ description: "Full stack trace if available" }),
};

server.prompt("debug", debugSession);

Role helpers on user and ai:

MethodWhat it produces
.say(text)A text message from this role
.attach(data, mimeType)An image message from this role (base64 data)
.embed(uri)A resource embed from this role — the client resolves the URI

Pre-filling an ai turn seeds the conversation with context or a persona before the LLM responds. This is useful for few-shot examples or role-setting.

Use cases

Single-message prompts — a string return works for simple template injection: code review requests, report starters, task descriptions.

Multi-message prompts with few-shot examples — use conversation() to show the LLM how to respond before it generates:

const sqlHelper = (args) =>
  conversation(({ user, ai }) => [
    user.say("Convert this to SQL: show me all users who signed up last week"),
    ai.say("SELECT * FROM users WHERE created_at >= NOW() - INTERVAL 7 DAY;"),
    user.say(`Now convert this: ${args.request}`),
  ]);

Persona templates — seed an ai turn to establish a role:

const techWriter = (args) =>
  conversation(({ user, ai }) => [
    ai.say("I am a technical writer. I turn complex topics into clear, concise documentation."),
    user.say(`Write documentation for: ${args.topic}`),
  ]);

Injecting ctx.userId

ctx.userId is the authenticated subscriber identity the mctx platform injects on every request. Use it to personalize a prompt for the calling user:

const myStandup = (args, ask, ctx) => {
  const userId = ctx?.userId;
  const greeting = userId
    ? `User ${userId}, here is your standup template:`
    : "Here is your standup template:";

  return conversation(({ user }) => [
    user.say(
      `${greeting}\n\n` +
        `Yesterday: ${args.yesterday}\n` +
        `Today: ${args.today}\n` +
        `Blockers: ${args.blockers || "None"}`,
    ),
  ]);
};

myStandup.description = "Generate a personalized daily standup summary";
myStandup.input = {
  yesterday: T.string({ required: true, description: "What you did yesterday" }),
  today: T.string({ required: true, description: "What you plan to do today" }),
  blockers: T.string({ description: "Anything blocking your progress" }),
};

server.prompt("my-standup", myStandup);

ctx may be undefined when running outside the mctx platform (local dev, HTTP tests). Always guard with optional chaining before accessing ctx.userId.


See also


See something wrong? Report it or suggest an improvement — your feedback helps make these docs better.