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.
- fastmcp/_vendor/__init__.py +1 -0
- fastmcp/_vendor/docket_di/README.md +7 -0
- fastmcp/_vendor/docket_di/__init__.py +163 -0
- fastmcp/cli/cli.py +112 -28
- fastmcp/cli/install/claude_code.py +1 -5
- fastmcp/cli/install/claude_desktop.py +1 -5
- fastmcp/cli/install/cursor.py +1 -5
- fastmcp/cli/install/gemini_cli.py +1 -5
- fastmcp/cli/install/mcp_json.py +1 -6
- fastmcp/cli/run.py +146 -5
- fastmcp/client/__init__.py +7 -9
- fastmcp/client/auth/oauth.py +18 -17
- fastmcp/client/client.py +100 -870
- fastmcp/client/elicitation.py +1 -1
- fastmcp/client/mixins/__init__.py +13 -0
- fastmcp/client/mixins/prompts.py +295 -0
- fastmcp/client/mixins/resources.py +325 -0
- fastmcp/client/mixins/task_management.py +157 -0
- fastmcp/client/mixins/tools.py +397 -0
- fastmcp/client/sampling/handlers/anthropic.py +2 -2
- fastmcp/client/sampling/handlers/openai.py +1 -1
- fastmcp/client/tasks.py +3 -3
- fastmcp/client/telemetry.py +47 -0
- fastmcp/client/transports/__init__.py +38 -0
- fastmcp/client/transports/base.py +82 -0
- fastmcp/client/transports/config.py +170 -0
- fastmcp/client/transports/http.py +145 -0
- fastmcp/client/transports/inference.py +154 -0
- fastmcp/client/transports/memory.py +90 -0
- fastmcp/client/transports/sse.py +89 -0
- fastmcp/client/transports/stdio.py +543 -0
- fastmcp/contrib/component_manager/README.md +4 -10
- fastmcp/contrib/component_manager/__init__.py +1 -2
- fastmcp/contrib/component_manager/component_manager.py +95 -160
- fastmcp/contrib/component_manager/example.py +1 -1
- fastmcp/contrib/mcp_mixin/example.py +4 -4
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
- fastmcp/decorators.py +41 -0
- fastmcp/dependencies.py +12 -1
- fastmcp/exceptions.py +4 -0
- fastmcp/experimental/server/openapi/__init__.py +18 -15
- fastmcp/mcp_config.py +13 -4
- fastmcp/prompts/__init__.py +6 -3
- fastmcp/prompts/function_prompt.py +465 -0
- fastmcp/prompts/prompt.py +321 -271
- fastmcp/resources/__init__.py +5 -3
- fastmcp/resources/function_resource.py +335 -0
- fastmcp/resources/resource.py +325 -115
- fastmcp/resources/template.py +215 -43
- fastmcp/resources/types.py +27 -12
- fastmcp/server/__init__.py +2 -2
- fastmcp/server/auth/__init__.py +14 -0
- fastmcp/server/auth/auth.py +30 -10
- fastmcp/server/auth/authorization.py +190 -0
- fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
- fastmcp/server/auth/oauth_proxy/consent.py +361 -0
- fastmcp/server/auth/oauth_proxy/models.py +178 -0
- fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
- fastmcp/server/auth/oauth_proxy/ui.py +277 -0
- fastmcp/server/auth/oidc_proxy.py +2 -2
- fastmcp/server/auth/providers/auth0.py +24 -94
- fastmcp/server/auth/providers/aws.py +26 -95
- fastmcp/server/auth/providers/azure.py +41 -129
- fastmcp/server/auth/providers/descope.py +18 -49
- fastmcp/server/auth/providers/discord.py +25 -86
- fastmcp/server/auth/providers/github.py +23 -87
- fastmcp/server/auth/providers/google.py +24 -87
- fastmcp/server/auth/providers/introspection.py +60 -79
- fastmcp/server/auth/providers/jwt.py +30 -67
- fastmcp/server/auth/providers/oci.py +47 -110
- fastmcp/server/auth/providers/scalekit.py +23 -61
- fastmcp/server/auth/providers/supabase.py +18 -47
- fastmcp/server/auth/providers/workos.py +34 -127
- fastmcp/server/context.py +372 -419
- fastmcp/server/dependencies.py +541 -251
- fastmcp/server/elicitation.py +20 -18
- fastmcp/server/event_store.py +3 -3
- fastmcp/server/http.py +16 -6
- fastmcp/server/lifespan.py +198 -0
- fastmcp/server/low_level.py +92 -2
- fastmcp/server/middleware/__init__.py +5 -1
- fastmcp/server/middleware/authorization.py +312 -0
- fastmcp/server/middleware/caching.py +101 -54
- fastmcp/server/middleware/middleware.py +6 -9
- fastmcp/server/middleware/ping.py +70 -0
- fastmcp/server/middleware/tool_injection.py +2 -2
- fastmcp/server/mixins/__init__.py +7 -0
- fastmcp/server/mixins/lifespan.py +217 -0
- fastmcp/server/mixins/mcp_operations.py +392 -0
- fastmcp/server/mixins/transport.py +342 -0
- fastmcp/server/openapi/__init__.py +41 -21
- fastmcp/server/openapi/components.py +16 -339
- fastmcp/server/openapi/routing.py +34 -118
- fastmcp/server/openapi/server.py +67 -392
- fastmcp/server/providers/__init__.py +71 -0
- fastmcp/server/providers/aggregate.py +261 -0
- fastmcp/server/providers/base.py +578 -0
- fastmcp/server/providers/fastmcp_provider.py +674 -0
- fastmcp/server/providers/filesystem.py +226 -0
- fastmcp/server/providers/filesystem_discovery.py +327 -0
- fastmcp/server/providers/local_provider/__init__.py +11 -0
- fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
- fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
- fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
- fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
- fastmcp/server/providers/local_provider/local_provider.py +465 -0
- fastmcp/server/providers/openapi/__init__.py +39 -0
- fastmcp/server/providers/openapi/components.py +332 -0
- fastmcp/server/providers/openapi/provider.py +405 -0
- fastmcp/server/providers/openapi/routing.py +109 -0
- fastmcp/server/providers/proxy.py +867 -0
- fastmcp/server/providers/skills/__init__.py +59 -0
- fastmcp/server/providers/skills/_common.py +101 -0
- fastmcp/server/providers/skills/claude_provider.py +44 -0
- fastmcp/server/providers/skills/directory_provider.py +153 -0
- fastmcp/server/providers/skills/skill_provider.py +432 -0
- fastmcp/server/providers/skills/vendor_providers.py +142 -0
- fastmcp/server/providers/wrapped_provider.py +140 -0
- fastmcp/server/proxy.py +34 -700
- fastmcp/server/sampling/run.py +341 -2
- fastmcp/server/sampling/sampling_tool.py +4 -3
- fastmcp/server/server.py +1214 -2171
- fastmcp/server/tasks/__init__.py +2 -1
- fastmcp/server/tasks/capabilities.py +13 -1
- fastmcp/server/tasks/config.py +66 -3
- fastmcp/server/tasks/handlers.py +65 -273
- fastmcp/server/tasks/keys.py +4 -6
- fastmcp/server/tasks/requests.py +474 -0
- fastmcp/server/tasks/routing.py +76 -0
- fastmcp/server/tasks/subscriptions.py +20 -11
- fastmcp/server/telemetry.py +131 -0
- fastmcp/server/transforms/__init__.py +244 -0
- fastmcp/server/transforms/namespace.py +193 -0
- fastmcp/server/transforms/prompts_as_tools.py +175 -0
- fastmcp/server/transforms/resources_as_tools.py +190 -0
- fastmcp/server/transforms/tool_transform.py +96 -0
- fastmcp/server/transforms/version_filter.py +124 -0
- fastmcp/server/transforms/visibility.py +526 -0
- fastmcp/settings.py +34 -96
- fastmcp/telemetry.py +122 -0
- fastmcp/tools/__init__.py +10 -3
- fastmcp/tools/function_parsing.py +201 -0
- fastmcp/tools/function_tool.py +467 -0
- fastmcp/tools/tool.py +215 -362
- fastmcp/tools/tool_transform.py +38 -21
- fastmcp/utilities/async_utils.py +69 -0
- fastmcp/utilities/components.py +152 -91
- fastmcp/utilities/inspect.py +8 -20
- fastmcp/utilities/json_schema.py +12 -5
- fastmcp/utilities/json_schema_type.py +17 -15
- fastmcp/utilities/lifespan.py +56 -0
- fastmcp/utilities/logging.py +12 -4
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
- fastmcp/utilities/openapi/parser.py +3 -3
- fastmcp/utilities/pagination.py +80 -0
- fastmcp/utilities/skills.py +253 -0
- fastmcp/utilities/tests.py +0 -16
- fastmcp/utilities/timeout.py +47 -0
- fastmcp/utilities/types.py +1 -1
- fastmcp/utilities/versions.py +285 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
- fastmcp-3.0.0b1.dist-info/RECORD +228 -0
- fastmcp/client/transports.py +0 -1170
- fastmcp/contrib/component_manager/component_service.py +0 -209
- fastmcp/prompts/prompt_manager.py +0 -117
- fastmcp/resources/resource_manager.py +0 -338
- fastmcp/server/tasks/converters.py +0 -206
- fastmcp/server/tasks/protocol.py +0 -359
- fastmcp/tools/tool_manager.py +0 -170
- fastmcp/utilities/mcp_config.py +0 -56
- fastmcp-2.14.4.dist-info/RECORD +0 -161
- /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
|
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
|
|
436
|
-
final_no_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[
|
fastmcp/cli/install/cursor.py
CHANGED
|
@@ -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[
|
fastmcp/cli/install/mcp_json.py
CHANGED
|
@@ -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[
|