Skip to main content
Glama
robert-mcdermott

Generative UI MCP Server

Generative UI MCP Server

A ready-to-run FastMCP server that lets an MCP-capable LLM generate ephemeral Prefab 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.

Related MCP server: Agent Bridge for Unity

Prerequisites

  • Python 3.10 or newer.

  • 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 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

cd generative-ui-mcp
uv sync

Install and verify Deno on macOS/Linux:

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

On Windows, use the official PowerShell installer:

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

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

uv run pytest

Preview an app

FastMCP provides a browser MCP Apps development host:

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:

{
  "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:

uv run generative-ui-mcp-stdio
uv run generative-ui-mcp --transport stdio
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:

{
  "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:

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

uv run generative-ui-mcp-sse

The default endpoint is:

http://127.0.0.1:8000/sse

Configure another address through environment variables:

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

Or use the unified command:

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

The equivalent FastMCP CLI command is:

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

Client configuration

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

A programmatic FastMCP client can connect explicitly:

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:

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.

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

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

Connect to:

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:

docker compose up --build

Connect to:

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:

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:

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

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

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

Suggested agent instructions

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

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:

{
  "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

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:

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.

Available Tools

2 tools
generate_prefab_uiGenerate Prefab UiA

Execute Prefab Python code in a sandbox and render the result.

The code runs in a Pyodide WASM sandbox with full Python support. Import everything you use. Use the components tool to look up available components and their import paths.

Always use PrefabApp as the outermost context manager — this enables streaming so the UI renders progressively as code is written:

from prefab_ui.components import Column, Heading, Text, Row, Badge
from prefab_ui.app import PrefabApp

with PrefabApp() as app:
    with Column(gap=4):
        Heading("Dashboard")
        with Row(gap=2):
            Text("Revenue: $1.2M")
            Badge("On Track", variant="success")

For interactive UIs, pass initial state as a dict and use .rx on stateful components for reactive bindings:

from prefab_ui.components import Column, Slider, Text
from prefab_ui.app import PrefabApp

with PrefabApp(state={"threshold": 50}) as app:
    with Column(gap=4):
        slider = Slider(value=50, min=0, max=100, name="threshold")
        Text(f"Threshold: {slider.rx}%")

slider.rx produces {{ threshold }}, a template expression that resolves against client-side state. Use Rx("key") directly, or apply pipe filters: Rx("balance").currency() produces {{ balance | currency }}.

Available pipes: upper, lower, currency, length, json, round(n), default(val), truncate(n).

Charts live in prefab_ui.components.charts:

from prefab_ui.components.charts import BarChart, ChartSeries

BarChart(
    data=[{"month": "Jan", "rev": 100}, {"month": "Feb", "rev": 200}],
    series=[ChartSeries(data_key="rev", label="Revenue")],
    x_axis="month",
)

Values passed via data are available as global variables in the code. Python features like loops, f-strings, and comprehensions all work.

Layout patterns:

  • Card sub-components (CardHeader, CardContent, CardFooter) have built-in padding. Don't add extra padding to them. For a simple card without sub-components, use Card(css_class="p-6").

  • Use Grid(columns=N, gap=4) for equal-width cards or panels. Grid handles sizing automatically — no flex classes needed. For unequal widths, pass a list: Grid(columns=[2, 1], gap=4) gives a 2:1 ratio.

  • Row is for inline elements (badges, icons + text, buttons). Prefer Grid when children should have equal or proportional widths. Row does not wrap by default.

  • Column and Row accept gap (Tailwind scale: 1-12), align (cross-axis), and justify (main-axis) as native props — prefer these over raw css_class for spacing.

  • Use css_class="overflow-hidden" on containers if chart or content edges should clip to the container boundary.

Args: code: Python code that builds a Prefab component tree. data: Values injected as variables in the sandbox namespace. sandbox: A Sandbox instance. If not provided, a new one is created on each call.

ParametersJSON Schema
NameRequiredDescriptionDefault
codeYes
dataNo

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so thoroughly. It discloses Pyodide WASM sandboxing, streaming behavior, reactive state mechanics, data injection as globals, and per-call sandbox creation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but well-structured: purpose, code examples, state handling, charts, layout patterns, then args. Each code sample earns its place for a code-generation tool, and the core instruction is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

It covers setup, state, pipes, charts, and layout guidance, which is unusually complete for a two-parameter tool. The lack of an output schema is acceptable since the output is a rendered UI, but the phantom 'sandbox' parameter and lack of explicit error/return behavior keep it from a perfect score.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, and the description compensates by explaining 'code' as building a Prefab component tree and 'data' as injected sandbox variables, reinforced by extensive examples. However, it documents a 'sandbox' argument that does not appear in the input schema, which could mislead an agent into passing an unsupported parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence states a specific action and resource: 'Execute Prefab Python code in a sandbox and render the result.' This clearly distinguishes the tool from the sibling search_prefab_components, which is for lookup rather than execution/rendering.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives concrete conditions: use the components tool for lookups, wrap with PrefabApp for streaming, use .rx for interactive UIs, and choose Grid vs Row based on layout needs. It does not explicitly contrast with sibling search_prefab_components, but the tool's role is evident from context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_prefab_componentsSearch Prefab ComponentsA

Search the Prefab component library.

Use this tool to look up exact argument names, accepted values, and usage examples before writing component code. The skill covers patterns and layout; this tool has the API details.

The query matches component names and descriptions. Space-separated terms match independently, so "Card Badge Metric" returns all three.

When a query matches a small number of components, full details (docstrings, args, examples) are shown automatically. For broad searches, a compact listing is returned instead. Use detail to override this behavior.

Args: query: Filter by component name or description. Space-separated terms are OR-matched. detail: Show full docstrings and args. Defaults to automatic (detailed for ≤5 matches, compact otherwise). limit: Max components to return in detail mode (default 8). No limit in compact mode.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMax components to return in detail mode (default 8). No limit in compact mode.
queryNoFilter by component name or description. Space-separated terms are OR-matched.
detailNoShow full docstrings and args. Defaults to automatic (detailed for ≤5 matches, compact otherwise).
componentsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full behavioral burden and succeeds. It discloses OR-matching semantics for space-separated terms, automatic detail vs compact mode behavior (≤5 matches threshold), the detail override, and the limit default in detail mode. This is substantial behavioral context beyond just stating 'search.'

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose, then adds usage context, then matching/override behavior, then an Args block. Every sentence earns its place, and the structure makes the nuanced automatic-mode behavior easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers query semantics, detail behavior, and limits, and an output schema is present, so return values need no explanation. The only meaningful omission is the 'components' parameter, which is left entirely undocumented. For a mostly simple search tool this is a minor but real gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 75%, so the baseline is 3. The description reinforces the schema's explanations of query, detail, and limit, but adds little semantic value beyond what the schema already provides. The 'components' parameter has no schema description and is never mentioned in the description, leaving a real gap for an agent trying to understand what to pass.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('Search the Prefab component library') and clarifies the tool's niche: 'this tool has the API details' while 'the skill covers patterns and layout.' This clearly distinguishes it from the sibling generate_prefab_ui and makes the search intent unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says to use the tool 'before writing component code' to look up argument names, accepted values, and usage examples. It also contrasts the tool with the broader skill. It does not explicitly name generate_prefab_ui as an alternative, but the usage context is clear enough for an agent to route correctly.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.1.0
    • First observedgenerate_prefab_ui
    • First observedsearch_prefab_components

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

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Unity Editor MCP SDK that exposes Unity Editor capabilities as MCP tools, enabling AI assistants like Claude Code to drive Unity Editor workflows through prefab inspection, asset manipulation, and preview rendering.
    MIT
  • F
    license
    B
    quality
    D
    maintenance
    An MCP server that gives AI assistants tools to generate AccelByte AGS uGUI prefabs in Unity projects, look up AccelByte Unity SDK best practices, search Bytewars C# example components, and configure SDK credentials from a Claude Code or Cursor session.
    23
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI coding agents to visually build and run AI generation pipelines on a canvas by adding, wiring, and executing nodes via MCP tools.
    Apache 2.0