fastmcp 2.14.4__py3-none-any.whl → 3.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. fastmcp/_vendor/__init__.py +1 -0
  2. fastmcp/_vendor/docket_di/README.md +7 -0
  3. fastmcp/_vendor/docket_di/__init__.py +163 -0
  4. fastmcp/cli/cli.py +112 -28
  5. fastmcp/cli/install/claude_code.py +1 -5
  6. fastmcp/cli/install/claude_desktop.py +1 -5
  7. fastmcp/cli/install/cursor.py +1 -5
  8. fastmcp/cli/install/gemini_cli.py +1 -5
  9. fastmcp/cli/install/mcp_json.py +1 -6
  10. fastmcp/cli/run.py +146 -5
  11. fastmcp/client/__init__.py +7 -9
  12. fastmcp/client/auth/oauth.py +18 -17
  13. fastmcp/client/client.py +100 -870
  14. fastmcp/client/elicitation.py +1 -1
  15. fastmcp/client/mixins/__init__.py +13 -0
  16. fastmcp/client/mixins/prompts.py +295 -0
  17. fastmcp/client/mixins/resources.py +325 -0
  18. fastmcp/client/mixins/task_management.py +157 -0
  19. fastmcp/client/mixins/tools.py +397 -0
  20. fastmcp/client/sampling/handlers/anthropic.py +2 -2
  21. fastmcp/client/sampling/handlers/openai.py +1 -1
  22. fastmcp/client/tasks.py +3 -3
  23. fastmcp/client/telemetry.py +47 -0
  24. fastmcp/client/transports/__init__.py +38 -0
  25. fastmcp/client/transports/base.py +82 -0
  26. fastmcp/client/transports/config.py +170 -0
  27. fastmcp/client/transports/http.py +145 -0
  28. fastmcp/client/transports/inference.py +154 -0
  29. fastmcp/client/transports/memory.py +90 -0
  30. fastmcp/client/transports/sse.py +89 -0
  31. fastmcp/client/transports/stdio.py +543 -0
  32. fastmcp/contrib/component_manager/README.md +4 -10
  33. fastmcp/contrib/component_manager/__init__.py +1 -2
  34. fastmcp/contrib/component_manager/component_manager.py +95 -160
  35. fastmcp/contrib/component_manager/example.py +1 -1
  36. fastmcp/contrib/mcp_mixin/example.py +4 -4
  37. fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
  38. fastmcp/decorators.py +41 -0
  39. fastmcp/dependencies.py +12 -1
  40. fastmcp/exceptions.py +4 -0
  41. fastmcp/experimental/server/openapi/__init__.py +18 -15
  42. fastmcp/mcp_config.py +13 -4
  43. fastmcp/prompts/__init__.py +6 -3
  44. fastmcp/prompts/function_prompt.py +465 -0
  45. fastmcp/prompts/prompt.py +321 -271
  46. fastmcp/resources/__init__.py +5 -3
  47. fastmcp/resources/function_resource.py +335 -0
  48. fastmcp/resources/resource.py +325 -115
  49. fastmcp/resources/template.py +215 -43
  50. fastmcp/resources/types.py +27 -12
  51. fastmcp/server/__init__.py +2 -2
  52. fastmcp/server/auth/__init__.py +14 -0
  53. fastmcp/server/auth/auth.py +30 -10
  54. fastmcp/server/auth/authorization.py +190 -0
  55. fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
  56. fastmcp/server/auth/oauth_proxy/consent.py +361 -0
  57. fastmcp/server/auth/oauth_proxy/models.py +178 -0
  58. fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
  59. fastmcp/server/auth/oauth_proxy/ui.py +277 -0
  60. fastmcp/server/auth/oidc_proxy.py +2 -2
  61. fastmcp/server/auth/providers/auth0.py +24 -94
  62. fastmcp/server/auth/providers/aws.py +26 -95
  63. fastmcp/server/auth/providers/azure.py +41 -129
  64. fastmcp/server/auth/providers/descope.py +18 -49
  65. fastmcp/server/auth/providers/discord.py +25 -86
  66. fastmcp/server/auth/providers/github.py +23 -87
  67. fastmcp/server/auth/providers/google.py +24 -87
  68. fastmcp/server/auth/providers/introspection.py +60 -79
  69. fastmcp/server/auth/providers/jwt.py +30 -67
  70. fastmcp/server/auth/providers/oci.py +47 -110
  71. fastmcp/server/auth/providers/scalekit.py +23 -61
  72. fastmcp/server/auth/providers/supabase.py +18 -47
  73. fastmcp/server/auth/providers/workos.py +34 -127
  74. fastmcp/server/context.py +372 -419
  75. fastmcp/server/dependencies.py +541 -251
  76. fastmcp/server/elicitation.py +20 -18
  77. fastmcp/server/event_store.py +3 -3
  78. fastmcp/server/http.py +16 -6
  79. fastmcp/server/lifespan.py +198 -0
  80. fastmcp/server/low_level.py +92 -2
  81. fastmcp/server/middleware/__init__.py +5 -1
  82. fastmcp/server/middleware/authorization.py +312 -0
  83. fastmcp/server/middleware/caching.py +101 -54
  84. fastmcp/server/middleware/middleware.py +6 -9
  85. fastmcp/server/middleware/ping.py +70 -0
  86. fastmcp/server/middleware/tool_injection.py +2 -2
  87. fastmcp/server/mixins/__init__.py +7 -0
  88. fastmcp/server/mixins/lifespan.py +217 -0
  89. fastmcp/server/mixins/mcp_operations.py +392 -0
  90. fastmcp/server/mixins/transport.py +342 -0
  91. fastmcp/server/openapi/__init__.py +41 -21
  92. fastmcp/server/openapi/components.py +16 -339
  93. fastmcp/server/openapi/routing.py +34 -118
  94. fastmcp/server/openapi/server.py +67 -392
  95. fastmcp/server/providers/__init__.py +71 -0
  96. fastmcp/server/providers/aggregate.py +261 -0
  97. fastmcp/server/providers/base.py +578 -0
  98. fastmcp/server/providers/fastmcp_provider.py +674 -0
  99. fastmcp/server/providers/filesystem.py +226 -0
  100. fastmcp/server/providers/filesystem_discovery.py +327 -0
  101. fastmcp/server/providers/local_provider/__init__.py +11 -0
  102. fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
  103. fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
  104. fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
  105. fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
  106. fastmcp/server/providers/local_provider/local_provider.py +465 -0
  107. fastmcp/server/providers/openapi/__init__.py +39 -0
  108. fastmcp/server/providers/openapi/components.py +332 -0
  109. fastmcp/server/providers/openapi/provider.py +405 -0
  110. fastmcp/server/providers/openapi/routing.py +109 -0
  111. fastmcp/server/providers/proxy.py +867 -0
  112. fastmcp/server/providers/skills/__init__.py +59 -0
  113. fastmcp/server/providers/skills/_common.py +101 -0
  114. fastmcp/server/providers/skills/claude_provider.py +44 -0
  115. fastmcp/server/providers/skills/directory_provider.py +153 -0
  116. fastmcp/server/providers/skills/skill_provider.py +432 -0
  117. fastmcp/server/providers/skills/vendor_providers.py +142 -0
  118. fastmcp/server/providers/wrapped_provider.py +140 -0
  119. fastmcp/server/proxy.py +34 -700
  120. fastmcp/server/sampling/run.py +341 -2
  121. fastmcp/server/sampling/sampling_tool.py +4 -3
  122. fastmcp/server/server.py +1214 -2171
  123. fastmcp/server/tasks/__init__.py +2 -1
  124. fastmcp/server/tasks/capabilities.py +13 -1
  125. fastmcp/server/tasks/config.py +66 -3
  126. fastmcp/server/tasks/handlers.py +65 -273
  127. fastmcp/server/tasks/keys.py +4 -6
  128. fastmcp/server/tasks/requests.py +474 -0
  129. fastmcp/server/tasks/routing.py +76 -0
  130. fastmcp/server/tasks/subscriptions.py +20 -11
  131. fastmcp/server/telemetry.py +131 -0
  132. fastmcp/server/transforms/__init__.py +244 -0
  133. fastmcp/server/transforms/namespace.py +193 -0
  134. fastmcp/server/transforms/prompts_as_tools.py +175 -0
  135. fastmcp/server/transforms/resources_as_tools.py +190 -0
  136. fastmcp/server/transforms/tool_transform.py +96 -0
  137. fastmcp/server/transforms/version_filter.py +124 -0
  138. fastmcp/server/transforms/visibility.py +526 -0
  139. fastmcp/settings.py +34 -96
  140. fastmcp/telemetry.py +122 -0
  141. fastmcp/tools/__init__.py +10 -3
  142. fastmcp/tools/function_parsing.py +201 -0
  143. fastmcp/tools/function_tool.py +467 -0
  144. fastmcp/tools/tool.py +215 -362
  145. fastmcp/tools/tool_transform.py +38 -21
  146. fastmcp/utilities/async_utils.py +69 -0
  147. fastmcp/utilities/components.py +152 -91
  148. fastmcp/utilities/inspect.py +8 -20
  149. fastmcp/utilities/json_schema.py +12 -5
  150. fastmcp/utilities/json_schema_type.py +17 -15
  151. fastmcp/utilities/lifespan.py +56 -0
  152. fastmcp/utilities/logging.py +12 -4
  153. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
  154. fastmcp/utilities/openapi/parser.py +3 -3
  155. fastmcp/utilities/pagination.py +80 -0
  156. fastmcp/utilities/skills.py +253 -0
  157. fastmcp/utilities/tests.py +0 -16
  158. fastmcp/utilities/timeout.py +47 -0
  159. fastmcp/utilities/types.py +1 -1
  160. fastmcp/utilities/versions.py +285 -0
  161. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
  162. fastmcp-3.0.0b1.dist-info/RECORD +228 -0
  163. fastmcp/client/transports.py +0 -1170
  164. fastmcp/contrib/component_manager/component_service.py +0 -209
  165. fastmcp/prompts/prompt_manager.py +0 -117
  166. fastmcp/resources/resource_manager.py +0 -338
  167. fastmcp/server/tasks/converters.py +0 -206
  168. fastmcp/server/tasks/protocol.py +0 -359
  169. fastmcp/tools/tool_manager.py +0 -170
  170. fastmcp/utilities/mcp_config.py +0 -56
  171. fastmcp-2.14.4.dist-info/RECORD +0 -161
  172. /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
  173. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
  174. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
  175. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1 @@
1
+ """Vendored third-party code for FastMCP."""
@@ -0,0 +1,7 @@
1
+ # Vendored Docket DI
2
+
3
+ This is a minimal vendored copy of the dependency injection engine from [Docket](https://github.com/chrisguidry/docket), pending its release as a standalone library.
4
+
5
+ When `fastmcp[tasks]` is installed, FastMCP uses Docket's DI classes directly for `isinstance` compatibility in worker contexts. This vendored version is only used when Docket is not installed, allowing basic `Depends()` functionality without the full Docket dependency.
6
+
7
+ Once the DI component is released separately, this vendored copy will be removed.
@@ -0,0 +1,163 @@
1
+ """Vendored dependency injection engine from Docket.
2
+
3
+ This is a minimal subset of docket.dependencies for FastMCP's DI system.
4
+ When docket is installed, FastMCP uses docket's classes directly for
5
+ isinstance compatibility. This vendored version is only used when docket
6
+ is not installed.
7
+
8
+ Original source: https://github.com/chrisguidry/docket
9
+ License: MIT
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import abc
15
+ import inspect
16
+ from contextlib import AsyncExitStack
17
+ from contextvars import ContextVar
18
+ from collections.abc import Awaitable, Callable
19
+ from contextlib import AbstractAsyncContextManager, AbstractContextManager
20
+ from typing import (
21
+ Any,
22
+ Generic,
23
+ TypeVar,
24
+ cast,
25
+ )
26
+
27
+ R = TypeVar("R")
28
+
29
+ # Cached signature lookup (simplified from docket.execution.get_signature)
30
+ _signature_cache: dict[Callable[..., Any], inspect.Signature] = {}
31
+
32
+
33
+ def get_signature(function: Callable[..., Any]) -> inspect.Signature:
34
+ """Get cached signature for a function."""
35
+ if function in _signature_cache:
36
+ return _signature_cache[function]
37
+
38
+ signature_attr = getattr(function, "__signature__", None)
39
+ if isinstance(signature_attr, inspect.Signature):
40
+ _signature_cache[function] = signature_attr
41
+ return signature_attr
42
+
43
+ signature = inspect.signature(function)
44
+ _signature_cache[function] = signature
45
+ return signature
46
+
47
+
48
+ class Dependency(abc.ABC):
49
+ """Base class for all dependencies.
50
+
51
+ Subclasses must implement __aenter__ to provide the dependency value.
52
+ The __aexit__ method is optional for cleanup.
53
+ """
54
+
55
+ single: bool = False
56
+
57
+ @abc.abstractmethod
58
+ async def __aenter__(self) -> Any: ...
59
+
60
+ async def __aexit__(self, *args: object) -> None: # noqa: B027
61
+ pass
62
+
63
+
64
+ DependencyFunction = Callable[..., Any]
65
+
66
+ _parameter_cache: dict[Callable[..., Any], dict[str, Dependency]] = {}
67
+
68
+
69
+ def get_dependency_parameters(
70
+ function: Callable[..., Any],
71
+ ) -> dict[str, Dependency]:
72
+ """Find parameters with Dependency defaults."""
73
+ if function in _parameter_cache:
74
+ return _parameter_cache[function]
75
+
76
+ dependencies: dict[str, Dependency] = {}
77
+ signature = get_signature(function)
78
+
79
+ for parameter, param in signature.parameters.items():
80
+ if not isinstance(param.default, Dependency):
81
+ continue
82
+ dependencies[parameter] = param.default
83
+
84
+ _parameter_cache[function] = dependencies
85
+ return dependencies
86
+
87
+
88
+ class _Depends(Dependency, Generic[R]):
89
+ """Wrapper for user-defined dependency functions."""
90
+
91
+ dependency: DependencyFunction
92
+
93
+ cache: ContextVar[dict[DependencyFunction, Any]] = ContextVar("cache")
94
+ stack: ContextVar[AsyncExitStack] = ContextVar("stack")
95
+
96
+ def __init__(self, dependency: DependencyFunction) -> None:
97
+ self.dependency = dependency
98
+
99
+ async def _resolve_parameters(self, function: DependencyFunction) -> dict[str, Any]:
100
+ stack = self.stack.get()
101
+ arguments: dict[str, Any] = {}
102
+ parameters = get_dependency_parameters(function)
103
+
104
+ for parameter, dependency in parameters.items():
105
+ arguments[parameter] = await stack.enter_async_context(dependency)
106
+
107
+ return arguments
108
+
109
+ async def __aenter__(self) -> R:
110
+ cache = self.cache.get()
111
+
112
+ if self.dependency in cache:
113
+ return cache[self.dependency]
114
+
115
+ stack = self.stack.get()
116
+ arguments = await self._resolve_parameters(self.dependency)
117
+
118
+ raw_value = self.dependency(**arguments)
119
+
120
+ # Handle different return types
121
+ resolved_value: R
122
+ if isinstance(raw_value, AbstractAsyncContextManager):
123
+ resolved_value = await stack.enter_async_context(raw_value)
124
+ elif isinstance(raw_value, AbstractContextManager):
125
+ resolved_value = stack.enter_context(raw_value)
126
+ elif inspect.iscoroutine(raw_value) or isinstance(raw_value, Awaitable):
127
+ resolved_value = await cast(Awaitable[R], raw_value)
128
+ else:
129
+ resolved_value = cast(R, raw_value)
130
+
131
+ cache[self.dependency] = resolved_value
132
+ return resolved_value
133
+
134
+
135
+ def Depends(dependency: DependencyFunction) -> Any:
136
+ """Include a user-defined function as a dependency.
137
+
138
+ Dependencies may be:
139
+ - Synchronous functions returning a value
140
+ - Asynchronous functions returning a value (awaitable)
141
+ - Synchronous context managers (using @contextmanager)
142
+ - Asynchronous context managers (using @asynccontextmanager)
143
+
144
+ Example:
145
+ ```python
146
+ def get_config() -> dict:
147
+ return {"api_url": "https://api.example.com"}
148
+
149
+ @mcp.tool
150
+ def my_tool(config: dict = Depends(get_config)) -> str:
151
+ return config["api_url"]
152
+ ```
153
+ """
154
+ return cast(Any, _Depends(dependency))
155
+
156
+
157
+ __all__ = [
158
+ "Dependency",
159
+ "Depends",
160
+ "_Depends",
161
+ "get_dependency_parameters",
162
+ "get_signature",
163
+ ]
fastmcp/cli/cli.py CHANGED
@@ -13,6 +13,7 @@ from typing import Annotated, Literal
13
13
 
14
14
  import cyclopts
15
15
  import pyperclip
16
+ from cyclopts import Parameter
16
17
  from rich.console import Console
17
18
  from rich.table import Table
18
19
 
@@ -37,6 +38,8 @@ app = cyclopts.App(
37
38
  name="fastmcp",
38
39
  help="FastMCP 2.0 - The fast, Pythonic way to build MCP servers and clients.",
39
40
  version=fastmcp.__version__,
41
+ # Disable automatic negative parameters by default
42
+ default_parameter=Parameter(negative=()),
40
43
  )
41
44
 
42
45
 
@@ -91,11 +94,7 @@ def version(
91
94
  *,
92
95
  copy: Annotated[
93
96
  bool,
94
- cyclopts.Parameter(
95
- "--copy",
96
- help="Copy version information to clipboard",
97
- negative="",
98
- ),
97
+ cyclopts.Parameter("--copy", help="Copy version information to clipboard"),
99
98
  ] = False,
100
99
  ):
101
100
  """Display version information and platform details."""
@@ -141,15 +140,12 @@ async def dev(
141
140
  cyclopts.Parameter(
142
141
  "--with-editable",
143
142
  help="Directory containing pyproject.toml to install in editable mode (can be used multiple times)",
144
- negative="",
145
143
  ),
146
144
  ] = None,
147
145
  with_packages: Annotated[
148
146
  list[str] | None,
149
147
  cyclopts.Parameter(
150
- "--with",
151
- help="Additional packages to install (can be used multiple times)",
152
- negative="",
148
+ "--with", help="Additional packages to install (can be used multiple times)"
153
149
  ),
154
150
  ] = None,
155
151
  inspector_version: Annotated[
@@ -194,6 +190,21 @@ async def dev(
194
190
  help="Run the command within the given project directory",
195
191
  ),
196
192
  ] = None,
193
+ reload: Annotated[
194
+ bool,
195
+ cyclopts.Parameter(
196
+ "--reload",
197
+ help="Enable auto-reload on file changes (enabled by default)",
198
+ negative="--no-reload",
199
+ ),
200
+ ] = True,
201
+ reload_dir: Annotated[
202
+ list[Path] | None,
203
+ cyclopts.Parameter(
204
+ "--reload-dir",
205
+ help="Directories to watch for changes (default: current directory)",
206
+ ),
207
+ ] = None,
197
208
  ) -> None:
198
209
  """Run an MCP server with the MCP Inspector for development.
199
210
 
@@ -257,10 +268,18 @@ async def dev(
257
268
  if inspector_version:
258
269
  inspector_cmd += f"@{inspector_version}"
259
270
 
271
+ # Build the fastmcp run command
272
+ fastmcp_cmd = ["fastmcp", "run", server_spec, "--no-banner"]
273
+
274
+ # Add reload flags if enabled - the server will handle reloading
275
+ if reload:
276
+ fastmcp_cmd.append("--reload")
277
+ if reload_dir:
278
+ for dir_path in reload_dir:
279
+ fastmcp_cmd.extend(["--reload-dir", str(dir_path)])
280
+
260
281
  # Use the environment from config (already has CLI overrides applied)
261
- uv_cmd = config.environment.build_command(
262
- ["fastmcp", "run", server_spec, "--no-banner"]
263
- )
282
+ uv_cmd = config.environment.build_command(fastmcp_cmd)
264
283
 
265
284
  # Set marker to prevent infinite loops when subprocess calls FastMCP
266
285
  env = dict(os.environ.items()) | env_vars | {"FASTMCP_UV_SPAWNED": "1"}
@@ -333,11 +352,7 @@ async def run(
333
352
  ] = None,
334
353
  no_banner: Annotated[
335
354
  bool,
336
- cyclopts.Parameter(
337
- "--no-banner",
338
- help="Don't show the server banner",
339
- negative="",
340
- ),
355
+ cyclopts.Parameter("--no-banner", help="Don't show the server banner"),
341
356
  ] = False,
342
357
  python: Annotated[
343
358
  str | None,
@@ -349,9 +364,7 @@ async def run(
349
364
  with_packages: Annotated[
350
365
  list[str] | None,
351
366
  cyclopts.Parameter(
352
- "--with",
353
- help="Additional packages to install (can be used multiple times)",
354
- negative="",
367
+ "--with", help="Additional packages to install (can be used multiple times)"
355
368
  ),
356
369
  ] = None,
357
370
  project: Annotated[
@@ -373,7 +386,6 @@ async def run(
373
386
  cyclopts.Parameter(
374
387
  "--skip-source",
375
388
  help="Skip source preparation step (use when source is already prepared)",
376
- negative="",
377
389
  ),
378
390
  ] = False,
379
391
  skip_env: Annotated[
@@ -381,7 +393,28 @@ async def run(
381
393
  cyclopts.Parameter(
382
394
  "--skip-env",
383
395
  help="Skip environment configuration (for internal use when already in a uv environment)",
384
- negative="",
396
+ ),
397
+ ] = False,
398
+ reload: Annotated[
399
+ bool,
400
+ cyclopts.Parameter(
401
+ "--reload",
402
+ negative="--no-reload",
403
+ help="Enable auto-reload on file changes (development mode)",
404
+ ),
405
+ ] = False,
406
+ reload_dir: Annotated[
407
+ list[Path] | None,
408
+ cyclopts.Parameter(
409
+ "--reload-dir",
410
+ help="Directories to watch for changes (default: current directory)",
411
+ ),
412
+ ] = None,
413
+ stateless: Annotated[
414
+ bool,
415
+ cyclopts.Parameter(
416
+ "--stateless",
417
+ help="Run in stateless mode (no session, used internally for reload)",
385
418
  ),
386
419
  ] = False,
387
420
  ) -> None:
@@ -432,8 +465,10 @@ async def run(
432
465
  final_log_level = log_level or config.deployment.log_level
433
466
  final_server_args = server_args or config.deployment.args
434
467
  # Use CLI override if provided, otherwise use settings
435
- # no_banner CLI flag overrides the show_cli_banner setting
436
- final_no_banner = no_banner if no_banner else not fastmcp.settings.show_cli_banner
468
+ # no_banner CLI flag overrides the show_server_banner setting
469
+ final_no_banner = (
470
+ no_banner if no_banner else not fastmcp.settings.show_server_banner
471
+ )
437
472
 
438
473
  logger.debug(
439
474
  "Running server or client",
@@ -448,6 +483,57 @@ async def run(
448
483
  },
449
484
  )
450
485
 
486
+ # Handle reload mode
487
+ if reload:
488
+ # SSE is incompatible with reload (no stateless mode exists)
489
+ if final_transport == "sse":
490
+ logger.warning(
491
+ "--reload is not supported with SSE transport (sessions are lost on restart). "
492
+ "Use streamable-http transport instead, or use --no-reload. "
493
+ "Running without reload."
494
+ )
495
+ # Fall through to normal execution
496
+ else:
497
+ # Build command for subprocess (with --no-reload to prevent infinite spawning)
498
+ reload_cmd = ["fastmcp", "run", server_spec]
499
+ if final_transport:
500
+ reload_cmd.extend(["--transport", final_transport])
501
+ if final_transport != "stdio":
502
+ if final_host:
503
+ reload_cmd.extend(["--host", final_host])
504
+ if final_port:
505
+ reload_cmd.extend(["--port", str(final_port)])
506
+ if final_path:
507
+ reload_cmd.extend(["--path", final_path])
508
+ if final_log_level:
509
+ reload_cmd.extend(["--log-level", final_log_level])
510
+ if final_no_banner:
511
+ reload_cmd.append("--no-banner")
512
+ reload_cmd.append("--no-reload") # Prevent infinite spawning
513
+ reload_cmd.append("--stateless") # Stateless mode for reload compatibility
514
+
515
+ # If environment setup is needed, wrap with uv
516
+ test_cmd = ["test"]
517
+ needs_uv = (
518
+ config.environment.build_command(test_cmd) != test_cmd and not skip_env
519
+ )
520
+ if needs_uv:
521
+ # Add --skip-env to prevent nested uv runs (child would spawn another uv)
522
+ reload_cmd.append("--skip-env")
523
+
524
+ if final_server_args:
525
+ reload_cmd.append("--")
526
+ reload_cmd.extend(final_server_args)
527
+
528
+ if needs_uv:
529
+ reload_cmd = config.environment.build_command(reload_cmd)
530
+
531
+ is_stdio = final_transport in ("stdio", None)
532
+ await run_module.run_with_reload(
533
+ reload_cmd, reload_dirs=reload_dir, is_stdio=is_stdio
534
+ )
535
+ return
536
+
451
537
  # Check if we need to use uv run (but skip if we're already in uv or user said to skip)
452
538
  # We check if the environment would modify the command
453
539
  test_cmd = ["test"]
@@ -514,6 +600,7 @@ async def run(
514
600
  server_args=list(final_server_args) if final_server_args else [],
515
601
  show_banner=not final_no_banner,
516
602
  skip_source=skip_source,
603
+ stateless=stateless,
517
604
  )
518
605
  except Exception as e:
519
606
  logger.exception(
@@ -554,9 +641,7 @@ async def inspect(
554
641
  with_packages: Annotated[
555
642
  list[str] | None,
556
643
  cyclopts.Parameter(
557
- "--with",
558
- help="Additional packages to install (can be used multiple times)",
559
- negative="",
644
+ "--with", help="Additional packages to install (can be used multiple times)"
560
645
  ),
561
646
  ] = None,
562
647
  project: Annotated[
@@ -578,7 +663,6 @@ async def inspect(
578
663
  cyclopts.Parameter(
579
664
  "--skip-env",
580
665
  help="Skip environment configuration (for internal use when already in a uv environment)",
581
- negative="",
582
666
  ),
583
667
  ] = False,
584
668
  ) -> None:
@@ -165,15 +165,12 @@ async def claude_code_command(
165
165
  cyclopts.Parameter(
166
166
  "--with-editable",
167
167
  help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
168
- negative="",
169
168
  ),
170
169
  ] = None,
171
170
  with_packages: Annotated[
172
171
  list[str] | None,
173
172
  cyclopts.Parameter(
174
- "--with",
175
- help="Additional packages to install (can be used multiple times)",
176
- negative="",
173
+ "--with", help="Additional packages to install (can be used multiple times)"
177
174
  ),
178
175
  ] = None,
179
176
  env_vars: Annotated[
@@ -181,7 +178,6 @@ async def claude_code_command(
181
178
  cyclopts.Parameter(
182
179
  "--env",
183
180
  help="Environment variables in KEY=VALUE format (can be used multiple times)",
184
- negative="",
185
181
  ),
186
182
  ] = None,
187
183
  env_file: Annotated[
@@ -137,15 +137,12 @@ async def claude_desktop_command(
137
137
  cyclopts.Parameter(
138
138
  "--with-editable",
139
139
  help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
140
- negative="",
141
140
  ),
142
141
  ] = None,
143
142
  with_packages: Annotated[
144
143
  list[str] | None,
145
144
  cyclopts.Parameter(
146
- "--with",
147
- help="Additional packages to install (can be used multiple times)",
148
- negative="",
145
+ "--with", help="Additional packages to install (can be used multiple times)"
149
146
  ),
150
147
  ] = None,
151
148
  env_vars: Annotated[
@@ -153,7 +150,6 @@ async def claude_desktop_command(
153
150
  cyclopts.Parameter(
154
151
  "--env",
155
152
  help="Environment variables in KEY=VALUE format (can be used multiple times)",
156
- negative="",
157
153
  ),
158
154
  ] = None,
159
155
  env_file: Annotated[
@@ -252,15 +252,12 @@ async def cursor_command(
252
252
  cyclopts.Parameter(
253
253
  "--with-editable",
254
254
  help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
255
- negative="",
256
255
  ),
257
256
  ] = None,
258
257
  with_packages: Annotated[
259
258
  list[str] | None,
260
259
  cyclopts.Parameter(
261
- "--with",
262
- help="Additional packages to install (can be used multiple times)",
263
- negative="",
260
+ "--with", help="Additional packages to install (can be used multiple times)"
264
261
  ),
265
262
  ] = None,
266
263
  env_vars: Annotated[
@@ -268,7 +265,6 @@ async def cursor_command(
268
265
  cyclopts.Parameter(
269
266
  "--env",
270
267
  help="Environment variables in KEY=VALUE format (can be used multiple times)",
271
- negative="",
272
268
  ),
273
269
  ] = None,
274
270
  env_file: Annotated[
@@ -162,15 +162,12 @@ async def gemini_cli_command(
162
162
  cyclopts.Parameter(
163
163
  "--with-editable",
164
164
  help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
165
- negative="",
166
165
  ),
167
166
  ] = None,
168
167
  with_packages: Annotated[
169
168
  list[str] | None,
170
169
  cyclopts.Parameter(
171
- "--with",
172
- help="Additional packages to install (can be used multiple times)",
173
- negative="",
170
+ "--with", help="Additional packages to install (can be used multiple times)"
174
171
  ),
175
172
  ] = None,
176
173
  env_vars: Annotated[
@@ -178,7 +175,6 @@ async def gemini_cli_command(
178
175
  cyclopts.Parameter(
179
176
  "--env",
180
177
  help="Environment variables in KEY=VALUE format (can be used multiple times)",
181
- negative="",
182
178
  ),
183
179
  ] = None,
184
180
  env_file: Annotated[
@@ -110,15 +110,12 @@ async def mcp_json_command(
110
110
  cyclopts.Parameter(
111
111
  "--with-editable",
112
112
  help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
113
- negative="",
114
113
  ),
115
114
  ] = None,
116
115
  with_packages: Annotated[
117
116
  list[str] | None,
118
117
  cyclopts.Parameter(
119
- "--with",
120
- help="Additional packages to install (can be used multiple times)",
121
- negative="",
118
+ "--with", help="Additional packages to install (can be used multiple times)"
122
119
  ),
123
120
  ] = None,
124
121
  env_vars: Annotated[
@@ -126,7 +123,6 @@ async def mcp_json_command(
126
123
  cyclopts.Parameter(
127
124
  "--env",
128
125
  help="Environment variables in KEY=VALUE format (can be used multiple times)",
129
- negative="",
130
126
  ),
131
127
  ] = None,
132
128
  env_file: Annotated[
@@ -141,7 +137,6 @@ async def mcp_json_command(
141
137
  cyclopts.Parameter(
142
138
  "--copy",
143
139
  help="Copy configuration to clipboard instead of printing to stdout",
144
- negative="",
145
140
  ),
146
141
  ] = False,
147
142
  python: Annotated[