Generative UI... in Python? — Jeremiah Lowin, Prefect

Read the talk

Generative UI… in Python?

Jeremiah Lowin explains how Prefab turns Python component trees into interactive MCP apps, then uses the same serializable representation to let agents generate interfaces. The useful constraint is a familiar enterprise job: sharing and collecting information through tables, forms, and charts.

From a talk by Jeremiah Lowin

At a glance

Ideas worth remembering

  • MCP Apps let the agent initiate an experience while the person interacts with a real interface and its backend.

  • Prefab’s scope makes Python UI composition practical: component trees, parameters, and reactive bindings cover structured information work without recreating the frontend ecosystem.

  • The JSON protocol is the central architectural mechanism. A serializable UI can be created by a human, generated by an agent, or passed between them for modification.

  • Changing a tool’s return value to a Prefab component produces an interactive result. Adding a grid and chart extends the team directory without replacing that working table.

  • Direct upload separates capability selection from payload transfer: the agent opens the app, and the file reaches the server without being reproduced in model-generated tool arguments.

  • Prefab changed generated-UI transport from JSON to a smaller Python representation, then converts it back to JSON after sandbox execution. Compact authoring and a serializable rendering protocol serve different jobs.

MCP Apps open a human interaction path

An ordinary MCP tool call puts the agent between the user and the server. The user makes a request, the agent chooses a tool, and the server returns its result into the agent’s context window. The agent then decides what to tell the user. Jeremiah Lowin, Prefect’s founder and CEO and the creator of FastMCP, begins with this routing problem: adding business logic to an agent does not by itself give a person an interface to that logic.

MCP Apps add a user-facing path. A tool can deliver an interface made of HTML, CSS, and JavaScript, which the person can click and use. The agent still initiates the experience, but subsequent interactions can communicate with the application’s backend. Booking a restaurant table, choosing an airline seat, or exploring a conference schedule can become an ordinary human interaction rather than a sequence of instructions the model must translate into tool calls.

Lowin also anticipates an extension allowing the agent to interact with the app alongside the user. His hypothetical example is visual chess: the person makes a move, then the agent acts through the same application. That is a proposed direction in this recording; the interfaces developed here focus on giving the human a direct way to act.

0:120:42
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

0:12 · section reference included

Scope the problem to tables, forms, and charts

FastMCP serves a largely Python engineering audience. MCP Apps, meanwhile, run conventional frontend technology. Bridging those worlds creates a tempting but sprawling assignment: reproduce frontend development inside Python. Lowin’s refusal is blunt: “I can’t pretend we’re gonna ship React in Python.”

The way forward begins with what these developers actually do. Enterprise Python engineers often need to share information across an organization or collect it from colleagues. That job supplies a smaller vocabulary:

  • Tables: Present records that people can inspect.
  • Forms: Collect structured information.
  • Charts: Communicate patterns in data.

Fully branded consumer experiences require a different degree of freedom. Restricting the initial problem makes it possible to offer useful interfaces without importing the entire frontend ecosystem into Python.

Prefab is the resulting framework. Its introductory card behaves like a familiar frontend example: type a name and the displayed text updates live. The strange part is that Python declares the interface. The nesting makes its structure recognizable even while the language feels out of place.

Prefab makes this manageable by composing existing, well-designed components. The Python author arranges and configures them rather than implementing every browser primitive. The component vocabulary supplies both the capability and the guardrails: it suits structured information work, while arbitrary frontend design remains outside the problem Lowin set out to solve.

2:593:23
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

2:59 · section reference included

Python describes the tree; JSON makes it portable

The Python domain-specific language uses context managers to express containment. Nesting components inside a context manager builds the corresponding nesting in the interface. Each component is a class that the author instantiates and parameterizes, including passing CSS classes for styling. Reading the Python therefore reveals the layout hierarchy.

Reactive variables describe relationships between components. A value can be bound to controls and displayed elsewhere, allowing changes to propagate inside the client without the author writing JavaScript. The framework supplies the browser implementation for those bindings; Python supplies their declaration.

What actually crosses from Python into the browser? The pipeline below separates the authoring language from the representation and renderer. Python builds a declarative UI, that UI is serialized into a JSON protocol, and a React application renders the protocol as the MCP App. React still does the frontend work.

The consequential step is serialization. Once an interface has a portable representation, an agent can generate it, receive it, or modify something a human created. The renderer can consume the same protocol regardless of who authored the interface. Lowin calls the Python DSL an accident discovered afterward: the original question was whether a UI could become serializable data, and the pleasant Python syntax emerged from that design.

From Python composition to a browser interface

Python DSLauthoring — Nested context managers and component instances describe the UI.
Declarative UIrepresentation — The composed interface becomes a representation that can be serialized.
JSON protocolrepresentation — A portable UI representation that agents can generate, receive, or edit.
React MCP Apprendering — Renders the protocol as an interactive browser interface.
The JSON protocol separates interface authorship from React rendering. Humans and agents can work with the representation without building a new frontend for each interface.
Read the diagram as text
  • Python DSL. authoring — Nested context managers and component instances describe the UI.
  • Declarative UI. representation — The composed interface becomes a representation that can be serialized.
  • JSON protocol. representation — A portable UI representation that agents can generate, receive, or edit.
  • React MCP App. rendering — Renders the protocol as an interactive browser interface.
  • Python DSL → Declarative UI: Builds.
  • Declarative UI → JSON protocol: Serializes.
  • JSON protocol → React MCP App: Renders.
6:226:52
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

6:22 · section reference included

Documentation that runs its own examples

The Prefab documentation applies another useful constraint: it is entirely rendered in Prefab. The data-table page contains a live table generated from its Python example. An example can be opened in the playground, edited as Python, and watched as the rendered interface changes.

Recording frame at 551 secondsOpen full source frame
Recording frame at 551 seconds

That creates a tight learning loop. The example is an interface the reader can use; changing its declaration changes the thing being explained. It also exercises the composition model on the documentation itself, beyond a single greeting card.

The team has used Prefab for small interactive data apps and presentations, including a dark theme intended for slides. Those experiments broaden its uses, but MCP servers remain the reason it was built. The next examples move through three increasingly involved integrations: interactive tool results, applications with backends, and agent-generated interfaces.

8:228:52
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

8:22 · section reference included

A team directory becomes an interactive tool

Start with a decorated FastMCP function that returns a Python dictionary. Its information goes to the agent. The first integration changes the function’s return value to a Prefab data-table component. FastMCP detects the component and prepares the MCP App machinery: the renderer, HTML, JavaScript, and CSS needed to display it.

Recording frame at 701 secondsOpen full source frame
Recording frame at 701 seconds

The observable result is a team directory in the Goose client. A request to show the directory brings up a table with searching, filtering, sorting, and pagination. Those behaviors come from the component and renderer. The author’s change is small, but the person receiving the result gains a way to inspect the directory directly rather than asking the agent to restate each view.

The same example then grows. Import a grid and a pie chart, place the chart and table inside the grid’s context manager, and the directory now appears beside a chart showing its breakdown. The progression is causal and visible: returning a component opens the interactive result; nesting two components adds a coordinated layout. The table provides individual records while the chart provides an aggregate view.

Lowin’s framework preference is “one line of code, one big noticeable change.” That is a design goal for incremental complexity, rather than a literal promise that every feature takes one line. The directory example illustrates it: each added component contributes recognizable behavior without requiring the author to construct the frontend infrastructure.

A separate demonstration connects several controls through shared reactive attributes. Text and values update together entirely on the client. The Rx class supplies reactive variables that can be referenced and formatted throughout the Python declaration; Prefab compiles those references into JavaScript. This is a distinct capability from placing the chart beside the table: shared bindings describe how components change together.

9:5210:22
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

9:52 · section reference included

A backend makes direct uploads possible

The second integration adds a backend to the interface. A FastMCP app class has a UI entry point, decorated with app.ui, that returns its initial Prefab components. Optional decorated backend tools can then be called from the UI. A form button, for example, can submit the person’s input to a method that writes it to a database.

File upload exposes why this matters. In the ordinary agent-mediated setup, adding an upload tool to the MCP server does not give the user a file-transfer channel. The agent must call the tool with the contents. Lowin’s example gives it a megabyte of text to reproduce character by character—the “world’s most expensive copy-paste operation.” The file arrives, but its transfer consumes model work unnecessarily.

FastMCP’s upload component changes the route. Ask the agent to open the upload interface, drag a file into it, and the app sends the file directly to the server. In the demonstration, the agent subsequently works with the uploaded file. Its involvement is still useful; reproducing the payload during transfer is the avoidable part. The client must support MCP Apps for this user-facing channel to work.

Which bytes need to pass through the agent? The comparison below separates the request to open an upload capability from the file payload. In the app path, the agent initiates the experience while the file takes a direct route to the server. That routing change explains the saving.

The file takes a different route

User supplies fileordinary upload tool — The example supplies a megabyte of text to the agent.
Agent reproduces contentsordinary upload tool — The payload must be included in the tool call.
MCP server receives fileordinary upload tool — The transfer succeeds through the agent-mediated route.
Agent opens upload appMCP App upload — The user asks for an upload capability.
User drags file into appMCP App upload — The upload component accepts the file directly.
Server receives fileMCP App upload — The payload bypasses the agent; the agent can work with it afterward.
The naive upload passes file contents through an agent-generated tool call. The MCP App lets the person transfer the file directly, after the agent opens the interface.
Read the diagram as text
  • User supplies file. ordinary upload tool — The example supplies a megabyte of text to the agent.
  • Agent reproduces contents. ordinary upload tool — The payload must be included in the tool call.
  • MCP server receives file. ordinary upload tool — The transfer succeeds through the agent-mediated route.
  • Agent opens upload app. MCP App upload — The user asks for an upload capability.
  • User drags file into app. MCP App upload — The upload component accepts the file directly.
  • Server receives file. MCP App upload — The payload bypasses the agent; the agent can work with it afterward.
  • User supplies file → Agent reproduces contents: File enters agent context.
  • Agent reproduces contents → MCP server receives file: Contents become tool input.
  • Agent opens upload app → User drags file into app: Makes upload interface available.
  • User drags file into app → Server receives file: Direct file transfer.
12:2212:52
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

12:22 · section reference included

Generative UI—and Python beating JSON on the wire

The third integration lets Claude generate the interface itself. In the recorded demo, a tool accepts Prefab’s serialized JSON protocol. As the agent streams the representation, the renderer displays what has arrived, attempting to repair incomplete JSON along the way. The UI grows progressively instead of waiting for a complete response.

Recording frame at 1026 secondsOpen full source frame
Recording frame at 1026 seconds

Prefab ships a skill that teaches the agent how to write these interfaces. This makes the component vocabulary available to another author: the model. Builders can choose a custom approach or restrict the available components to their needs. Lowin also leaves room for client-native implementations; when a client already offers the desired generative UI, using that may be preferable.

Then the team discovered a surprising representation tradeoff. Lowin reports that the Python form of a UI was about 70% smaller than its JSON form in their comparison. The recording does not specify the size measure or comparison workload, so this is an implementation observation rather than a universal compression ratio. It was enough to change what Prefab sends over the wire.

The newer path streams Python, executes it in a sandbox, converts it to JSON on the server, and renders that JSON. The intermediate protocol remains intact; only the agent’s transmitted representation changes. Lowin reports token, cost, and latency benefits from the more compact input. The tradeoff is that this path now requires sandboxed code execution, whose isolation mechanisms are not developed in the talk.

Python therefore ends up doing two jobs: giving humans a readable composition language and giving agents a compact way to transmit the same interface. JSON retains the architectural job that motivated the project—a serializable representation consumed by the React renderer. To try the integration described in the recording, Lowin points to the Prefab documentation and the optional FastMCP installation: import components, return them from tools, and let the framework deliver the app.

14:5115:22
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

14:51 · section reference included

Resources

From the talk

  • The MCP framework that detects returned Prefab components and delivers interactive apps; useful for framework installation and integration examples.

Transcript & slides

Loading transcript and slides…

Transcript download