Prompts & PromptController¶
Prompts are reusable, parameterized instruction templates exposed via prompts/list and prompts/get. Unlike tools (which execute side effects or computations) and resources (which return raw state), a prompt returns a structured sequence of PromptMessage objects that the client feeds into an LLM conversation.
litestar-mcp supports three ways to author prompts:
- Standalone
@mcp_promptfunctions registered viaLitestarMCP(prompts=[...]). - Route-bound
mcp_prompt=Truehandlers exposed over both HTTP andprompts/get. - Class-based
PromptControllersubclasses grouping related@promptmethods under a shared namespaceprefix, prependedinstructions, Litestarguards, and dependency injection (Provide,FromDishka[T]).
Standalone & Route-Bound Prompts¶
Use @mcp_prompt for MCP-only prompt templates, or tag an existing Litestar route handler with mcp_prompt=True (or mcp_prompt="<name>") when the prompt should also be callable as a standard HTTP endpoint:
"""Snippet: marking and registering MCP prompts. Referenced from docs/usage/marking_routes.rst."""
from litestar import Litestar, get
from litestar.params import FromQuery
from litestar_mcp import LitestarMCP, mcp_prompt
def build() -> "Litestar":
# start-example
@mcp_prompt(description="Summarise a document in a chosen style.")
async def summarize(text: "str", style: "str" = "concise") -> "str":
"""Build a summarisation prompt.
Args:
text: The document to summarise.
style: Summary style, e.g. ``concise`` or ``detailed``.
"""
return f"Summarise the following in a {style} style:\n\n{text}"
@get(
"/prompts/code-review",
mcp_prompt=True,
mcp_prompt_description="Ask the model to review a code diff.",
)
async def code_review(code: "FromQuery[str]") -> "dict[str, object]":
"""Handler-based prompt: routed under HTTP *and* exposed via ``prompts/get``."""
return {"messages": [{"role": "user", "content": {"type": "text", "text": f"Review: {code}"}}]}
app = Litestar(
route_handlers=[code_review],
plugins=[LitestarMCP(prompts=[summarize])],
)
# end-example
return app
For route-bound prompts, you can customize metadata with mcp_prompt_description, mcp_prompt_title, mcp_prompt_arguments, and mcp_prompt_icons (see the MCPOptKeys Reference Table). When arguments is omitted, litestar-mcp introspects the callable's signature, excludes DI parameters, and populates argument descriptions from the function's Google-style Args: docstring section.
Class-Based PromptController¶
When building domain-specific prompt suites or multi-agent applications, subclass PromptController and decorate methods with @prompt:
class CoffeePromptController(PromptController):
"""Prompt templates for greeting customers and authoring seasonal menus."""
prefix = "coffee"
instructions = (
"Persona: Warm, knowledgeable neighborhood specialty coffee barista.\n"
"Tone: Welcoming, concise, and focused on quality craft brewing.\n"
)
@prompt(title="Welcome Greeting", description="Greet a customer and introduce daily specials.")
def welcome_customer(self, customer_name: str = "Friend") -> str:
"""Generate a warm barista greeting."""
return f"Welcome to the coffee bar, {customer_name}! What can we craft for you today?"
@prompt(title="Tasting Notes", description="Generate tasting notes for a given roast.")
def roast_notes(self, roast_name: str) -> list[PromptMessage]:
"""Produce structured tasting notes for single-origin coffees."""
return [
PromptMessage.user(
f"Produce professional SCA-style tasting notes and brewing parameters for `{roast_name}`."
)
]
Register a PromptController either by adding it to Litestar(route_handlers=[CoffeePromptController]) or by passing it in MCPConfig(prompt_controllers=[CoffeePromptController]).
PromptController Capabilities¶
- Namespace
prefix: Settingprefix = "coffee"registers@promptmethods ascoffee/welcome_customerandcoffee/roast_notes(qualify_prompt_name). - Prepended
instructions(apply_prompt_instructions): Wheninstructionsis defined on aPromptController,prompts/getautomatically prependsf"{instructions.strip()}\n\n"to the text of the first"user"role message (or inserts a leading"user"text message if no user text message is present). - Internal Dispatch Protection: Each
@promptmethod is mounted on an internal synthetic controller path (/_litestar_mcp/<module>.<qualname>) withinclude_in_schema=Falseand protected by therequire_internal_dispatchguard so it can only be invoked through MCP internal ASGI dispatch (execute_handler), never as a public unguarded HTTP POST route. - Controller
guardsand Universal DI: BecausePromptControllersubclasseslitestar.Controller, you can declareguards = [...]anddependencies = {"key": Provide(...)}at the class level, and injectProvidedependencies or DishkaFromDishka[T]parameters directly into@promptmethods.
Return Value Normalization¶
Every prompt callable or @prompt method can return any of the following Python types, which _normalize_prompt_result converts into the canonical MCP messages list:
| Return Type | Normalized Wire Output (result.messages) |
|---|---|
str |
[{"role": "user", "content": {"type": "text", "text": "<value>"}}] |
PromptMessage |
[message.to_dict()] (use PromptMessage.user("...") or PromptMessage.assistant("...")) |
dict[str, Any] with "messages" |
Used directly as the top-level prompts/get result dictionary (with controller instructions applied to result["messages"]). |
dict[str, Any] with "role" & "content" |
Wrapped in a single-element list [result]. |
Sequence[PromptMessage] or list[dict] |
Each element is normalized in order into a PromptMessage dictionary. |
| Any other object | Coerced via str(value) into a single "user" text message. |
Capability Gating & Error Contract¶
- Capability Gating:
server/discoverandinitializeadvertise"prompts": {"listChanged": True}undercapabilitiesonly when at least one visible prompt is registered. - String-Only Wire Arguments: Per the MCP specification, all values inside
params.argumentsonprompts/getmust be strings. Passing a non-string argument value, omitting a required argument, or passing an unknown argument name raises JSON-RPCINVALID_PARAMS(-32602). - Execution Errors: If a prompt callable or route handler raises an exception or returns an HTTP error status during
prompts/get,MCPHandlerServiceraises JSON-RPCINTERNAL_ERROR(-32603) with"message": "Prompt execution failed"and preserves the HTTP status code inerror.data.statusCode.