FastMCPApp handles the wiring.
You’ll build up to the contacts app above by the end of this page. Let’s start with something smaller.
A minimal interactive app
The smallest interactive app: a form that saves a note, and a list that updates when the user submits.notes_app. Calling it opens the UI. When the user submits the form, CallTool("add_note") fires, the server saves the note, returns the updated list, and SetState("notes", RESULT) writes that list back into state. ForEach("notes") re-renders. The model never sees add_note — it’s UI-only.
Why not just @mcp.tool(app=True)?
A fair question. Any Interactive Tool can call a server tool — there’s nothing stopping you from putting CallTool("add_note") inside a regular @mcp.tool(app=True). It works for one or two tools. Things get harder once the app grows:
- Which tools should the model see, and which are UI-only?
- What happens to
CallTool("add_note")when you mount this server under a namespace and the tool becomesnotes_add_note? - How do you keep it all wired correctly as you compose servers?
FastMCPApp owns these concerns. Entry points register as model-visible. Backend tools register as UI-only by default. Backend tools get globally stable identifiers that survive namespacing, and CallTool accepts function references, so references stay valid when you compose servers.
The rest of this page covers each piece in turn.
@app.ui() — entry points
Entry points are what the model sees. They return a PrefabApp and default to visibility=["model"], showing up in the LLM tool list but not callable from within the UI.
@app.ui() supports the same options as @mcp.tool: name, description, title, tags, icons, auth, and timeout.
@app.tool() — backend tools
Backend tools do the work. By default they’re visible only to the UI (visibility=["app"]), not the model.
model=True:
name, description, auth, and timeout.
CallTool — UI → backend
CallTool is how the UI invokes a backend tool. Pass the tool’s name (or a direct function reference):
Rx:
Handling results
Server calls are async. Useon_success and on_error callbacks:
RESULT is a reactive reference to the tool’s return value, available inside on_success. ERROR (from prefab_ui.rx) is the counterpart inside on_error. Callbacks can be a single action or a list; they execute in order and short-circuit on error.
result_key shorthand
When a tool’s return value should replace a state key, use result_key:
Actions
CallTool is one of several actions. Actions attach to handlers like on_click, on_submit, and on_change.
Client-side actions run instantly in the browser, no server round-trip:
Loading states
A common pattern: disable a button and show a spinner while a call is in flight.Forms
Forms collect input and submit it to a tool. When submitted, named input values become the tool’s arguments.Manual forms
CallTool receives {"title": ..., "priority": ..., "description": ...}.
Forms from Pydantic models
For structured input,Form.from_model() generates the whole form — inputs, labels, validation:
str becomes a text input, Literal becomes a select, bool becomes a checkbox. Field titles and defaults are respected.
Composition and namespacing
The reasonFastMCPApp exists — and why you’d pick it over plain @mcp.tool(app=True) with string-based CallTool — is composition safety.
When you mount a server under a namespace, tool names get prefixed:
CallTool("save_contact") would now be broken. But CallTool(save_contact) with a function reference resolves to a globally stable identifier that bypasses the namespace. Your app works the same whether standalone or mounted.
Mounting
FastMCPApp is a Provider. Add it to a server with providers= or add_provider:
save.
Running standalone
For development,FastMCPApp has a run() shortcut that wraps itself in a temporary FastMCP server:
A full example: contact manager
This brings everything together — entry point, backend tools, Pydantic form, manual form, state, actions, and multi-visibility.examples/apps/contacts/contacts_server.py.
Next steps
- Interactive Tools — the building blocks: charts, tables, dashboards, reactive state
- Examples — complete working servers
- Development — preview and test app tools locally
- Prefab UI docs — full component reference

