Prompts are user-controlled message templates. Unlike tools (which the LLM calls), prompts are selected by users and rendered into conversation messages.
Prompts flow through the MCP protocol like this:
- A client discovers prompts via
prompts/list (each prompt may include arguments and metadata).
- A client renders a prompt via
prompts/get with string-valued arguments.
- The server executes your prompt renderer.
- The server returns a
GetPromptResult containing messages (and optional description) per MCP spec.
Define prompts with @prompt(...) and register them with server.collect(...) (or inside with server.binding(): ...).
Decorator signature
prompt(...): prompt(name: str, *, description=None, title=None, arguments=None, icons=None, meta=None)
Your renderer is called like:
fn(arguments: dict[str, str] | None) → returns messages / mapping / GetPromptResult / None
Basic prompt
from dedalus_mcp import MCPServer, prompt, types
@prompt(
"code-review",
description="Review code for issues",
arguments=[
types.PromptArgument(name="language", required=False),
types.PromptArgument(name="focus", required=False),
],
)
def code_review(arguments: dict[str, str] | None):
args = arguments or {}
language = args.get("language", "python")
focus = args.get("focus")
focus_text = f" Focus on: {focus}." if focus else ""
return [
("assistant", "You are a senior code reviewer."),
("user", f"Review the following {language} code.{focus_text}"),
]
server = MCPServer("assistant")
server.collect(code_review)
The description tells the client/LLM what the prompt is for. arguments=[...] defines what the client should pass to prompts/get.
Arguments (required vs optional)
Dedalus MCP treats arguments as required only if you mark them required=True in the decorator’s arguments=[...].
from dedalus_mcp import MCPServer, prompt, types
@prompt(
"translate",
description="Translate text",
arguments=[
types.PromptArgument(name="text", required=True),
types.PromptArgument(name="target_lang", required=False),
],
)
def translate(arguments: dict[str, str] | None):
args = arguments or {}
text = args["text"]
target = args.get("target_lang", "English")
return [("user", f"Translate this to {target}: {text}")]
server = MCPServer("assistant")
server.collect(translate)
If required args are missing, Dedalus raises an MCP error with INVALID_PARAMS.
Complex argument values (lists/dicts)
MCP prompt arguments are strings. If you need structured values, pass JSON strings and parse them yourself:
import json
from dedalus_mcp import prompt, types
@prompt(
"summarize",
description="Summarize a document",
arguments=[types.PromptArgument(name="focus_areas_json", required=False)],
)
def summarize(arguments: dict[str, str] | None):
args = arguments or {}
focus_areas = json.loads(args.get("focus_areas_json", "[]"))
return [("user", f"Summarize this document. Focus on: {focus_areas}")]
Prompt renderers can return:
- A list/iterable of messages, where each item can be:
- a
(role, content) tuple
- a mapping like
{"role": "...", "content": ...}
- a
PromptMessage instance
- A mapping with explicit control:
- required:
"messages"
- optional:
"description"
GetPromptResult
None (produces zero messages)
Not supported: returning a raw str (Dedalus raises TypeError so you always provide role + content).
Explicit control (mapping)
from dedalus_mcp import prompt
@prompt("status", description="Daily status template")
def status(arguments: dict[str, str] | None):
return {
"description": "Status template",
"messages": [
("assistant", "You summarize daily status reports."),
("user", "Write yesterday/today/blockers."),
],
}
Message content
For message content, you can use:
- a
str (auto-coerced to text content)
- a full content-block mapping (e.g.
{"type": "text", "text": "..."})
- a content-block instance from
dedalus_mcp.types (e.g. TextContent, ImageContent, etc.)
Async prompts
from dedalus_mcp import prompt
@prompt("db-summary", description="Summarize database state")
async def db_summary(arguments: dict[str, str] | None):
# await fetch_db_stats(...)
return [
("assistant", "You analyze database metrics."),
("user", "Summarize current DB health and recent anomalies."),
]
Prefer async def for I/O.
Decorator options
from dedalus_mcp import prompt
@prompt(
"analyze",
description="Analyze code",
title="Code Analysis",
icons=[{"type": "url", "url": "https://example.com/icon.png"}],
meta={"category": "code"},
arguments=[{"name": "language", "required": False}],
)
def analyze(arguments: dict[str, str] | None):
language = (arguments or {}).get("language", "python")
return [("user", f"Analyze this {language} code for bugs, style, and security.")]
Context access
If a prompt is rendered during an MCP request, it can access context via get_context() (for logging, progress, etc.).
Note: get_context() only works inside an active MCP request handler; calling it outside a request raises LookupError.
from dedalus_mcp import prompt, get_context
@prompt("generate-report", description="Generate a report request")
async def generate_report(arguments: dict[str, str] | None):
ctx = get_context()
await ctx.info("Rendering prompt", data={"args": arguments or {}})
return [("user", "Generate a concise weekly report for this project.")]
Testing
Test prompt renderers like normal functions:
def test_code_review_prompt():
msgs = code_review({"language": "python", "focus": "error handling"})
assert len(msgs) == 2
Integration-style test via the server API (mirrors prompts/get):
import pytest
from dedalus_mcp import MCPServer
@pytest.mark.asyncio
async def test_prompt_rendering():
server = MCPServer("test")
server.collect(code_review)
result = await server.invoke_prompt("code-review", arguments={"language": "rust"})
assert result.messages
Last modified on January 27, 2026