From 285ec34aa3c202d65aa02ba31ecf0b230101c292 Mon Sep 17 00:00:00 2001
From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Date: Thu, 14 May 2026 22:12:31 -0400
Subject: [PATCH] chore: remove stale src/ and other files deleted in main
---
.github/workflows/publish.yml | 26 -
docs/apps/interactive-apps.mdx | 538 ----
docs/apps/patterns.mdx | 431 ---
docs/apps/providers/generative.mdx | 74 -
examples/auth/authkit_dcr/README.md | 25 -
examples/auth/authkit_dcr/client.py | 33 -
examples/auth/authkit_dcr/server.py | 32 -
.../mcp/prompts/assistant.py | 39 -
.../mcp/resources/config.py | 55 -
.../mcp/tools/calculator.py | 24 -
.../filesystem-provider/mcp/tools/greeting.py | 28 -
src/fastmcp/__init__.py | 59 -
src/fastmcp/apps/__init__.py | 18 -
src/fastmcp/apps/app.py | 428 ---
src/fastmcp/apps/approval.py | 198 --
src/fastmcp/apps/choice.py | 141 -
src/fastmcp/apps/config.py | 177 --
src/fastmcp/apps/file_upload.py | 393 ---
src/fastmcp/apps/form.py | 184 --
src/fastmcp/apps/generative.py | 199 --
src/fastmcp/cli/__init__.py | 3 -
src/fastmcp/cli/__main__.py | 5 -
src/fastmcp/cli/apps_dev.py | 1806 -------------
src/fastmcp/cli/auth.py | 13 -
src/fastmcp/cli/cimd.py | 218 --
src/fastmcp/cli/cli.py | 1112 --------
src/fastmcp/cli/client.py | 986 -------
src/fastmcp/cli/discovery.py | 375 ---
src/fastmcp/cli/generate.py | 808 ------
src/fastmcp/cli/install/__init__.py | 26 -
src/fastmcp/cli/install/claude_code.py | 242 --
src/fastmcp/cli/install/claude_desktop.py | 232 --
src/fastmcp/cli/install/cursor.py | 321 ---
src/fastmcp/cli/install/gemini_cli.py | 239 --
src/fastmcp/cli/install/goose.py | 209 --
src/fastmcp/cli/install/mcp_json.py | 191 --
src/fastmcp/cli/install/shared.py | 195 --
src/fastmcp/cli/install/stdio.py | 156 --
src/fastmcp/cli/run.py | 485 ----
src/fastmcp/cli/tasks.py | 110 -
src/fastmcp/client/__init__.py | 30 -
src/fastmcp/client/auth/__init__.py | 4 -
src/fastmcp/client/auth/bearer.py | 17 -
src/fastmcp/client/auth/oauth.py | 430 ---
src/fastmcp/client/client.py | 875 -------
src/fastmcp/client/elicitation.py | 80 -
src/fastmcp/client/logging.py | 54 -
src/fastmcp/client/messages.py | 128 -
src/fastmcp/client/mixins/__init__.py | 13 -
src/fastmcp/client/mixins/prompts.py | 318 ---
src/fastmcp/client/mixins/resources.py | 370 ---
src/fastmcp/client/mixins/task_management.py | 157 --
src/fastmcp/client/mixins/tools.py | 441 ----
src/fastmcp/client/oauth_callback.py | 251 --
src/fastmcp/client/progress.py | 41 -
src/fastmcp/client/roots.py | 78 -
src/fastmcp/client/sampling/__init__.py | 71 -
.../client/sampling/handlers/__init__.py | 0
.../client/sampling/handlers/anthropic.py | 447 ----
.../client/sampling/handlers/google_genai.py | 386 ---
.../client/sampling/handlers/openai.py | 508 ----
src/fastmcp/client/tasks.py | 551 ----
src/fastmcp/client/telemetry.py | 47 -
src/fastmcp/client/transports/__init__.py | 38 -
src/fastmcp/client/transports/base.py | 82 -
src/fastmcp/client/transports/config.py | 210 --
src/fastmcp/client/transports/http.py | 214 --
src/fastmcp/client/transports/inference.py | 154 --
src/fastmcp/client/transports/memory.py | 97 -
src/fastmcp/client/transports/sse.py | 152 --
src/fastmcp/client/transports/stdio.py | 570 ----
src/fastmcp/contrib/README.md | 19 -
.../contrib/bulk_tool_caller/README.md | 35 -
.../contrib/bulk_tool_caller/__init__.py | 3 -
.../bulk_tool_caller/bulk_tool_caller.py | 151 --
.../contrib/bulk_tool_caller/example.py | 17 -
.../contrib/component_manager/README.md | 164 --
.../contrib/component_manager/__init__.py | 3 -
.../component_manager/component_manager.py | 121 -
.../contrib/component_manager/example.py | 59 -
src/fastmcp/contrib/mcp_mixin/README.md | 147 --
src/fastmcp/contrib/mcp_mixin/__init__.py | 8 -
src/fastmcp/contrib/mcp_mixin/example.py | 52 -
src/fastmcp/contrib/mcp_mixin/mcp_mixin.py | 321 ---
src/fastmcp/decorators.py | 41 -
src/fastmcp/dependencies.py | 40 -
src/fastmcp/exceptions.py | 52 -
src/fastmcp/experimental/__init__.py | 0
src/fastmcp/experimental/sampling/__init__.py | 0
.../sampling/handlers/__init__.py | 5 -
.../experimental/sampling/handlers/openai.py | 5 -
.../experimental/server/openapi/__init__.py | 42 -
.../experimental/transforms/__init__.py | 1 -
.../experimental/transforms/code_mode.py | 574 ----
.../utilities/openapi/__init__.py | 37 -
src/fastmcp/mcp_config.py | 371 ---
src/fastmcp/prompts/__init__.py | 20 -
src/fastmcp/prompts/base.py | 440 ----
src/fastmcp/prompts/function_prompt.py | 484 ----
src/fastmcp/py.typed | 0
src/fastmcp/resources/__init__.py | 32 -
src/fastmcp/resources/base.py | 467 ----
src/fastmcp/resources/function_resource.py | 345 ---
src/fastmcp/resources/template.py | 607 -----
src/fastmcp/resources/types.py | 188 --
src/fastmcp/server/__init__.py | 13 -
src/fastmcp/server/app.py | 19 -
src/fastmcp/server/apps.py | 22 -
src/fastmcp/server/auth/__init__.py | 75 -
src/fastmcp/server/auth/auth.py | 785 ------
src/fastmcp/server/auth/authorization.py | 182 --
src/fastmcp/server/auth/cimd.py | 797 ------
src/fastmcp/server/auth/handlers/authorize.py | 326 ---
src/fastmcp/server/auth/jwt_issuer.py | 270 --
src/fastmcp/server/auth/middleware.py | 96 -
.../server/auth/oauth_proxy/__init__.py | 14 -
.../server/auth/oauth_proxy/consent.py | 512 ----
src/fastmcp/server/auth/oauth_proxy/models.py | 252 --
src/fastmcp/server/auth/oauth_proxy/proxy.py | 2084 ---------------
src/fastmcp/server/auth/oauth_proxy/ui.py | 300 ---
src/fastmcp/server/auth/oidc_proxy.py | 491 ----
src/fastmcp/server/auth/providers/__init__.py | 0
src/fastmcp/server/auth/providers/auth0.py | 131 -
src/fastmcp/server/auth/providers/aws.py | 202 --
src/fastmcp/server/auth/providers/azure.py | 763 ------
src/fastmcp/server/auth/providers/clerk.py | 384 ---
src/fastmcp/server/auth/providers/debug.py | 114 -
src/fastmcp/server/auth/providers/descope.py | 209 --
src/fastmcp/server/auth/providers/discord.py | 284 --
src/fastmcp/server/auth/providers/github.py | 299 ---
src/fastmcp/server/auth/providers/google.py | 361 ---
.../server/auth/providers/in_memory.py | 364 ---
.../server/auth/providers/introspection.py | 305 ---
src/fastmcp/server/auth/providers/jwt.py | 594 -----
src/fastmcp/server/auth/providers/oci.py | 176 --
.../server/auth/providers/propelauth.py | 234 --
src/fastmcp/server/auth/providers/scalekit.py | 212 --
src/fastmcp/server/auth/providers/supabase.py | 181 --
src/fastmcp/server/auth/providers/workos.py | 397 ---
.../server/auth/redirect_validation.py | 224 --
src/fastmcp/server/auth/ssrf.py | 356 ---
src/fastmcp/server/context.py | 1403 ----------
src/fastmcp/server/dependencies.py | 1591 ------------
src/fastmcp/server/elicitation.py | 464 ----
src/fastmcp/server/event_store.py | 177 --
src/fastmcp/server/http.py | 382 ---
src/fastmcp/server/lifespan.py | 198 --
src/fastmcp/server/low_level.py | 347 ---
src/fastmcp/server/middleware/__init__.py | 15 -
.../server/middleware/authorization.py | 321 ---
src/fastmcp/server/middleware/caching.py | 552 ----
src/fastmcp/server/middleware/dereference.py | 78 -
.../server/middleware/error_handling.py | 216 --
src/fastmcp/server/middleware/logging.py | 256 --
src/fastmcp/server/middleware/middleware.py | 205 --
src/fastmcp/server/middleware/ping.py | 70 -
.../server/middleware/rate_limiting.py | 231 --
.../server/middleware/response_limiting.py | 125 -
src/fastmcp/server/middleware/timing.py | 156 --
.../server/middleware/tool_injection.py | 141 -
src/fastmcp/server/mixins/__init__.py | 7 -
src/fastmcp/server/mixins/lifespan.py | 251 --
src/fastmcp/server/mixins/mcp_operations.py | 373 ---
src/fastmcp/server/mixins/transport.py | 365 ---
src/fastmcp/server/openapi/__init__.py | 57 -
src/fastmcp/server/openapi/components.py | 30 -
src/fastmcp/server/openapi/routing.py | 48 -
src/fastmcp/server/openapi/server.py | 125 -
src/fastmcp/server/providers/__init__.py | 71 -
src/fastmcp/server/providers/aggregate.py | 274 --
src/fastmcp/server/providers/base.py | 604 -----
.../server/providers/fastmcp_provider.py | 720 -----
src/fastmcp/server/providers/filesystem.py | 226 --
.../server/providers/filesystem_discovery.py | 388 ---
.../providers/local_provider/__init__.py | 11 -
.../local_provider/decorators/__init__.py | 15 -
.../local_provider/decorators/prompts.py | 258 --
.../local_provider/decorators/resources.py | 242 --
.../local_provider/decorators/tools.py | 448 ----
.../local_provider/local_provider.py | 465 ----
.../server/providers/openapi/README.md | 266 --
.../server/providers/openapi/__init__.py | 39 -
.../server/providers/openapi/components.py | 421 ---
.../server/providers/openapi/provider.py | 433 ---
.../server/providers/openapi/routing.py | 109 -
src/fastmcp/server/providers/proxy.py | 1077 --------
.../server/providers/skills/__init__.py | 59 -
.../server/providers/skills/_common.py | 107 -
.../providers/skills/claude_provider.py | 44 -
.../providers/skills/directory_provider.py | 153 --
.../server/providers/skills/skill_provider.py | 449 ----
.../providers/skills/vendor_providers.py | 142 -
.../server/providers/wrapped_provider.py | 144 -
src/fastmcp/server/proxy.py | 43 -
src/fastmcp/server/sampling/__init__.py | 10 -
src/fastmcp/server/sampling/run.py | 703 -----
src/fastmcp/server/sampling/sampling_tool.py | 204 --
src/fastmcp/server/server.py | 2311 -----------------
src/fastmcp/server/tasks/__init__.py | 38 -
src/fastmcp/server/tasks/capabilities.py | 42 -
src/fastmcp/server/tasks/config.py | 147 --
src/fastmcp/server/tasks/elicitation.py | 346 ---
src/fastmcp/server/tasks/handlers.py | 244 --
src/fastmcp/server/tasks/keys.py | 91 -
src/fastmcp/server/tasks/notifications.py | 300 ---
src/fastmcp/server/tasks/requests.py | 479 ----
src/fastmcp/server/tasks/routing.py | 76 -
src/fastmcp/server/tasks/subscriptions.py | 222 --
src/fastmcp/server/telemetry.py | 131 -
src/fastmcp/server/transforms/__init__.py | 240 --
src/fastmcp/server/transforms/catalog.py | 245 --
src/fastmcp/server/transforms/namespace.py | 193 --
.../server/transforms/prompts_as_tools.py | 169 --
.../server/transforms/resources_as_tools.py | 180 --
.../server/transforms/search/__init__.py | 31 -
src/fastmcp/server/transforms/search/base.py | 269 --
src/fastmcp/server/transforms/search/bm25.py | 144 -
src/fastmcp/server/transforms/search/regex.py | 55 -
.../server/transforms/tool_transform.py | 96 -
.../server/transforms/version_filter.py | 148 --
src/fastmcp/server/transforms/visibility.py | 526 ----
src/fastmcp/settings.py | 381 ---
src/fastmcp/telemetry.py | 122 -
src/fastmcp/tools/__init__.py | 20 -
src/fastmcp/tools/base.py | 597 -----
src/fastmcp/tools/function_parsing.py | 275 --
src/fastmcp/tools/function_tool.py | 494 ----
src/fastmcp/tools/tool_transform.py | 998 -------
src/fastmcp/types.py | 32 -
src/fastmcp/utilities/__init__.py | 1 -
src/fastmcp/utilities/async_utils.py | 83 -
src/fastmcp/utilities/auth.py | 91 -
src/fastmcp/utilities/cli.py | 269 --
src/fastmcp/utilities/components.py | 255 --
src/fastmcp/utilities/exceptions.py | 49 -
src/fastmcp/utilities/http.py | 8 -
src/fastmcp/utilities/inspect.py | 494 ----
src/fastmcp/utilities/json_schema.py | 522 ----
src/fastmcp/utilities/json_schema_type.py | 650 -----
src/fastmcp/utilities/lifespan.py | 56 -
src/fastmcp/utilities/logging.py | 238 --
.../utilities/mcp_server_config/__init__.py | 25 -
.../mcp_server_config/v1/__init__.py | 0
.../v1/environments/__init__.py | 6 -
.../mcp_server_config/v1/environments/base.py | 29 -
.../mcp_server_config/v1/environments/uv.py | 271 --
.../mcp_server_config/v1/mcp_server_config.py | 447 ----
.../mcp_server_config/v1/schema.json | 365 ---
.../mcp_server_config/v1/sources/__init__.py | 0
.../mcp_server_config/v1/sources/base.py | 29 -
.../v1/sources/filesystem.py | 217 --
src/fastmcp/utilities/mime.py | 27 -
src/fastmcp/utilities/openapi/README.md | 211 --
src/fastmcp/utilities/openapi/__init__.py | 61 -
src/fastmcp/utilities/openapi/director.py | 325 ---
src/fastmcp/utilities/openapi/formatters.py | 355 ---
.../openapi/json_schema_converter.py | 344 ---
src/fastmcp/utilities/openapi/models.py | 88 -
src/fastmcp/utilities/openapi/parser.py | 825 ------
src/fastmcp/utilities/openapi/schemas.py | 606 -----
src/fastmcp/utilities/pagination.py | 80 -
src/fastmcp/utilities/skills.py | 257 --
src/fastmcp/utilities/tests.py | 270 --
src/fastmcp/utilities/timeout.py | 47 -
src/fastmcp/utilities/token_cache.py | 173 --
src/fastmcp/utilities/types.py | 499 ----
src/fastmcp/utilities/ui.py | 626 -----
src/fastmcp/utilities/version_check.py | 153 --
src/fastmcp/utilities/versions.py | 333 ---
tests/server/test_run_server.py | 98 -
270 files changed, 69961 deletions(-)
delete mode 100644 .github/workflows/publish.yml
delete mode 100644 docs/apps/interactive-apps.mdx
delete mode 100644 docs/apps/patterns.mdx
delete mode 100644 docs/apps/providers/generative.mdx
delete mode 100644 examples/auth/authkit_dcr/README.md
delete mode 100644 examples/auth/authkit_dcr/client.py
delete mode 100644 examples/auth/authkit_dcr/server.py
delete mode 100644 examples/filesystem-provider/mcp/prompts/assistant.py
delete mode 100644 examples/filesystem-provider/mcp/resources/config.py
delete mode 100644 examples/filesystem-provider/mcp/tools/calculator.py
delete mode 100644 examples/filesystem-provider/mcp/tools/greeting.py
delete mode 100644 src/fastmcp/__init__.py
delete mode 100644 src/fastmcp/apps/__init__.py
delete mode 100644 src/fastmcp/apps/app.py
delete mode 100644 src/fastmcp/apps/approval.py
delete mode 100644 src/fastmcp/apps/choice.py
delete mode 100644 src/fastmcp/apps/config.py
delete mode 100644 src/fastmcp/apps/file_upload.py
delete mode 100644 src/fastmcp/apps/form.py
delete mode 100644 src/fastmcp/apps/generative.py
delete mode 100644 src/fastmcp/cli/__init__.py
delete mode 100644 src/fastmcp/cli/__main__.py
delete mode 100644 src/fastmcp/cli/apps_dev.py
delete mode 100644 src/fastmcp/cli/auth.py
delete mode 100644 src/fastmcp/cli/cimd.py
delete mode 100644 src/fastmcp/cli/cli.py
delete mode 100644 src/fastmcp/cli/client.py
delete mode 100644 src/fastmcp/cli/discovery.py
delete mode 100644 src/fastmcp/cli/generate.py
delete mode 100644 src/fastmcp/cli/install/__init__.py
delete mode 100644 src/fastmcp/cli/install/claude_code.py
delete mode 100644 src/fastmcp/cli/install/claude_desktop.py
delete mode 100644 src/fastmcp/cli/install/cursor.py
delete mode 100644 src/fastmcp/cli/install/gemini_cli.py
delete mode 100644 src/fastmcp/cli/install/goose.py
delete mode 100644 src/fastmcp/cli/install/mcp_json.py
delete mode 100644 src/fastmcp/cli/install/shared.py
delete mode 100644 src/fastmcp/cli/install/stdio.py
delete mode 100644 src/fastmcp/cli/run.py
delete mode 100644 src/fastmcp/cli/tasks.py
delete mode 100644 src/fastmcp/client/__init__.py
delete mode 100644 src/fastmcp/client/auth/__init__.py
delete mode 100644 src/fastmcp/client/auth/bearer.py
delete mode 100644 src/fastmcp/client/auth/oauth.py
delete mode 100644 src/fastmcp/client/client.py
delete mode 100644 src/fastmcp/client/elicitation.py
delete mode 100644 src/fastmcp/client/logging.py
delete mode 100644 src/fastmcp/client/messages.py
delete mode 100644 src/fastmcp/client/mixins/__init__.py
delete mode 100644 src/fastmcp/client/mixins/prompts.py
delete mode 100644 src/fastmcp/client/mixins/resources.py
delete mode 100644 src/fastmcp/client/mixins/task_management.py
delete mode 100644 src/fastmcp/client/mixins/tools.py
delete mode 100644 src/fastmcp/client/oauth_callback.py
delete mode 100644 src/fastmcp/client/progress.py
delete mode 100644 src/fastmcp/client/roots.py
delete mode 100644 src/fastmcp/client/sampling/__init__.py
delete mode 100644 src/fastmcp/client/sampling/handlers/__init__.py
delete mode 100644 src/fastmcp/client/sampling/handlers/anthropic.py
delete mode 100644 src/fastmcp/client/sampling/handlers/google_genai.py
delete mode 100644 src/fastmcp/client/sampling/handlers/openai.py
delete mode 100644 src/fastmcp/client/tasks.py
delete mode 100644 src/fastmcp/client/telemetry.py
delete mode 100644 src/fastmcp/client/transports/__init__.py
delete mode 100644 src/fastmcp/client/transports/base.py
delete mode 100644 src/fastmcp/client/transports/config.py
delete mode 100644 src/fastmcp/client/transports/http.py
delete mode 100644 src/fastmcp/client/transports/inference.py
delete mode 100644 src/fastmcp/client/transports/memory.py
delete mode 100644 src/fastmcp/client/transports/sse.py
delete mode 100644 src/fastmcp/client/transports/stdio.py
delete mode 100644 src/fastmcp/contrib/README.md
delete mode 100644 src/fastmcp/contrib/bulk_tool_caller/README.md
delete mode 100644 src/fastmcp/contrib/bulk_tool_caller/__init__.py
delete mode 100644 src/fastmcp/contrib/bulk_tool_caller/bulk_tool_caller.py
delete mode 100644 src/fastmcp/contrib/bulk_tool_caller/example.py
delete mode 100644 src/fastmcp/contrib/component_manager/README.md
delete mode 100644 src/fastmcp/contrib/component_manager/__init__.py
delete mode 100644 src/fastmcp/contrib/component_manager/component_manager.py
delete mode 100644 src/fastmcp/contrib/component_manager/example.py
delete mode 100644 src/fastmcp/contrib/mcp_mixin/README.md
delete mode 100644 src/fastmcp/contrib/mcp_mixin/__init__.py
delete mode 100644 src/fastmcp/contrib/mcp_mixin/example.py
delete mode 100644 src/fastmcp/contrib/mcp_mixin/mcp_mixin.py
delete mode 100644 src/fastmcp/decorators.py
delete mode 100644 src/fastmcp/dependencies.py
delete mode 100644 src/fastmcp/exceptions.py
delete mode 100644 src/fastmcp/experimental/__init__.py
delete mode 100644 src/fastmcp/experimental/sampling/__init__.py
delete mode 100644 src/fastmcp/experimental/sampling/handlers/__init__.py
delete mode 100644 src/fastmcp/experimental/sampling/handlers/openai.py
delete mode 100644 src/fastmcp/experimental/server/openapi/__init__.py
delete mode 100644 src/fastmcp/experimental/transforms/__init__.py
delete mode 100644 src/fastmcp/experimental/transforms/code_mode.py
delete mode 100644 src/fastmcp/experimental/utilities/openapi/__init__.py
delete mode 100644 src/fastmcp/mcp_config.py
delete mode 100644 src/fastmcp/prompts/__init__.py
delete mode 100644 src/fastmcp/prompts/base.py
delete mode 100644 src/fastmcp/prompts/function_prompt.py
delete mode 100644 src/fastmcp/py.typed
delete mode 100644 src/fastmcp/resources/__init__.py
delete mode 100644 src/fastmcp/resources/base.py
delete mode 100644 src/fastmcp/resources/function_resource.py
delete mode 100644 src/fastmcp/resources/template.py
delete mode 100644 src/fastmcp/resources/types.py
delete mode 100644 src/fastmcp/server/__init__.py
delete mode 100644 src/fastmcp/server/app.py
delete mode 100644 src/fastmcp/server/apps.py
delete mode 100644 src/fastmcp/server/auth/__init__.py
delete mode 100644 src/fastmcp/server/auth/auth.py
delete mode 100644 src/fastmcp/server/auth/authorization.py
delete mode 100644 src/fastmcp/server/auth/cimd.py
delete mode 100644 src/fastmcp/server/auth/handlers/authorize.py
delete mode 100644 src/fastmcp/server/auth/jwt_issuer.py
delete mode 100644 src/fastmcp/server/auth/middleware.py
delete mode 100644 src/fastmcp/server/auth/oauth_proxy/__init__.py
delete mode 100644 src/fastmcp/server/auth/oauth_proxy/consent.py
delete mode 100644 src/fastmcp/server/auth/oauth_proxy/models.py
delete mode 100644 src/fastmcp/server/auth/oauth_proxy/proxy.py
delete mode 100644 src/fastmcp/server/auth/oauth_proxy/ui.py
delete mode 100644 src/fastmcp/server/auth/oidc_proxy.py
delete mode 100644 src/fastmcp/server/auth/providers/__init__.py
delete mode 100644 src/fastmcp/server/auth/providers/auth0.py
delete mode 100644 src/fastmcp/server/auth/providers/aws.py
delete mode 100644 src/fastmcp/server/auth/providers/azure.py
delete mode 100644 src/fastmcp/server/auth/providers/clerk.py
delete mode 100644 src/fastmcp/server/auth/providers/debug.py
delete mode 100644 src/fastmcp/server/auth/providers/descope.py
delete mode 100644 src/fastmcp/server/auth/providers/discord.py
delete mode 100644 src/fastmcp/server/auth/providers/github.py
delete mode 100644 src/fastmcp/server/auth/providers/google.py
delete mode 100644 src/fastmcp/server/auth/providers/in_memory.py
delete mode 100644 src/fastmcp/server/auth/providers/introspection.py
delete mode 100644 src/fastmcp/server/auth/providers/jwt.py
delete mode 100644 src/fastmcp/server/auth/providers/oci.py
delete mode 100644 src/fastmcp/server/auth/providers/propelauth.py
delete mode 100644 src/fastmcp/server/auth/providers/scalekit.py
delete mode 100644 src/fastmcp/server/auth/providers/supabase.py
delete mode 100644 src/fastmcp/server/auth/providers/workos.py
delete mode 100644 src/fastmcp/server/auth/redirect_validation.py
delete mode 100644 src/fastmcp/server/auth/ssrf.py
delete mode 100644 src/fastmcp/server/context.py
delete mode 100644 src/fastmcp/server/dependencies.py
delete mode 100644 src/fastmcp/server/elicitation.py
delete mode 100644 src/fastmcp/server/event_store.py
delete mode 100644 src/fastmcp/server/http.py
delete mode 100644 src/fastmcp/server/lifespan.py
delete mode 100644 src/fastmcp/server/low_level.py
delete mode 100644 src/fastmcp/server/middleware/__init__.py
delete mode 100644 src/fastmcp/server/middleware/authorization.py
delete mode 100644 src/fastmcp/server/middleware/caching.py
delete mode 100644 src/fastmcp/server/middleware/dereference.py
delete mode 100644 src/fastmcp/server/middleware/error_handling.py
delete mode 100644 src/fastmcp/server/middleware/logging.py
delete mode 100644 src/fastmcp/server/middleware/middleware.py
delete mode 100644 src/fastmcp/server/middleware/ping.py
delete mode 100644 src/fastmcp/server/middleware/rate_limiting.py
delete mode 100644 src/fastmcp/server/middleware/response_limiting.py
delete mode 100644 src/fastmcp/server/middleware/timing.py
delete mode 100644 src/fastmcp/server/middleware/tool_injection.py
delete mode 100644 src/fastmcp/server/mixins/__init__.py
delete mode 100644 src/fastmcp/server/mixins/lifespan.py
delete mode 100644 src/fastmcp/server/mixins/mcp_operations.py
delete mode 100644 src/fastmcp/server/mixins/transport.py
delete mode 100644 src/fastmcp/server/openapi/__init__.py
delete mode 100644 src/fastmcp/server/openapi/components.py
delete mode 100644 src/fastmcp/server/openapi/routing.py
delete mode 100644 src/fastmcp/server/openapi/server.py
delete mode 100644 src/fastmcp/server/providers/__init__.py
delete mode 100644 src/fastmcp/server/providers/aggregate.py
delete mode 100644 src/fastmcp/server/providers/base.py
delete mode 100644 src/fastmcp/server/providers/fastmcp_provider.py
delete mode 100644 src/fastmcp/server/providers/filesystem.py
delete mode 100644 src/fastmcp/server/providers/filesystem_discovery.py
delete mode 100644 src/fastmcp/server/providers/local_provider/__init__.py
delete mode 100644 src/fastmcp/server/providers/local_provider/decorators/__init__.py
delete mode 100644 src/fastmcp/server/providers/local_provider/decorators/prompts.py
delete mode 100644 src/fastmcp/server/providers/local_provider/decorators/resources.py
delete mode 100644 src/fastmcp/server/providers/local_provider/decorators/tools.py
delete mode 100644 src/fastmcp/server/providers/local_provider/local_provider.py
delete mode 100644 src/fastmcp/server/providers/openapi/README.md
delete mode 100644 src/fastmcp/server/providers/openapi/__init__.py
delete mode 100644 src/fastmcp/server/providers/openapi/components.py
delete mode 100644 src/fastmcp/server/providers/openapi/provider.py
delete mode 100644 src/fastmcp/server/providers/openapi/routing.py
delete mode 100644 src/fastmcp/server/providers/proxy.py
delete mode 100644 src/fastmcp/server/providers/skills/__init__.py
delete mode 100644 src/fastmcp/server/providers/skills/_common.py
delete mode 100644 src/fastmcp/server/providers/skills/claude_provider.py
delete mode 100644 src/fastmcp/server/providers/skills/directory_provider.py
delete mode 100644 src/fastmcp/server/providers/skills/skill_provider.py
delete mode 100644 src/fastmcp/server/providers/skills/vendor_providers.py
delete mode 100644 src/fastmcp/server/providers/wrapped_provider.py
delete mode 100644 src/fastmcp/server/proxy.py
delete mode 100644 src/fastmcp/server/sampling/__init__.py
delete mode 100644 src/fastmcp/server/sampling/run.py
delete mode 100644 src/fastmcp/server/sampling/sampling_tool.py
delete mode 100644 src/fastmcp/server/server.py
delete mode 100644 src/fastmcp/server/tasks/__init__.py
delete mode 100644 src/fastmcp/server/tasks/capabilities.py
delete mode 100644 src/fastmcp/server/tasks/config.py
delete mode 100644 src/fastmcp/server/tasks/elicitation.py
delete mode 100644 src/fastmcp/server/tasks/handlers.py
delete mode 100644 src/fastmcp/server/tasks/keys.py
delete mode 100644 src/fastmcp/server/tasks/notifications.py
delete mode 100644 src/fastmcp/server/tasks/requests.py
delete mode 100644 src/fastmcp/server/tasks/routing.py
delete mode 100644 src/fastmcp/server/tasks/subscriptions.py
delete mode 100644 src/fastmcp/server/telemetry.py
delete mode 100644 src/fastmcp/server/transforms/__init__.py
delete mode 100644 src/fastmcp/server/transforms/catalog.py
delete mode 100644 src/fastmcp/server/transforms/namespace.py
delete mode 100644 src/fastmcp/server/transforms/prompts_as_tools.py
delete mode 100644 src/fastmcp/server/transforms/resources_as_tools.py
delete mode 100644 src/fastmcp/server/transforms/search/__init__.py
delete mode 100644 src/fastmcp/server/transforms/search/base.py
delete mode 100644 src/fastmcp/server/transforms/search/bm25.py
delete mode 100644 src/fastmcp/server/transforms/search/regex.py
delete mode 100644 src/fastmcp/server/transforms/tool_transform.py
delete mode 100644 src/fastmcp/server/transforms/version_filter.py
delete mode 100644 src/fastmcp/server/transforms/visibility.py
delete mode 100644 src/fastmcp/settings.py
delete mode 100644 src/fastmcp/telemetry.py
delete mode 100644 src/fastmcp/tools/__init__.py
delete mode 100644 src/fastmcp/tools/base.py
delete mode 100644 src/fastmcp/tools/function_parsing.py
delete mode 100644 src/fastmcp/tools/function_tool.py
delete mode 100644 src/fastmcp/tools/tool_transform.py
delete mode 100644 src/fastmcp/types.py
delete mode 100644 src/fastmcp/utilities/__init__.py
delete mode 100644 src/fastmcp/utilities/async_utils.py
delete mode 100644 src/fastmcp/utilities/auth.py
delete mode 100644 src/fastmcp/utilities/cli.py
delete mode 100644 src/fastmcp/utilities/components.py
delete mode 100644 src/fastmcp/utilities/exceptions.py
delete mode 100644 src/fastmcp/utilities/http.py
delete mode 100644 src/fastmcp/utilities/inspect.py
delete mode 100644 src/fastmcp/utilities/json_schema.py
delete mode 100644 src/fastmcp/utilities/json_schema_type.py
delete mode 100644 src/fastmcp/utilities/lifespan.py
delete mode 100644 src/fastmcp/utilities/logging.py
delete mode 100644 src/fastmcp/utilities/mcp_server_config/__init__.py
delete mode 100644 src/fastmcp/utilities/mcp_server_config/v1/__init__.py
delete mode 100644 src/fastmcp/utilities/mcp_server_config/v1/environments/__init__.py
delete mode 100644 src/fastmcp/utilities/mcp_server_config/v1/environments/base.py
delete mode 100644 src/fastmcp/utilities/mcp_server_config/v1/environments/uv.py
delete mode 100644 src/fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py
delete mode 100644 src/fastmcp/utilities/mcp_server_config/v1/schema.json
delete mode 100644 src/fastmcp/utilities/mcp_server_config/v1/sources/__init__.py
delete mode 100644 src/fastmcp/utilities/mcp_server_config/v1/sources/base.py
delete mode 100644 src/fastmcp/utilities/mcp_server_config/v1/sources/filesystem.py
delete mode 100644 src/fastmcp/utilities/mime.py
delete mode 100644 src/fastmcp/utilities/openapi/README.md
delete mode 100644 src/fastmcp/utilities/openapi/__init__.py
delete mode 100644 src/fastmcp/utilities/openapi/director.py
delete mode 100644 src/fastmcp/utilities/openapi/formatters.py
delete mode 100644 src/fastmcp/utilities/openapi/json_schema_converter.py
delete mode 100644 src/fastmcp/utilities/openapi/models.py
delete mode 100644 src/fastmcp/utilities/openapi/parser.py
delete mode 100644 src/fastmcp/utilities/openapi/schemas.py
delete mode 100644 src/fastmcp/utilities/pagination.py
delete mode 100644 src/fastmcp/utilities/skills.py
delete mode 100644 src/fastmcp/utilities/tests.py
delete mode 100644 src/fastmcp/utilities/timeout.py
delete mode 100644 src/fastmcp/utilities/token_cache.py
delete mode 100644 src/fastmcp/utilities/types.py
delete mode 100644 src/fastmcp/utilities/ui.py
delete mode 100644 src/fastmcp/utilities/version_check.py
delete mode 100644 src/fastmcp/utilities/versions.py
delete mode 100644 tests/server/test_run_server.py
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
deleted file mode 100644
index 5f2fe8d53..000000000
--- a/.github/workflows/publish.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: Publish FastMCP to PyPI
-on:
- release:
- types: [published]
- workflow_dispatch:
-
-jobs:
- pypi-publish:
- name: Upload to PyPI
- runs-on: ubuntu-latest
- permissions:
- id-token: write # For PyPI's trusted publishing
- steps:
- - name: Checkout
- uses: actions/checkout@v6
- with:
- fetch-depth: 0
-
- - name: "Install uv"
- uses: astral-sh/setup-uv@v7
-
- - name: Build
- run: uv build
-
- - name: Publish to PyPi
- run: uv publish -v dist/*
diff --git a/docs/apps/interactive-apps.mdx b/docs/apps/interactive-apps.mdx
deleted file mode 100644
index fb2963114..000000000
--- a/docs/apps/interactive-apps.mdx
+++ /dev/null
@@ -1,538 +0,0 @@
----
-title: FastMCPApp
-sidebarTitle: FastMCPApp
-description: Managed tool binding, visibility, and composition for apps with heavy server interaction.
-icon: puzzle-piece
-tag: NEW
----
-
-import { VersionBadge } from '/snippets/version-badge.mdx'
-
-
-
-
-[Prefab](https://prefab.prefect.io) is in early, active development — its API changes frequently and breaking changes can occur with any release. Always pin `prefab-ui` to a specific version in your dependencies.
-
-
-Any [Prefab app](/apps/prefab) can call server tools — there's nothing stopping you from using `CallTool("tool_name")` in a regular `@mcp.tool(app=True)`. But once you have multiple backend tools, the management overhead adds up: Which tools should the model see vs. only the UI? What happens to string-based tool references when servers are composed under namespaces? How do you keep things wired correctly as the app grows?
-
-`FastMCPApp` is a class that solves these problems. It gives you two decorators that work together:
-
-- **`@app.ui()`** — entry-point tools the model calls to open the app. These return a Prefab UI.
-- **`@app.tool()`** — backend tools the UI calls via `CallTool`. These do the work.
-
-Backend tools get globally stable identifiers that survive namespacing. Visibility is managed automatically — the model sees entry points, the UI sees backends. And `CallTool` accepts function references instead of strings, so references are refactorable and composition-safe.
-
-## Your First Interactive App
-
-Here's a minimal app with a form that saves data:
-
-```python
-from prefab_ui.actions import SetState, ShowToast
-from prefab_ui.actions.mcp import CallTool
-from prefab_ui.app import PrefabApp
-from prefab_ui.components import (
- Badge, Button, Column, ForEach, Form,
- Heading, Input, Row, Separator, Text,
-)
-from prefab_ui.rx import RESULT
-from fastmcp import FastMCP, FastMCPApp
-
-app = FastMCPApp("Notes")
-
-notes_db: list[dict] = []
-
-
-@app.tool()
-def add_note(title: str, body: str) -> list[dict]:
- """Save a note and return all notes."""
- notes_db.append({"title": title, "body": body})
- return list(notes_db)
-
-
-@app.ui()
-def notes_app() -> PrefabApp:
- """Open the notes app."""
- with Column(gap=6, css_class="p-6") as view:
- Heading("Notes")
-
- with ForEach("notes") as note:
- with Row(gap=2, align="center"):
- Text(note.title, css_class="font-semibold")
- Badge(note.body)
-
- Separator()
-
- with Form(
- on_submit=CallTool(
- "add_note",
- on_success=[
- SetState("notes", RESULT),
- ShowToast("Note saved!", variant="success"),
- ],
- on_error=ShowToast("Failed to save", variant="error"),
- )
- ):
- Input(name="title", label="Title", required=True)
- Input(name="body", label="Body", required=True)
- Button("Add Note")
-
- return PrefabApp(view=view, state={"notes": list(notes_db)})
-
-
-mcp = FastMCP("Notes Server", providers=[app])
-```
-
-When the model calls `notes_app`, the user sees a form. Submitting it calls `add_note` on the server, updates the state with the result, and shows a toast — all without leaving the UI.
-
-Let's break down the key concepts.
-
-## Entry Points: @app.ui()
-
-Entry points are what the model sees and calls to open your app. They return a Prefab UI, just like display tools:
-
-```python
-@app.ui()
-def dashboard() -> PrefabApp:
- """The model calls this to open the dashboard."""
- with Column(gap=4, css_class="p-6") as view:
- Heading("Dashboard")
- # ... build UI ...
- return PrefabApp(view=view)
-```
-
-Entry points default to `visibility=["model"]` — they show up in the tool list for the LLM but aren't callable from within the app UI. They support the same options as `@mcp.tool`: `name`, `description`, `title`, `tags`, `icons`, `auth`, and `timeout`.
-
-```python
-@app.ui(title="Contact Manager", description="Open the contact management interface")
-def contact_manager() -> PrefabApp:
- ...
-```
-
-## Backend Tools: @app.tool()
-
-Backend tools do the work. The UI calls them via `CallTool`; they run on the server and return data:
-
-```python
-@app.tool()
-def save_contact(name: str, email: str) -> list[dict]:
- """Save a contact and return the updated list."""
- db.append({"name": name, "email": email})
- return list(db)
-```
-
-By default, backend tools are only visible to the app UI (`visibility=["app"]`). The model doesn't see them in the tool list. If you want a tool callable by both the model and the UI, pass `model=True`:
-
-```python
-@app.tool(model=True)
-def list_contacts() -> list[dict]:
- """Both the model and the UI can call this."""
- return list(db)
-```
-
-Backend tools support `name`, `description`, `auth`, and `timeout`:
-
-```python
-@app.tool(description="Search contacts by name or email", timeout=10.0)
-def search(query: str) -> list[dict]:
- ...
-```
-
-## Connecting UI to Backend: CallTool
-
-`CallTool` is the bridge between the UI and the server. Pass the name of a backend tool registered with `@app.tool()`:
-
-```python
-from prefab_ui.actions.mcp import CallTool
-
-# Reference a backend tool by name
-CallTool("save_contact", arguments={"name": "Alice", "email": "alice@example.com"})
-
-# Arguments can reference state with Rx
-from prefab_ui.rx import STATE
-
-CallTool("search", arguments={"query": STATE.search_term})
-```
-
-FastMCPApp resolves the name to the tool's stable global key automatically, so `CallTool("save_contact")` keeps working even when the server is mounted under a namespace.
-
-You can also pass the function directly — `CallTool(save_contact)` — which can be convenient when the tool is defined in the same file. Both forms resolve identically.
-
-### Handling Results
-
-Server calls are asynchronous. Use `on_success` and `on_error` callbacks to handle outcomes:
-
-```python
-from prefab_ui.actions import SetState, ShowToast
-from prefab_ui.rx import RESULT
-
-CallTool(
- "save_contact",
- on_success=[
- SetState("contacts", RESULT),
- ShowToast("Saved!", variant="success"),
- ],
- on_error=ShowToast("Something went wrong", variant="error"),
-)
-```
-
-`RESULT` is a reactive reference to the value the tool returned — available inside `on_success` callbacks. Similarly, `ERROR` (from `prefab_ui.rx`) is available inside `on_error`.
-
-Callbacks can be a single action or a list of actions. They execute in order, and an error in any action short-circuits the rest.
-
-### result_key Shorthand
-
-When a tool returns data that should replace a state key, `result_key` is a convenient shorthand for `on_success=SetState(key, RESULT)`:
-
-```python
-CallTool("list_contacts", result_key="contacts")
-
-# equivalent to:
-CallTool(
- "list_contacts",
- on_success=SetState("contacts", RESULT),
-)
-```
-
-## Actions
-
-`CallTool` is one of several actions available in Prefab. Actions are events attached to component handlers like `on_click`, `on_submit`, and `on_change`.
-
-### Client Actions
-
-These run instantly in the browser — no server round-trip:
-
-```python
-from prefab_ui.actions import SetState, ToggleState, AppendState, PopState, ShowToast
-
-# Set a value
-SetState("count", 42)
-
-# Toggle a boolean
-ToggleState("expanded")
-
-# Append to a list
-AppendState("items", {"name": "New Item"})
-
-# Remove by index
-PopState("items", 0)
-
-# Show a notification
-ShowToast("Done!", variant="success")
-```
-
-### Chaining Actions
-
-Pass a list to execute multiple actions in sequence:
-
-```python
-from prefab_ui.components import Button
-from prefab_ui.actions import SetState, ShowToast
-
-Button(
- "Reset",
- on_click=[
- SetState("query", ""),
- SetState("results", []),
- ShowToast("Cleared", variant="default"),
- ],
-)
-```
-
-### Loading States
-
-A common pattern: show a loading indicator while a server call is in flight.
-
-```python
-from prefab_ui.actions import SetState, ShowToast
-from prefab_ui.actions.mcp import CallTool
-from prefab_ui.components import Button
-from prefab_ui.rx import RESULT, Rx
-
-saving = Rx("saving")
-
-Button(
- saving.then("Saving...", "Save"),
- disabled=saving,
- on_click=[
- SetState("saving", True),
- CallTool(
- "save_data",
- on_success=[
- SetState("saving", False),
- SetState("result", RESULT),
- ShowToast("Saved!", variant="success"),
- ],
- on_error=[
- SetState("saving", False),
- ShowToast("Failed", variant="error"),
- ],
- ),
- ],
-)
-
-# Pass state={"saving": False} to PrefabApp when returning
-```
-
-## Forms
-
-Forms are the most common way to collect input and send it to the server. When a form submits, all named input values are gathered and passed as arguments to the `CallTool` action.
-
-### Manual Forms
-
-Build forms with individual input components:
-
-```python
-from prefab_ui.components import Form, Input, Select, SelectOption, Textarea, Button
-from prefab_ui.actions.mcp import CallTool
-from prefab_ui.actions import ShowToast
-
-with Form(
- on_submit=CallTool(
- "create_ticket",
- on_success=ShowToast("Ticket created!", variant="success"),
- )
-):
- Input(name="title", label="Title", required=True)
- with Select(name="priority", label="Priority"):
- SelectOption("Low", value="low")
- SelectOption("Medium", value="medium")
- SelectOption("High", value="high")
- SelectOption("Critical", value="critical")
- Textarea(name="description", label="Description")
- Button("Create Ticket")
-```
-
-When submitted, the CallTool receives `{"title": "...", "priority": "...", "description": "..."}` as arguments to `create_ticket`.
-
-### Pydantic Model Forms
-
-For structured data, `Form.from_model()` generates the entire form from a Pydantic model — inputs, labels, and submit wiring:
-
-```python
-from typing import Literal
-
-from pydantic import BaseModel, Field
-from prefab_ui.components import Column, Heading, Form
-from prefab_ui.actions.mcp import CallTool
-from prefab_ui.actions import SetState, ShowToast
-from prefab_ui.app import PrefabApp
-from prefab_ui.rx import RESULT
-
-class BugReport(BaseModel):
- title: str = Field(title="Bug Title")
- severity: Literal["low", "medium", "high", "critical"] = Field(
- title="Severity", default="medium"
- )
- description: str = Field(title="Description")
-
-
-@app.ui()
-def report_bug() -> PrefabApp:
- """File a bug report."""
- with Column(gap=4, css_class="p-6") as view:
- Heading("Report a Bug")
- Form.from_model(
- BugReport,
- on_submit=CallTool(
- "create_bug",
- on_success=ShowToast("Bug filed!", variant="success"),
- on_error=ShowToast("Failed to submit", variant="error"),
- ),
- )
- return PrefabApp(view=view)
-
-
-@app.tool()
-def create_bug(data: BugReport) -> str:
- """Create a bug report."""
- # save to database...
- return f"Created: {data.title}"
-```
-
-`str` fields become text inputs, `Literal` becomes a select dropdown, `bool` becomes a checkbox. Field titles and defaults are respected.
-
-## Composition and Namespacing
-
-The reason `FastMCPApp` exists — and why you'd use it instead of plain `@mcp.tool(app=True)` with `CallTool("tool_name")` — is composition safety.
-
-When you mount a server under a namespace, tool names get prefixed:
-
-```python
-from fastmcp import FastMCP
-
-platform = FastMCP("Platform")
-platform.mount("contacts", contacts_server)
-
-# "save_contact" becomes "contacts_save_contact"
-```
-
-If your UI used `CallTool("save_contact")`, it would break — the tool is now named `contacts_save_contact`. But `CallTool(save_contact)` with a function reference resolves to a globally stable key (like `save_contact-a1b2c3d4`) that bypasses the namespace entirely.
-
-This is why `FastMCPApp` assigns global keys to backend tools, and why `CallTool` accepts function references. Your app works the same whether it's running standalone or mounted inside a larger platform.
-
-### Mounting an App
-
-`FastMCPApp` is a Provider. Add it to a server with `providers=` or `add_provider`:
-
-```python
-from fastmcp import FastMCP, FastMCPApp
-
-app = FastMCPApp("Contacts")
-
-@app.ui()
-def contact_manager() -> PrefabApp:
- ...
-
-@app.tool()
-def save_contact(name: str, email: str) -> dict:
- ...
-
-
-# Option 1: providers list
-mcp = FastMCP("Platform", providers=[app])
-
-# Option 2: add_provider
-mcp = FastMCP("Platform")
-mcp.add_provider(app)
-```
-
-Multiple apps can coexist on the same server:
-
-```python
-mcp = FastMCP("Platform", providers=[contacts_app, inventory_app, billing_app])
-```
-
-Each app's backend tools have their own global keys, so there's no collision even if two apps have a tool named `save`.
-
-### Running Standalone
-
-For development, `FastMCPApp` has a convenience `run()` method that wraps itself in a temporary `FastMCP` server:
-
-```python
-app = FastMCPApp("Contacts")
-# ... register tools ...
-
-if __name__ == "__main__":
- app.run()
-```
-
-## Complete Example: Contact Manager
-
-This pulls together everything — entry points, backend tools, callable references, forms (both manual and Pydantic), state management, and actions:
-
-```python expandable
-from __future__ import annotations
-
-from typing import Literal
-
-from prefab_ui.actions import SetState, ShowToast
-from prefab_ui.actions.mcp import CallTool
-from prefab_ui.app import PrefabApp
-from prefab_ui.components import (
- Badge, Button, Column, ForEach, Form,
- Heading, Input, Muted, Row, Separator, Text,
-)
-from prefab_ui.rx import RESULT, Rx
-from pydantic import BaseModel, Field
-from fastmcp import FastMCP, FastMCPApp
-
-# Data
-
-contacts_db: list[dict] = [
- {"name": "Arthur Dent", "email": "arthur@earth.com", "category": "Customer"},
- {"name": "Ford Prefect", "email": "ford@betelgeuse.org", "category": "Partner"},
-]
-
-
-class ContactModel(BaseModel):
- name: str = Field(title="Full Name", min_length=1)
- email: str = Field(title="Email")
- category: Literal["Customer", "Vendor", "Partner", "Other"] = "Other"
-
-
-# App
-
-app = FastMCPApp("Contacts")
-
-
-@app.tool()
-def save_contact(data: ContactModel) -> list[dict]:
- """Save a new contact and return the updated list."""
- contacts_db.append(data.model_dump())
- return list(contacts_db)
-
-
-@app.tool()
-def search_contacts(query: str) -> list[dict]:
- """Filter contacts by name or email."""
- q = query.lower()
- return [
- c for c in contacts_db
- if q in c["name"].lower() or q in c["email"].lower()
- ]
-
-
-@app.tool(model=True)
-def list_contacts() -> list[dict]:
- """Return all contacts. Visible to both the model and the UI."""
- return list(contacts_db)
-
-
-@app.ui()
-def contact_manager() -> PrefabApp:
- """Open the contact manager."""
- with Column(gap=6, css_class="p-6") as view:
- Heading("Contacts")
-
- with ForEach("contacts") as contact:
- with Row(gap=2, align="center"):
- Text(contact.name, css_class="font-medium")
- Muted(contact.email)
- Badge(contact.category)
-
- Separator()
-
- Heading("Add Contact", level=3)
- Form.from_model(
- ContactModel,
- on_submit=CallTool(
- "save_contact",
- on_success=[
- SetState("contacts", RESULT),
- ShowToast("Contact saved!", variant="success"),
- ],
- on_error=ShowToast("Failed to save", variant="error"),
- ),
- )
-
- Separator()
-
- Heading("Search", level=3)
- with Form(
- on_submit=CallTool(
- "search_contacts",
- arguments={"query": Rx("query")},
- on_success=SetState("contacts", RESULT),
- )
- ):
- Input(name="query", placeholder="Search by name or email...")
- Button("Search")
-
- return PrefabApp(view=view, state={"contacts": list(contacts_db)})
-
-
-mcp = FastMCP("Contacts Server", providers=[app])
-
-if __name__ == "__main__":
- mcp.run()
-```
-
-This example is also available as a runnable server at `examples/apps/contacts/contacts_server.py`.
-
-## Next Steps
-
-- **[Prefab Apps](/apps/prefab)** — Components, state, and reactive displays (the building blocks)
-- **[Patterns](/apps/patterns)** — Copy-paste examples for common UIs
-- **[Development](/apps/development)** — Preview and test app tools locally
-- **[Prefab UI Docs](https://prefab.prefect.io)** — Full component reference and advanced patterns
diff --git a/docs/apps/patterns.mdx b/docs/apps/patterns.mdx
deleted file mode 100644
index b0ff80376..000000000
--- a/docs/apps/patterns.mdx
+++ /dev/null
@@ -1,431 +0,0 @@
----
-title: Patterns
-sidebarTitle: Patterns
-description: Copy-paste examples for common tool UIs.
-icon: grid-2-plus
-tag: NEW
----
-
-import { VersionBadge } from '/snippets/version-badge.mdx'
-
-
-
-Each pattern below is a complete, copy-pasteable tool. They're organized by what you're building — pick the one closest to your use case, paste it, and adapt.
-
-For the full set of available components — layout containers, form controls, overlays, and more — see the [Prefab component reference](https://prefab.prefect.io/docs/components).
-
-## Charts
-
-Prefab includes [bar, line, area, pie, radar, and radial charts](https://prefab.prefect.io/docs/components/charts). They render client-side with tooltips, legends, and responsive sizing.
-
-### Bar Chart
-
-```python
-from prefab_ui.app import PrefabApp
-from prefab_ui.components import Column, Heading
-from prefab_ui.components.charts import BarChart, ChartSeries
-from fastmcp import FastMCP
-
-mcp = FastMCP("Charts")
-
-
-@mcp.tool(app=True)
-def quarterly_revenue(year: int) -> PrefabApp:
- """Show quarterly revenue as a bar chart."""
- data = [
- {"quarter": "Q1", "revenue": 42000, "costs": 28000},
- {"quarter": "Q2", "revenue": 51000, "costs": 31000},
- {"quarter": "Q3", "revenue": 47000, "costs": 29000},
- {"quarter": "Q4", "revenue": 63000, "costs": 35000},
- ]
-
- with Column(gap=4, css_class="p-6") as view:
- Heading(f"{year} Revenue vs Costs")
- BarChart(
- data=data,
- series=[
- ChartSeries(data_key="revenue", label="Revenue"),
- ChartSeries(data_key="costs", label="Costs"),
- ],
- x_axis="quarter",
- show_legend=True,
- )
-
- return PrefabApp(view=view)
-```
-
-Multiple `ChartSeries` entries plot different data keys. Add `stacked=True` to stack bars, or `horizontal=True` to flip the axes.
-
-### Area Chart
-
-`LineChart` and `AreaChart` share the same API as `BarChart`, with `curve` for interpolation and `show_dots` for data points:
-
-```python
-from prefab_ui.app import PrefabApp
-from prefab_ui.components import Column, Heading
-from prefab_ui.components.charts import AreaChart, ChartSeries
-from fastmcp import FastMCP
-
-mcp = FastMCP("Charts")
-
-
-@mcp.tool(app=True)
-def usage_trend() -> PrefabApp:
- """Show API usage over time."""
- data = [
- {"date": "Feb 1", "requests": 1200},
- {"date": "Feb 2", "requests": 1350},
- {"date": "Feb 3", "requests": 980},
- {"date": "Feb 4", "requests": 1500},
- {"date": "Feb 5", "requests": 1420},
- ]
-
- with Column(gap=4, css_class="p-6") as view:
- Heading("API Usage")
- AreaChart(
- data=data,
- series=[ChartSeries(data_key="requests", label="Requests")],
- x_axis="date",
- curve="smooth",
- height=250,
- )
-
- return PrefabApp(view=view)
-```
-
-### Pie and Donut Charts
-
-`PieChart` uses `data_key` (the numeric value) and `name_key` (the label). Set `inner_radius` for a donut:
-
-```python
-from prefab_ui.app import PrefabApp
-from prefab_ui.components import Column, Heading
-from prefab_ui.components.charts import PieChart
-from fastmcp import FastMCP
-
-mcp = FastMCP("Charts")
-
-
-@mcp.tool(app=True)
-def ticket_breakdown() -> PrefabApp:
- """Show open tickets by category."""
- data = [
- {"category": "Bug", "count": 23},
- {"category": "Feature", "count": 15},
- {"category": "Docs", "count": 8},
- {"category": "Infra", "count": 12},
- ]
-
- with Column(gap=4, css_class="p-6") as view:
- Heading("Open Tickets")
- PieChart(
- data=data,
- data_key="count",
- name_key="category",
- show_legend=True,
- inner_radius=60,
- )
-
- return PrefabApp(view=view)
-```
-
-## Data Tables
-
-[DataTable](https://prefab.prefect.io/docs/components/data-display/data-table) provides sortable columns, full-text search, and pagination — all client-side:
-
-```python
-from prefab_ui.app import PrefabApp
-from prefab_ui.components import Column, Heading, DataTable, DataTableColumn
-from fastmcp import FastMCP
-
-mcp = FastMCP("Directory")
-
-
-@mcp.tool(app=True)
-def employee_directory() -> PrefabApp:
- """Show a searchable, sortable employee directory."""
- employees = [
- {"name": "Alice Chen", "department": "Engineering", "role": "Staff Engineer", "location": "SF"},
- {"name": "Bob Martinez", "department": "Design", "role": "Lead Designer", "location": "NYC"},
- {"name": "Carol Johnson", "department": "Engineering", "role": "Senior Engineer", "location": "London"},
- {"name": "David Kim", "department": "Product", "role": "Product Manager", "location": "SF"},
- {"name": "Eva Müller", "department": "Engineering", "role": "Engineer", "location": "Berlin"},
- ]
-
- with Column(gap=4, css_class="p-6") as view:
- Heading("Employee Directory")
- DataTable(
- columns=[
- DataTableColumn(key="name", header="Name", sortable=True),
- DataTableColumn(key="department", header="Department", sortable=True),
- DataTableColumn(key="role", header="Role"),
- DataTableColumn(key="location", header="Office", sortable=True),
- ],
- rows=employees,
- search=True,
- paginated=True,
- page_size=15,
- )
-
- return PrefabApp(view=view)
-```
-
-## Status Displays
-
-Cards, badges, progress bars, and grids combine naturally for dashboards:
-
-```python
-from prefab_ui.app import PrefabApp
-from prefab_ui.components import (
- Column, Row, Grid, Heading, Text, Muted, Badge,
- Card, CardContent, Progress, Separator,
-)
-from fastmcp import FastMCP
-
-mcp = FastMCP("Monitoring")
-
-
-@mcp.tool(app=True)
-def system_status() -> PrefabApp:
- """Show current system health."""
- services = [
- {"name": "API Gateway", "status": "healthy", "ok": True, "latency_ms": 12, "uptime_pct": 99.9},
- {"name": "Database", "status": "healthy", "ok": True, "latency_ms": 3, "uptime_pct": 99.99},
- {"name": "Cache", "status": "degraded", "ok": False, "latency_ms": 45, "uptime_pct": 98.2},
- {"name": "Queue", "status": "healthy", "ok": True, "latency_ms": 8, "uptime_pct": 99.8},
- ]
- all_ok = all(s["ok"] for s in services)
-
- with Column(gap=4, css_class="p-6") as view:
- with Row(gap=2, align="center"):
- Heading("System Status")
- Badge(
- "All Healthy" if all_ok else "Degraded",
- variant="success" if all_ok else "destructive",
- )
- Separator()
- with Grid(columns=2, gap=4):
- for svc in services:
- with Card():
- with CardContent():
- with Row(gap=2, align="center"):
- Text(svc["name"], css_class="font-medium")
- Badge(
- svc["status"],
- variant="success" if svc["ok"] else "destructive",
- )
- Muted(f"Response: {svc['latency_ms']}ms")
- Progress(value=svc["uptime_pct"])
-
- return PrefabApp(view=view)
-```
-
-## Reactive Displays
-
-These patterns use state and `Rx()` for client-side interactivity — no server calls needed.
-
-### Feature Toggles
-
-```python
-from prefab_ui.app import PrefabApp
-from prefab_ui.components import Column, Heading, Switch, Alert, If, Separator
-from prefab_ui.rx import Rx
-from fastmcp import FastMCP
-
-mcp = FastMCP("Flags")
-
-
-@mcp.tool(app=True)
-def feature_flags() -> PrefabApp:
- """Toggle feature flags with live preview."""
- with Column(gap=4, css_class="p-6") as view:
- Heading("Feature Flags")
- Switch(name="dark_mode", label="Dark Mode")
- Switch(name="beta", label="Beta Features")
- Separator()
- with If(Rx("dark_mode")):
- Alert(title="Dark mode enabled", description="UI will use dark theme.")
- with If(Rx("beta")):
- Alert(
- title="Beta features active",
- description="Experimental features are now visible.",
- variant="warning",
- )
-
- return PrefabApp(view=view, state={"dark_mode": False, "beta": False})
-```
-
-### Tabs
-
-```python
-from prefab_ui.app import PrefabApp
-from prefab_ui.components import (
- Column, Heading, Text, Muted, Badge, Row,
- DataTable, DataTableColumn, Tabs, Tab, ForEach,
-)
-from fastmcp import FastMCP
-
-mcp = FastMCP("Projects")
-
-
-@mcp.tool(app=True)
-def project_overview() -> PrefabApp:
- """Show project details organized in tabs."""
- project = {
- "name": "FastMCP v3",
- "description": "Next generation MCP framework with Apps support.",
- "status": "Active",
- "members": [
- {"name": "Alice Chen", "role": "Lead"},
- {"name": "Bob Martinez", "role": "Design"},
- ],
- "activity": [
- {"timestamp": "2 hours ago", "message": "Merged PR #342"},
- {"timestamp": "1 day ago", "message": "Released v3.0.1"},
- ],
- }
-
- with Column(gap=4, css_class="p-6") as view:
- Heading(project["name"])
- with Tabs():
- with Tab("Overview"):
- Text(project["description"])
- with Row(gap=4):
- Badge(project["status"])
-
- with Tab("Members"):
- DataTable(
- columns=[
- DataTableColumn(key="name", header="Name", sortable=True),
- DataTableColumn(key="role", header="Role"),
- ],
- rows=project["members"],
- )
-
- with Tab("Activity"):
- with ForEach("activity") as item:
- with Row(gap=2):
- Muted(item.timestamp)
- Text(item.message)
-
- return PrefabApp(view=view, state={"activity": project["activity"]})
-```
-
-### Accordion
-
-```python
-from prefab_ui.app import PrefabApp
-from prefab_ui.components import (
- Column, Heading, Row, Text, Badge, Progress,
- Accordion, AccordionItem,
-)
-from fastmcp import FastMCP
-
-mcp = FastMCP("API Monitor")
-
-
-@mcp.tool(app=True)
-def api_health() -> PrefabApp:
- """Show health details for each API endpoint."""
- endpoints = [
- {"path": "/api/users", "status": 200, "healthy": True, "avg_ms": 45, "p99_ms": 120, "uptime_pct": 99.9},
- {"path": "/api/orders", "status": 200, "healthy": True, "avg_ms": 82, "p99_ms": 250, "uptime_pct": 99.7},
- {"path": "/api/search", "status": 200, "healthy": True, "avg_ms": 150, "p99_ms": 500, "uptime_pct": 99.5},
- {"path": "/api/webhooks", "status": 503, "healthy": False, "avg_ms": 2000, "p99_ms": 5000, "uptime_pct": 95.1},
- ]
-
- with Column(gap=4, css_class="p-6") as view:
- Heading("API Health")
- with Accordion(multiple=True):
- for ep in endpoints:
- with AccordionItem(ep["path"]):
- with Row(gap=4):
- Badge(
- f"{ep['status']}",
- variant="success" if ep["healthy"] else "destructive",
- )
- Text(f"Avg: {ep['avg_ms']}ms")
- Text(f"P99: {ep['p99_ms']}ms")
- Progress(value=ep["uptime_pct"])
-
- return PrefabApp(view=view)
-```
-
-## Interactive Patterns
-
-These patterns call server tools. For context on `FastMCPApp`, `@app.tool()`, and `CallTool`, see [FastMCPApp](/apps/interactive-apps).
-
-### Contact Form
-
-```python
-from prefab_ui.actions import SetState, ShowToast
-from prefab_ui.actions.mcp import CallTool
-from prefab_ui.app import PrefabApp
-from prefab_ui.components import (
- Badge, Button, Column, ForEach, Form, Heading,
- Input, Muted, Row, Select, SelectOption, Separator, Text, Textarea,
-)
-from prefab_ui.rx import RESULT
-from fastmcp import FastMCP, FastMCPApp
-
-app = FastMCPApp("Contacts")
-
-contacts_db: list[dict] = [
- {"name": "Zaphod Beeblebrox", "email": "zaphod@galaxy.gov", "category": "Partner"},
-]
-
-
-@app.tool()
-def save_contact(
- name: str, email: str, category: str = "Other", notes: str = "",
-) -> list[dict]:
- """Save a new contact and return the updated list."""
- contacts_db.append({"name": name, "email": email, "category": category})
- return list(contacts_db)
-
-
-@app.ui()
-def contact_form() -> PrefabApp:
- """Contact list with an add form."""
- with Column(gap=6, css_class="p-6") as view:
- Heading("Contacts")
-
- with ForEach("contacts") as contact:
- with Row(gap=2, align="center"):
- Text(contact.name, css_class="font-medium")
- Muted(contact.email)
- Badge(contact.category)
-
- Separator()
-
- with Form(
- on_submit=CallTool(
- "save_contact",
- on_success=[
- SetState("contacts", RESULT),
- ShowToast("Contact saved!", variant="success"),
- ],
- on_error=ShowToast("Failed to save", variant="error"),
- )
- ):
- Input(name="name", label="Full Name", required=True)
- Input(name="email", label="Email", input_type="email", required=True)
- with Select(name="category", label="Category"):
- SelectOption("Customer", value="Customer")
- SelectOption("Vendor", value="Vendor")
- SelectOption("Partner", value="Partner")
- SelectOption("Other", value="Other")
- Textarea(name="notes", label="Notes", placeholder="Optional notes...")
- Button("Save Contact")
-
- return PrefabApp(view=view, state={"contacts": list(contacts_db)})
-
-
-mcp = FastMCP("Server", providers=[app])
-```
-
-## Next Steps
-
-- **[FastMCPApp](/apps/interactive-apps)** — Managed tool binding for server-connected UIs
-- **[Development](/apps/development)** — Preview app tools locally with `fastmcp dev apps`
-- **[Prefab UI Docs](https://prefab.prefect.io)** — Full component reference, layout guides, and more
diff --git a/docs/apps/providers/generative.mdx b/docs/apps/providers/generative.mdx
deleted file mode 100644
index a0795c939..000000000
--- a/docs/apps/providers/generative.mdx
+++ /dev/null
@@ -1,74 +0,0 @@
----
-title: Generative UI
-sidebarTitle: Generative UI
-description: Let the LLM generate custom UIs at runtime
-icon: wand-magic-sparkles
-tag: NEW
----
-
-import { VersionBadge } from '/snippets/version-badge.mdx'
-
-
-
-`GenerativeUI` lets the LLM write Prefab Python code at runtime and render it as a streaming interactive UI. Instead of calling pre-built tools with fixed interfaces, the model creates tailored visualizations for whatever data it's working with.
-
-```python
-from fastmcp import FastMCP
-from fastmcp.apps.generative import GenerativeUI
-
-mcp = FastMCP("My Server")
-mcp.add_provider(GenerativeUI())
-```
-
-This registers:
-
-| Component | Type | Purpose |
-|-----------|------|---------|
-| `generate_prefab_ui` | Tool | Accepts Python code, executes in Pyodide sandbox, renders result |
-| `search_prefab_components` | Tool | Lets the LLM discover available Prefab components |
-| Generative renderer | Resource | `ui://` resource with browser-side Pyodide for streaming |
-
-The LLM writes real Python — loops, f-strings, computation — using Prefab's component library (charts, tables, forms, cards, layout primitives). As the model generates tokens, the host streams partial code to the renderer via `ontoolinputpartial`, so the user watches the UI build up in real time.
-
-## Configuration
-
-```python
-GenerativeUI(
- tool_name="generate_prefab_ui", # Rename the generation tool
- components_tool_name="search_prefab_components", # Rename the search tool
- include_components_tool=True, # Set False to omit the search tool
-)
-```
-
-## What the LLM Sees
-
-The tool description includes code examples that teach the LLM the Prefab patterns. The LLM calls `generate_prefab_ui` with a `code` argument containing Prefab Python, and optionally a `data` argument to pass in real data from the conversation:
-
-```python
-# The LLM generates something like:
-generate_prefab_ui(
- code="""
-from prefab_ui.components import Column, Heading
-from prefab_ui.components.charts import BarChart, ChartSeries
-from prefab_ui.app import PrefabApp
-
-with PrefabApp() as app:
- with Column(gap=4):
- Heading("Revenue")
- BarChart(data=data, series=[ChartSeries(data_key="revenue")], x_axis="quarter")
-""",
- data={"data": [{"quarter": "Q1", "revenue": 42000}, ...]}
-)
-```
-
-The component search tool lets the LLM discover what's available before writing code — `search_prefab_components("Chart")` returns matching components with import paths.
-
-## Requirements
-
-Requires `fastmcp[apps]` (installs `prefab-ui`). The Pyodide sandbox for server-side validation requires Deno, which installs automatically on first use. The streaming renderer loads Pyodide from CDN in the browser — CSP is configured automatically.
-
-The sandbox includes the Python standard library and Prefab. External packages (NumPy, pandas, etc.) are not available.
-
-## Learn More
-
-The full **[Generative UI guide](/apps/generative)** covers the streaming mechanics in detail, how to pass data, the component search tool, and sandbox limitations.
diff --git a/examples/auth/authkit_dcr/README.md b/examples/auth/authkit_dcr/README.md
deleted file mode 100644
index 808246199..000000000
--- a/examples/auth/authkit_dcr/README.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# AuthKit DCR Example
-
-Demonstrates FastMCP server protection with AuthKit Dynamic Client Registration.
-
-## Setup
-
-1. Set your AuthKit domain:
-
- ```bash
- export AUTHKIT_DOMAIN="https://your-app.authkit.app"
- ```
-
-2. Run the server:
-
- ```bash
- python server.py
- ```
-
-3. In another terminal, run the client:
-
- ```bash
- python client.py
- ```
-
-The client will open your browser for AuthKit authentication.
diff --git a/examples/auth/authkit_dcr/client.py b/examples/auth/authkit_dcr/client.py
deleted file mode 100644
index 562637bec..000000000
--- a/examples/auth/authkit_dcr/client.py
+++ /dev/null
@@ -1,33 +0,0 @@
-"""OAuth client example for connecting to FastMCP servers.
-
-This example demonstrates how to connect to an OAuth-protected FastMCP server.
-
-To run:
- python client.py
-"""
-
-import asyncio
-
-from fastmcp.client import Client
-from fastmcp.client.auth import OAuth
-
-SERVER_URL = "http://127.0.0.1:8000/mcp"
-
-
-async def main():
- # AuthKit defaults DCR clients to client_secret_basic, which conflicts
- # with how MCP SDKs send credentials. Force "none" to register as a
- # public client and avoid token exchange errors.
- auth = OAuth(additional_client_metadata={"token_endpoint_auth_method": "none"})
- async with Client(SERVER_URL, auth=auth) as client:
- assert await client.ping()
- print("Successfully authenticated!")
-
- tools = await client.list_tools()
- print(f"Available tools ({len(tools)}):")
- for tool in tools:
- print(f" - {tool.name}: {tool.description}")
-
-
-if __name__ == "__main__":
- asyncio.run(main())
diff --git a/examples/auth/authkit_dcr/server.py b/examples/auth/authkit_dcr/server.py
deleted file mode 100644
index 8974376d2..000000000
--- a/examples/auth/authkit_dcr/server.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""AuthKit DCR server example for FastMCP.
-
-This example demonstrates how to protect a FastMCP server with AuthKit DCR.
-
-Required environment variables:
-- FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN: Your AuthKit domain (e.g., "https://your-app.authkit.app")
-
-To run:
- python server.py
-"""
-
-import os
-
-from fastmcp import FastMCP
-from fastmcp.server.auth.providers.workos import AuthKitProvider
-
-auth = AuthKitProvider(
- authkit_domain=os.getenv("AUTHKIT_DOMAIN") or "",
- base_url="http://localhost:8000",
-)
-
-mcp = FastMCP("AuthKit DCR Example Server", auth=auth)
-
-
-@mcp.tool
-def echo(message: str) -> str:
- """Echo the provided message."""
- return message
-
-
-if __name__ == "__main__":
- mcp.run(transport="http", port=8000)
diff --git a/examples/filesystem-provider/mcp/prompts/assistant.py b/examples/filesystem-provider/mcp/prompts/assistant.py
deleted file mode 100644
index 0950e3423..000000000
--- a/examples/filesystem-provider/mcp/prompts/assistant.py
+++ /dev/null
@@ -1,39 +0,0 @@
-"""Assistant prompts."""
-
-from fastmcp.prompts import prompt
-
-
-@prompt
-def code_review(code: str, language: str = "python") -> str:
- """Generate a code review prompt.
-
- Args:
- code: The code to review.
- language: Programming language (default: python).
- """
- return f"""Please review this {language} code:
-
-```{language}
-{code}
-```
-
-Focus on:
-- Code quality and readability
-- Potential bugs or issues
-- Performance considerations
-- Best practices"""
-
-
-@prompt(
- name="explain-concept",
- description="Generate a prompt to explain a technical concept.",
- tags={"education", "explanation"},
-)
-def explain(topic: str, audience: str = "developer") -> str:
- """Generate an explanation prompt.
-
- Args:
- topic: The concept to explain.
- audience: Target audience level.
- """
- return f"Explain {topic} to a {audience}. Use clear examples and analogies."
diff --git a/examples/filesystem-provider/mcp/resources/config.py b/examples/filesystem-provider/mcp/resources/config.py
deleted file mode 100644
index 03a3f7b11..000000000
--- a/examples/filesystem-provider/mcp/resources/config.py
+++ /dev/null
@@ -1,55 +0,0 @@
-"""Configuration resources - static and templated."""
-
-import json
-
-from fastmcp.resources import resource
-
-
-# Static resource - no parameters in URI
-@resource("config://app")
-def get_app_config() -> str:
- """Get application configuration."""
- return json.dumps(
- {
- "name": "FilesystemDemo",
- "version": "1.0.0",
- "features": ["tools", "resources", "prompts"],
- },
- indent=2,
- )
-
-
-# Resource template - {env} is a parameter
-@resource("config://env/{env}")
-def get_env_config(env: str) -> str:
- """Get environment-specific configuration.
-
- Args:
- env: Environment name (dev, staging, prod).
- """
- configs = {
- "dev": {"debug": True, "log_level": "DEBUG", "database": "localhost"},
- "staging": {"debug": True, "log_level": "INFO", "database": "staging-db"},
- "prod": {"debug": False, "log_level": "WARNING", "database": "prod-db"},
- }
- config = configs.get(env, {"error": f"Unknown environment: {env}"})
- return json.dumps(config, indent=2)
-
-
-# Resource with custom metadata
-@resource(
- "config://features",
- name="feature-flags",
- mime_type="application/json",
- tags={"config", "features"},
-)
-def get_feature_flags() -> str:
- """Get feature flags configuration."""
- return json.dumps(
- {
- "dark_mode": True,
- "beta_features": False,
- "max_upload_size_mb": 100,
- },
- indent=2,
- )
diff --git a/examples/filesystem-provider/mcp/tools/calculator.py b/examples/filesystem-provider/mcp/tools/calculator.py
deleted file mode 100644
index f8d901067..000000000
--- a/examples/filesystem-provider/mcp/tools/calculator.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""Math tools with custom metadata."""
-
-from fastmcp.tools import tool
-
-
-@tool(
- name="add-numbers", # Custom name (default would be "add")
- description="Add two numbers together.",
- tags={"math", "arithmetic"},
-)
-def add(a: float, b: float) -> float:
- """Add two numbers."""
- return a + b
-
-
-@tool(tags={"math", "arithmetic"})
-def multiply(a: float, b: float) -> float:
- """Multiply two numbers.
-
- Args:
- a: First number.
- b: Second number.
- """
- return a * b
diff --git a/examples/filesystem-provider/mcp/tools/greeting.py b/examples/filesystem-provider/mcp/tools/greeting.py
deleted file mode 100644
index f124902a1..000000000
--- a/examples/filesystem-provider/mcp/tools/greeting.py
+++ /dev/null
@@ -1,28 +0,0 @@
-"""Greeting tools - multiple tools in one file."""
-
-from fastmcp.tools import tool
-
-
-@tool
-def greet(name: str) -> str:
- """Greet someone by name.
-
- Args:
- name: The person's name.
- """
- return f"Hello, {name}!"
-
-
-@tool
-def farewell(name: str) -> str:
- """Say goodbye to someone.
-
- Args:
- name: The person's name.
- """
- return f"Goodbye, {name}!"
-
-
-# Helper functions without decorators are ignored
-def _format_message(msg: str) -> str:
- return msg.strip().capitalize()
diff --git a/src/fastmcp/__init__.py b/src/fastmcp/__init__.py
deleted file mode 100644
index 3208b064a..000000000
--- a/src/fastmcp/__init__.py
+++ /dev/null
@@ -1,59 +0,0 @@
-"""FastMCP - An ergonomic MCP interface."""
-
-import importlib
-import warnings
-from importlib.metadata import version as _version
-from typing import TYPE_CHECKING
-
-from fastmcp.settings import Settings
-from fastmcp.utilities.logging import configure_logging as _configure_logging
-
-if TYPE_CHECKING:
- from fastmcp.client import Client as Client
- from fastmcp.apps.app import FastMCPApp as FastMCPApp
-
-settings = Settings()
-if settings.log_enabled:
- _configure_logging(
- level=settings.log_level,
- enable_rich_tracebacks=settings.enable_rich_tracebacks,
- )
-
-from fastmcp.exceptions import FastMCPDeprecationWarning
-from fastmcp.server.server import FastMCP
-from fastmcp.server.context import Context
-import fastmcp.server
-
-__version__ = _version("fastmcp")
-
-if settings.deprecation_warnings:
- warnings.simplefilter("default", FastMCPDeprecationWarning)
-
-
-# --- Lazy imports for performance (see #3292) ---
-# Client and the client submodule are deferred so that server-only users
-# don't pay for the client import chain. Do not convert back to top-level.
-
-
-def __getattr__(name: str) -> object:
- if name == "Client":
- from fastmcp.client import Client
-
- return Client
- if name == "FastMCPApp":
- from fastmcp.apps.app import FastMCPApp
-
- return FastMCPApp
- if name == "client":
- return importlib.import_module("fastmcp.client")
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
-
-
-__all__ = [
- "Client",
- "Context",
- "FastMCP",
- "FastMCPApp",
- "FastMCPDeprecationWarning",
- "settings",
-]
diff --git a/src/fastmcp/apps/__init__.py b/src/fastmcp/apps/__init__.py
deleted file mode 100644
index d8fc21696..000000000
--- a/src/fastmcp/apps/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""FastMCP Apps — interactive UIs for MCP tools.
-
-This package contains the app-related components:
-
-- ``FastMCPApp`` — composable provider for interactive apps with backend tools
-- ``AppConfig`` — configuration for MCP App tools and resources
-- ``ResourceCSP`` / ``ResourcePermissions`` — security configuration
-"""
-
-from fastmcp.apps.app import FastMCPApp as FastMCPApp
-from fastmcp.apps.config import AppConfig as AppConfig
-from fastmcp.apps.config import PrefabAppConfig as PrefabAppConfig
-from fastmcp.apps.config import ResourceCSP as ResourceCSP
-from fastmcp.apps.config import ResourcePermissions as ResourcePermissions
-from fastmcp.apps.config import UI_EXTENSION_ID as UI_EXTENSION_ID
-from fastmcp.apps.config import app_config_to_meta_dict as app_config_to_meta_dict
-from fastmcp.utilities.mime import UI_MIME_TYPE as UI_MIME_TYPE
-from fastmcp.utilities.mime import resolve_ui_mime_type as resolve_ui_mime_type
diff --git a/src/fastmcp/apps/app.py b/src/fastmcp/apps/app.py
deleted file mode 100644
index f4fed7e15..000000000
--- a/src/fastmcp/apps/app.py
+++ /dev/null
@@ -1,428 +0,0 @@
-"""FastMCPApp — a Provider that represents a composable MCP application.
-
-FastMCPApp binds entry-point tools (model calls these) together with backend
-tools (the UI calls these via CallTool). Backend tools are tagged with
-``meta["fastmcp"]["app"]`` so they can be found through the provider chain
-even when transforms (namespace, visibility, etc.) have renamed or hidden
-them — the server sets a context var that tells ``Provider.get_tool`` to
-fall back to a direct lookup for app-visible tools.
-
-Usage::
-
- from fastmcp import FastMCP, FastMCPApp
-
- app = FastMCPApp("Dashboard")
-
- @app.ui()
- def show_dashboard() -> Component:
- return Column(...)
-
- @app.tool()
- def save_contact(name: str, email: str) -> str:
- return name
-
- server = FastMCP("Platform")
- server.add_provider(app)
-"""
-
-from __future__ import annotations
-
-import inspect
-from collections.abc import AsyncIterator, Callable, Sequence
-from contextlib import asynccontextmanager, suppress
-from typing import Any, Literal, TypeVar, overload
-
-from mcp.types import AnyFunction, Icon, ToolAnnotations
-
-from fastmcp.server.auth.authorization import AuthCheck
-from fastmcp.server.providers.base import Provider
-from fastmcp.server.providers.local_provider import LocalProvider
-from fastmcp.tools.base import Tool
-from fastmcp.utilities.logging import get_logger
-
-logger = get_logger(__name__)
-
-F = TypeVar("F", bound=Callable[..., Any])
-
-
-# ---------------------------------------------------------------------------
-# CallTool resolver
-# ---------------------------------------------------------------------------
-
-
-def _make_resolver(app_name: str | None = None) -> Any:
- """Create a CallTool resolver that prefixes tool names with the app name.
-
- When ``app_name`` is set, tool references like ``CallTool("store_files")``
- or ``CallTool(store_files)`` are resolved to
- ``ResolvedTool(name="Files___store_files")``. This produces stable
- identifiers that bypass transforms and work without host ``_meta``
- forwarding.
- """
-
- def _prefix(name: str) -> str:
- if app_name and "___" not in name:
- return f"{app_name}___{name}"
- return name
-
- def _resolve_tool_ref(fn: Any) -> Any:
- from prefab_ui.app import ResolvedTool
-
- if isinstance(fn, str):
- return ResolvedTool(name=_prefix(fn))
-
- fmeta: Any = None
- try:
- from fastmcp.decorators import get_fastmcp_meta
-
- fmeta = get_fastmcp_meta(fn)
- except Exception:
- pass
-
- if fmeta is not None:
- name: str | None = getattr(fmeta, "name", None)
- if name is not None:
- return ResolvedTool(name=_prefix(name))
-
- fn_name = getattr(fn, "__name__", None)
- if fn_name is not None:
- return ResolvedTool(name=_prefix(fn_name))
-
- raise ValueError(f"Cannot resolve tool reference: {fn!r}")
-
- return _resolve_tool_ref
-
-
-def _dispatch_decorator(
- name_or_fn: str | AnyFunction | None,
- name: str | None,
- register: Callable[[Any, str | None], Any],
- decorator_name: str,
-) -> Any:
- """Shared dispatch logic for @app.tool() and @app.ui() calling patterns."""
- if inspect.isroutine(name_or_fn):
- return register(name_or_fn, name)
-
- if isinstance(name_or_fn, str):
- if name is not None:
- raise TypeError(
- "Cannot specify both a name as first argument and as keyword argument."
- )
- tool_name: str | None = name_or_fn
- elif name_or_fn is None:
- tool_name = name
- else:
- raise TypeError(
- f"First argument to @{decorator_name} must be a function, string, or None, "
- f"got {type(name_or_fn)}"
- )
-
- def decorator(fn: F) -> F:
- return register(fn, tool_name)
-
- return decorator
-
-
-# ---------------------------------------------------------------------------
-# FastMCPApp
-# ---------------------------------------------------------------------------
-
-
-class FastMCPApp(Provider):
- """A Provider that represents an MCP application.
-
- Binds together entry-point tools (``@app.ui``), backend tools
- (``@app.tool``), and the Prefab renderer resource. Backend tools
- are tagged with ``meta["fastmcp"]["app"]`` so ``Provider.get_tool``
- can find them by original name even when transforms have been applied.
- """
-
- def __init__(self, name: str) -> None:
- if "___" in name:
- raise ValueError(
- f"App name {name!r} must not contain '___' "
- "(reserved as the app tool routing separator)"
- )
- super().__init__()
- self.name = name
- self._local = LocalProvider(on_duplicate="error")
-
- def __repr__(self) -> str:
- return f"FastMCPApp({self.name!r})"
-
- # ------------------------------------------------------------------
- # @app.tool() — backend tools called by the UI
- # ------------------------------------------------------------------
-
- @overload
- def tool(
- self,
- name_or_fn: F,
- *,
- name: str | None = None,
- description: str | None = None,
- model: bool = False,
- auth: AuthCheck | list[AuthCheck] | None = None,
- timeout: float | None = None,
- ) -> F: ...
-
- @overload
- def tool(
- self,
- name_or_fn: str | None = None,
- *,
- name: str | None = None,
- description: str | None = None,
- model: bool = False,
- auth: AuthCheck | list[AuthCheck] | None = None,
- timeout: float | None = None,
- ) -> Callable[[F], F]: ...
-
- def tool(
- self,
- name_or_fn: str | AnyFunction | None = None,
- *,
- name: str | None = None,
- description: str | None = None,
- model: bool = False,
- auth: AuthCheck | list[AuthCheck] | None = None,
- timeout: float | None = None,
- ) -> Any:
- """Register a backend tool that the UI calls via CallTool.
-
- Backend tools default to ``visibility=["app"]``. Pass ``model=True``
- to also expose the tool to the model (``visibility=["app", "model"]``).
-
- Supports multiple calling patterns::
-
- @app.tool
- def save(name: str): ...
-
- @app.tool()
- def save(name: str): ...
-
- @app.tool("custom_name")
- def save(name: str): ...
- """
- visibility: list[Literal["app", "model"]] = (
- ["app", "model"] if model else ["app"]
- )
-
- def _register(fn: F, tool_name: str | None) -> F:
- resolved_name = tool_name or getattr(fn, "__name__", None)
- if resolved_name is None:
- raise ValueError(f"Cannot determine tool name for {fn!r}")
-
- from fastmcp.apps.config import AppConfig, app_config_to_meta_dict
-
- app_config = AppConfig(visibility=visibility)
- meta: dict[str, Any] = {
- "ui": app_config_to_meta_dict(app_config),
- "fastmcp": {"app": self.name},
- }
-
- tool_obj = Tool.from_function(
- fn,
- name=resolved_name,
- description=description,
- meta=meta,
- timeout=timeout,
- auth=auth,
- )
- self._local._add_component(tool_obj)
- return fn
-
- return _dispatch_decorator(name_or_fn, name, _register, "tool")
-
- # ------------------------------------------------------------------
- # @app.ui() — entry-point tools the model calls to open the app
- # ------------------------------------------------------------------
-
- @overload
- def ui(
- self,
- name_or_fn: F,
- *,
- name: str | None = None,
- description: str | None = None,
- title: str | None = None,
- tags: set[str] | None = None,
- icons: list[Icon] | None = None,
- annotations: ToolAnnotations | None = None,
- auth: AuthCheck | list[AuthCheck] | None = None,
- timeout: float | None = None,
- ) -> F: ...
-
- @overload
- def ui(
- self,
- name_or_fn: str | None = None,
- *,
- name: str | None = None,
- description: str | None = None,
- title: str | None = None,
- tags: set[str] | None = None,
- icons: list[Icon] | None = None,
- annotations: ToolAnnotations | None = None,
- auth: AuthCheck | list[AuthCheck] | None = None,
- timeout: float | None = None,
- ) -> Callable[[F], F]: ...
-
- def ui(
- self,
- name_or_fn: str | AnyFunction | None = None,
- *,
- name: str | None = None,
- description: str | None = None,
- title: str | None = None,
- tags: set[str] | None = None,
- icons: list[Icon] | None = None,
- annotations: ToolAnnotations | None = None,
- auth: AuthCheck | list[AuthCheck] | None = None,
- timeout: float | None = None,
- ) -> Any:
- """Register a UI entry-point tool that the model calls.
-
- Entry-point tools default to ``visibility=["model"]`` and auto-wire
- the Prefab renderer resource and CSP. They are tagged with the app
- name so structured content includes ``_meta.fastmcp.app``.
-
- Supports multiple calling patterns::
-
- @app.ui
- def dashboard() -> Component: ...
-
- @app.ui()
- def dashboard() -> Component: ...
-
- @app.ui("my_dashboard")
- def dashboard() -> Component: ...
- """
-
- def _register(fn: F, tool_name: str | None) -> F:
- from fastmcp.apps.config import AppConfig, app_config_to_meta_dict
- from fastmcp.server.providers.local_provider.decorators.tools import (
- PREFAB_RENDERER_URI,
- _ensure_prefab_renderer,
- )
-
- try:
- from prefab_ui.renderer import get_renderer_csp
-
- from fastmcp.apps.config import ResourceCSP
-
- csp = get_renderer_csp()
- app_config = AppConfig(
- resource_uri=PREFAB_RENDERER_URI,
- visibility=["model"],
- csp=ResourceCSP(
- resource_domains=csp.get("resource_domains"),
- connect_domains=csp.get("connect_domains"),
- ),
- )
- except ImportError:
- app_config = AppConfig(
- resource_uri=PREFAB_RENDERER_URI,
- visibility=["model"],
- )
-
- meta: dict[str, Any] = {
- "ui": app_config_to_meta_dict(app_config),
- "fastmcp": {"app": self.name},
- }
-
- tool_obj = Tool.from_function(
- fn,
- name=tool_name,
- description=description,
- title=title,
- tags=tags,
- icons=icons,
- annotations=annotations,
- meta=meta,
- timeout=timeout,
- auth=auth,
- )
- self._local._add_component(tool_obj)
-
- # Register the Prefab renderer resource on the internal provider
- with suppress(ImportError):
- _ensure_prefab_renderer(self._local)
-
- return fn
-
- return _dispatch_decorator(name_or_fn, name, _register, "ui")
-
- # ------------------------------------------------------------------
- # Programmatic tool addition
- # ------------------------------------------------------------------
-
- def add_tool(
- self,
- tool: Tool | Callable[..., Any],
- ) -> Tool:
- """Add a tool to this app programmatically.
-
- The tool is tagged with this app's name for routing.
- """
- if not isinstance(tool, Tool):
- tool = Tool._ensure_tool(tool)
-
- meta = dict(tool.meta) if tool.meta else {}
- meta.setdefault("fastmcp", {})["app"] = self.name
- ui = meta.setdefault("ui", {})
- if "visibility" not in ui:
- ui["visibility"] = ["app"]
- tool.meta = meta
-
- self._local._add_component(tool)
- return tool
-
- # ------------------------------------------------------------------
- # Provider interface — delegate to internal LocalProvider
- # ------------------------------------------------------------------
-
- async def _list_tools(self) -> Sequence[Tool]:
- return await self._local._list_tools()
-
- async def _get_tool(self, name: str, version: Any = None) -> Tool | None:
- return await self._local._get_tool(name, version)
-
- async def _list_resources(self) -> Sequence[Any]:
- return await self._local._list_resources()
-
- async def _get_resource(self, uri: str, version: Any = None) -> Any | None:
- return await self._local._get_resource(uri, version)
-
- async def _list_resource_templates(self) -> Sequence[Any]:
- return await self._local._list_resource_templates()
-
- async def _get_resource_template(self, uri: str, version: Any = None) -> Any | None:
- return await self._local._get_resource_template(uri, version)
-
- async def _list_prompts(self) -> Sequence[Any]:
- return await self._local._list_prompts()
-
- async def _get_prompt(self, name: str, version: Any = None) -> Any | None:
- return await self._local._get_prompt(name, version)
-
- @asynccontextmanager
- async def lifespan(self) -> AsyncIterator[None]:
- async with self._local.lifespan():
- yield
-
- # ------------------------------------------------------------------
- # Convenience runner
- # ------------------------------------------------------------------
-
- def run(
- self,
- transport: Literal["stdio", "http", "sse", "streamable-http"] | None = None,
- **kwargs: Any,
- ) -> None:
- """Create a temporary FastMCP server and run this app standalone."""
- from fastmcp.server.server import FastMCP
-
- server = FastMCP(self.name)
- server.add_provider(self)
- server.run(transport=transport, **kwargs)
diff --git a/src/fastmcp/apps/approval.py b/src/fastmcp/apps/approval.py
deleted file mode 100644
index 17b124e1f..000000000
--- a/src/fastmcp/apps/approval.py
+++ /dev/null
@@ -1,198 +0,0 @@
-"""Approval — a Provider that adds human-in-the-loop approval to any server.
-
-The LLM presents a summary of what it's about to do, and the user
-approves or rejects via buttons. The result is sent back into the
-conversation as a message, prompting the LLM's next turn.
-
-Requires ``fastmcp[apps]`` (prefab-ui).
-
-Usage::
-
- from fastmcp import FastMCP
- from fastmcp.apps.approval import Approval
-
- mcp = FastMCP("My Server")
- mcp.add_provider(Approval())
-"""
-
-from __future__ import annotations
-
-from typing import Literal
-
-try:
- from prefab_ui.actions import SetState
- from prefab_ui.actions.mcp import SendMessage
- from prefab_ui.app import PrefabApp
- from prefab_ui.components import (
- H3,
- Button,
- Card,
- CardContent,
- CardFooter,
- CardHeader,
- Column,
- Muted,
- Row,
- Text,
- )
- from prefab_ui.components.control_flow import If
- from prefab_ui.rx import STATE
-except ImportError as _exc:
- raise ImportError(
- "Approval requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
- ) from _exc
-
-
-from fastmcp.apps.app import FastMCPApp
-
-
-class Approval(FastMCPApp):
- """A Provider that adds human-in-the-loop approval to a server.
-
- The LLM calls the ``request_approval`` tool with a summary and
- optional details. The user sees an approval card with Approve and
- Reject buttons. Clicking either sends a message back into the
- conversation (via ``SendMessage``), triggering the LLM's next turn.
-
- The message appears as if the user sent it, so the LLM sees
- something like ``'"Deploy v3.2 to production" is APPROVED'``.
-
- Example::
-
- from fastmcp import FastMCP
- from fastmcp.apps.approval import Approval
-
- mcp = FastMCP("My Server")
- mcp.add_provider(Approval())
-
- Customized::
-
- Approval(
- title="Deploy Gate",
- approve_text="Ship it",
- approve_variant="default",
- reject_text="Abort",
- reject_variant="destructive",
- )
- """
-
- def __init__(
- self,
- name: str = "Approval",
- *,
- title: str = "Approval Required",
- approve_text: str = "Approve",
- reject_text: str = "Reject",
- approve_variant: Literal[
- "default", "destructive", "success", "info"
- ] = "default",
- reject_variant: Literal[
- "default", "outline", "destructive", "success", "info"
- ] = "outline",
- ) -> None:
- super().__init__(name)
- self._title = title
- self._approve_text = approve_text
- self._reject_text = reject_text
- self._approve_variant = approve_variant
- self._reject_variant = reject_variant
- self._register_tools()
-
- def __repr__(self) -> str:
- return f"Approval({self.name!r})"
-
- def _register_tools(self) -> None:
- provider = self
-
- @self.ui()
- def request_approval(
- summary: str,
- details: str | None = None,
- title: str | None = None,
- approve_text: str | None = None,
- reject_text: str | None = None,
- approve_variant: str | None = None,
- reject_variant: str | None = None,
- ) -> PrefabApp:
- """Request human approval before proceeding with an action.
-
- Call this tool proactively whenever you are about to take a
- significant or irreversible action and want the user to
- confirm first. Do NOT wait for the user to ask you to seek
- approval — use your judgment about when confirmation is
- appropriate.
-
- The user will see an approval card with the summary, optional
- details, and Approve/Reject buttons. When they click a button,
- their decision appears as a message in the conversation (as if
- the user typed it), like:
-
- "Deploy v3.2 to production" — I selected: Approve
-
- or:
-
- "Deploy v3.2 to production" — I selected: Reject
-
- IMPORTANT: After calling this tool, you MUST stop and wait
- for the user's response. Do not continue, do not take any
- other actions, do not generate further output until you see
- the "I selected:" message. If approved, continue with the
- action. If rejected, acknowledge and ask how to proceed.
-
- Args:
- summary: Brief description of the action requiring approval
- (shown prominently to the user).
- details: Optional longer explanation, context, or
- consequences of the action.
- title: Heading for the approval card (default: "Approval Required").
- approve_text: Label for the approve button (default: "Approve").
- reject_text: Label for the reject button (default: "Reject").
- approve_variant: Button style — "default", "destructive",
- "success", or "info".
- reject_variant: Button style for the reject button
- (same options plus "outline").
- """
- _title = title or provider._title
- _approve = approve_text or provider._approve_text
- _reject = reject_text or provider._reject_text
- _approve_v = approve_variant or provider._approve_variant
- _reject_v = reject_variant or provider._reject_variant
-
- approve_msg = f'"{summary}" — I selected: {_approve}'
- reject_msg = f'"{summary}" — I selected: {_reject}'
-
- with Card(css_class="max-w-lg mx-auto") as view:
- with CardHeader():
- H3(_title)
-
- with CardContent(), Column(gap=3):
- Text(summary, css_class="font-medium")
- if details:
- Muted(details)
-
- with CardFooter():
- with If(STATE.decided):
- Muted("Response sent.")
- with If(~STATE.decided): # noqa: SIM117
- with Row(gap=2, css_class="w-full justify-end"):
- Button(
- _reject,
- variant=_reject_v,
- on_click=[
- SendMessage(reject_msg),
- SetState("decided", True),
- ],
- )
- Button(
- _approve,
- variant=_approve_v,
- on_click=[
- SendMessage(approve_msg),
- SetState("decided", True),
- ],
- )
-
- return PrefabApp(
- view=view,
- state={"decided": False},
- )
diff --git a/src/fastmcp/apps/choice.py b/src/fastmcp/apps/choice.py
deleted file mode 100644
index aaffef903..000000000
--- a/src/fastmcp/apps/choice.py
+++ /dev/null
@@ -1,141 +0,0 @@
-"""Choice — a Provider that lets the user pick from a set of options.
-
-The LLM presents options, the user clicks one, and the selection
-flows back into the conversation as a message.
-
-Requires ``fastmcp[apps]`` (prefab-ui).
-
-Usage::
-
- from fastmcp import FastMCP
- from fastmcp.apps.choice import Choice
-
- mcp = FastMCP("My Server")
- mcp.add_provider(Choice())
-"""
-
-from __future__ import annotations
-
-from typing import Literal
-
-try:
- from prefab_ui.actions import SetState
- from prefab_ui.actions.mcp import SendMessage
- from prefab_ui.app import PrefabApp
- from prefab_ui.components import (
- H3,
- Button,
- Card,
- CardContent,
- CardFooter,
- CardHeader,
- Column,
- Muted,
- Text,
- )
- from prefab_ui.components.control_flow import If
- from prefab_ui.rx import STATE
-except ImportError as _exc:
- raise ImportError(
- "Choice requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
- ) from _exc
-
-from fastmcp.apps.app import FastMCPApp
-
-
-class Choice(FastMCPApp):
- """A Provider that lets the user choose from a set of options.
-
- The LLM calls ``choose`` with a prompt and a list of options.
- The user sees a card with one button per option. Clicking a button
- sends the selection back into the conversation via ``SendMessage``,
- triggering the LLM's next turn.
-
- Example::
-
- from fastmcp import FastMCP
- from fastmcp.apps.choice import Choice
-
- mcp = FastMCP("My Server")
- mcp.add_provider(Choice())
- """
-
- def __init__(
- self,
- name: str = "Choice",
- *,
- title: str = "Choose an Option",
- variant: Literal[
- "default", "outline", "destructive", "success", "info"
- ] = "outline",
- ) -> None:
- super().__init__(name)
- self._title = title
- self._variant = variant
- self._register_tools()
-
- def __repr__(self) -> str:
- return f"Choice({self.name!r})"
-
- def _register_tools(self) -> None:
- provider = self
-
- @self.ui()
- def choose(
- prompt: str,
- options: list[str],
- title: str | None = None,
- ) -> PrefabApp:
- """Present the user with a set of options to choose from.
-
- Call this tool when you need the user to make a decision
- between discrete alternatives. Use it proactively — don't
- ask the user to type their choice in chat when you can
- present clean, clickable options instead.
-
- The user will see a card with one button per option. When
- they click one, their choice appears as a message in the
- conversation (as if the user typed it), like:
-
- "Which deployment strategy?" — I selected: Blue-green
-
- IMPORTANT: After calling this tool, you MUST stop and wait
- for the user's response. Do not continue or take any other
- actions until you see the "I selected:" message.
-
- Args:
- prompt: The question or decision to present to the user.
- options: List of options the user can choose from.
- title: Optional heading for the card.
- """
- _title = title or provider._title
-
- with Card(css_class="max-w-lg mx-auto") as view:
- with CardHeader():
- H3(_title)
-
- with CardContent():
- Text(prompt, css_class="font-medium")
-
- with CardFooter():
- with If(STATE.decided):
- Muted("Response sent.")
- with If(~STATE.decided): # noqa: SIM117
- with Column(gap=2, css_class="w-full"):
- for option in options:
- Button(
- option,
- variant=provider._variant,
- css_class="w-full justify-start",
- on_click=[
- SendMessage(
- f'"{prompt}" — I selected: {option}'
- ),
- SetState("decided", True),
- ],
- )
-
- return PrefabApp(
- view=view,
- state={"decided": False},
- )
diff --git a/src/fastmcp/apps/config.py b/src/fastmcp/apps/config.py
deleted file mode 100644
index c55cd0b13..000000000
--- a/src/fastmcp/apps/config.py
+++ /dev/null
@@ -1,177 +0,0 @@
-"""MCP Apps support — extension negotiation and typed UI metadata models.
-
-Provides constants and Pydantic models for the MCP Apps extension
-(io.modelcontextprotocol/ui), enabling tools and resources to carry
-UI metadata for clients that support interactive app rendering.
-"""
-
-from __future__ import annotations
-
-from typing import Any, Literal
-
-from pydantic import BaseModel, Field
-
-from fastmcp.utilities.mime import UI_MIME_TYPE as UI_MIME_TYPE
-from fastmcp.utilities.mime import resolve_ui_mime_type as resolve_ui_mime_type
-
-UI_EXTENSION_ID = "io.modelcontextprotocol/ui"
-
-
-class ResourceCSP(BaseModel):
- """Content Security Policy for MCP App resources.
-
- Declares which external origins the app is allowed to connect to or
- load resources from. Hosts use these declarations to build the
- ``Content-Security-Policy`` header for the sandboxed iframe.
- """
-
- connect_domains: list[str] | None = Field(
- default=None,
- alias="connectDomains",
- description="Origins allowed for fetch/XHR/WebSocket (connect-src)",
- )
- resource_domains: list[str] | None = Field(
- default=None,
- alias="resourceDomains",
- description="Origins allowed for scripts, images, styles, fonts (script-src etc.)",
- )
- frame_domains: list[str] | None = Field(
- default=None,
- alias="frameDomains",
- description="Origins allowed for nested iframes (frame-src)",
- )
- base_uri_domains: list[str] | None = Field(
- default=None,
- alias="baseUriDomains",
- description="Allowed base URIs for the document (base-uri)",
- )
-
- model_config = {"populate_by_name": True, "extra": "allow"}
-
-
-class ResourcePermissions(BaseModel):
- """Iframe sandbox permissions for MCP App resources.
-
- Each field, when set (typically to ``{}``), requests that the host
- grant the corresponding Permission Policy feature to the sandboxed
- iframe. Hosts MAY honour these; apps should use JS feature detection
- as a fallback.
- """
-
- camera: dict[str, Any] | None = Field(
- default=None, description="Request camera access"
- )
- 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"}
-
-
-class PrefabAppConfig(AppConfig):
- """App configuration for Prefab tools with sensible defaults.
-
- Like ``app=True`` but customizable. Auto-wires the Prefab renderer
- URI and merges the renderer's CSP with any additional domains you
- specify. The renderer resource is registered automatically.
-
- Example::
-
- @mcp.tool(app=PrefabAppConfig()) # same as app=True
-
- @mcp.tool(app=PrefabAppConfig(
- csp=ResourceCSP(frame_domains=["https://example.com"]),
- ))
- """
-
- def model_post_init(self, __context: Any) -> None:
- # Set the renderer URI if not explicitly overridden
- if self.resource_uri is None:
- self.resource_uri = "ui://prefab/renderer.html"
-
- # Merge renderer CSP with user-provided CSP
- try:
- from prefab_ui.renderer import get_renderer_csp
-
- renderer_csp = get_renderer_csp()
- except ImportError:
- renderer_csp = {}
-
- if renderer_csp:
- user_csp = self.csp or ResourceCSP()
- # Start from the user's CSP (preserves model_extra for
- # forward-compat directives), then merge renderer domains.
- merged_data = user_csp.model_dump(exclude_none=True)
- merged_data["connect_domains"] = _merge_domains(
- renderer_csp.get("connect_domains"),
- user_csp.connect_domains,
- )
- merged_data["resource_domains"] = _merge_domains(
- renderer_csp.get("resource_domains"),
- user_csp.resource_domains,
- )
- self.csp = ResourceCSP(**merged_data)
-
-
-def _merge_domains(base: list[str] | None, extra: list[str] | None) -> list[str] | None:
- """Merge two domain lists, deduplicating."""
- if base is None and extra is None:
- return None
- combined = list(base or [])
- for d in extra or []:
- if d not in combined:
- combined.append(d)
- return combined or None
-
-
-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
diff --git a/src/fastmcp/apps/file_upload.py b/src/fastmcp/apps/file_upload.py
deleted file mode 100644
index 8cc91e039..000000000
--- a/src/fastmcp/apps/file_upload.py
+++ /dev/null
@@ -1,393 +0,0 @@
-"""FileUpload — a Provider that adds drag-and-drop file upload to any server.
-
-Lets users upload files directly to the server through an interactive UI,
-bypassing the LLM context window entirely. The LLM can then read and work
-with uploaded files through model-visible tools.
-
-Requires ``fastmcp[apps]`` (prefab-ui).
-
-Usage::
-
- from fastmcp import FastMCP
- from fastmcp.apps import FileUpload
-
- mcp = FastMCP("My Server")
- mcp.add_provider(FileUpload())
-
-For custom persistence, override the storage methods::
-
- class S3Upload(FileUpload):
- def on_store(self, files, ctx):
- # write to S3, return summaries
- ...
-
- def on_list(self, ctx):
- # list from S3
- ...
-
- def on_read(self, name, ctx):
- # read from S3
- ...
-"""
-
-from __future__ import annotations
-
-try:
- from prefab_ui.actions import SetState, ShowToast
- from prefab_ui.actions.mcp import CallTool
- from prefab_ui.app import PrefabApp
- from prefab_ui.components import (
- H3,
- Badge,
- Button,
- Card,
- CardContent,
- CardFooter,
- CardHeader,
- Column,
- DropZone,
- Muted,
- Row,
- Separator,
- Small,
- Text,
- )
- from prefab_ui.components.control_flow import Else, ForEach, If
- from prefab_ui.rx import ERROR, RESULT, STATE, Rx
-except ImportError as _exc:
- raise ImportError(
- "FileUpload requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
- ) from _exc
-
-import base64
-from datetime import datetime
-from typing import Any
-
-from fastmcp.apps.app import FastMCPApp
-from fastmcp.server.context import Context
-
-_TEXT_EXTENSIONS = frozenset(
- (".csv", ".json", ".txt", ".md", ".py", ".yaml", ".yml", ".toml")
-)
-
-
-def _format_size(size: int) -> str:
- if size < 1024:
- return f"{size} B"
- elif size < 1024 * 1024:
- return f"{size / 1024:.1f} KB"
- else:
- return f"{size / (1024 * 1024):.1f} MB"
-
-
-def _make_summary(entry: dict[str, Any]) -> dict[str, Any]:
- return {
- "name": entry["name"],
- "type": entry["type"],
- "size": entry["size"],
- "size_display": _format_size(entry["size"]),
- "uploaded_at": entry["uploaded_at"],
- }
-
-
-class FileUpload(FastMCPApp):
- """A Provider that adds file upload capabilities to a server.
-
- Registers a drag-and-drop UI tool, a backend storage tool, and
- model-visible tools for listing and reading uploaded files.
-
- Files are scoped by MCP session and stored in memory by default.
- Override ``on_store``, ``on_list``, and ``on_read`` for custom
- persistence (filesystem, S3, database, etc.). Each method receives
- the current ``Context``, giving access to session ID, auth tokens,
- and request metadata for partitioning and authorization.
-
- **Session scoping:** The default storage uses ``ctx.session_id`` to
- isolate files by session. This works with stdio, SSE, and stateful
- HTTP transports. In **stateless HTTP** mode, each request creates a
- new session, so files won't persist across requests. For stateless
- deployments, override the storage methods to partition by a stable
- identifier from the auth context::
-
- class UserScopedUpload(FileUpload):
- def on_store(self, files, ctx):
- user_id = ctx.access_token["sub"]
- ...
-
- Example::
-
- from fastmcp import FastMCP
- from fastmcp.apps.file_upload import FileUpload
-
- mcp = FastMCP("My Server")
- mcp.add_provider(FileUpload())
- """
-
- def __init__(
- self,
- name: str = "Files",
- *,
- max_file_size: int = 10 * 1024 * 1024,
- title: str = "File Upload",
- description: str = (
- "Drop files to upload them to the server. "
- "The model can then read and analyze them "
- "without using the context window."
- ),
- drop_label: str = "Drop files here",
- ) -> None:
- super().__init__(name)
- self._max_file_size = max_file_size
- self._title = title
- self._description = description
- self._drop_label = drop_label
-
- # Default in-memory store, keyed by session_id
- self._store: dict[str, dict[str, dict[str, Any]]] = {}
-
- self._register_tools()
-
- def __repr__(self) -> str:
- return f"FileUpload({self.name!r})"
-
- # ------------------------------------------------------------------
- # Storage interface — override these for custom persistence
- # ------------------------------------------------------------------
-
- def _get_scope_key(self, ctx: Context) -> str:
- """Return the key used to partition file storage.
-
- Defaults to ``ctx.session_id``, which is stable for stdio, SSE,
- and stateful HTTP. The default ``on_store``/``on_list``/``on_read``
- implementations call this to partition the in-memory store.
-
- Override to scope by user, tenant, or any other dimension::
-
- def _get_scope_key(self, ctx):
- return ctx.access_token["sub"]
- """
- try:
- return ctx.session_id
- except RuntimeError:
- return "__default__"
-
- def on_store(
- self,
- files: list[dict[str, Any]],
- ctx: Context,
- ) -> list[dict[str, Any]]:
- """Store uploaded files and return summaries.
-
- Args:
- files: List of file dicts, each with ``name``, ``size``,
- ``type``, and ``data`` (base64-encoded content).
- ctx: The current request context. Use for session ID,
- auth tokens, or any metadata needed for partitioning.
-
- Override this method for custom persistence. The default
- implementation stores files in memory, scoped by
- ``_get_scope_key(ctx)``.
-
- Returns:
- List of file summary dicts (``name``, ``type``, ``size``,
- ``size_display``, ``uploaded_at``).
- """
- scope = self._get_scope_key(ctx)
- session_files = self._store.setdefault(scope, {})
- for f in files:
- session_files[f["name"]] = {
- "name": f["name"],
- "size": f["size"],
- "type": f["type"],
- "data": f["data"],
- "uploaded_at": datetime.now().isoformat(timespec="seconds"),
- }
- return [_make_summary(e) for e in session_files.values()]
-
- def on_list(self, ctx: Context) -> list[dict[str, Any]]:
- """List all stored files.
-
- Args:
- ctx: The current request context.
-
- Override this method for custom persistence. The default
- implementation returns files from the current scope.
-
- Returns:
- List of file summary dicts.
- """
- scope = self._get_scope_key(ctx)
- session_files = self._store.get(scope, {})
- return [_make_summary(e) for e in session_files.values()]
-
- def on_read(self, name: str, ctx: Context) -> dict[str, Any]:
- """Read a file's contents by name.
-
- Args:
- name: The filename to read.
- ctx: The current request context.
-
- Override this method for custom persistence. The default
- implementation reads from the current scope's in-memory store.
- Text files are decoded from base64; binary files return a
- truncated base64 preview.
-
- Returns:
- Dict with file metadata and ``content`` (text) or
- ``content_base64`` (binary preview).
-
- Raises:
- ValueError: If the file is not found.
- """
- scope = self._get_scope_key(ctx)
- session_files = self._store.get(scope, {})
- if name not in session_files:
- available = list(session_files.keys())
- raise ValueError(f"File {name!r} not found. Available: {available}")
- entry = session_files[name]
- result: dict[str, Any] = {
- "name": entry["name"],
- "size": entry["size"],
- "type": entry["type"],
- "uploaded_at": entry["uploaded_at"],
- }
- is_text = entry["type"].startswith("text/") or any(
- entry["name"].endswith(ext) for ext in _TEXT_EXTENSIONS
- )
- if is_text:
- try:
- result["content"] = base64.b64decode(entry["data"]).decode("utf-8")
- except UnicodeDecodeError:
- result["content_base64"] = entry["data"][:200] + "..."
- else:
- result["content_base64"] = entry["data"][:200] + "..."
- return result
-
- # ------------------------------------------------------------------
- # Tool registration
- # ------------------------------------------------------------------
-
- def _register_tools(self) -> None:
- provider = self
-
- @self.tool()
- def store_files(files: list[dict], ctx: Context) -> list[dict]:
- """Store uploaded files. Receives file objects with name, size, type, data (base64)."""
- for f in files:
- if f.get("size", 0) > provider._max_file_size:
- raise ValueError(
- f"File {f.get('name', '?')!r} exceeds max size "
- f"({_format_size(f['size'])} > "
- f"{_format_size(provider._max_file_size)})"
- )
- return provider.on_store(files, ctx)
-
- @self.tool(model=True)
- def list_files(ctx: Context) -> list[dict]:
- """List all uploaded files with metadata."""
- return provider.on_list(ctx)
-
- @self.tool(model=True)
- def read_file(name: str, ctx: Context) -> dict:
- """Read an uploaded file's contents by name."""
- return provider.on_read(name, ctx)
-
- @self.ui()
- def file_manager(ctx: Context) -> PrefabApp:
- """Upload and manage files. Drop files here to send them to the server."""
- with Card(css_class="max-w-2xl mx-auto") as view:
- with CardHeader(), Row(gap=2, align="center"):
- H3(provider._title)
- with If(STATE.stored.length()):
- Badge(
- STATE.stored.length(), # ty:ignore[invalid-argument-type]
- variant="secondary",
- )
-
- with CardContent(), Column(gap=4):
- Muted(provider._description)
-
- DropZone(
- name="pending",
- icon="inbox",
- label=provider._drop_label,
- description=(
- "Any file type, up to "
- f"{_format_size(provider._max_file_size)}"
- ),
- multiple=True,
- max_size=provider._max_file_size,
- )
-
- with If(STATE.pending.length()), Column(gap=2):
- with (
- ForEach("pending"),
- Row(gap=2, align="center"),
- Column(gap=0),
- ):
- Small(Rx("$item.name")) # ty:ignore[invalid-argument-type]
- Muted(Rx("$item.type")) # ty:ignore[invalid-argument-type]
-
- Button(
- "Upload to Server",
- on_click=CallTool(
- "store_files",
- arguments={
- "files": Rx("pending"),
- },
- on_success=[
- SetState("stored", RESULT),
- SetState("pending", []),
- ShowToast(
- "Files uploaded!",
- variant="success",
- ),
- ],
- on_error=ShowToast(
- ERROR, # ty:ignore[invalid-argument-type]
- variant="error",
- ),
- ),
- )
-
- with If(STATE.stored.length()):
- Separator()
- Text(
- "Uploaded",
- css_class="font-medium text-sm",
- )
- with (
- ForEach("stored") as f,
- Row(
- gap=2,
- align="center",
- css_class="justify-between",
- ),
- ):
- with Column(gap=0):
- Small(f.name) # ty:ignore[invalid-argument-type]
- Muted(f.uploaded_at) # ty:ignore[invalid-argument-type]
- with Row(gap=2):
- Badge(f.type, variant="secondary") # ty:ignore[invalid-argument-type]
- Badge(
- f.size_display, # ty:ignore[invalid-argument-type]
- variant="outline",
- )
-
- with CardFooter(), Row(align="center", css_class="w-full"):
- with If(STATE.stored.length()):
- Muted(
- f"{STATE.stored.length()}"
- f" {STATE.stored.length().pluralize('file')}"
- " on server"
- )
- with Else():
- Muted("No files uploaded yet")
-
- return PrefabApp(
- view=view,
- state={
- "pending": [],
- "stored": provider.on_list(ctx),
- },
- )
diff --git a/src/fastmcp/apps/form.py b/src/fastmcp/apps/form.py
deleted file mode 100644
index 1d6f67b5a..000000000
--- a/src/fastmcp/apps/form.py
+++ /dev/null
@@ -1,184 +0,0 @@
-"""FormInput — a Provider that collects structured input from the user.
-
-Define a Pydantic model for the data you need, and ``FormInput``
-generates a form UI. The user fills it out, the submission is
-validated, and an optional callback processes the result.
-
-Requires ``fastmcp[apps]`` (prefab-ui).
-
-Usage::
-
- from pydantic import BaseModel
- from fastmcp import FastMCP
- from fastmcp.apps.form import FormInput
-
- class ShippingAddress(BaseModel):
- street: str
- city: str
- state: str
- zip_code: str
-
- mcp = FastMCP("My Server")
- mcp.add_provider(FormInput(model=ShippingAddress))
-"""
-
-from __future__ import annotations
-
-import json
-from collections.abc import Callable
-from typing import Any
-
-try:
- from prefab_ui.actions import SetState
- from prefab_ui.actions.mcp import CallTool, SendMessage
- from prefab_ui.app import PrefabApp
- from prefab_ui.components import (
- H3,
- Card,
- CardContent,
- CardFooter,
- CardHeader,
- Column,
- Form,
- Muted,
- )
- from prefab_ui.components.control_flow import If
- from prefab_ui.rx import RESULT, STATE
-except ImportError as _exc:
- raise ImportError(
- "FormInput requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
- ) from _exc
-
-import pydantic
-
-from fastmcp.apps.app import FastMCPApp
-
-
-class FormInput(FastMCPApp):
- """A Provider that collects structured input via a Pydantic model.
-
- Define a model for the data you need, and ``FormInput`` generates
- a form from it using ``Form.from_model()``. Field types, labels,
- descriptions, and validation are all derived from the model.
-
- Optionally provide an ``on_submit`` callback to process the
- validated data. The callback receives a model instance and returns
- a string that goes back to the LLM. Without a callback, the
- validated JSON is sent directly.
-
- Example::
-
- from pydantic import BaseModel
- from fastmcp import FastMCP
- from fastmcp.apps.form import FormInput
-
- class Contact(BaseModel):
- name: str
- email: str
-
- mcp = FastMCP("My Server")
- mcp.add_provider(FormInput(model=Contact))
-
- With a callback::
-
- def save_contact(contact: Contact) -> str:
- db.insert(contact.model_dump())
- return f"Saved {contact.name}"
-
- mcp.add_provider(FormInput(model=Contact, on_submit=save_contact))
- """
-
- def __init__(
- self,
- model: type[pydantic.BaseModel],
- *,
- name: str | None = None,
- title: str | None = None,
- submit_text: str = "Submit",
- tool_name: str | None = None,
- on_submit: Callable[..., str] | None = None,
- send_message: bool = False,
- ) -> None:
- app_name = name or model.__name__
- super().__init__(app_name)
- self._model = model
- self._title = title or model.__name__
- self._submit_text = submit_text
- self._tool_name = tool_name or f"collect_{model.__name__.lower()}"
- self._on_submit = on_submit
- self._send_message = send_message
- self._register_tools()
-
- def __repr__(self) -> str:
- return f"FormInput({self._model.__name__!r})"
-
- def _register_tools(self) -> None:
- provider = self
- model = self._model
-
- @self.tool()
- def submit_form(data: dict[str, Any]) -> str:
- """Validate and process form submission."""
- validated = model.model_validate(data)
- if provider._on_submit is not None:
- return provider._on_submit(validated)
- return json.dumps(validated.model_dump(mode="json"))
-
- @self.ui(
- name=provider._tool_name,
- description=(
- f"Collect {model.__name__} information from the user via a form. "
- f"Call this tool when you need the user to provide "
- f"{model.__name__} data. The user will see a validated form. "
- f"After calling this tool, STOP and wait for the user to submit."
- ),
- )
- def collect_input(
- prompt: str,
- title: str | None = None,
- submit_text: str | None = None,
- ) -> PrefabApp:
- """Collect structured input from the user.
-
- Args:
- prompt: Tell the user what you need and why.
- title: Optional heading for the form card.
- submit_text: Optional label for the submit button.
- """
- _title = title or provider._title
- _submit = submit_text or provider._submit_text
-
- with Card(css_class="max-w-lg mx-auto") as view:
- with CardHeader():
- H3(_title)
-
- with CardContent(), Column(gap=4):
- Muted(prompt)
-
- on_success_actions: list[Any] = [
- SetState("submitted", True),
- ]
- if provider._send_message:
- on_success_actions.insert(
- 0,
- SendMessage(RESULT), # ty:ignore[invalid-argument-type]
- )
-
- Form.from_model(
- model,
- submit_label=_submit,
- on_submit=[
- CallTool(
- "submit_form",
- on_success=on_success_actions,
- ),
- ],
- )
-
- with CardFooter(), If(STATE.submitted):
- Muted("Submitted.")
-
- return PrefabApp(
- view=view,
- state={"submitted": False},
- )
diff --git a/src/fastmcp/apps/generative.py b/src/fastmcp/apps/generative.py
deleted file mode 100644
index b3cbe3c33..000000000
--- a/src/fastmcp/apps/generative.py
+++ /dev/null
@@ -1,199 +0,0 @@
-"""GenerativeUI — a Provider that adds LLM-generated UI capabilities.
-
-Registers tools and resources from ``prefab_ui.generative`` so that an
-LLM can write Prefab Python code, execute it in a sandbox, and render
-the result as a streaming interactive UI.
-
-Requires ``fastmcp[apps]`` (prefab-ui).
-
-Usage::
-
- from fastmcp import FastMCP
- from fastmcp.apps.generative import GenerativeUI
-
- mcp = FastMCP("My Server")
- mcp.add_provider(GenerativeUI())
-"""
-
-try:
- import prefab_ui.generative as _gen
- from prefab_ui.renderer import (
- get_generative_renderer_csp,
- get_generative_renderer_html,
- )
-except ImportError as _exc:
- raise ImportError(
- "GenerativeUI requires prefab-ui. Install with: pip install 'fastmcp[apps]'"
- ) from _exc
-
-import json
-from collections.abc import AsyncIterator, Sequence
-from contextlib import asynccontextmanager
-from typing import Any
-
-from fastmcp.apps.config import AppConfig, ResourceCSP, app_config_to_meta_dict
-from fastmcp.server.providers.base import Provider
-from fastmcp.server.providers.local_provider import LocalProvider
-from fastmcp.tools.base import Tool
-from fastmcp.utilities.logging import get_logger
-from fastmcp.utilities.mime import UI_MIME_TYPE
-
-logger = get_logger(__name__)
-
-
-def _build_csp() -> ResourceCSP:
- """Build CSP from the generative renderer's declared requirements."""
- csp = get_generative_renderer_csp()
- return ResourceCSP(
- resource_domains=csp.get("resource_domains"),
- connect_domains=csp.get("connect_domains"),
- )
-
-
-class GenerativeUI(Provider):
- """A Provider that adds generative UI capabilities to a server.
-
- Registers:
-
- - A ``generate_ui`` tool that accepts Prefab Python code, executes
- it in a Pyodide sandbox, and returns the rendered PrefabApp.
- Supports streaming via ``ontoolinputpartial``.
- - A ``components`` tool that searches the Prefab component library.
- - The generative renderer resource with CSP for Pyodide CDN access.
-
- Example::
-
- from fastmcp import FastMCP
- from fastmcp.apps.generative import GenerativeUI
-
- mcp = FastMCP("My Server")
- mcp.add_provider(GenerativeUI())
- """
-
- def __init__(
- self,
- *,
- tool_name: str = "generate_prefab_ui",
- include_components_tool: bool = True,
- components_tool_name: str = "search_prefab_components",
- ) -> None:
- super().__init__()
- self._tool_name = tool_name
- self._components_tool_name = components_tool_name
- self._include_components_tool = include_components_tool
- self._local = LocalProvider(on_duplicate="error")
- self._sandbox: Any = None
- self._setup_done = False
-
- def __repr__(self) -> str:
- return f"GenerativeUI(tool_name={self._tool_name!r})"
-
- def _get_sandbox(self) -> Any:
- """Lazily create the Pyodide sandbox."""
- if self._sandbox is None:
- from prefab_ui.sandbox import Sandbox
-
- self._sandbox = Sandbox()
- return self._sandbox
-
- def _ensure_setup(self) -> None:
- """Lazily register tools and resources on first access."""
- if self._setup_done:
- return
-
- csp = _build_csp()
- app_config = AppConfig(resource_uri=_gen.RESOURCE_URI, csp=csp)
-
- # -- generate_ui tool --
- # Wraps prefab_ui.generative.execute with sandbox lifecycle management.
-
- from prefab_ui.app import PrefabApp
-
- sandbox_ref = self # capture for closure
-
- async def generate_ui(
- code: str,
- data: str | dict[str, Any] | None = None,
- ) -> PrefabApp:
- parsed_data: dict[str, Any] | None
- if isinstance(data, str):
- parsed_data = json.loads(data) if data.strip() else None
- else:
- parsed_data = data
- return await _gen.execute(
- code,
- data=parsed_data,
- sandbox=sandbox_ref._get_sandbox(),
- )
-
- tool = Tool.from_function(
- generate_ui,
- name=self._tool_name,
- description=_gen.execute.__doc__ or "",
- meta={"ui": app_config_to_meta_dict(app_config)},
- )
- self._local._add_component(tool)
-
- # -- components tool --
-
- if self._include_components_tool:
- components_tool = Tool.from_function(
- _gen.search_components,
- name=self._components_tool_name,
- description=_gen.search_components.__doc__ or "",
- )
- self._local._add_component(components_tool)
-
- # -- generative renderer resource --
-
- from fastmcp.resources.types import TextResource
-
- resource_config = AppConfig(csp=csp)
- resource = TextResource(
- uri=_gen.RESOURCE_URI, # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
- name="Prefab Generative Renderer",
- text=get_generative_renderer_html(),
- mime_type=UI_MIME_TYPE,
- meta={"ui": app_config_to_meta_dict(resource_config)},
- )
- self._local._add_component(resource)
-
- self._setup_done = True
-
- # ------------------------------------------------------------------
- # Provider interface
- # ------------------------------------------------------------------
-
- async def _list_tools(self) -> Sequence[Tool]:
- self._ensure_setup()
- return await self._local._list_tools()
-
- async def _get_tool(self, name: str, version: Any = None) -> Tool | None:
- self._ensure_setup()
- return await self._local._get_tool(name, version)
-
- async def _list_resources(self) -> Sequence[Any]:
- self._ensure_setup()
- return await self._local._list_resources()
-
- async def _get_resource(self, uri: str, version: Any = None) -> Any | None:
- self._ensure_setup()
- return await self._local._get_resource(uri, version)
-
- async def _list_resource_templates(self) -> Sequence[Any]:
- return []
-
- async def _get_resource_template(self, uri: str, version: Any = None) -> Any | None:
- return None
-
- async def _list_prompts(self) -> Sequence[Any]:
- return []
-
- async def _get_prompt(self, name: str, version: Any = None) -> Any | None:
- return None
-
- @asynccontextmanager
- async def lifespan(self) -> AsyncIterator[None]:
- self._ensure_setup()
- async with self._local.lifespan():
- yield
diff --git a/src/fastmcp/cli/__init__.py b/src/fastmcp/cli/__init__.py
deleted file mode 100644
index 091667730..000000000
--- a/src/fastmcp/cli/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-"""FastMCP CLI package."""
-
-from .cli import app
diff --git a/src/fastmcp/cli/__main__.py b/src/fastmcp/cli/__main__.py
deleted file mode 100644
index aca24b145..000000000
--- a/src/fastmcp/cli/__main__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-"""FastMCP CLI as a runnable package"""
-
-from .cli import app
-
-app()
diff --git a/src/fastmcp/cli/apps_dev.py b/src/fastmcp/cli/apps_dev.py
deleted file mode 100644
index 9a3d7be2d..000000000
--- a/src/fastmcp/cli/apps_dev.py
+++ /dev/null
@@ -1,1806 +0,0 @@
-"""Dev server for previewing FastMCPApp UIs locally.
-
-Starts the user's MCP server on a configurable port, then starts a lightweight
-Starlette dev server that:
-
- - Serves a Prefab-based tool picker at GET /
- - Proxies /mcp to the user's server (avoids browser CORS restrictions)
- - Serves the AppBridge host page at GET /launch
-
-The host page uses @modelcontextprotocol/ext-apps to connect to the MCP server
-and render the selected UI tool inside an iframe.
-
-Startup sequence
-----------------
-1. Download ext-apps app-bridge.js from npm and patch its bare
- ``@modelcontextprotocol/sdk/…`` imports to use concrete esm.sh URLs.
-2. Detect the exact Zod v4 module URL that esm.sh serves for that SDK version
- and build an import-map entry that redirects the broken ``v4.mjs`` (which
- only re-exports ``{z, default}``) to ``v4/classic/index.mjs`` (which
- correctly exports every named Zod v4 function). Import maps apply to the
- full module graph in the document, including cross-origin esm.sh modules.
-3. Serve both the patched JS and the import-map JSON from the dev server.
-"""
-
-from __future__ import annotations
-
-import asyncio
-import contextlib
-import io
-import json
-import logging
-import os
-import re
-import signal
-import sys
-import tarfile
-import tempfile
-import time
-import urllib.request
-import webbrowser
-from pathlib import Path
-from typing import Any
-from urllib.parse import quote
-
-import httpcore
-import httpx
-import uvicorn
-from starlette.applications import Starlette
-from starlette.requests import Request
-from starlette.responses import HTMLResponse, Response, StreamingResponse
-from starlette.routing import Route
-
-from fastmcp.utilities.logging import get_logger
-
-logger = get_logger(__name__)
-
-
-# ---------------------------------------------------------------------------
-# MCP message log (captures proxy traffic for the dev UI log panel)
-# ---------------------------------------------------------------------------
-
-
-class _MessageLog:
- """In-memory buffer of MCP JSON-RPC messages flowing through the proxy."""
-
- def __init__(self) -> None:
- self._entries: list[dict[str, Any]] = []
- self._counter = 0
- self._request_methods: dict[int | str, str] = {}
- self._request_times: dict[int | str, float] = {}
-
- def log_request(self, body: dict[str, Any]) -> None:
- method = body.get("method", "unknown")
- jsonrpc_id = body.get("id")
- timestamp = time.time()
- if jsonrpc_id is not None:
- self._request_methods[jsonrpc_id] = method
- self._request_times[jsonrpc_id] = timestamp
- self._counter += 1
- self._entries.append(
- {
- "id": self._counter,
- "timestamp": timestamp,
- "direction": "request",
- "method": method,
- "body": body,
- }
- )
-
- def log_response(self, body: dict[str, Any]) -> None:
- # Server-initiated notifications have "method" but no "id"
- if "method" in body and "id" not in body:
- self._counter += 1
- self._entries.append(
- {
- "id": self._counter,
- "timestamp": time.time(),
- "direction": "notification",
- "method": body.get("method", "unknown"),
- "body": body,
- }
- )
- return
-
- jsonrpc_id = body.get("id")
- method = (
- self._request_methods.pop(jsonrpc_id, None)
- if jsonrpc_id is not None
- else None
- )
- request_time = (
- self._request_times.pop(jsonrpc_id, None)
- if jsonrpc_id is not None
- else None
- )
- timestamp = time.time()
- duration_ms = (
- round((timestamp - request_time) * 1000, 1) if request_time else None
- )
- self._counter += 1
- self._entries.append(
- {
- "id": self._counter,
- "timestamp": timestamp,
- "direction": "response",
- "method": method,
- "body": body,
- "duration_ms": duration_ms,
- }
- )
-
- def get_since(self, since_id: int = 0) -> list[dict[str, Any]]:
- return [e for e in self._entries if e["id"] > since_id]
-
- def log_bridge(self, body: dict[str, Any]) -> None:
- method = body.get("method", "unknown")
- self._counter += 1
- self._entries.append(
- {
- "id": self._counter,
- "timestamp": time.time(),
- "direction": "bridge",
- "method": method,
- "body": body,
- }
- )
-
- def clear(self) -> None:
- self._entries.clear()
- self._request_methods.clear()
- self._request_times.clear()
-
-
-def _log_response_bytes(log: _MessageLog, raw: bytes, content_type: str) -> None:
- """Parse accumulated proxy response bytes and log as message entries."""
- if not raw:
- return
- try:
- if "text/event-stream" in content_type:
- for line in raw.decode("utf-8", errors="replace").splitlines():
- if line.startswith("data: "):
- with contextlib.suppress(json.JSONDecodeError):
- log.log_response(json.loads(line[6:]))
- else:
- body = json.loads(raw)
- if isinstance(body, list):
- for item in body:
- log.log_response(item)
- else:
- log.log_response(body)
- except (json.JSONDecodeError, TypeError):
- pass
-
-
-_EXT_APPS_VERSION = "1.0.1"
-# Pin to the SDK version ext-apps 1.0.1 was compiled against so the client
-# and transport modules are API-compatible with the app-bridge internals.
-_MCP_SDK_VERSION = "1.25.2"
-
-# ---------------------------------------------------------------------------
-# Shared AppBridge host shell
-# ---------------------------------------------------------------------------
-
-# Both the picker and the app launcher use the same host-page structure: an
-# iframe that hosts a Prefab renderer, wired to the MCP server via AppBridge.
-# The only differences are (a) which URL loads in the iframe and (b) what
-# oninitialized does.
-#
-# app-bridge.js is served locally (see _fetch_app_bridge_bundle).
-# Client/Transport are loaded from esm.sh.
-# The import map (injected as {import_map_tag}) patches the broken esm.sh
-# Zod v4 module so all Zod named exports are visible to the SDK at runtime.
-
-_HOST_SHELL = """\
-
-
-
-
- {title}
-{import_map_tag}
-
-
-
- {status_text}
-
-
-
-
-"""
-
-# ---------------------------------------------------------------------------
-# Host page HTML
-# ---------------------------------------------------------------------------
-
-_HOST_HTML_TEMPLATE = """\
-
-
-
-
- FastMCP Dev — {tool_name}
-{import_map_tag}
-
-
-
- Launching {tool_name}…
-
-
-
-
-"""
-
-# ---------------------------------------------------------------------------
-# Dev log panel (injected into host pages)
-# ---------------------------------------------------------------------------
-
-_LOG_PANEL_HTML = """\
-
-
-
-
-
-
Show
-
-
-
-
-
-
-
-
-
-
-
-
-"""
-
-
-def _inject_log_panel(html: str) -> str:
- """Inject the MCP message log panel before prefab-ui not installed. Run: pip install fastmcp[apps]