fastmcp/examples/apps/datatable_server.py
Jeremiah Lowin 3ff1472ea9
Add Prefab Apps integration for MCP tool UIs (#3316)
* 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.
2026-02-27 14:37:57 -05:00

165 lines
4.1 KiB
Python

"""DataTable MCP App — interactive, sortable data views with Prefab.
Demonstrates `fastmcp[apps]` with Prefab UI components:
- `app=True` for automatic renderer wiring
- `PrefabApp` with `DataTable` for rich tabular views
- Searchable, sortable, paginated tables
- Layout composition with `Column`, `Heading`, `Text`, and `Badge`
Usage:
uv run python datatable_server.py # HTTP (port 8000)
uv run python datatable_server.py --stdio # stdio for MCP clients
"""
from __future__ import annotations
from prefab_ui.app import PrefabApp
from prefab_ui.components import (
Badge,
Column,
DataTable,
DataTableColumn,
Heading,
Muted,
Row,
)
from fastmcp import FastMCP
mcp = FastMCP("Team Directory")
EMPLOYEES = [
{
"name": "Alice Chen",
"role": "Engineering",
"level": "Senior",
"location": "San Francisco",
"status": "active",
},
{
"name": "Bob Martinez",
"role": "Design",
"level": "Lead",
"location": "New York",
"status": "active",
},
{
"name": "Carol Johnson",
"role": "Engineering",
"level": "Staff",
"location": "London",
"status": "active",
},
{
"name": "David Kim",
"role": "Product",
"level": "Senior",
"location": "San Francisco",
"status": "away",
},
{
"name": "Eva Müller",
"role": "Engineering",
"level": "Mid",
"location": "Berlin",
"status": "active",
},
{
"name": "Frank Okafor",
"role": "Data Science",
"level": "Senior",
"location": "Lagos",
"status": "active",
},
{
"name": "Grace Liu",
"role": "Engineering",
"level": "Junior",
"location": "Singapore",
"status": "active",
},
{
"name": "Hassan Ali",
"role": "Design",
"level": "Senior",
"location": "Dubai",
"status": "away",
},
{
"name": "Iris Tanaka",
"role": "Product",
"level": "Lead",
"location": "Tokyo",
"status": "active",
},
{
"name": "James Wright",
"role": "Engineering",
"level": "Senior",
"location": "London",
"status": "inactive",
},
{
"name": "Karen Petrov",
"role": "Data Science",
"level": "Lead",
"location": "Berlin",
"status": "active",
},
{
"name": "Liam O'Brien",
"role": "Engineering",
"level": "Mid",
"location": "Dublin",
"status": "active",
},
]
@mcp.tool(app=True)
def list_team(department: str | None = None) -> PrefabApp:
"""Browse the team directory with sorting and search.
Args:
department: Filter by department (e.g. "Engineering", "Design").
Leave empty to show everyone.
"""
if department:
rows = [e for e in EMPLOYEES if e["role"].lower() == department.lower()]
else:
rows = EMPLOYEES
active = sum(1 for e in rows if e["status"] == "active")
with Column(gap=6, css_class="p-6") as view:
with Column(gap=1):
Heading("Team Directory")
with Row(gap=2):
Muted(f"{len(rows)} members")
Muted(f"{active} active", css_class="text-success")
if department:
Badge(department, variant="outline")
DataTable(
columns=[
DataTableColumn(key="name", header="Name", sortable=True),
DataTableColumn(key="role", header="Department", sortable=True),
DataTableColumn(key="level", header="Level", sortable=True),
DataTableColumn(key="location", header="Location", sortable=True),
DataTableColumn(key="status", header="Status", sortable=True),
],
rows=rows,
searchable=True,
paginated=True,
page_size=10,
)
return PrefabApp(
title="Team Directory",
view=view,
state={"total": len(rows), "active": active},
)
if __name__ == "__main__":
mcp.run()