PyFu

FastMCP Prompts

Python Web Development Frameworks

Prompts are reusable prompt templates the server offers to clients. A prompt is a function decorated with @mcp.prompt that returns the text (or message list) the client should send to its model; the client lists prompts with prompts/list and fetches a rendered one with prompts/get, passing any arguments the prompt declares.

from fastmcp import FastMCP

mcp = FastMCP("internal-tools")

@mcp.prompt
def review(code: str) -> str:
    return f"Review the following code for bugs:\n\n{code}"

The point of a server-side prompt is to centralize prompt engineering: the client asks for review with some code, and the server returns the assembled prompt text.

Why prompts matter from an offensive security perspective

Prompts are templates that get built from arguments, which puts them on the same footing as every other template in this handbook. Two angles matter.

The first is disclosure. prompts/list and prompts/get reveal the server’s prompt logic: the system instructions, the guardrail wording, the internal context the operator bakes into requests. That is intelligence an attacker uses to craft injections against the wider system, and it is readable before any tool runs. On a no-auth server it is free reconnaissance, the prompt-template equivalent of the resource enumeration in FastMCP Resources.

The second is template injection. If a prompt is assembled by interpolating untrusted values into a template, the danger depends on how it is rendered. A plain f-string like the example merely places attacker text inside the prompt, which is classic prompt injection: the code argument can carry instructions that hijack the downstream model, the MCP-delivered version of Prompt Injection in Python LLM Backends. The more serious case is when the prompt body is compiled by a template engine. If user input reaches the source of a Jinja2 template rather than being passed as a rendering variable, the result is server-side template injection that executes Python on the server, not just on the model, which is exactly the chain in Server Side Template Injection (SSTI) in AI Prompt Templates.

When you assess a server, read every prompt for leaked internal logic, then trace each prompt argument to see whether it lands inside a rendered string (prompt injection) or inside the template source itself (SSTI, and potential code execution). As with tools and resources, there is no per-prompt authorization, so anything the transport exposes is fetchable; see Running a FastMCP Server and Unauthenticated FastMCP Servers.

Mitigation

Never let a prompt argument reach the source of a template engine; pass it only as a rendering variable so the worst case is text in a prompt, not code on the server. Build prompt bodies from fixed, developer-authored strings and interpolate untrusted arguments as data, and if you must use a template engine, render a constant template with the argument supplied as a context variable rather than concatenating it into the template string. Keep sensitive system instructions and guardrail wording out of prompts that untrusted callers can fetch, and require authentication on any network transport so prompt enumeration is not handed to anyone who reaches the port. This collapses the SSTI risk to ordinary Prompt Injection in Python LLM Backends, which you then handle downstream rather than on the server.

@mcp.prompt
def review(code: str) -> str:
    # `code` is data placed into a fixed prompt, never the template source.
    return f"Review the following code for bugs:\n\n{code}"