Skip to content

Rename ui= to app= and consolidate ToolUI/ResourceUI into AppConfig#3117

Merged
jlowin merged 3 commits intomainfrom
rename-ui-to-app
Feb 9, 2026
Merged

Rename ui= to app= and consolidate ToolUI/ResourceUI into AppConfig#3117
jlowin merged 3 commits intomainfrom
rename-ui-to-app

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Feb 9, 2026

The MCP Apps decorator parameter was ui=ToolUI(...) / ui=ResourceUI(...), which didn't match the feature's name ("Apps") or the established task=True/TaskConfig convention. This renames the user-facing API to app=True / app=AppConfig(...) and merges the two config classes into one.

# Before
@mcp.tool(ui=ToolUI(resource_uri="ui://my-app/view.html"))
@mcp.resource("ui://my-app/view.html", ui=ResourceUI(csp=ResourceCSP(...)))

# After
@mcp.tool(app=AppConfig(resource_uri="ui://my-app/view.html"))
@mcp.resource("ui://my-app/view.html", app=AppConfig(csp=ResourceCSP(...)))

Resources now validate that tool-only fields (resource_uri, visibility) aren't set on AppConfig when used with @mcp.resource(). Wire format (meta["ui"], _meta.ui) is unchanged — this is purely a user-facing API rename. Breaking change from beta 2, noted in v3-features tracking.

@marvin-context-protocol marvin-context-protocol Bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. labels Feb 9, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 9, 2026

Walkthrough

This change replaces the public UI models ToolUI and ResourceUI with a single AppConfig model in fastmcp.server.apps, and renames the decorator parameter from ui= to app= on @mcp.tool() and @mcp.resource() (the wire format in meta["ui"] is unchanged). AppConfig consolidates fields (resource_uri, visibility, csp, permissions, domain, prefers_border), adds validation to disallow tool-only fields on resources, and introduces app_config_to_meta_dict for wire-format conversion. Imports, examples, and runtime wiring were updated to the new AppConfig-based API.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: renaming the ui= parameter to app= and consolidating ToolUI/ResourceUI into AppConfig.
Description check ✅ Passed The PR description comprehensively covers the change, includes before/after code examples, explains the rationale, and notes the breaking change status. However, the Contributors Checklist items are not explicitly checked.

✏️ 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 rename-ui-to-app

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
src/fastmcp/server/apps.py (1)

115-119: Dict passthrough returns the caller's mutable reference.

When app is already a dict, it's returned as-is. If any downstream code mutates the result (e.g., popping keys), it will silently modify the caller's original dict. Consider returning a shallow copy for safety.

Proposed defensive copy
 def app_config_to_meta_dict(app: AppConfig | dict[str, Any]) -> dict[str, Any]:
     """Convert an AppConfig or dict to the wire-format dict for ``meta["ui"]``."""
     if isinstance(app, AppConfig):
         return app.model_dump(by_alias=True, exclude_none=True)
-    return app
+    return dict(app)

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

🧹 Nitpick comments (4)
src/fastmcp/tools/function_tool.py (1)

76-76: Consider a narrower type annotation for app.

Any works but is looser than the corresponding parameter in server.py (AppConfig | dict[str, Any] | bool | None). If avoiding an import of AppConfig here, a TYPE_CHECKING guard would preserve the type information for static analysis without runtime coupling.

That said, this is a metadata carrier on an internal dataclass, so Any is pragmatic.

src/fastmcp/server/providers/local_provider/decorators/tools.py (1)

54-63: Duplicated app→meta merging logic across two sites.

This merging logic (lines 54–63) is nearly identical to server.py lines 1510–1516. Currently they don't conflict because server.tool() merges first and passes app=None through ToolMeta, while this path handles ToolMeta.app set directly. However, having two independent merge sites increases the risk of future divergence.

Consider extracting a shared helper (e.g., merge_app_into_meta(meta, app)) to keep the logic in one place.

src/fastmcp/server/server.py (1)

1642-1654: Resource validation is bypassed when app is a raw dict.

The resource_uri/visibility guards only fire for isinstance(app, AppConfig). A user passing app={"resourceUri": "...", "visibility": ["app"]} would skip validation and silently produce incorrect wire metadata on a resource.

This may be acceptable since raw dicts are the "expert escape hatch," but it's worth noting. If you want parity, you could also check for those keys in the dict path:

Proposed fix (optional)
         if isinstance(app, AppConfig):
             if app.resource_uri is not None:
                 raise ValueError(
                     "resource_uri cannot be set on resources — "
                     "the resource itself is the UI. "
                     "Use resource_uri on tools to point to a UI resource."
                 )
             if app.visibility is not None:
                 raise ValueError(
                     "visibility cannot be set on resources — it only applies to tools."
                 )
+        elif isinstance(app, dict):
+            if "resourceUri" in app or "resource_uri" in app:
+                raise ValueError(
+                    "resource_uri cannot be set on resources — "
+                    "the resource itself is the UI. "
+                    "Use resource_uri on tools to point to a UI resource."
+                )
+            if "visibility" in app:
+                raise ValueError(
+                    "visibility cannot be set on resources — it only applies to tools."
+                )
docs/development/v3-notes/v3-features.mdx (1)

1-3: Frontmatter is missing the description field.

Per coding guidelines, every MDX documentation page must begin with YAML frontmatter containing both title and description. As per coding guidelines, "Every MDX documentation page must begin with YAML frontmatter containing title and description."

Comment thread docs/development/v3-notes/v3-features.mdx
Comment thread src/fastmcp/server/apps.py Outdated
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

marvin-context-protocol Bot commented Feb 9, 2026

Test Failure Analysis

Summary: OpenAPI performance test failed on Ubuntu due to CI runner load - initialization took 215ms instead of expected <100ms.

Root Cause: The test test_server_performance_no_latency in tests/server/providers/openapi/test_comprehensive.py:736 asserts that OpenAPIProvider initialization completes in under 100ms. On this run, it took ~215ms (2.15x the threshold), likely due to:

  1. CI runner CPU contention - this run had 4214 other tests executing in parallel
  2. The test measures wall-clock time including Python import overhead and httpx.AsyncClient initialization
  3. Hard performance thresholds are inherently flaky on shared CI infrastructure

This failure is unrelated to PR #3117 (ui→app rename), which only touches app configuration code and doesn't affect OpenAPIProvider initialization logic.

Suggested Solution:

Option 1: Increase the threshold (test_comprehensive.py:736)

# Current:
assert initialization_time < 0.1  # Should be under 100ms

# Suggested:
assert initialization_time < 0.3  # Should be under 300ms

Option 2: Mark as flaky and allow retries

@pytest.mark.flaky(reruns=2)
async def test_server_performance_no_latency(self, comprehensive_openapi_spec):

Option 3: Remove hard threshold - Test that initialization happens without errors rather than enforcing a specific time limit, since the actual performance varies widely by environment.

My recommendation: Option 1 (increase threshold to 300ms) is simplest and still catches genuine performance regressions while being more tolerant of CI variability.

Detailed Analysis

Test Failure Log

tests/server/providers/openapi/test_comprehensive.py:736: AssertionError
assert 0.21575212478637695 < 0.1

The test times this code block:

async with httpx.AsyncClient(base_url="https://api.example.com") as client:
    provider = OpenAPIProvider(
        openapi_spec=comprehensive_openapi_spec,
        client=client,
    )

Why This Happened Now

  • This PR doesn't touch OpenAPIProvider code at all
  • The previous run (f7cdd20) passed with the same test
  • Performance tests are inherently flaky on shared CI runners
  • 100ms is a very tight threshold for operations that include async context manager setup

Historical Context

Recent test runs on main branch show mixed results, with occasional failures suggesting timing sensitivity is a known issue.

Related Files
  • tests/server/providers/openapi/test_comprehensive.py:719-742 - The flaky performance test
  • src/fastmcp/server/providers/openapi/__init__.py - OpenAPIProvider implementation (not modified by this PR)

🤖 Updated analysis for workflow run #21809237545 by Marvin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. enhancement Improvement to existing functionality. For issues and smaller PR improvements.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant