Skip to content

Add MCP Apps Phase 1 — SDK compatibility (SEP-1865)#3009

Merged
jlowin merged 6 commits intomainfrom
feat/mcp-apps-phase1
Jan 29, 2026
Merged

Add MCP Apps Phase 1 — SDK compatibility (SEP-1865)#3009
jlowin merged 6 commits intomainfrom
feat/mcp-apps-phase1

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Jan 28, 2026

FastMCP servers can now participate in the MCP Apps ecosystem. Phase 1 is the wire-level plumbing — extension negotiation, typed UI metadata on tools and resources, and the ui:// resource scheme. Users who bring their own HTML bundles can serve them as spec-compliant MCP Apps without any additional framework.

from fastmcp import FastMCP
from fastmcp.server.apps import ToolUI, ResourceUI

mcp = FastMCP("My Server")

@mcp.resource("ui://my-app/view.html")
def app_html() -> str:
    return open("./dist/index.html").read()

@mcp.tool(ui=ToolUI(resource_uri="ui://my-app/view.html", visibility=["app"]))
async def delete_user(id: str) -> dict:
    return {"deleted": True}

The server advertises io.modelcontextprotocol/ui in capabilities, ui:// resources default to the correct MIME type, and _meta.ui flows through to all clients (per the spec, visibility enforcement is the host's job, not the server's). Tools can detect client extension support at runtime via ctx.client_supports_extension(extension_id) — a general-purpose method that scales to future extensions.

Phase 2 (component DSL, renderer, FastMCPApp) builds on this foundation.

@jlowin jlowin added the feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues. label Jan 28, 2026
@marvin-context-protocol marvin-context-protocol Bot added server Related to FastMCP server implementation or server-side functionality. v3 Targeted for FastMCP 3 labels Jan 28, 2026
@jlowin jlowin added the DON'T MERGE PR is not ready for merging. Used by authors to prevent premature merging. label Jan 28, 2026
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

marvin-context-protocol Bot commented Jan 28, 2026

Test Failure Analysis

Summary: Two tests are failing because resolve_ui_mime_type() is called before decorator validation, causing an AttributeError instead of the expected TypeError when the @resource decorator is used without parentheses.

Root Cause: In src/fastmcp/server/server.py:1596, the new resolve_ui_mime_type(uri, mime_type) function is called immediately when the decorator is invoked. When the decorator is used incorrectly (without parentheses), uri receives the function object instead of a string. The function then tries to call uri.lower() in src/fastmcp/server/apps.py:91, which fails with AttributeError: 'function' object has no attribute 'lower'.

The validation check that should raise the helpful TypeError happens later in src/fastmcp/server/providers/local_provider/decorators/resources.py:160-165, but it never gets reached because the AttributeError is raised first.

Suggested Solution: Move the resolve_ui_mime_type() call to happen AFTER the validation check, or add defensive type checking to resolve_ui_mime_type() to handle non-string inputs gracefully.

Option 1 (Recommended): Add type guard to resolve_ui_mime_type:

def resolve_ui_mime_type(uri: str, explicit_mime_type: str | None) -> str | None:
    """Return the appropriate MIME type for a resource URI."""
    if explicit_mime_type is not None:
        return explicit_mime_type
    # Guard against incorrect decorator usage (uri will be a function object)
    if not isinstance(uri, str):
        return None
    # Case-insensitive scheme check per RFC 3986
    if uri.lower().startswith("ui://"):
        return UI_MIME_TYPE
    return None

Option 2: Move the call to resolve_ui_mime_type() inside the inner_decorator function in server.py, after the validation check in LocalProvider.resource(). However, this would require changes to the LocalProvider interface.

Detailed Analysis

Failed Tests

  • tests/server/providers/test_local_provider_resources.py::TestResourceDecorator::test_resource_decorator_incorrect_usage
  • tests/server/providers/test_local_provider_resources.py::TestTemplateDecorator::test_template_decorator_incorrect_usage

Stack Trace

@mcp.resource  # Missing parentheses
def get_data() -> str:
    return "Hello, world!"

# This triggers:
src/fastmcp/server/server.py:1596: in resource
    mime_type = resolve_ui_mime_type(uri, mime_type)
src/fastmcp/server/apps.py:91: in resolve_ui_mime_type
    if uri.lower().startswith("ui://"):
        ^^^^^^^^^
E   AttributeError: 'function' object has no attribute 'lower'

Expected Behavior

The tests expect this error:

TypeError: The @resource decorator was used incorrectly. 
It requires a URI as the first argument. 
Use @resource('uri') instead of @resource

This error is defined in src/fastmcp/server/providers/local_provider/decorators/resources.py:160-165 but never gets reached.

Related Files
  • src/fastmcp/server/apps.py:91 - Where the AttributeError is raised
  • src/fastmcp/server/server.py:1596 - Where resolve_ui_mime_type is called too early
  • src/fastmcp/server/providers/local_provider/decorators/resources.py:160-165 - Where the proper validation should happen
  • tests/server/providers/test_local_provider_resources.py:315-324, 536-545 - The failing tests

Updated analysis for latest workflow run. Previous analyses addressed different failures.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 28, 2026

Walkthrough

Adds Phase 1 MCP Apps (SDK Compatibility): new constants UI_EXTENSION_ID, UI_MIME_TYPE; Pydantic models ToolUI and ResourceUI; helpers ui_to_meta_dict() and resolve_ui_mime_type(). Extends tool() and resource() APIs to accept ui metadata and merge it into meta["ui"]. Applies UI_MIME_TYPE default for ui:// URIs and ensures resources use resolved MIME types. Adds client_supports_extension() to Context and MiddlewareServerSession, advertises the UI extension in server capabilities, and updates docs and FileSystemProvider notes.

Possibly related PRs

  • jlowin/fastmcp PR 2822: Overlaps edits to the same v3 features documentation file (v3-features.mdx).
  • jlowin/fastmcp PR 2734: Modifies resource/template MIME-resolution and resource creation behavior, directly related to the MIME changes in template.py and function_resource.py.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding MCP Apps Phase 1 SDK support to FastMCP servers, with specific reference to the feature ticket.
Description check ✅ Passed The description provides a comprehensive overview of Phase 1 changes, includes code examples, explains the extension negotiation mechanism, and references future work.
Docstring Coverage ✅ Passed Docstring coverage is 89.47% 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

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: 0

Caution

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

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

1663-1676: Add ui parameter to prompt() decorator for consistency with tool() and resource().

The tool() and resource() decorators both have a ui parameter (ToolUI and ResourceUI respectively), but prompt() is missing this parameter. Per the requirement that changes affecting tool interactions must be adopted and applied across all major MCP object types (Tools, Resources, Resource Templates, and Prompts), the prompt() decorator should also support the ui parameter to maintain consistency. Consider adding ui: PromptUI | dict[str, Any] | None = None or clarifying the architectural reason for excluding it from prompts.

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: db962695ee

ℹ️ 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 src/fastmcp/server/server.py Outdated
Comment on lines +1590 to +1592
# Default MIME type for ui:// scheme resources
if mime_type is None and isinstance(uri, str) and uri.startswith("ui://"):
mime_type = UI_MIME_TYPE
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply ui:// MIME defaults outside FastMCP.resource

This default only runs for FastMCP.resource, so ui:// resources registered via the standalone fastmcp.resources.resource decorator (used by FileSystemProvider) or via resource templates still keep the generic text/plain default from ResourceMeta. That means UI bundles discovered through filesystem-based servers will advertise mimeType: text/plain, so MCP Apps clients that key off the MIME type won’t recognize them as UI resources. Consider moving the ui:// MIME default into a shared helper and applying it in the standalone decorator (and template path) as well.

Useful? React with 👍 / 👎.

@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: The static analysis job failed because the type checker v0.0.14 detected two unused # type: ignore comments in tests/server/test_dependencies.py.

Root Cause: The type checker has improved in v0.0.14 and no longer reports the type errors that these suppression comments were originally added for. The Depends() pattern for dependency injection now type-checks cleanly without needing to suppress any errors.

Suggested Solution: Remove the two unused # type: ignore comments:

  1. Line 70: Remove # type: ignore[invalid-parameter-default] from the greet_user function
  2. Line 201: Remove # type: ignore[assignment] from the process_data function

The code will continue to work correctly—these comments are no longer needed because the underlying type errors have been resolved.

Detailed Analysis

The CI logs show:

warning[unused-ignore-comment]: Unused blanket `type: ignore` directive
  --> tests/server/test_dependencies.py:70:83
   |
70 |     async def greet_user(name: str, user_id: int = Depends(get_user_id)) -> str:  # type: ignore[invalid-parameter-default]
   |                                                                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning[unused-ignore-comment]: Unused blanket `type: ignore` directive
   --> tests/server/test_dependencies.py:201:80
    |
201 |     def process_data(value: int, config: str = Depends(fetch_config)) -> str:  # type: ignore[assignment]
    |                                                                                ^^^^^^^^^^^^^^^^^^^^^^^^^^

This is happening because:

  1. The latest commit cfe97f94 fixed some ty errors and updated one of these comments from [assignment] to [invalid-parameter-default]
  2. The CI is using ty v0.0.14 (specified as ty>=0.0.7 in pyproject.toml)
  3. ty v0.0.14 has better type narrowing and no longer needs these suppressions
Related Files
  • tests/server/test_dependencies.py:70 - Remove # type: ignore[invalid-parameter-default]
  • tests/server/test_dependencies.py:201 - Remove # type: ignore[assignment]

@jlowin jlowin added the mcp apps Related to MCP Apps - user-facing applications with frontend bundles served by MCP servers. label Jan 29, 2026
Per review feedback: the ui:// MIME type default was only being applied
in FastMCP.resource(), leaving standalone @resource decorators and
resource templates with the generic text/plain default.

Now resolve_ui_mime_type() is used in all three paths, so ui:// resources
are correctly identified regardless of how they're registered.
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: 1

🧹 Nitpick comments (2)
src/fastmcp/server/apps.py (1)

88-91: Consider case-insensitive URI scheme matching.

Per RFC 3986, URI schemes are case-insensitive. While ui:// is likely always lowercase in practice, a case-insensitive check would be more robust.

♻️ Optional: Case-insensitive scheme check
-    if uri.startswith("ui://"):
+    if uri.lower().startswith("ui://"):
         return UI_MIME_TYPE
src/fastmcp/server/server.py (1)

1595-1602: Redundant type check for uri parameter.

The isinstance(uri, str) check at line 1596 is always true since the uri parameter is typed as str. The check can be removed.

♻️ Optional: Remove redundant isinstance check
         # Apply default MIME type for ui:// scheme resources
-        if isinstance(uri, str):
-            mime_type = resolve_ui_mime_type(uri, mime_type)
+        mime_type = resolve_ui_mime_type(uri, mime_type)

Comment thread docs/development/v3-notes/v3-features.mdx
@jlowin jlowin removed the DON'T MERGE PR is not ready for merging. Used by authors to prevent premature merging. label Jan 29, 2026
@jlowin jlowin merged commit d52534a into main Jan 29, 2026
13 checks passed
@jlowin jlowin deleted the feat/mcp-apps-phase1 branch January 29, 2026 21:28
gfortaine pushed a commit to gfortaine/fastmcp that referenced this pull request Feb 4, 2026
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. mcp apps Related to MCP Apps - user-facing applications with frontend bundles served by MCP servers. server Related to FastMCP server implementation or server-side functionality. v3 Targeted for FastMCP 3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant