---
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'
[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]"
```
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
]
```
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