Skip to content

Add prefab auto-wiring for MCP Apps#3119

Merged
jlowin merged 1 commit intofeature/appsfrom
prefab-auto-wiring
Feb 9, 2026
Merged

Add prefab auto-wiring for MCP Apps#3119
jlowin merged 1 commit intofeature/appsfrom
prefab-auto-wiring

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Feb 9, 2026

When prefab-ui is installed, tools that return prefab types (UIResponse, Component) get automatically wired to a shared renderer resource at ui://prefab/renderer.html. Three paths trigger this:

  1. Explicit app=True on @mcp.tool()
  2. Return type inference — annotating -> UIResponse or -> Component
  3. convert_result handlingTool.convert_result() detects prefab objects at runtime and serializes them as structured content
@mcp.tool(app=True)
def dashboard() -> UIResponse:
    with Column() as view:
        Heading("Sales")
        Text("Looking good")
    return UIResponse(view=view, text="Sales dashboard")

The auto-wiring happens in add_tool() on LocalProvider — it checks whether the tool needs prefab support, lazily registers the renderer resource (once), and expands meta["ui"] from True to the full AppConfig with the renderer URI and CSP. All prefab imports are guarded behind try/except ImportError so nothing breaks without the optional dependency.

Tools that return prefab types (UIResponse, Component) automatically get
wired to the shared prefab renderer resource. Works via app=True,
return type inference, or both.
@marvin-context-protocol marvin-context-protocol Bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. server Related to FastMCP server implementation or server-side functionality. labels Feb 9, 2026
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: All CI jobs are failing during the uv sync step because the prefab-ui editable dependency at ../prefab cannot be found.

Root Cause: The PR adds prefab-ui as a local editable dependency pointing to ../prefab:

[tool.uv.sources]
prefab-ui = { path = "../prefab", editable = true }

In GitHub Actions, the repository is checked out to /home/runner/work/fastmcp/fastmcp, so ../prefab resolves to /home/runner/work/fastmcp/prefab, which doesn't exist. The prefab repository needs to be checked out as a sibling to fastmcp in the workflow.

Suggested Solution: Update the GitHub Actions workflows to check out the prefab repository alongside fastmcp. Add this step before the setup-uv step in the workflow files:

- name: Checkout prefab repository
  uses: actions/checkout@v4
  with:
    repository: jlowin/prefab  # or the actual prefab repo path
    path: prefab

This assumes:

  1. The prefab repository is at jlowin/prefab (or adjust to the correct repo path)
  2. The prefab repo is public or the workflow has access to it

Alternative Solution: If prefab-ui should be installed from PyPI or a git URL during CI instead of using a local editable install, update the [tool.uv.sources] section to use a different source for CI environments.

Detailed Analysis

Error from logs (all jobs):

error: Failed to generate package metadata for \`prefab-ui @ editable+../prefab\`
  Caused by: Distribution not found at: file:///home/runner/work/fastmcp/prefab

Files modified in PR:

  • pyproject.toml: Added apps = ["prefab-ui>=0.1.0"] to [project.optional-dependencies]
  • pyproject.toml: Added prefab-ui = { path = "../prefab", editable = true } to [tool.uv.sources]
  • Multiple source files import from prefab_ui with proper try/except ImportError guards
  • tests/test_apps_prefab.py: New tests that import prefab_ui

The editable dependency works fine for local development where the prefab directory exists as a sibling, but fails in CI where only fastmcp is checked out.

Related Files

Workflow files to update:

  • .github/workflows/*.yml (all test workflows that run uv sync)

Configuration files:

  • pyproject.toml: Line 107-108 contains the [tool.uv.sources] configuration

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2936e25665

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pyproject.toml
Comment on lines +107 to +108
[tool.uv.sources]
prefab-ui = { path = "../prefab", editable = true }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove hardcoded local prefab-ui source override

With [tool.uv.sources] prefab-ui = { path = "../prefab", editable = true } in the repo config, uv sync will always try to resolve prefab-ui from a sibling ../prefab directory. That path doesn’t exist for typical CI or fresh checkouts, so the required workflow in this repo (running uv sync before tests) will fail even though prefab-ui is available on PyPI. This is a regression introduced by the commit because it forces a local-only source override instead of a normal dependency.

Useful? React with 👍 / 👎.

@jlowin jlowin added feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues. and removed enhancement Improvement to existing functionality. For issues and smaller PR improvements. labels Feb 9, 2026
@jlowin jlowin merged commit aafd26d into feature/apps Feb 9, 2026
5 of 12 checks passed
@jlowin jlowin deleted the prefab-auto-wiring branch February 9, 2026 02:10
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 9, 2026

Walkthrough

This PR adds prefab UI support to FastMCP by introducing new MCP example applications demonstrating chart and data table components, updating resource metadata propagation to include meta fields, and integrating prefab UI type handling throughout the tool parsing and decoration pipeline. The implementation uses lazy loading to avoid hard dependencies on the prefab_ui package, with runtime checks guarding all prefab-related functionality.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description provides comprehensive technical context explaining the three triggering paths, implementation details, and includes a code example. However, it does not follow the required template structure with Contributors and Review checklists. Consider following the repository's PR description template by including the Contributors Checklist and Review Checklist sections with appropriate checkbox items marked.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add prefab auto-wiring for MCP Apps' clearly and concisely summarizes the main objective: adding automatic wiring functionality for prefab UI components in MCP applications.
Docstring Coverage ✅ Passed Docstring coverage is 84.21% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch prefab-auto-wiring

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/fastmcp/resources/types.py (1)

98-99: ⚠️ Potential issue | 🟡 Minor

Inconsistent meta propagation: FileResource, HttpResource, and DirectoryResource don't pass meta.

TextResource and BinaryResource forward self.meta to ResourceContent, but the other three concrete Resource subclasses in this file still construct ResourceContent without it. Since the base Resource model has a meta field (inherited from FastMCPComponent), these subclasses silently drop it.

Proposed fix
 # FileResource.read
             return ResourceResult(
-                contents=[ResourceContent(content=content, mime_type=self.mime_type)]
+                contents=[ResourceContent(content=content, mime_type=self.mime_type, meta=self.meta)]
             )

 # HttpResource.read
             return ResourceResult(
                 contents=[
-                    ResourceContent(content=response.text, mime_type=self.mime_type)
+                    ResourceContent(content=response.text, mime_type=self.mime_type, meta=self.meta)
                 ]
             )

 # DirectoryResource.read
             return ResourceResult(
-                contents=[ResourceContent(content=content, mime_type=self.mime_type)]
+                contents=[ResourceContent(content=content, mime_type=self.mime_type, meta=self.meta)]
             )
🧹 Nitpick comments (3)
src/fastmcp/server/providers/local_provider/decorators/tools.py (2)

185-188: Mutating tool meta after _add_component — verify this is safe.

_maybe_apply_prefab_ui is called after the tool is already stored in provider._components (line 185). Since _expand_prefab_ui_meta mutates tool.meta in-place (line 110), this works because the provider holds a reference to the same object. However, if any downstream code copies/snapshots tool metadata at registration time, the expanded meta would be missed. This ordering is worth a brief comment.

Alternative: apply prefab wiring before registration
-        self._add_component(tool)
-        if not enabled:
-            self.disable(keys={tool.key})
-        _maybe_apply_prefab_ui(self, tool)
+        _maybe_apply_prefab_ui(self, tool)
+        self._add_component(tool)
+        if not enabled:
+            self.disable(keys={tool.key})

41-58: get_args import can be hoisted to module level.

The from typing import get_args on line 52 is inside the function body. It's a stdlib import with no cost, and hoisting it to the top would follow the existing import style.

examples/apps/chart_server.py (1)

71-76: Consider using Literal for the curve parameter.

Using Literal["linear", "smooth", "step"] instead of str would give MCP clients an enum in the input schema, improving discoverability and preventing invalid values.

Proposed change
+from typing import Literal
+
 `@mcp.tool`(app=True)
-def sales_trend(curve: str = "linear") -> UIResponse:
+def sales_trend(curve: Literal["linear", "smooth", "step"] = "linear") -> UIResponse:

Comment on lines +119 to +131
@mcp.tool(app=True)
def list_team(department: str | None = None) -> UIResponse:
"""Browse the team directory with sorting and search.

Args:
department: Filter by department (e.g. "Engineering", "Design").
Leave empty to show everyone.
"""
if department:
rows = [e for e in EMPLOYEES if e["role"].lower() == department.lower()]
else:
rows = EMPLOYEES

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Data field "role" is labeled "Department" everywhere else — naming mismatch.

The parameter is department, the docstring says "department", and the DataTableColumn header on line 146 reads "Department", but the filter and data key is "role". This inconsistency in an example file could confuse users who copy this pattern. Consider renaming the dict key to "department" in EMPLOYEES (and updating the DataTableColumn key accordingly), or renaming the parameter/docstring to "role".

Proposed fix: rename the key in the data
 EMPLOYEES = [
     {
         "name": "Alice Chen",
-        "role": "Engineering",
+        "department": "Engineering",
         "level": "Senior",

(Apply to all entries, then update the filter and DataTable column key)

-        rows = [e for e in EMPLOYEES if e["role"].lower() == department.lower()]
+        rows = [e for e in EMPLOYEES if e["department"].lower() == department.lower()]
-                DataTableColumn(key="role", header="Department", sortable=True),
+                DataTableColumn(key="department", header="Department", sortable=True),

Comment on lines +71 to +75
)

renderer_key = f"resource:{PREFAB_RENDERER_URI}@"
if renderer_key in provider._components:
return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# First, let's look at the actual file to understand the context
echo "=== File context ==="
cat -n src/fastmcp/server/providers/local_provider/decorators/tools.py | sed -n '65,85p'

echo ""
echo "=== Searching for KEY_PREFIX usage ==="
rg -n 'KEY_PREFIX' --type=py -C2

echo ""
echo "=== Searching for .key property/method ==="
rg -nP '^\s+def key\b|^\s+@property' --type=py -A3 | head -80

Repository: jlowin/fastmcp

Length of output: 13058


🏁 Script executed:

#!/bin/bash

# Search for how _components keys are constructed
echo "=== Searching for _components key construction ==="
rg -n '_components\[' --type=py -C2

echo ""
echo "=== Searching for resource key format ==="
rg -n 'resource:' --type=py -C2

echo ""
echo "=== Search for PREFAB_RENDERER_URI ==="
rg -n 'PREFAB_RENDERER_URI' --type=py -C2

Repository: jlowin/fastmcp

Length of output: 50371


🏁 Script executed:

#!/bin/bash

# Look at FastMCPComponent to understand key format
echo "=== FastMCPComponent class ==="
fd 'component' --type=f --name='*.py' | xargs grep -l 'class FastMCPComponent' | head -5

# Find where keys are generated
echo ""
echo "=== Searching for key generation in _add_component ==="
rg -n '_add_component' --type=py -B2 -A10 | head -100

Repository: jlowin/fastmcp

Length of output: 9854


🏁 Script executed:

#!/bin/bash

# Check the Resource.make_key method specifically
echo "=== Resource class key construction ==="
rg -n 'class Resource|KEY_PREFIX|def make_key' src/fastmcp/resources/resource.py -A2

# Look at how the renderer resource is created in the actual function
echo ""
echo "=== Full _ensure_prefab_renderer function ==="
cat -n src/fastmcp/server/providers/local_provider/decorators/tools.py | sed -n '57,100p'

Repository: jlowin/fastmcp

Length of output: 2277


🏁 Script executed:

#!/bin/bash

# Check if Resource.make_key is used anywhere in similar scenarios
echo "=== Usage of Resource.make_key in codebase ==="
rg 'Resource\.make_key|Tool\.make_key' --type=py -B2 -A2

# Also check if component.key property is used to check existence
echo ""
echo "=== Pattern of checking component existence ==="
rg 'in provider\._components|_components\[.*\.key' --type=py -B2 -A1 | head -50

Repository: jlowin/fastmcp

Length of output: 6569


🏁 Script executed:

#!/bin/bash

# Check what happens when _add_component encounters a duplicate
echo "=== _add_component duplicate handling ==="
sed -n '178,205p' src/fastmcp/server/providers/local_provider/local_provider.py

# Check PREFAB_RENDERER_URI definition
echo ""
echo "=== PREFAB_RENDERER_URI definition ==="
rg -n 'PREFAB_RENDERER_URI' src/fastmcp/server/providers/local_provider/decorators/tools.py | head -5
rg -n 'PREFAB_RENDERER_URI.*=' --type=py -B2 -A2

Repository: jlowin/fastmcp

Length of output: 1906


Use Resource.make_key() instead of hardcoded key format.

The manual key f"resource:{PREFAB_RENDERER_URI}@" hardcodes the resource prefix and format. If Resource.KEY_PREFIX or key generation logic changes, this check will silently fail. The codebase already uses Resource.make_key() for similar scenarios (e.g., in local_provider.py). Replace with:

renderer_key = f"{Resource.make_key(PREFAB_RENDERER_URI)}@"
if renderer_key in provider._components:
    return

Alternatively, use a provider-level flag as suggested if you prefer to avoid component key format dependency entirely.

jlowin added a commit that referenced this pull request Feb 27, 2026
* Add prefab auto-wiring for MCP Apps (#3119)

Tools that return prefab types (UIResponse, Component) automatically get
wired to the shared prefab renderer resource. Works via app=True,
return type inference, or both.

* Prefab compatibility updates

* Use published prefab-ui >=0.6.0, remove local source override

* Migrate UIResponse to PrefabApp for Prefab UI integration

PrefabApp is a pure data object with to_json(), html(), and csp()
methods. Tools can return PrefabApp, bare Components, or
ToolResult with structured_content for custom LLM fallback text.

* Add Prefab UI apps documentation

* Add mini apps and full apps documentation pages

Mini apps covers the common single-screen patterns: charts (bar, line,
area, pie), data tables with sorting/search/pagination, forms (manual
and Pydantic-generated), status displays, conditional content, and
layout composition with tabs and accordions.

Full apps covers multi-page applications using Pages/Page components,
shared state across pages, and using ToolCall with result_key for
server-driven state updates.

* Reframe apps docs around motivation, add generative UIs page

The docs now lead with the problem — MCP tools stuff data into the LLM
context window, and building HTML/JS/CSS frontends is a non-starter for
Python developers — before introducing Prefab as the solution. Mini apps
are framed as the primary use case: focused, single-purpose UIs that
present data visually and collect structured input.

New generative UIs page covers the concept of LLMs producing component
JSON directly, enabling adaptive dashboards, tailored forms, and
exploratory workflows.

* Tag Prefab docs pages as SOON instead of NEW

* Rename Low-Level API to Custom HTML Apps

The page is about using the MCP Apps extension directly, not a FastMCP
or Prefab internal API. Reframed to make clear this is the open MCP
protocol with FastMCP providing convenience wrappers.

* Tighten apps docs and widen content area

Strip editorial motivation from all app doc pages — let code examples
do the talking. Add content-area max-width override (44rem) to style.css.

* Restructure apps docs, fix code issues

Rename Prefab UI → Prefab Apps, mini-apps → patterns, remove
generative-uis and full-apps pages. Rewrite prefab page to lead with
what users do (declare a UI, return it) before explaining internals.
Patterns page now has fully self-contained copy-pasteable examples with
explicit imports and links to prefab docs. Forms show the two-tool
pattern (form + handler). Add patterns_server.py example.

Code fixes: move get_args to module-level import, remove dead
AuthCheckCallable type alias, fix ToolCall→CallTool in all docs.

* Remove unused ToolResult import from chart_server

* Handle composite Prefab types in type inference and schema suppression

_has_prefab_return_type and the output schema suppression logic only
checked bare classes, missing unions (Column | None) and Annotated
wrappers (Annotated[PrefabApp | None, ...]). Recurse through Union,
types.UnionType, and Annotated to detect Prefab types in composite
annotations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues. server Related to FastMCP server implementation or server-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant