Skip to content

MCP Prompts

MCP prompts are reusable, operator-authored templates that AI clients discover via prompts/list and invoke via prompts/get. Each prompt lives as an object in the mcp-prompt reserved collection. The body is a Twig template — the full cms.* function library is available alongside declared argument values.

Use prompts when you want to ship a starting workflow to an AI agent — a house-style blog scaffold, a brand-voice rewrite template, or a site-wide content audit outline. For data retrieval, use Saved-Query Tools instead.

PromptsSaved-Query Tools
ReturnsRendered text (one user-role message)Filtered collection data
AI agent uses it asA starting point or instructionA data query
Defined inmcp-prompt collectionCollection schema MCP tab
Twig in bodyYes — full cms.* availableNo
  1. In the admin, open MCP → Prompts and click Add Prompt.
  2. Fill in Name (snake_case, e.g. draft_blog_post), Description, and Body.
  3. Save. The prompt appears in prompts/list immediately — no rebuild, no restart.
Name: draft_blog_post
Description: Draft a new blog post in our house style.
Body: Write a {{ args.tone | default('professional') }} blog post titled "{{ args.title }}".
Use H2 subheadings, keep paragraphs under 5 sentences,
and end with a call to action.
Args: title (required), tone (optional)

An AI client invokes it as:

{
"method": "prompts/get",
"params": {
"name": "draft_blog_post",
"arguments": { "title": "10 Tips for Summer Photography", "tone": "conversational" }
}
}

The server renders the Twig body with the supplied arguments and returns one user-role message.

Return a structured writing brief that matches editorial standards:

You are writing for {{ cms.config('displayName') }}.
Write a {{ args.format | default('long-form') }} article titled: {{ args.title }}
House rules:
- Sentences: ≤ 25 words.
- Tone: {{ args.tone | default('professional') }}.
- Always include a tldr summary in the opening paragraph.
- End with a bulleted "Key Takeaways" section.
Begin the article now.

Args: title (required), format (optional), tone (optional).

Pull live collection data into the prompt body using cms.collection.object():

{% set post = cms.collection.object('blog', args.post_id) %}
You are editing an existing blog post.
Title: {{ post.title }}
Published: {{ post.date }}
Current body:
{{ post.body | striptags }}
Rewrite the body in a {{ args.tone | default('professional') }} tone.
Keep the same structure. Return only the rewritten body, no title.

Args: post_id (required), tone (optional).

The agent passes the post’s ID; the prompt fetches the live record and injects it. Editors can trigger a rewrite by invoking prompts/get from Claude Desktop, Cursor, or any MCP client.

Give the agent a cross-collection overview to answer high-level questions:

Site: {{ cms.config('displayName') }}
Date: {{ "now" | date("Y-m-d") }}
Content summary:
{% for collection in cms.collections %}
- {{ collection.labelPlural }}: {{ collection.count }} objects
{% endfor %}
Your task: {{ args.task }}
Focus on any content gaps, outdated objects (older than {{ args.months | default(6) }} months),
or collections with fewer than 5 objects.

Args: task (required), months (optional).

The body template receives these variables at render time:

VariableTypeNotes
args.*string (or typed if declared)Caller-supplied argument values. Only declared args are injected — undeclared keys are silently dropped.
cms.*objectFull T3 Twig adapter: cms.object(), cms.collection(), cms.config(), cms.collections, cms.env, etc.
Twig builtinsAll Twig filters and functions: date, default, striptags, upper, for, if, etc.

Arguments with required: true are validated before rendering. If a required argument is absent, the server returns a JSON-RPC error — the body template is never called.

Each prompt has an Access setting:

SettingWho can call the prompt
(inherit from target)Inherits the MCP access of the collection or Data View named in Target. A blank target defaults to admin.
Admin onlyAPI-key authenticated callers only.
AuthenticatedOAuth Bearer token callers (and admin).
PublicAnonymous AI agents (and all above).

Target accepts either, in one picker — entries are labelled with their kind, because the two are separate namespaces and an id can exist in both.

When an id does exist as both, it resolves to the collection. That order is deliberate rather than alphabetical: collections are filtered by access group, and Data Views are not — a view’s own mcp.access is its entire gate, by design. Resolving a colliding id to the collection lands on the stricter of the two models; the other order would let a view named after a collection silently widen a prompt’s reach.

That difference is worth knowing whenever you inherit from a view. authenticated on a collection means “signed-in callers whose groups grant access to it”; authenticated on a view means “any signed-in caller”, with no group filtering. If a prompt’s reach should respect access groups, target a collection or set Access explicitly.

One subtlety: whether the target exists as a collection decides which branch runs, not whether that collection sets mcp.access. A real collection with no MCP access configured resolves to admin and stops — it does not fall through to a same-named view.

Persona enforcement runs at two points:

  • prompts/list — inaccessible prompts are filtered out entirely. A public caller never sees an admin prompt in the list.
  • prompts/get — the handler re-checks access at call time. A caller who guesses an admin prompt name receives a JSON-RPC error, not the rendered content.

Arguments declared in the Arguments card field are typed and validated:

FieldNotes
NameSnake_case (^[a-z][a-z0-9_]*$). Referenced in the body as {{ args.name }}.
DescriptionShown to AI agents in the prompts/list response so they know what to pass.
RequiredToggle. Missing required args return a JSON-RPC error before the body renders.

Optional arguments with no default in the body render as an empty string when absent. Use Twig’s default filter: {{ args.tone | default('professional') }}.

Only declared argument names are injected into the template context. Extra keys passed by the caller are silently dropped.

Extensions can ship code-defined prompts alongside the collection-stored ones this page covers. They share the same prompts/list and prompts/get surface; the difference is authoring path — PHP in an extension vs. JSON object in the admin.

See Extending MCP → Registering code-defined prompts for the full guide: signature, when-to-use comparison vs. collection-stored, worked examples, access tiers, and collision policy.

The id field on the mcp-prompt schema is readonly and auto-generated from the Name you enter. It follows the snakeCase ID setting — see ID Field for details. The name field is the canonical identifier AI clients use to call the prompt; keep it stable once published.

  • Single user-role message. Each prompt returns one user-role PromptMessage. Multi-turn scaffolds (system + user) are a v2 target.
  • Synchronous render. The body is rendered inline during prompts/get. Long-running cms.* calls (e.g. fetching many objects) block the response. Keep the body lightweight.
  • No per-prompt persona override. Access is set at the prompt level. Collection inheritance is coarse-grained — there is no field-level or argument-level access control.
  • No listChanged push in v1. Clients that have called prompts/list are not notified when a new prompt is added until they re-list (e.g. on reconnect).
  • MCP Server — personas, transport, core tool catalog, collection MCP settings.
  • Saved-Query Tools — parameterised data queries; the right tool when you need filtered collection content rather than a workflow template.
  • Extending MCPregisterMcpPrompt() in the full context of the extension MCP API surface.