Prompts

Prompts are predefined interaction templates provided by the MCP server. They allow servers to guide the LLM’s behavior by suggesting a conversation structure or specific context for the user. Unlike tools, which are intended for execution, prompts are intended for context setting and templated interactions.

In mcp-cpp-sdk, prompts are registered with a name, description, and an optional set of arguments. Clients can discover these prompts and request them with specific arguments to get a list of messages to include in the conversation.

Registering a Basic Prompt

Prompts are registered with add_prompt, which is a template over the handler’s input and output types: add_prompt<In, Out>(prompt, handler). Passing nlohmann::json for both is the untyped route, and suits simple cases where you don’t need full type safety for the handler parameters or result.

// Prompt: greet (untyped JSON handler)
Prompt greet_prompt;
greet_prompt.name = "greet";
greet_prompt.description = "Greet someone by name";

PromptArgument who_arg;
who_arg.name = "who";
who_arg.description = "Name of the person to greet";
who_arg.required = true;

greet_prompt.arguments = std::vector<PromptArgument>{std::move(who_arg)};

server.add_prompt<nlohmann::json, nlohmann::json>(
    greet_prompt, [](const nlohmann::json& params) -> nlohmann::json {
        // params is the whole GetPromptRequestParams, not just the arguments:
        // {"name":"greet","arguments":{"who":"world"}}
        const auto arguments = params.value("arguments", nlohmann::json::object());
        const auto who = arguments.value("who", std::string{"world"});

        PromptMessage msg;
        msg.role = Role::eUser;
        TextContent text_content;
        text_content.text = "Say hello to " + who + ".";
        msg.content = std::move(text_content);

        GetPromptResult result;
        result.description = "Greeting prompt";
        result.messages.push_back(std::move(msg));
        return result;
    });

Warning

A prompt handler receives the entire GetPromptRequestParams, not just the arguments. For a prompts/get request naming greet with argument who, the handler is passed {"name": "greet", "arguments": {"who": "world"}}, so the argument is read as params.value("arguments", json::object()).value("who", ...). Reading params.value("who", ...) directly compiles, raises no error, and silently yields the default value.

This differs from tool handlers, which receive the tool arguments already unwrapped. A prompt carries a name its handler may need, so the whole parameter object is passed through.

The handler returns a nlohmann::json object representing the GetPromptResult.

Parameterized Prompts and Arguments

For more complex scenarios, you can use the typed add_prompt overload which utilizes the protocol’s internal structures. This approach provides better type safety and allows for more detailed metadata about prompt arguments.

Prompt arguments are defined using the PromptArgument struct, where you can specify whether an argument is required and provide a detailed description.

Prompt review_prompt;
review_prompt.name = "code_review";
review_prompt.description = "Generate a code review prompt for the given language and code";

PromptArgument lang_arg;
lang_arg.name = "language";
lang_arg.description = "Programming language";
lang_arg.required = true;

PromptArgument code_arg;
code_arg.name = "code";
code_arg.description = "The code snippet to review";
code_arg.required = true;

review_prompt.arguments = std::vector<PromptArgument>{std::move(lang_arg), std::move(code_arg)};

server.add_prompt<GetPromptRequestParams, GetPromptResult>(
    review_prompt, [](GetPromptRequestParams params) -> GetPromptResult {
        std::string language = "unknown";
        std::string code;
        if (params.arguments) {
            if (auto it = params.arguments->find("language"); it != params.arguments->end()) {
                language = it->second;
            }
            if (auto it = params.arguments->find("code"); it != params.arguments->end()) {
                code = it->second;
            }
        }

        PromptMessage msg;
        msg.role = Role::eUser;
        TextContent text_content;
        text_content.text = "Please review the following " + language + " code:\n\n```" +
                            language + "\n" + code + "\n```";
        msg.content = std::move(text_content);

        GetPromptResult result;
        result.description = "Code review prompt for " + language;
        result.messages.push_back(std::move(msg));
        return result;
    });

In this example, the code_review prompt takes two required arguments: language and code. The handler extracts these from the GetPromptRequestParams and constructs a GetPromptResult.

Structured Prompt Results

The result of a prompt is a GetPromptResult object, which primarily contains a list of PromptMessage objects. Each message has a role (e.g., user or assistant) and content.

Roles

Roles are defined by the mcp::Role enum. These roles are critical for the LLM to understand the conversation flow and distinguish between user input and previous assistant responses.

  • Role::eUser: Represents messages originating from the user. In most cases, prompt results consist primarily of user messages that set the stage for the LLM.

  • Role::eAssistant: Represents messages that were previously generated by the assistant. These are useful for providing few-shot examples or simulating a continuing conversation.

Content Blocks

The content of a message is a ContentBlock. While the most common type is TextContent, MCP supports other content types like images and resources.

Text Content

In mcp-cpp-sdk, you typically use TextContent for prompt responses. This is the standard way to provide instructions or context to the LLM.

PromptMessage msg;
msg.role = Role::eUser;
TextContent text;
text.text = "Review this code...";
msg.content = std::move(text);

Image Content

For multimodal models, you can also include images in your prompts. This is done by providing base64 encoded image data and its MIME type.

ImageContent image;
image.data = "base64_encoded_image_data";
image.mimeType = "image/png";
msg.content = std::move(image);

Resource Content

Prompts can also reference resources directly. This allows you to include dynamic content from your server’s resources in the prompt response.

EmbeddedResource resource;
resource.resource.uri = "mcp://system/config";
resource.resource.text = "System configuration data...";
msg.content = std::move(resource);

Advanced Registration

The add_prompt method is a template that supports various handler signatures, including asynchronous handlers and handlers that receive a mcp::Context.

Async Handlers

Asynchronous handlers are essential for operations that might take time, such as fetching data from an external database or performing complex computations before returning the prompt content.

server.add_prompt<GetPromptRequestParams, GetPromptResult>(
    metadata,
    [](GetPromptRequestParams params) -> mcp::Task<GetPromptResult> {
        // Perform async operations here
        co_return result;
    });

Using Context

Handlers can also receive a mcp::Context& as their first argument. This provides access to logging, progress reporting, and cancellation checks.

server.add_prompt<GetPromptRequestParams, GetPromptResult>(
    metadata,
    [](mcp::Context& ctx, GetPromptRequestParams params) -> mcp::Task<GetPromptResult> {
        co_await ctx.log_info("Generating prompt for " + params.name);
        if (ctx.is_cancelled()) {
            throw std::runtime_error("Request cancelled");
        }
        // ... logic ...
        co_return result;
    });

Prompt List Change Notifications

If your server’s available prompts change dynamically, you should notify connected clients so they can refresh their cache. Use the notify_prompts_list_changed method:

co_await server.notify_prompts_list_changed();

This sends a notifications/prompts/list_changed message to the client, prompting them to call prompts/list again if they need the updated list.

Prompt Capabilities

To enable prompts, include them in your server capabilities during initialization. If the client does not see prompt capabilities, it may not attempt to list or get prompts.

mcp::ServerCapabilities caps;
caps.prompts = mcp::ServerCapabilities::PromptsCapability{
    .listChanged = true // Enable list change notifications
};

mcp::Server server(info, std::move(caps));

By setting listChanged to true, you inform the client that your server is capable of sending notifications when the prompt list changes, allowing for a more dynamic and responsive integration.