Skip to main content
An MCP app is a UI running inside a conversation. Your MCP server supplies a PrefabApp, and the host displays it with the Prefab renderer. Components, state, expressions, and actions work as usual. The host also connects the app to server tools, the agent, and the surrounding conversation. The server owns your Python logic and persistent data. The renderer owns the open view’s state and handles interactions. The host connects that view to the server and conversation, and decides how to present it. These boundaries matter: changing a state key updates the display; calling a backend tool can save that change; sharing context lets the agent learn about it. Use FastMCP to register and serve the app. This guide covers the interactions you can build once it opens. Host support varies, particularly for app-provided tools, conversation updates, and display modes.

How the Pieces Connect

The host is the application displaying the Prefab renderer. It owns both the connection to your MCP server and the bridge to each open app. The agent uses the capabilities that host makes available. Calls can move in either direction: App-provided tools are discovered from each running app rather than added to the server’s static tool list. The host already knows which bridge advertised a tool, so two open apps can expose the same name without an app-instance argument.

Calling Server Tools

CallTool sends an operation from the UI to your MCP server. Use it for work that needs your database, services, or Python code. Arguments can read the app’s live state, so a search button can send the current query or a save button can submit the current form. The response is available as RESULT inside on_success. Your response actions decide what to do with it: update state, show feedback, or populate a Slot with returned components. The following view assumes a registered get_status(service: str) tool returning an object such as {"status": "healthy"}. Refreshing writes that response into the displayed status; a failed call shows the error instead.
You can also pass a registered function to CallTool. FastMCP resolves the function’s backend address when serializing the view, including routing prefixes. The CallTool reference covers callable references and result handling. Backend helpers can be app-only, so the host offers them to the UI without presenting them as direct model tools.

App-Provided Tools

App-provided tools require explicit host support, which is currently limited. Without it, the app may render normally, but the agent cannot discover or invoke its tools.
App-provided tools let an agent interact with a particular app after it opens. The user might click “Increment” while the agent asks to add five; both should change the same counter and update its display. Define that interaction once with AppTool, expose it through PrefabApp.app_tools, and use its .invoke() method to connect the button. For an operation backed by Python, pass the registered function to AppTool. Give the app tool an explicit name and input schema so it works with published FastMCP resolvers. The schema describes the backend inputs, including bound parameters; Prefab removes those parameters from the inputs exposed to the agent. The function runs on your MCP server. The app supplies context from its live state through bind, applies the response through on_success, and returns the explicit result to the agent. In the counter below, bind supplies counter_id, so the agent only needs to provide amount. The result reads the count after the response has updated the view’s state. This server stores counters in memory. FastMCPApp groups the backend function and the tool that opens a counter. Each call to open_counter creates a new counter and returns its own PrefabApp. The backend function is registered once and serves all those views.
Once a counter opens, the host can offer its get_count() and increment(amount) tools to the agent. Starting from zero, an increment with amount=5 sends this view’s counter ID and the amount to the Python function, updates the displayed count, and returns {"count": 5}. Calling get_count then reads that same live state. Clicking the button next invokes the increment interaction directly in the app and displays 6.

Local Operations

An app tool can also run Prefab actions entirely in the browser. Pass a name and actions instead of a backend function. Declare arguments with input_schema using a Pydantic model or JSON Schema, and read them through EVENT in the actions. Omitting the schema declares an operation with no arguments. This example gives both the user and the agent a reset operation. It also exposes get_count, which omits actions and returns the current value. The required result object chooses what the caller receives from either operation.

Calls and Lifetime

Each open view has its own app tools. Two counters can both expose increment; the host routes a call to the selected view, and that view supplies its bound ID. The caller cannot override bound arguments. These IDs identify application data; your backend remains responsible for authorizing access to it. Button and agent invocations validate their arguments and execute in order for that view. Bindings read current state when a call starts, and the declared result is evaluated after its response actions finish. If the backend fails, on_error can display feedback while the agent receives a tool error. Completed state changes and backend operations remain in effect. Replacing or closing the view removes its app tools and invalidates pending calls. An old response cannot update a replacement view, although a backend operation already in progress may still finish. The AppTool reference covers validation, result scopes, and the full parameter contract.

Messages and Context

The app can share what the user is looking at or help them ask about it. UpdateContext supplies information for future model turns. SendMessage sends a user message, such as a request to explain a chart. The host decides how to include context and how to handle the message and any resulting agent turn. Use context updates when a selection, preference, or filter should inform the next question. You choose the fields to share; updating local state alone does not send those fields to the model. Use a message when the user explicitly asks to continue the conversation. Here, editing the topic shares it as context, and clicking “Explain this” sends a request containing the current topic.
App tools provide another way for the agent to learn about the view: a read-only tool such as get_count returns the current value when called. Choose context updates for information that should accompany future turns and app tools for operations the agent can request explicitly.

Agent Turns

An app-provided tool permits the agent to interact with the open app; it does not cause the agent to act. UpdateContext supplies state for a future turn, while SendMessage asks the host to start one. If the user will prompt the agent themselves, you can omit the message. For a turn-based game, expose both a read operation and a move operation. After a human move, update the rendered board, publish the new state, and send a short message. The agent can then refresh the state with get_game_state and act with make_move. This example assumes the MCP server provides app-only play_human_move and play_agent_move tools.
The make_move app tool calls the backend, applies its response to the same state the components render, and returns the updated game to the agent. The revision lets the backend reject stale moves. For games with hidden information, return only the caller’s permitted view; do not expose the entire browser state through context or result.

Display Modes

The host controls where the app appears. RequestDisplayMode asks for inline, fullscreen, or picture-in-picture presentation. The host decides which modes are available and may return a different mode from the one requested. The reactive HOST variable reports the host’s current display mode, theme, and container dimensions when supplied. Use it in expressions to adapt the UI as its environment changes. For example, this view requests more space and displays the mode the host actually reports.
Use HOST.availableDisplayModes when deciding which controls to offer, and HOST.displayMode to read the current mode. Fields depend on what the host provides. In a standalone preview, HOST is absent; defaults let the view render there while you develop. Exercise server and conversation interactions in an MCP host to verify their behavior in that environment.