Move app modules to fastmcp.apps package#3616
Conversation
FastMCPApp and AppConfig now live under fastmcp.apps as a top-level package, reflecting that apps are a first-class pillar alongside servers and clients. - fastmcp.apps.app — FastMCPApp - fastmcp.apps.config — AppConfig, ResourceCSP, etc. - fastmcp.apps — re-exports config (FastMCPApp excluded to avoid circular imports with server; import from fastmcp.apps.app or fastmcp) - fastmcp.server.app and fastmcp.server.apps remain as backward- compatible re-export shims
Creates fastmcp.apps as a top-level package for all app-related code: - fastmcp.apps.app — FastMCPApp - fastmcp.apps.config — AppConfig, ResourceCSP, etc. - fastmcp.apps — re-exports everything Moves UI_MIME_TYPE and resolve_ui_mime_type to fastmcp.utilities.mime to break the circular import between resources and apps. Old import paths (fastmcp.server.app, fastmcp.server.apps) emit DeprecationWarnings via __getattr__ and re-export from the new locations.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b456f32ea8
ℹ️ 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".
| def __getattr__(name: str) -> object: | ||
| warnings.warn( | ||
| f"Importing {name!r} from 'fastmcp.server.app' is deprecated. " | ||
| "Use 'fastmcp.apps.app' or 'from fastmcp import FastMCPApp' instead.", | ||
| DeprecationWarning, |
There was a problem hiding this comment.
Preserve wildcard imports in deprecated server.app shim
This compatibility module now only exposes __getattr__, so from fastmcp.server.app import * no longer brings in FastMCPApp (or the other previously exported symbols) and instead imports almost nothing. That is a runtime backward-compatibility break for code still on the deprecated path, even though this migration intends old imports to keep working while warning. Re-exporting an __all__ (or explicit symbol bindings) from fastmcp.apps.app would preserve expected behavior.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 27062a33cc
ℹ️ 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".
| def __getattr__(name: str) -> object: | ||
| warnings.warn( | ||
| f"Importing {name!r} from 'fastmcp.server.apps' is deprecated. " | ||
| "Use 'from fastmcp.apps import ...' instead.", | ||
| DeprecationWarning, | ||
| stacklevel=2, | ||
| ) | ||
| microphone: dict[str, Any] | None = Field( | ||
| default=None, description="Request microphone access" | ||
| ) | ||
| geolocation: dict[str, Any] | None = Field( | ||
| default=None, description="Request geolocation access" | ||
| ) | ||
| clipboard_write: dict[str, Any] | None = Field( | ||
| default=None, | ||
| alias="clipboardWrite", | ||
| description="Request clipboard-write access", | ||
| ) | ||
|
|
||
| model_config = {"populate_by_name": True, "extra": "allow"} | ||
|
|
||
|
|
||
| class AppConfig(BaseModel): | ||
| """Configuration for MCP App tools and resources. | ||
|
|
||
| Controls how a tool or resource participates in the MCP Apps extension. | ||
| On tools, ``resource_uri`` and ``visibility`` specify which UI resource | ||
| to render and where the tool appears. On resources, those fields must | ||
| be left unset (the resource itself is the UI). | ||
|
|
||
| All fields use ``exclude_none`` serialization so only explicitly-set | ||
| values appear on the wire. Aliases match the MCP Apps wire format | ||
| (camelCase). | ||
| """ | ||
|
|
||
| resource_uri: str | None = Field( | ||
| default=None, | ||
| alias="resourceUri", | ||
| description="URI of the UI resource (typically ui:// scheme). Tools only.", | ||
| ) | ||
| visibility: list[Literal["app", "model"]] | None = Field( | ||
| default=None, | ||
| description="Where this tool is visible: 'app', 'model', or both. Tools only.", | ||
| ) | ||
| csp: ResourceCSP | None = Field( | ||
| default=None, description="Content Security Policy for the app iframe" | ||
| ) | ||
| permissions: ResourcePermissions | None = Field( | ||
| default=None, description="Iframe sandbox permissions" | ||
| ) | ||
| domain: str | None = Field(default=None, description="Domain for the iframe") | ||
| prefers_border: bool | None = Field( | ||
| default=None, | ||
| alias="prefersBorder", | ||
| description="Whether the UI prefers a visible border", | ||
| ) | ||
|
|
||
| model_config = {"populate_by_name": True, "extra": "allow"} | ||
|
|
||
|
|
||
| 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 | ||
|
|
||
|
|
||
| def resolve_ui_mime_type(uri: str, explicit_mime_type: str | None) -> str | None: | ||
| """Return the appropriate MIME type for a resource URI. | ||
|
|
||
| For ``ui://`` scheme resources, defaults to ``UI_MIME_TYPE`` when no | ||
| explicit MIME type is provided. This ensures UI resources are correctly | ||
| identified regardless of how they're registered (via FastMCP.resource, | ||
| the standalone @resource decorator, or resource templates). | ||
|
|
||
| Args: | ||
| uri: The resource URI string | ||
| explicit_mime_type: The MIME type explicitly provided by the user | ||
| from fastmcp.apps import config as _config | ||
|
|
||
| Returns: | ||
| The resolved MIME type (explicit value, UI default, or None) | ||
| """ | ||
| if explicit_mime_type is not None: | ||
| return explicit_mime_type | ||
| # Case-insensitive scheme check per RFC 3986 | ||
| if uri.lower().startswith("ui://"): | ||
| return UI_MIME_TYPE | ||
| return None | ||
| return getattr(_config, name) |
There was a problem hiding this comment.
Preserve wildcard imports in deprecated server.apps shim
The deprecation shim now exposes only __getattr__, so from fastmcp.server.apps import * no longer imports legacy symbols like AppConfig, ResourceCSP, or UI_MIME_TYPE and instead pulls almost nothing from the module namespace. This is a runtime backward-compatibility break for code that still relies on the deprecated path while migrating, despite the intent that old imports continue to work with warnings.
Useful? React with 👍 / 👎.
Apps are a first-class pillar alongside servers and clients. Their code should live at the top level, not buried under
fastmcp.server.This moves
FastMCPAppandAppConfig/ResourceCSPtofastmcp.apps:The old paths (
fastmcp.server.app,fastmcp.server.apps) still work but emitDeprecationWarning. The top-levelfrom fastmcp import FastMCPAppis unchanged.Breaking the circular import required extracting
UI_MIME_TYPEandresolve_ui_mime_typetofastmcp.utilities.mime— the resources package needed these from apps, and apps imported Provider from the server package, which imported resources. Moving the leaf dependency out of theappspackage breaks the cycle cleanly.