> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dedaluslabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Prompts

> Expose reusable message templates to MCP clients with the @prompt decorator

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:

1. A client discovers prompts via `prompts/list` (each prompt may include `arguments` and metadata).
2. A client renders a prompt via `prompts/get` with **string-valued** `arguments`.
3. The server executes your prompt renderer.
4. 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

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
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=[...]`.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
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:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
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}")]
```

***

## Return formats

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)

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
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

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
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

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
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`.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
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:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
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`):

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
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
```
