Skip to main content
Glama
robert-mcdermott

Generative UI MCP Server

README.md
# Generative UI MCP Server

A ready-to-run [FastMCP](https://gofastmcp.com/) server that lets an MCP-capable LLM generate ephemeral [Prefab](https://prefab.prefect.io/) interfaces at runtime.

It supports:

- **STDIO** (stdin/stdout), for a local MCP client that launches one process per session.
- **Legacy SSE**, for an older network MCP client at `http://HOST:PORT/sse`.
- **Streamable HTTP**, included as the recommended network alternative at `http://HOST:PORT/mcp`.

> MCP documentation calls the local transport **STDIO**, not “stdout.” Requests arrive on stdin and responses leave on stdout. Never print application logs to stdout while using this mode.

## What the server provides

FastMCP's `GenerativeUI` provider registers:

- `generate_prefab_ui`: executes model-authored Prefab Python in a Pyodide sandbox and returns an MCP App.
- `search_prefab_components`: searches the installed Prefab component catalog.
- A `ui://` streaming renderer used by an MCP Apps-compatible host.

The LLM runs in the client/host (for example, Phlox), not in this server. The client gathers data and generates the Python supplied to `generate_prefab_ui`.

## Prerequisites

- Python 3.10 or newer.
- [`uv`](https://docs.astral.sh/uv/) installed and on `PATH`.
- An **MCP Apps-compatible host**. A tool-only MCP client can call these tools and inspect results, but it cannot display the interactive iframe UI unless it implements MCP Apps/AppBridge.
- Outbound HTTPS from the browser to the Pyodide CDN for the streaming renderer.
- [Deno](https://docs.deno.com/runtime/getting_started/installation/) for final server-side Pyodide validation. The FastMCP documentation says it can install Deno on first use, but this pinned package combination reports an actionable error when `deno` is absent. Install it explicitly for deterministic operation.

Versions are deliberately pinned in `pyproject.toml` and `uv.lock`:

- `fastmcp[apps]==4.0.5`
- `prefab-ui==0.20.2`

## Install

```bash
cd generative-ui-mcp
uv sync
```

Install and verify Deno on macOS/Linux:

```bash
./scripts/install-deno.sh
export PATH="$HOME/.deno/bin:$PATH"
deno --version
```

On Windows, use the official PowerShell installer:

```powershell
irm https://deno.land/install.ps1 | iex
```

Open a new shell after installation. Then verify registration and the component catalog:

```bash
uv run pytest
```

## Preview an app

FastMCP provides a browser MCP Apps development host:

```bash
uv run fastmcp dev apps src/generative_ui_mcp/server.py:mcp
```

Open `http://127.0.0.1:8080`, select `generate_prefab_ui`, select edit as JSON and enter the following:


```json
{
  "code": "from prefab_ui.app import PrefabApp\nfrom prefab_ui.components import Card, CardContent, Column, Heading, Progress, Text\n\n\nwith PrefabApp() as app:\n    with Column(gap=4, css_class=\"p-6\"):\n        Heading(\"Build status\")\n        with Card():\n            with CardContent():\n                Text(\"Deployment is 72% complete\")\n                Progress(value=72)\n",
  "data": {}
}
```

The development command starts an HTTP MCP server on port 8000 and the preview host on port 8080. It is the easiest way to verify UI rendering before connecting Phlox or another real host.

## STDIO mode

STDIO is the default and recommended local-process transport. The MCP client launches the server, communicates over stdin/stdout, and terminates it when the session ends.

### Run directly

Any of these commands starts the same STDIO server:

```bash
uv run generative-ui-mcp-stdio
```

```bash
uv run generative-ui-mcp --transport stdio
```

```bash
uv run fastmcp run src/generative_ui_mcp/server.py:mcp --transport stdio
```

The process appears to wait silently. That is expected: it is waiting for framed MCP messages on stdin.

### Client configuration

Copy `examples/mcp-stdio.json` and replace the project path with an absolute path:

```json
{
  "mcpServers": {
    "generative-ui": {
      "transport": "stdio",
      "command": "uv",
      "args": [
        "--directory",
        "/opt/generative-ui-mcp",
        "run",
        "generative-ui-mcp-stdio"
      ]
    }
  }
}
```

Some clients omit the explicit `transport` field and infer STDIO from `command` and `args`.

### Important logging rule

Do not use `print()` for diagnostics in STDIO mode because stdout carries the MCP protocol. Use Python logging, which writes to stderr by default:

```python
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info("This goes to stderr")
```

## Legacy SSE mode

SSE is FastMCP's older network transport. It remains useful for clients that explicitly require it, but FastMCP recommends Streamable HTTP for new network deployments.

### Start SSE

```bash
uv run generative-ui-mcp-sse
```

The default endpoint is:

```text
http://127.0.0.1:8000/sse
```

Configure another address through environment variables:

```bash
MCP_HOST=0.0.0.0 \
MCP_PORT=9000 \
MCP_SSE_PATH=/sse \
uv run generative-ui-mcp-sse
```

Or use the unified command:

```bash
uv run generative-ui-mcp \
  --transport sse \
  --host 0.0.0.0 \
  --port 9000 \
  --path /sse
```

The equivalent FastMCP CLI command is:

```bash
uv run fastmcp run src/generative_ui_mcp/server.py:mcp \
  --transport sse \
  --host 0.0.0.0 \
  --port 8000
```

### Client configuration

```json
{
  "mcpServers": {
    "generative-ui": {
      "transport": "sse",
      "url": "http://127.0.0.1:8000/sse"
    }
  }
}
```

A programmatic FastMCP client can connect explicitly:

```python
import asyncio
from fastmcp import Client
from fastmcp.client.transports import SSETransport

async def main() -> None:
    transport = SSETransport("http://127.0.0.1:8000/sse")
    async with Client(transport) as client:
        tools = await client.list_tools()
        print([tool.name for tool in tools])

asyncio.run(main())
```

The server also exposes the legacy message-posting route used by the SSE transport; clients discover and use it through the SSE connection. Do not configure that route as the server URL.

### Reverse proxy

Disable buffering for event streams. An nginx location can use:

```nginx
location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_http_version 1.1;
    proxy_set_header Connection '';
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;
}
```

Use TLS and authentication before exposing the endpoint outside a trusted network.

### SSE limitations

- It is retained for backward compatibility.
- It negotiates the legacy MCP protocol era.
- FastMCP SSE does not support the CLI auto-reload mode.
- It is less suitable than Streamable HTTP for current bidirectional and sessionless MCP behavior.

## Recommended network mode

Although requested examples include legacy SSE, use Streamable HTTP unless the client specifically requires SSE:

```bash
uv run generative-ui-mcp \
  --transport http \
  --host 0.0.0.0 \
  --port 8000 \
  --path /mcp
```

Connect to:

```text
http://127.0.0.1:8000/mcp
```

This transport can use SSE inside HTTP responses for streaming, but it is **not** the legacy two-endpoint SSE transport.

## Docker: SSE service

Build and start the included legacy SSE service:

```bash
docker compose up --build
```

Connect to:

```text
http://127.0.0.1:8000/sse
```

The Compose file intentionally publishes only on loopback. Change the binding only after adding authentication and network controls.

To run the image as Streamable HTTP instead:

```bash
docker build -t generative-ui-mcp .

docker run --rm -p 127.0.0.1:8000:8000 \
  generative-ui-mcp \
  uv run --no-sync generative-ui-mcp \
    --transport http --host 0.0.0.0 --port 8000 --path /mcp
```

STDIO is generally launched directly by the MCP client rather than placed in a long-running container. If required, configure the client to execute `docker run -i --rm generative-ui-mcp uv run --no-sync generative-ui-mcp-stdio`; the `-i` flag is mandatory so stdin remains attached.

## Phlox configuration

Phlox must first implement generic MCP Apps hosting: preserve UI metadata and structured content, read `ui://` resources, render them in a sandboxed iframe, and implement the AppBridge channel. Merely registering this server as an ordinary MCP tool server will expose the tools but will not render the app.

### Local Phlox and STDIO

If Phlox supports launching local MCP processes, use the STDIO configuration above. Ensure the Phlox process can execute `uv` and read this project directory.

### Containerized Phlox and SSE

If both services run in the same Compose network, do not use `localhost` from the Phlox container. Register:

```text
http://generative-ui-mcp:8000/sse
```

For new Phlox development, prefer a Streamable HTTP sidecar at:

```text
http://generative-ui-mcp:8000/mcp
```

## Suggested agent instructions

Add guidance similar to this to the host's agent policy:

```text
Use search_prefab_components and generate_prefab_ui when an interactive
chart, dashboard, table, report, calculator, or form would communicate a
result better than text. Gather and validate the source data first. Pass
large datasets through the data argument instead of embedding them in code.
Use only Python's standard library and Prefab components in generated code.
Do not generate a UI when a concise text response is sufficient.
```

## Passing data

`generate_prefab_ui` accepts `code` and `data`. Keys in `data` become globals in the sandbox, allowing generated code to refer to data without embedding it into source:

```json
{
  "code": "from prefab_ui.app import PrefabApp\nfrom prefab_ui.components import DataTable, DataTableColumn\nwith PrefabApp() as app:\n    DataTable(rows=records, columns=[DataTableColumn(key='name', header='Name')])",
  "data": {
    "records": [
      {"name": "alpha"},
      {"name": "beta"}
    ]
  }
}
```

Do data access and substantial analysis in the host's normal tools first. Generated code is limited to Python's standard library, Prefab, and explicitly passed data; NumPy, pandas, requests, and arbitrary external packages are unavailable in the sandbox.

## Security

This server executes model-generated Python, but FastMCP runs that code in Pyodide rather than as normal host Python. Treat the entire feature as untrusted nonetheless:

- Keep FastMCP and Prefab exactly pinned and upgrade deliberately.
- Restrict who can invoke `generate_prefab_ui`.
- Limit input/result sizes and request rates in the host or reverse proxy.
- Do not place secrets in the `data` argument; they become visible to the browser app.
- Render MCP Apps only in a sandboxed iframe.
- Validate every app-originated tool call in the MCP host.
- Use TLS and authentication for network transports.
- Bind development servers to loopback.
- Review browser CSP and permitted CDN access for controlled environments.
- Log to stderr in STDIO mode.

This project intentionally does not include authentication because auth design depends on the MCP host and deployment environment. Do not expose the SSE or HTTP listener to an untrusted network as-is.

## Troubleshooting

### Tools appear but no UI renders

The MCP client is not an MCP Apps-compatible host, or it discarded the tool's UI metadata or `structuredContent`. Test with `fastmcp dev apps` first.

### First generation is slow or fails

Confirm that `deno --version` succeeds in the same environment and under the same service account that launches the server. The included container installs Deno explicitly. For offline production environments, preinstall Deno and ensure browser-side Pyodide assets are available under the chosen CSP strategy.

### Browser shows a blank app

Check the host's AppBridge messages, iframe sandbox policy, CSP errors, and outbound access to the Pyodide CDN. The FastMCP development host includes an inspector for JSON-RPC and AppBridge traffic.

### SSE connects but results never arrive

Disable buffering in the reverse proxy and extend read timeouts. Confirm that the client is explicitly using an SSE transport and the `/sse` URL, rather than trying to infer Streamable HTTP from the URL.

### STDIO client reports malformed JSON

Remove all `print()` calls and anything else writing to stdout. Send logs to stderr.

### ImportError in generated code

Generated code can use the standard library and Prefab, but not arbitrary third-party packages. Perform analysis elsewhere and pass plain JSON-compatible data through `data`.

## Project layout

```text
generative-ui-mcp/
├── src/generative_ui_mcp/
│   ├── __init__.py
│   ├── __main__.py
│   ├── entrypoints.py
│   └── server.py
├── examples/
│   ├── mcp-sse.json
│   └── mcp-stdio.json
├── tests/test_server.py
├── scripts/install-deno.sh
├── Dockerfile
├── docker-compose.yml
├── pyproject.toml
├── uv.lock
└── README.md
```

## Update dependencies

Inspect updates explicitly:

```bash
uv lock --upgrade-package fastmcp --upgrade-package prefab-ui
uv run pytest
uv run fastmcp dev apps src/generative_ui_mcp/server.py:mcp
```

After validating MCP metadata, generated output, iframe rendering, and client compatibility, commit the new exact versions and lockfile together.

TDQS

A4.5/5.0

Scored across 2 tools

Disambiguation5/5

generate_prefab_ui executes and renders UI code, while search_prefab_components is a reference lookup for available components and APIs. There is no overlap between rendering and searching, so an agent cannot confuse their roles.

Naming Consistency5/5

Both tools follow the same lowercase verb_object pattern with the shared prefab_ prefix: generate_* and search_*. The minor difference between ui and components is not a meaningful inconsistency.

Tool Count3/5

Two tools feels thin, but the server's scope is deliberately narrow: generate/render UI and search the component library. The count is acceptable but leaves little room for additional workflow support.

Completeness5/5

The tool pairing covers the full intended workflow: discover components with search_prefab_components, then generate and render them with generate_prefab_ui. No essential operation for a generative UI server is missing.

Maintenance

ActivityMaintained
ResponsivenessNo issues