---
title: Apps
sidebarTitle: Overview
description: Give your tools interactive UIs rendered directly in the conversation.
icon: grid-2
tag: NEW
---

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

<VersionBadge version="3.0.0" />

MCP Apps let tools return interactive UIs — charts, sortable tables, forms, dashboards — rendered in a sandboxed iframe inside the host client's conversation.

FastMCP implements the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) and gives you two ways to build interactive UIs with [Prefab](https://prefab.prefect.io), depending on how much server-side interaction you need.

<Note>
The examples throughout the Apps docs require the `apps` extra:

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

This installs [Prefab UI](https://prefab.prefect.io), the component library used to build app UIs. Pin `prefab-ui` to a specific version in production — it's in early development and its API changes frequently.
</Note>

## Prefab Apps

<VersionBadge version="3.1.0" />

The quickest way to give a tool a visual UI. You return a [Prefab](https://prefab.prefect.io) component or `PrefabApp` from an otherwise standard MCP tool, and when the host calls it, the app is rendered instead of plain text:

```python
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column, Heading, BarChart, ChartSeries
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)
```

Prefab apps aren't limited to static displays. Prefab's state system and client-side actions (toggles, tabs, conditionals) all work. You can even call other tools from the UI using `CallTool` with string names. There's no hard wall on what a Prefab app can do.

See [Prefab Apps](/apps/prefab) for the full guide.

## FastMCPApp

<VersionBadge version="3.2.0" />

When your app has a lot of server-side interaction — forms that save data, search that queries a database, multi-step workflows — managing the connection between UI and backend tools gets complicated fast. Which tools should the model see vs. only the UI? What happens to tool references when servers are composed under namespaces? How do you keep `CallTool("save_contact")` working when the tool name changes?

`FastMCPApp` is a class that solves these problems. It gives you two decorators that work together:

- **`@app.ui()`** — entry-point tools the model calls to open the app
- **`@app.tool()`** — backend tools the UI calls via `CallTool`

Backend tools get stable global identifiers that survive namespacing, visibility is managed automatically (the model sees entry points, the UI sees backends), and `CallTool` accepts function references instead of string names:

```python
from prefab_ui.actions import SetState, ShowToast
from prefab_ui.actions.mcp import CallTool
from prefab_ui.app import PrefabApp
from prefab_ui.components import (
    Column, Heading, Form, Input, Button, ForEach, Row, Text, Badge, Separator,
)
from prefab_ui.rx import RESULT
from fastmcp import FastMCP, FastMCPApp

app = FastMCPApp("Contacts")


@app.tool()
def save_contact(name: str, email: str) -> list[dict]:
    """Save a contact and return the updated list."""
    db.append({"name": name, "email": email})
    return list(db)


@app.ui()
def contact_manager() -> PrefabApp:
    """Open the contact manager."""
    with Column(gap=6, css_class="p-6") as view:
        Heading("Contacts")
        with ForEach("contacts") as contact:
            with Row(gap=2):
                Text(contact.name)
                Badge(contact.email)
        Separator()
        with Form(
            on_submit=CallTool(
                "save_contact",
                on_success=[
                    SetState("contacts", RESULT),
                    ShowToast("Saved!", variant="success"),
                ],
            )
        ):
            Input(name="name", label="Name", required=True)
            Input(name="email", label="Email", required=True)
            Button("Save")

    return PrefabApp(view=view, state={"contacts": list(db)})


mcp = FastMCP("Server", providers=[app])
```

You *can* build server-interactive UIs without `FastMCPApp` — it's all the same protocol underneath. But once you have multiple tools, composition concerns, or visibility requirements, `FastMCPApp` handles the complexity so you don't have to.

See [FastMCPApp](/apps/interactive-apps) for the full guide.

## Which Approach?

| Scenario | Approach |
| -------- | -------- |
| Visual output — charts, tables, status dashboards | [Prefab app](/apps/prefab) — `@mcp.tool(app=True)` |
| Client-side interactivity — toggles, tabs, conditionals | [Prefab app](/apps/prefab) — state + `Rx()` |
| Light server interaction — one or two tool calls | [Prefab app](/apps/prefab) — `CallTool("tool_name")` |
| Heavy server interaction — forms, CRUD, search, multi-step | [FastMCPApp](/apps/interactive-apps) — managed tool binding |
| Composed servers — apps mounted under namespaces | [FastMCPApp](/apps/interactive-apps) — stable global keys |
| Custom rendering — maps, 3D, specific JS frameworks | [Custom HTML](/apps/low-level) — raw MCP Apps extension |

The boundary isn't sharp. Start with a Prefab app; graduate to `FastMCPApp` when the tool-management complexity justifies it.

## Custom HTML Apps

Both approaches above use [Prefab UI](https://prefab.prefect.io) to build UIs in pure Python. If you need full control — your own HTML, CSS, JavaScript, a specific framework — you can use the [MCP Apps extension directly](/apps/low-level). You write the HTML yourself and communicate with the host via the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) SDK.

## Previewing Apps Locally

The `fastmcp dev apps` command launches a browser-based preview for your app tools — no MCP host client needed. See [Development](/apps/development).

```bash
fastmcp dev apps server.py
```
