Skip to main content
New in version 3.1.0
Prefab is in extremely early, active development — its API changes frequently and breaking changes can occur with any release. The FastMCP integration is equally new and under rapid development. These docs are included for users who want to work on the cutting edge; production use is not recommended. Always pin prefab-ui to a specific version in your dependencies (see below).
Prefab UI 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 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.
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.
# 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:
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 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:
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:
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 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:
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:
@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 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:
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 — Charts, tables, forms, and other common tool UIs
  • Custom HTML Apps — When you need your own HTML, CSS, and JavaScript
  • Prefab UI Docs — Components, state, expressions, and actions