---
title: Prefab Apps
sidebarTitle: Prefab Apps
description: Build interactive tool UIs in pure Python — no HTML or JavaScript required.
icon: palette
tag: SOON
---

import { VersionBadge } from '/snippets/version-badge.mdx'

<VersionBadge version="3.1.0" />

[Prefab UI](https://prefab.prefect.io) is a declarative UI framework for Python. You describe what your interface should look like — a chart, a table, a form — and return it from your tool. FastMCP takes care of everything else: registering the renderer, wiring the protocol metadata, and delivering the component tree to the host.

Prefab started as a component library inside FastMCP and grew into a full framework for building interactive applications — with its own state management, reactive expression system, and action model. The [Prefab documentation](https://prefab.prefect.io) covers all of this in depth. This page focuses on the FastMCP integration: what you return from a tool, and what FastMCP does with it.

```bash
pip install "fastmcp[apps]"
```

<Tip>
Prefab UI is in active early development and its API changes frequently. We strongly recommend pinning `prefab-ui` to a specific version in your project's dependencies. Installing `fastmcp[apps]` pulls in `prefab-ui` but won't pin it — so a routine `pip install --upgrade` could introduce breaking changes.

```toml
# pyproject.toml
dependencies = [
    "fastmcp[apps]",
    "prefab-ui==0.8.0",  # pin to a known working version
]
```
</Tip>

Here's the simplest possible Prefab App — a tool that returns a bar chart:

```python
from prefab_ui.components import Column, Heading, BarChart, ChartSeries
from prefab_ui.app import PrefabApp
from fastmcp import FastMCP

mcp = FastMCP("Dashboard")


@mcp.tool(app=True)
def revenue_chart(year: int) -> PrefabApp:
    """Show annual revenue as an interactive bar chart."""
    data = [
        {"quarter": "Q1", "revenue": 42000},
        {"quarter": "Q2", "revenue": 51000},
        {"quarter": "Q3", "revenue": 47000},
        {"quarter": "Q4", "revenue": 63000},
    ]

    with Column(gap=4, css_class="p-6") as view:
        Heading(f"{year} Revenue")
        BarChart(
            data=data,
            series=[ChartSeries(data_key="revenue", label="Revenue")],
            x_axis="quarter",
        )

    return PrefabApp(view=view)
```

That's it — you declare a layout using Python's `with` statement, and return it. When the host calls this tool, the user sees an interactive bar chart instead of a JSON blob. The [Patterns](/apps/patterns) page has more examples: area charts, data tables, forms, status dashboards, and more.

## What You Return

### Components

The simplest way to get started. If you're returning a visual representation of data and don't need Prefab's more advanced features like initial state or stylesheets, just return the components directly. FastMCP wraps them in a `PrefabApp` automatically:

```python
from prefab_ui.components import Column, Heading, Badge
from fastmcp import FastMCP

mcp = FastMCP("Status")


@mcp.tool(app=True)
def status_badge() -> Column:
    """Show system status."""
    with Column(gap=2) as view:
        Heading("All Systems Operational")
        Badge("Healthy", variant="success")
    return view
```

Want a chart? Return a chart. Want a table? Return a table. FastMCP handles the wiring.

### PrefabApp

When you need more control — setting initial state values that components can read and react to, or configuring the rendering engine — return a `PrefabApp` explicitly:

```python
from prefab_ui.components import Column, Heading, Text, Button, If, Badge
from prefab_ui.actions import ToggleState
from prefab_ui.app import PrefabApp
from fastmcp import FastMCP

mcp = FastMCP("Demo")


@mcp.tool(app=True)
def toggle_demo() -> PrefabApp:
    """Interactive toggle with state."""
    with Column(gap=4, css_class="p-6") as view:
        Button("Toggle", on_click=ToggleState("show"))
        with If("{{ show }}"):
            Badge("Visible!", variant="success")

    return PrefabApp(view=view, state={"show": False})
```

The `state` dict provides the initial values. Components reference state with `{{ expression }}` templates. State mutations like `ToggleState` happen entirely in the browser — no server round-trip. The [Prefab state guide](https://prefab.prefect.io/docs/concepts/state) covers this in detail.

### ToolResult

Every tool result has two audiences: the renderer (which displays the UI) and the LLM (which reads the text content to understand what happened). By default, Prefab Apps send `"[Rendered Prefab UI]"` as the text content, which tells the LLM almost nothing.

If you want the LLM to understand the result — so it can reference the data in conversation, summarize it, or decide what to do next — wrap your return in a `ToolResult` with a meaningful `content` string:

```python
from prefab_ui.components import Column, Heading, BarChart, ChartSeries
from prefab_ui.app import PrefabApp
from fastmcp import FastMCP
from fastmcp.tools import ToolResult

mcp = FastMCP("Sales")


@mcp.tool(app=True)
def sales_overview(year: int) -> ToolResult:
    """Show sales data visually and summarize for the model."""
    data = get_sales_data(year)
    total = sum(row["revenue"] for row in data)

    with Column(gap=4, css_class="p-6") as view:
        Heading("Sales Overview")
        BarChart(data=data, series=[ChartSeries(data_key="revenue")])

    return ToolResult(
        content=f"Total revenue for {year}: ${total:,} across {len(data)} quarters",
        structured_content=view,
    )
```

The user sees the chart. The LLM sees `"Total revenue for 2025: $203,000 across 4 quarters"` and can reason about it.

## Type Inference

If your tool's return type annotation is a Prefab type — `PrefabApp`, `Component`, or their `Optional` variants — FastMCP detects this and enables app rendering automatically:

```python
@mcp.tool
def greet(name: str) -> PrefabApp:
    return PrefabApp(view=Heading(f"Hello, {name}!"))
```

This is equivalent to `@mcp.tool(app=True)`. Explicit `app=True` is recommended for clarity, and is required when the return type doesn't reveal a Prefab type (e.g., `-> ToolResult`).

## How It Works

Behind the scenes, when a tool returns a Prefab component or `PrefabApp`, FastMCP:

1. **Registers a shared renderer** — a `ui://prefab/renderer.html` resource containing the JavaScript rendering engine, fetched once by the host and reused across all your Prefab tools.
2. **Wires the tool metadata** — so the host knows to load the renderer iframe when displaying the tool result.
3. **Serializes the component tree** — your Python components become `structuredContent` on the tool result, which the renderer interprets and displays.

None of this requires any configuration. The `app=True` flag (or type inference) is the only thing you need.

## Mixing with Custom HTML Apps

Prefab tools and [custom HTML tools](/apps/low-level) coexist in the same server. Prefab tools share a single renderer resource; custom tools point to their own. Both use the same MCP Apps protocol:

```python
from fastmcp.server.apps import AppConfig

@mcp.tool(app=True)
def team_directory() -> PrefabApp:
    ...

@mcp.tool(app=AppConfig(resource_uri="ui://my-app/map.html"))
def map_view() -> str:
    ...
```

## Next Steps

- **[Patterns](/apps/patterns)** — Charts, tables, forms, and other common tool UIs
- **[Custom HTML Apps](/apps/low-level)** — When you need your own HTML, CSS, and JavaScript
- **[Prefab UI Docs](https://prefab.prefect.io)** — Components, state, expressions, and actions
