60e0ffc959
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
198 lines
8.1 KiB
Plaintext
198 lines
8.1 KiB
Plaintext
---
|
|
title: Quickstart
|
|
sidebarTitle: Quickstart
|
|
description: Build your first FastMCP app in under a minute.
|
|
icon: rocket
|
|
tag: NEW
|
|
---
|
|
|
|
import { VersionBadge } from '/snippets/version-badge.mdx'
|
|
import { PrefabDemoFrame } from '/snippets/prefab-demo-frame.mdx'
|
|
|
|
<VersionBadge version="3.2.0" />
|
|
|
|
By the end of this page, you'll have a working tool that returns this:
|
|
|
|
<PrefabDemoFrame demo="team-directory" height="545px" title="Team directory demo" />
|
|
|
|
A pie chart the user can hover, a table they can sort and search — and a single Python tool.
|
|
|
|
## Install
|
|
|
|
```bash
|
|
pip install "fastmcp[apps]"
|
|
```
|
|
|
|
The `apps` extra pulls in [Prefab](https://prefab.prefect.io), the Python component library used to build app UIs.
|
|
|
|
## Write the tool
|
|
|
|
Create `server.py`. The interesting parts: `app=True` tells FastMCP this tool renders a UI, and `with PrefabApp() as app:` is the canonical pattern for composing one.
|
|
|
|
```python server.py expandable
|
|
from collections import Counter
|
|
|
|
from prefab_ui.app import PrefabApp
|
|
from prefab_ui.components import Column, DataTable, DataTableColumn, Grid
|
|
from prefab_ui.components.charts import PieChart
|
|
from fastmcp import FastMCP
|
|
|
|
mcp = FastMCP("My First App")
|
|
|
|
|
|
@mcp.tool(app=True)
|
|
def team_directory() -> PrefabApp:
|
|
"""Browse the team directory."""
|
|
members = [
|
|
{"name": "Alice Chen", "role": "Staff Engineer", "office": "San Francisco"},
|
|
{"name": "Bob Martinez", "role": "Lead Designer", "office": "New York"},
|
|
{"name": "Carol Johnson", "role": "Senior Engineer", "office": "London"},
|
|
{"name": "David Kim", "role": "Product Manager", "office": "San Francisco"},
|
|
{"name": "Eva Mueller", "role": "Engineer", "office": "Berlin"},
|
|
{"name": "Frank Lee", "role": "Data Scientist", "office": "San Francisco"},
|
|
{"name": "Grace Park", "role": "Engineering Manager", "office": "New York"},
|
|
]
|
|
|
|
office_counts = [
|
|
{"office": office, "count": count}
|
|
for office, count in Counter(m["office"] for m in members).items()
|
|
]
|
|
|
|
with PrefabApp() as app:
|
|
with Column(gap=4, css_class="p-6"):
|
|
with Grid(columns=[1, 2], gap=4):
|
|
PieChart(
|
|
data=office_counts,
|
|
data_key="count",
|
|
name_key="office",
|
|
show_legend=True,
|
|
)
|
|
DataTable(
|
|
columns=[
|
|
DataTableColumn(key="name", header="Name", sortable=True),
|
|
DataTableColumn(key="role", header="Role", sortable=True),
|
|
DataTableColumn(key="office", header="Office", sortable=True),
|
|
],
|
|
rows=members,
|
|
search=True,
|
|
)
|
|
|
|
return app
|
|
```
|
|
|
|
The Prefab code reads top-to-bottom. `PrefabApp()` is the root; everything inside its `with` block becomes the UI. `Column` stacks children vertically, `Grid` lays them out in columns. `DataTable` takes rows and column definitions and gives you sort and search for free.
|
|
|
|
`app=True` does the rest: it sets up the renderer resource, the content security policy, and the metadata that tells the host "this tool returns a UI." The host loads the result in a sandboxed iframe where the user can interact with it — all client-side, no round-trips.
|
|
|
|
## Preview it
|
|
|
|
FastMCP ships a dev server that renders your app tools in a browser, no MCP host needed:
|
|
|
|
```bash
|
|
fastmcp dev apps server.py
|
|
```
|
|
|
|
Open `http://localhost:8080`, pick `team_directory`, and try sorting columns and searching.
|
|
|
|
<Frame>
|
|
<img src="/apps/images/app-quickstart-dev-2.png" alt="The team directory rendered in the fastmcp dev apps preview, showing a pie chart, searchable table, and a detail card after clicking a row" />
|
|
</Frame>
|
|
|
|
## Make it reactive
|
|
|
|
The UI above renders once from your Python. Prefab apps can also respond to user input live, without any server round-trips. The key concept is **state**: a client-side key-value store that components read from and write to.
|
|
|
|
Click a row in the demo below to see a detail card appear:
|
|
|
|
<PrefabDemoFrame demo="team-directory-reactive" height="675px" title="Reactive team directory demo" />
|
|
|
|
Add a few imports, give each member a couple more fields, wire up a click handler, and render a detail card when something's selected:
|
|
|
|
```python expandable server.py
|
|
from collections import Counter
|
|
|
|
from prefab_ui.actions import SetState
|
|
from prefab_ui.app import PrefabApp
|
|
from prefab_ui.components import (
|
|
Badge, Card, CardContent, CardHeader, Column, DataTable, DataTableColumn,
|
|
Grid, H3, Row, Small, Text,
|
|
)
|
|
from prefab_ui.components.charts import PieChart
|
|
from prefab_ui.components.control_flow import If
|
|
from prefab_ui.rx import Rx, STATE
|
|
from fastmcp import FastMCP
|
|
|
|
mcp = FastMCP("My First App")
|
|
|
|
MEMBERS = [
|
|
{"name": "Alice Chen", "role": "Staff Engineer", "office": "San Francisco", "email": "alice@company.com", "projects": 3},
|
|
{"name": "Bob Martinez", "role": "Lead Designer", "office": "New York", "email": "bob@company.com", "projects": 5},
|
|
# ... more members ...
|
|
]
|
|
|
|
OFFICE_COUNTS = [
|
|
{"office": o, "count": c}
|
|
for o, c in Counter(m["office"] for m in MEMBERS).items()
|
|
]
|
|
|
|
|
|
@mcp.tool(app=True)
|
|
def team_directory() -> PrefabApp:
|
|
"""Browse the team directory."""
|
|
with PrefabApp(state={"selected": None}) as app:
|
|
with Column(gap=4, css_class="p-6"):
|
|
with Grid(columns=[1, 2], gap=4):
|
|
PieChart(
|
|
data=OFFICE_COUNTS,
|
|
data_key="count",
|
|
name_key="office",
|
|
show_legend=True,
|
|
)
|
|
DataTable(
|
|
columns=[
|
|
DataTableColumn(key="name", header="Name", sortable=True),
|
|
DataTableColumn(key="role", header="Role", sortable=True),
|
|
DataTableColumn(key="office", header="Office", sortable=True),
|
|
],
|
|
rows=MEMBERS,
|
|
search=True,
|
|
on_row_click=SetState("selected", Rx("$event")),
|
|
)
|
|
|
|
with If(STATE.selected):
|
|
with Card():
|
|
with CardHeader():
|
|
with Row(gap=2, align="center"):
|
|
H3(Rx("selected.name"))
|
|
Badge(Rx("selected.office"))
|
|
with CardContent():
|
|
with Grid(columns=3, gap=4):
|
|
with Column(gap=0):
|
|
Small("Role")
|
|
Text(Rx("selected.role"))
|
|
with Column(gap=0):
|
|
Small("Email")
|
|
Text(Rx("selected.email"))
|
|
with Column(gap=0):
|
|
Small("Active Projects")
|
|
Text(Rx("selected.projects"))
|
|
|
|
return app
|
|
```
|
|
|
|
Three new ideas do all the work:
|
|
|
|
- **`on_row_click=SetState("selected", Rx("$event"))`** — clicking a row writes its data into the `selected` state key. `$event` is the clicked row dict.
|
|
- **`Rx("selected.name")`** — a reactive reference. It doesn't hold a Python value; it compiles to a browser-side expression that re-evaluates whenever `selected` changes, so `Text(Rx("selected.name"))` always shows the latest clicked name.
|
|
- **`If(STATE.selected)`** — conditionally renders its body. Before any click, `selected` is `None` and the card stays hidden.
|
|
|
|
The `state={"selected": None}` dict on `PrefabApp` sets the initial value. Everything else happens in the browser — no round-trips to your server when the user clicks.
|
|
|
|
## Where to go next
|
|
|
|
You've built a tool that returns an interactive, reactive UI. This pattern covers a huge range of use cases: build a visualization, return it, and the user gets it rendered right in the conversation.
|
|
|
|
- **[Interactive Tools](/apps/prefab)** — charts, tables, dashboards, reactive state, with live demos
|
|
- **[FastMCPApp](/apps/fastmcp-app)** — when the UI needs to call back to your server (forms, search, CRUD)
|
|
- **[Examples](/apps/examples)** — complete working servers you can run today
|