mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-20 12:34:17 +02:00
* Add prefab auto-wiring for MCP Apps (#3119) Tools that return prefab types (UIResponse, Component) automatically get wired to the shared prefab renderer resource. Works via app=True, return type inference, or both. * Prefab compatibility updates * Use published prefab-ui >=0.6.0, remove local source override * Migrate UIResponse to PrefabApp for Prefab UI integration PrefabApp is a pure data object with to_json(), html(), and csp() methods. Tools can return PrefabApp, bare Components, or ToolResult with structured_content for custom LLM fallback text. * Add Prefab UI apps documentation * Add mini apps and full apps documentation pages Mini apps covers the common single-screen patterns: charts (bar, line, area, pie), data tables with sorting/search/pagination, forms (manual and Pydantic-generated), status displays, conditional content, and layout composition with tabs and accordions. Full apps covers multi-page applications using Pages/Page components, shared state across pages, and using ToolCall with result_key for server-driven state updates. * Reframe apps docs around motivation, add generative UIs page The docs now lead with the problem — MCP tools stuff data into the LLM context window, and building HTML/JS/CSS frontends is a non-starter for Python developers — before introducing Prefab as the solution. Mini apps are framed as the primary use case: focused, single-purpose UIs that present data visually and collect structured input. New generative UIs page covers the concept of LLMs producing component JSON directly, enabling adaptive dashboards, tailored forms, and exploratory workflows. * Tag Prefab docs pages as SOON instead of NEW * Rename Low-Level API to Custom HTML Apps The page is about using the MCP Apps extension directly, not a FastMCP or Prefab internal API. Reframed to make clear this is the open MCP protocol with FastMCP providing convenience wrappers. * Tighten apps docs and widen content area Strip editorial motivation from all app doc pages — let code examples do the talking. Add content-area max-width override (44rem) to style.css. * Restructure apps docs, fix code issues Rename Prefab UI → Prefab Apps, mini-apps → patterns, remove generative-uis and full-apps pages. Rewrite prefab page to lead with what users do (declare a UI, return it) before explaining internals. Patterns page now has fully self-contained copy-pasteable examples with explicit imports and links to prefab docs. Forms show the two-tool pattern (form + handler). Add patterns_server.py example. Code fixes: move get_args to module-level import, remove dead AuthCheckCallable type alias, fix ToolCall→CallTool in all docs. * Remove unused ToolResult import from chart_server * Handle composite Prefab types in type inference and schema suppression _has_prefab_return_type and the output schema suppression logic only checked bare classes, missing unions (Column | None) and Annotated wrappers (Annotated[PrefabApp | None, ...]). Recurse through Union, types.UnionType, and Annotated to detect Prefab types in composite annotations.
191 lines
7.4 KiB
Text
191 lines
7.4 KiB
Text
---
|
|
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
|