CallTool("tool_name") in a regular @mcp.tool(app=True). But once you have multiple backend tools, the management overhead adds up: Which tools should the model see vs. only the UI? What happens to string-based tool references when servers are composed under namespaces? How do you keep things wired correctly as the app grows?
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. These return a Prefab UI.@app.tool()— backend tools the UI calls viaCallTool. These do the work.
CallTool accepts function references instead of strings, so references are refactorable and composition-safe.
Your First Interactive App
Here’s a minimal app with a form that saves data:notes_app, the user sees a form. Submitting it calls add_note on the server, updates the state with the result, and shows a toast — all without leaving the UI.
Let’s break down the key concepts.
Entry Points: @app.ui()
Entry points are what the model sees and calls to open your app. They return a Prefab UI, just like display tools:visibility=["model"] — they show up in the tool list for the LLM but aren’t callable from within the app UI. They support the same options as @mcp.tool: name, description, title, tags, icons, auth, and timeout.
Backend Tools: @app.tool()
Backend tools do the work. The UI calls them viaCallTool; they run on the server and return data:
visibility=["app"]). The model doesn’t see them in the tool list. If you want a tool callable by both the model and the UI, pass model=True:
name, description, auth, and timeout:
Connecting UI to Backend: CallTool
CallTool is the bridge between the UI and the server. Pass the name of a backend tool registered with @app.tool():
CallTool("save_contact") keeps working even when the server is mounted under a namespace.
You can also pass the function directly — CallTool(save_contact) — which can be convenient when the tool is defined in the same file. Both forms resolve identically.
Handling Results
Server calls are asynchronous. Useon_success and on_error callbacks to handle outcomes:
RESULT is a reactive reference to the value the tool returned — available inside on_success callbacks. Similarly, ERROR (from prefab_ui.rx) is available inside on_error.
Callbacks can be a single action or a list of actions. They execute in order, and an error in any action short-circuits the rest.
result_key Shorthand
When a tool returns data that should replace a state key,result_key is a convenient shorthand for on_success=SetState(key, RESULT):
Actions
CallTool is one of several actions available in Prefab. Actions are events attached to component handlers like on_click, on_submit, and on_change.
Client Actions
These run instantly in the browser — no server round-trip:Chaining Actions
Pass a list to execute multiple actions in sequence:Loading States
A common pattern: show a loading indicator while a server call is in flight.Forms
Forms are the most common way to collect input and send it to the server. When a form submits, all named input values are gathered and passed as arguments to theCallTool action.
Manual Forms
Build forms with individual input components:{"title": "...", "priority": "...", "description": "..."} as arguments to create_ticket.
Pydantic Model Forms
For structured data,Form.from_model() generates the entire form from a Pydantic model — inputs, labels, and submit wiring:
str fields become text inputs, Literal becomes a select dropdown, bool becomes a checkbox. Field titles and defaults are respected.
Composition and Namespacing
The reasonFastMCPApp exists — and why you’d use it instead of plain @mcp.tool(app=True) with CallTool("tool_name") — is composition safety.
When you mount a server under a namespace, tool names get prefixed:
CallTool("save_contact"), it would break — the tool is now named contacts_save_contact. But CallTool(save_contact) with a function reference resolves to a globally stable key (like save_contact-a1b2c3d4) that bypasses the namespace entirely.
This is why FastMCPApp assigns global keys to backend tools, and why CallTool accepts function references. Your app works the same whether it’s running standalone or mounted inside a larger platform.
Mounting an App
FastMCPApp is a Provider. Add it to a server with providers= or add_provider:
save.
Running Standalone
For development,FastMCPApp has a convenience run() method that wraps itself in a temporary FastMCP server:
Complete Example: Contact Manager
This pulls together everything — entry points, backend tools, callable references, forms (both manual and Pydantic), state management, and actions:examples/apps/contacts/contacts_server.py.
Next Steps
- Prefab Apps — Components, state, and reactive displays (the building blocks)
- Patterns — Copy-paste examples for common UIs
- Development — Preview and test app tools locally
- Prefab UI Docs — Full component reference and advanced patterns

