fastmcp 2.12.5__py3-none-any.whl → 2.14.0__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/__init__.py +2 -23
- fastmcp/cli/__init__.py +0 -3
- fastmcp/cli/__main__.py +5 -0
- fastmcp/cli/cli.py +19 -33
- fastmcp/cli/install/claude_code.py +6 -6
- fastmcp/cli/install/claude_desktop.py +3 -3
- fastmcp/cli/install/cursor.py +18 -12
- fastmcp/cli/install/gemini_cli.py +3 -3
- fastmcp/cli/install/mcp_json.py +3 -3
- fastmcp/cli/install/shared.py +0 -15
- fastmcp/cli/run.py +13 -8
- fastmcp/cli/tasks.py +110 -0
- fastmcp/client/__init__.py +9 -9
- fastmcp/client/auth/oauth.py +123 -225
- fastmcp/client/client.py +697 -95
- fastmcp/client/elicitation.py +11 -5
- fastmcp/client/logging.py +18 -14
- fastmcp/client/messages.py +7 -5
- fastmcp/client/oauth_callback.py +85 -171
- fastmcp/client/roots.py +2 -1
- fastmcp/client/sampling.py +1 -1
- fastmcp/client/tasks.py +614 -0
- fastmcp/client/transports.py +117 -30
- fastmcp/contrib/component_manager/__init__.py +1 -1
- fastmcp/contrib/component_manager/component_manager.py +2 -2
- fastmcp/contrib/component_manager/component_service.py +10 -26
- fastmcp/contrib/mcp_mixin/README.md +32 -1
- fastmcp/contrib/mcp_mixin/__init__.py +2 -2
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -2
- fastmcp/dependencies.py +25 -0
- fastmcp/experimental/sampling/handlers/openai.py +3 -3
- fastmcp/experimental/server/openapi/__init__.py +20 -21
- fastmcp/experimental/utilities/openapi/__init__.py +16 -47
- fastmcp/mcp_config.py +3 -4
- fastmcp/prompts/__init__.py +1 -1
- fastmcp/prompts/prompt.py +54 -51
- fastmcp/prompts/prompt_manager.py +16 -101
- fastmcp/resources/__init__.py +5 -5
- fastmcp/resources/resource.py +43 -21
- fastmcp/resources/resource_manager.py +9 -168
- fastmcp/resources/template.py +161 -61
- fastmcp/resources/types.py +30 -24
- fastmcp/server/__init__.py +1 -1
- fastmcp/server/auth/__init__.py +9 -14
- fastmcp/server/auth/auth.py +197 -46
- fastmcp/server/auth/handlers/authorize.py +326 -0
- fastmcp/server/auth/jwt_issuer.py +236 -0
- fastmcp/server/auth/middleware.py +96 -0
- fastmcp/server/auth/oauth_proxy.py +1469 -298
- fastmcp/server/auth/oidc_proxy.py +91 -20
- fastmcp/server/auth/providers/auth0.py +40 -21
- fastmcp/server/auth/providers/aws.py +29 -3
- fastmcp/server/auth/providers/azure.py +312 -131
- fastmcp/server/auth/providers/debug.py +114 -0
- fastmcp/server/auth/providers/descope.py +86 -29
- fastmcp/server/auth/providers/discord.py +308 -0
- fastmcp/server/auth/providers/github.py +29 -8
- fastmcp/server/auth/providers/google.py +48 -9
- fastmcp/server/auth/providers/in_memory.py +29 -5
- fastmcp/server/auth/providers/introspection.py +281 -0
- fastmcp/server/auth/providers/jwt.py +48 -31
- fastmcp/server/auth/providers/oci.py +233 -0
- fastmcp/server/auth/providers/scalekit.py +238 -0
- fastmcp/server/auth/providers/supabase.py +188 -0
- fastmcp/server/auth/providers/workos.py +35 -17
- fastmcp/server/context.py +236 -116
- fastmcp/server/dependencies.py +503 -18
- fastmcp/server/elicitation.py +286 -48
- fastmcp/server/event_store.py +177 -0
- fastmcp/server/http.py +71 -20
- fastmcp/server/low_level.py +165 -2
- fastmcp/server/middleware/__init__.py +1 -1
- fastmcp/server/middleware/caching.py +476 -0
- fastmcp/server/middleware/error_handling.py +14 -10
- fastmcp/server/middleware/logging.py +50 -39
- fastmcp/server/middleware/middleware.py +29 -16
- fastmcp/server/middleware/rate_limiting.py +3 -3
- fastmcp/server/middleware/tool_injection.py +116 -0
- fastmcp/server/openapi/__init__.py +35 -0
- fastmcp/{experimental/server → server}/openapi/components.py +15 -10
- fastmcp/{experimental/server → server}/openapi/routing.py +3 -3
- fastmcp/{experimental/server → server}/openapi/server.py +6 -5
- fastmcp/server/proxy.py +72 -48
- fastmcp/server/server.py +1415 -733
- fastmcp/server/tasks/__init__.py +21 -0
- fastmcp/server/tasks/capabilities.py +22 -0
- fastmcp/server/tasks/config.py +89 -0
- fastmcp/server/tasks/converters.py +205 -0
- fastmcp/server/tasks/handlers.py +356 -0
- fastmcp/server/tasks/keys.py +93 -0
- fastmcp/server/tasks/protocol.py +355 -0
- fastmcp/server/tasks/subscriptions.py +205 -0
- fastmcp/settings.py +125 -113
- fastmcp/tools/__init__.py +1 -1
- fastmcp/tools/tool.py +138 -55
- fastmcp/tools/tool_manager.py +30 -112
- fastmcp/tools/tool_transform.py +12 -21
- fastmcp/utilities/cli.py +67 -28
- fastmcp/utilities/components.py +10 -5
- fastmcp/utilities/inspect.py +79 -23
- fastmcp/utilities/json_schema.py +4 -4
- fastmcp/utilities/json_schema_type.py +8 -8
- fastmcp/utilities/logging.py +118 -8
- fastmcp/utilities/mcp_config.py +1 -2
- fastmcp/utilities/mcp_server_config/__init__.py +3 -3
- fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
- fastmcp/utilities/mcp_server_config/v1/environments/uv.py +6 -6
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +5 -5
- fastmcp/utilities/mcp_server_config/v1/schema.json +3 -0
- fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
- fastmcp/{experimental/utilities → utilities}/openapi/README.md +7 -35
- fastmcp/utilities/openapi/__init__.py +63 -0
- fastmcp/{experimental/utilities → utilities}/openapi/director.py +14 -15
- fastmcp/{experimental/utilities → utilities}/openapi/formatters.py +5 -5
- fastmcp/{experimental/utilities → utilities}/openapi/json_schema_converter.py +7 -3
- fastmcp/{experimental/utilities → utilities}/openapi/parser.py +37 -16
- fastmcp/utilities/tests.py +92 -5
- fastmcp/utilities/types.py +86 -16
- fastmcp/utilities/ui.py +626 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/METADATA +24 -15
- fastmcp-2.14.0.dist-info/RECORD +156 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/WHEEL +1 -1
- fastmcp/cli/claude.py +0 -135
- fastmcp/server/auth/providers/bearer.py +0 -25
- fastmcp/server/openapi.py +0 -1083
- fastmcp/utilities/openapi.py +0 -1568
- fastmcp/utilities/storage.py +0 -204
- fastmcp-2.12.5.dist-info/RECORD +0 -134
- fastmcp/{experimental/server → server}/openapi/README.md +0 -0
- fastmcp/{experimental/utilities → utilities}/openapi/models.py +3 -3
- fastmcp/{experimental/utilities → utilities}/openapi/schemas.py +2 -2
- {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/__init__.py
CHANGED
|
@@ -27,30 +27,9 @@ if settings.deprecation_warnings:
|
|
|
27
27
|
warnings.simplefilter("default", DeprecationWarning)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def __getattr__(name: str):
|
|
31
|
-
"""
|
|
32
|
-
Used to deprecate the module-level Image class; can be removed once it is no longer imported to root.
|
|
33
|
-
"""
|
|
34
|
-
if name == "Image":
|
|
35
|
-
# Deprecated in 2.8.1
|
|
36
|
-
if settings.deprecation_warnings:
|
|
37
|
-
warnings.warn(
|
|
38
|
-
"The top-level `fastmcp.Image` import is deprecated "
|
|
39
|
-
"and will be removed in a future version. "
|
|
40
|
-
"Please use `fastmcp.utilities.types.Image` instead.",
|
|
41
|
-
DeprecationWarning,
|
|
42
|
-
stacklevel=2,
|
|
43
|
-
)
|
|
44
|
-
from fastmcp.utilities.types import Image
|
|
45
|
-
|
|
46
|
-
return Image
|
|
47
|
-
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
48
|
-
|
|
49
|
-
|
|
50
30
|
__all__ = [
|
|
51
|
-
"FastMCP",
|
|
52
|
-
"Context",
|
|
53
|
-
"client",
|
|
54
31
|
"Client",
|
|
32
|
+
"Context",
|
|
33
|
+
"FastMCP",
|
|
55
34
|
"settings",
|
|
56
35
|
]
|
fastmcp/cli/__init__.py
CHANGED
fastmcp/cli/__main__.py
ADDED
fastmcp/cli/cli.py
CHANGED
|
@@ -19,7 +19,7 @@ from rich.table import Table
|
|
|
19
19
|
import fastmcp
|
|
20
20
|
from fastmcp.cli import run as run_module
|
|
21
21
|
from fastmcp.cli.install import install_app
|
|
22
|
-
from fastmcp.
|
|
22
|
+
from fastmcp.cli.tasks import tasks_app
|
|
23
23
|
from fastmcp.utilities.cli import is_already_in_uv_subprocess, load_and_merge_config
|
|
24
24
|
from fastmcp.utilities.inspect import (
|
|
25
25
|
InspectFormat,
|
|
@@ -28,7 +28,6 @@ from fastmcp.utilities.inspect import (
|
|
|
28
28
|
)
|
|
29
29
|
from fastmcp.utilities.logging import get_logger
|
|
30
30
|
from fastmcp.utilities.mcp_server_config import MCPServerConfig
|
|
31
|
-
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
|
32
31
|
|
|
33
32
|
logger = get_logger("cli")
|
|
34
33
|
console = Console()
|
|
@@ -46,9 +45,7 @@ def _get_npx_command():
|
|
|
46
45
|
# Try both npx.cmd and npx.exe on Windows
|
|
47
46
|
for cmd in ["npx.cmd", "npx.exe", "npx"]:
|
|
48
47
|
try:
|
|
49
|
-
subprocess.run(
|
|
50
|
-
[cmd, "--version"], check=True, capture_output=True, shell=True
|
|
51
|
-
)
|
|
48
|
+
subprocess.run([cmd, "--version"], check=True, capture_output=True)
|
|
52
49
|
return cmd
|
|
53
50
|
except subprocess.CalledProcessError:
|
|
54
51
|
continue
|
|
@@ -80,7 +77,7 @@ def with_argv(args: list[str] | None):
|
|
|
80
77
|
original = sys.argv[:]
|
|
81
78
|
try:
|
|
82
79
|
# Preserve the script name (sys.argv[0]) and replace the rest
|
|
83
|
-
sys.argv = [sys.argv[0]
|
|
80
|
+
sys.argv = [sys.argv[0], *args]
|
|
84
81
|
yield
|
|
85
82
|
finally:
|
|
86
83
|
sys.argv = original
|
|
@@ -106,7 +103,7 @@ def version(
|
|
|
106
103
|
"MCP version": importlib.metadata.version("mcp"),
|
|
107
104
|
"Python version": platform.python_version(),
|
|
108
105
|
"Platform": platform.platform(),
|
|
109
|
-
"FastMCP root path": Path(fastmcp.__file__).resolve().parents[1],
|
|
106
|
+
"FastMCP root path": Path(fastmcp.__file__ or ".").resolve().parents[1],
|
|
110
107
|
}
|
|
111
108
|
|
|
112
109
|
g = Table.grid(padding=(0, 1))
|
|
@@ -226,29 +223,11 @@ async def dev(
|
|
|
226
223
|
)
|
|
227
224
|
|
|
228
225
|
try:
|
|
229
|
-
# Load server to check for deprecated dependencies
|
|
230
226
|
if not config:
|
|
231
227
|
logger.error("No configuration available")
|
|
232
228
|
sys.exit(1)
|
|
233
229
|
assert config is not None # For type checker
|
|
234
|
-
|
|
235
|
-
if server.dependencies:
|
|
236
|
-
import warnings
|
|
237
|
-
|
|
238
|
-
warnings.warn(
|
|
239
|
-
f"Server '{server.name}' uses deprecated 'dependencies' parameter (deprecated in FastMCP 2.11.4). "
|
|
240
|
-
"Please migrate to fastmcp.json configuration file. "
|
|
241
|
-
"See https://gofastmcp.com/docs/deployment/server-configuration for details.",
|
|
242
|
-
DeprecationWarning,
|
|
243
|
-
stacklevel=2,
|
|
244
|
-
)
|
|
245
|
-
# Merge server dependencies with environment dependencies
|
|
246
|
-
env_deps = config.environment.dependencies or []
|
|
247
|
-
all_deps = list(set(env_deps + server.dependencies))
|
|
248
|
-
if not config.environment:
|
|
249
|
-
config.environment = UVEnvironment(dependencies=all_deps)
|
|
250
|
-
else:
|
|
251
|
-
config.environment.dependencies = all_deps
|
|
230
|
+
await config.source.load_server()
|
|
252
231
|
|
|
253
232
|
env_vars = {}
|
|
254
233
|
if ui_port:
|
|
@@ -277,12 +256,10 @@ async def dev(
|
|
|
277
256
|
# Set marker to prevent infinite loops when subprocess calls FastMCP
|
|
278
257
|
env = dict(os.environ.items()) | env_vars | {"FASTMCP_UV_SPAWNED": "1"}
|
|
279
258
|
|
|
280
|
-
# Run the MCP Inspector command
|
|
281
|
-
shell = sys.platform == "win32"
|
|
259
|
+
# Run the MCP Inspector command
|
|
282
260
|
process = subprocess.run(
|
|
283
|
-
[npx_cmd, inspector_cmd
|
|
261
|
+
[npx_cmd, inspector_cmd, *uv_cmd],
|
|
284
262
|
check=True,
|
|
285
|
-
shell=shell,
|
|
286
263
|
env=env,
|
|
287
264
|
)
|
|
288
265
|
sys.exit(process.returncode)
|
|
@@ -506,7 +483,7 @@ async def run(
|
|
|
506
483
|
process = subprocess.run(cmd, check=True, env=env)
|
|
507
484
|
sys.exit(process.returncode)
|
|
508
485
|
except subprocess.CalledProcessError as e:
|
|
509
|
-
logger.
|
|
486
|
+
logger.exception(
|
|
510
487
|
f"Failed to run: {e}",
|
|
511
488
|
extra={
|
|
512
489
|
"server_spec": server_spec,
|
|
@@ -530,7 +507,7 @@ async def run(
|
|
|
530
507
|
skip_source=skip_source,
|
|
531
508
|
)
|
|
532
509
|
except Exception as e:
|
|
533
|
-
logger.
|
|
510
|
+
logger.exception(
|
|
534
511
|
f"Failed to run: {e}",
|
|
535
512
|
extra={
|
|
536
513
|
"server_spec": server_spec,
|
|
@@ -717,6 +694,10 @@ async def inspect(
|
|
|
717
694
|
console.print(f" Name: {info.name}")
|
|
718
695
|
if info.version:
|
|
719
696
|
console.print(f" Version: {info.version}")
|
|
697
|
+
if info.website_url:
|
|
698
|
+
console.print(f" Website: {info.website_url}")
|
|
699
|
+
if info.icons:
|
|
700
|
+
console.print(f" Icons: {len(info.icons)}")
|
|
720
701
|
console.print(f" Generation: {info.server_generation}")
|
|
721
702
|
if info.instructions:
|
|
722
703
|
console.print(f" Instructions: {info.instructions}")
|
|
@@ -766,7 +747,7 @@ async def inspect(
|
|
|
766
747
|
console.print(formatted_json.decode("utf-8"))
|
|
767
748
|
|
|
768
749
|
except Exception as e:
|
|
769
|
-
logger.
|
|
750
|
+
logger.exception(
|
|
770
751
|
f"Failed to inspect server: {e}",
|
|
771
752
|
extra={
|
|
772
753
|
"server_spec": server_spec,
|
|
@@ -838,11 +819,13 @@ async def prepare(
|
|
|
838
819
|
)
|
|
839
820
|
sys.exit(1)
|
|
840
821
|
|
|
822
|
+
assert config_path is not None
|
|
841
823
|
config_file = Path(config_path)
|
|
842
824
|
if not config_file.exists():
|
|
843
825
|
logger.error(f"Configuration file not found: {config_path}")
|
|
844
826
|
sys.exit(1)
|
|
845
827
|
|
|
828
|
+
assert output_dir is not None
|
|
846
829
|
output_path = Path(output_dir)
|
|
847
830
|
|
|
848
831
|
try:
|
|
@@ -873,6 +856,9 @@ app.command(project_app)
|
|
|
873
856
|
# Add install subcommands using proper Cyclopts pattern
|
|
874
857
|
app.command(install_app)
|
|
875
858
|
|
|
859
|
+
# Add tasks subcommand group
|
|
860
|
+
app.command(tasks_app)
|
|
861
|
+
|
|
876
862
|
|
|
877
863
|
if __name__ == "__main__":
|
|
878
864
|
app()
|
|
@@ -110,9 +110,9 @@ def install_claude_code(
|
|
|
110
110
|
env_config = UVEnvironment(
|
|
111
111
|
python=python_version,
|
|
112
112
|
dependencies=(with_packages or []) + ["fastmcp"],
|
|
113
|
-
requirements=
|
|
114
|
-
project=
|
|
115
|
-
editable=
|
|
113
|
+
requirements=with_requirements,
|
|
114
|
+
project=project,
|
|
115
|
+
editable=with_editable,
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
# Build server spec from parsed components
|
|
@@ -125,15 +125,15 @@ def install_claude_code(
|
|
|
125
125
|
full_command = env_config.build_command(["fastmcp", "run", server_spec])
|
|
126
126
|
|
|
127
127
|
# Build claude mcp add command
|
|
128
|
-
cmd_parts = [claude_cmd, "mcp", "add"]
|
|
128
|
+
cmd_parts = [claude_cmd, "mcp", "add", name]
|
|
129
129
|
|
|
130
|
-
# Add environment variables if specified
|
|
130
|
+
# Add environment variables if specified
|
|
131
131
|
if env_vars:
|
|
132
132
|
for key, value in env_vars.items():
|
|
133
133
|
cmd_parts.extend(["-e", f"{key}={value}"])
|
|
134
134
|
|
|
135
135
|
# Add server name and command
|
|
136
|
-
cmd_parts.
|
|
136
|
+
cmd_parts.append("--")
|
|
137
137
|
cmd_parts.extend(full_command)
|
|
138
138
|
|
|
139
139
|
try:
|
|
@@ -76,9 +76,9 @@ def install_claude_desktop(
|
|
|
76
76
|
env_config = UVEnvironment(
|
|
77
77
|
python=python_version,
|
|
78
78
|
dependencies=(with_packages or []) + ["fastmcp"],
|
|
79
|
-
requirements=
|
|
80
|
-
project=
|
|
81
|
-
editable=
|
|
79
|
+
requirements=with_requirements,
|
|
80
|
+
project=project,
|
|
81
|
+
editable=with_editable,
|
|
82
82
|
)
|
|
83
83
|
# Build server spec from parsed components
|
|
84
84
|
if server_object:
|
fastmcp/cli/install/cursor.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""Cursor integration for FastMCP install using Cyclopts."""
|
|
2
2
|
|
|
3
3
|
import base64
|
|
4
|
+
import os
|
|
4
5
|
import subprocess
|
|
5
6
|
import sys
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Annotated
|
|
9
|
+
from urllib.parse import quote, urlparse
|
|
8
10
|
|
|
9
11
|
import cyclopts
|
|
10
12
|
from rich import print
|
|
@@ -36,8 +38,9 @@ def generate_cursor_deeplink(
|
|
|
36
38
|
config_json = server_config.model_dump_json(exclude_none=True)
|
|
37
39
|
config_b64 = base64.urlsafe_b64encode(config_json.encode()).decode()
|
|
38
40
|
|
|
39
|
-
# Generate the deeplink URL
|
|
40
|
-
|
|
41
|
+
# Generate the deeplink URL with properly encoded server name
|
|
42
|
+
encoded_name = quote(server_name, safe="")
|
|
43
|
+
deeplink = f"cursor://anysphere.cursor-deeplink/mcp/install?name={encoded_name}&config={config_b64}"
|
|
41
44
|
|
|
42
45
|
return deeplink
|
|
43
46
|
|
|
@@ -51,17 +54,20 @@ def open_deeplink(deeplink: str) -> bool:
|
|
|
51
54
|
Returns:
|
|
52
55
|
True if the command succeeded, False otherwise
|
|
53
56
|
"""
|
|
57
|
+
parsed = urlparse(deeplink)
|
|
58
|
+
if parsed.scheme != "cursor":
|
|
59
|
+
logger.warning(f"Invalid deeplink scheme: {parsed.scheme}")
|
|
60
|
+
return False
|
|
61
|
+
|
|
54
62
|
try:
|
|
55
63
|
if sys.platform == "darwin": # macOS
|
|
56
64
|
subprocess.run(["open", deeplink], check=True, capture_output=True)
|
|
57
65
|
elif sys.platform == "win32": # Windows
|
|
58
|
-
|
|
59
|
-
["start", deeplink], shell=True, check=True, capture_output=True
|
|
60
|
-
)
|
|
66
|
+
os.startfile(deeplink)
|
|
61
67
|
else: # Linux and others
|
|
62
68
|
subprocess.run(["xdg-open", deeplink], check=True, capture_output=True)
|
|
63
69
|
return True
|
|
64
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
70
|
+
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
|
|
65
71
|
return False
|
|
66
72
|
|
|
67
73
|
|
|
@@ -110,9 +116,9 @@ def install_cursor_workspace(
|
|
|
110
116
|
env_config = UVEnvironment(
|
|
111
117
|
python=python_version,
|
|
112
118
|
dependencies=(with_packages or []) + ["fastmcp"],
|
|
113
|
-
requirements=
|
|
114
|
-
project=
|
|
115
|
-
editable=
|
|
119
|
+
requirements=with_requirements,
|
|
120
|
+
project=project,
|
|
121
|
+
editable=with_editable,
|
|
116
122
|
)
|
|
117
123
|
# Build server spec from parsed components
|
|
118
124
|
if server_object:
|
|
@@ -180,9 +186,9 @@ def install_cursor(
|
|
|
180
186
|
env_config = UVEnvironment(
|
|
181
187
|
python=python_version,
|
|
182
188
|
dependencies=(with_packages or []) + ["fastmcp"],
|
|
183
|
-
requirements=
|
|
184
|
-
project=
|
|
185
|
-
editable=
|
|
189
|
+
requirements=with_requirements,
|
|
190
|
+
project=project,
|
|
191
|
+
editable=with_editable,
|
|
186
192
|
)
|
|
187
193
|
# Build server spec from parsed components
|
|
188
194
|
if server_object:
|
|
@@ -107,9 +107,9 @@ def install_gemini_cli(
|
|
|
107
107
|
env_config = UVEnvironment(
|
|
108
108
|
python=python_version,
|
|
109
109
|
dependencies=(with_packages or []) + ["fastmcp"],
|
|
110
|
-
requirements=
|
|
111
|
-
project=
|
|
112
|
-
editable=
|
|
110
|
+
requirements=with_requirements,
|
|
111
|
+
project=project,
|
|
112
|
+
editable=with_editable,
|
|
113
113
|
)
|
|
114
114
|
|
|
115
115
|
# Build server spec from parsed components
|
fastmcp/cli/install/mcp_json.py
CHANGED
|
@@ -51,9 +51,9 @@ def install_mcp_json(
|
|
|
51
51
|
env_config = UVEnvironment(
|
|
52
52
|
python=python_version,
|
|
53
53
|
dependencies=(with_packages or []) + ["fastmcp"],
|
|
54
|
-
requirements=
|
|
55
|
-
project=
|
|
56
|
-
editable=
|
|
54
|
+
requirements=with_requirements,
|
|
55
|
+
project=project,
|
|
56
|
+
editable=with_editable,
|
|
57
57
|
)
|
|
58
58
|
# Build server spec from parsed components
|
|
59
59
|
if server_object:
|
fastmcp/cli/install/shared.py
CHANGED
|
@@ -105,21 +105,6 @@ async def process_common_args(
|
|
|
105
105
|
)
|
|
106
106
|
name = file.stem
|
|
107
107
|
|
|
108
|
-
# Get server dependencies if available
|
|
109
|
-
# TODO: Remove dependencies handling (deprecated in v2.11.4)
|
|
110
|
-
server_dependencies = getattr(server, "dependencies", []) if server else []
|
|
111
|
-
if server_dependencies:
|
|
112
|
-
import warnings
|
|
113
|
-
|
|
114
|
-
warnings.warn(
|
|
115
|
-
"Server uses deprecated 'dependencies' parameter (deprecated in FastMCP 2.11.4). "
|
|
116
|
-
"Please migrate to fastmcp.json configuration file. "
|
|
117
|
-
"See https://gofastmcp.com/docs/deployment/server-configuration for details.",
|
|
118
|
-
DeprecationWarning,
|
|
119
|
-
stacklevel=2,
|
|
120
|
-
)
|
|
121
|
-
with_packages = list(set(with_packages + server_dependencies))
|
|
122
|
-
|
|
123
108
|
# Process environment variables if provided
|
|
124
109
|
env_dict: dict[str, str] | None = None
|
|
125
110
|
if env_file or env_vars:
|
fastmcp/cli/run.py
CHANGED
|
@@ -172,7 +172,7 @@ async def run_command(
|
|
|
172
172
|
|
|
173
173
|
# handle v1 servers
|
|
174
174
|
if isinstance(server, FastMCP1x):
|
|
175
|
-
|
|
175
|
+
await run_v1_server_async(server, host=host, port=port, transport=transport)
|
|
176
176
|
return
|
|
177
177
|
|
|
178
178
|
kwargs = {}
|
|
@@ -197,24 +197,29 @@ async def run_command(
|
|
|
197
197
|
sys.exit(1)
|
|
198
198
|
|
|
199
199
|
|
|
200
|
-
def
|
|
200
|
+
async def run_v1_server_async(
|
|
201
201
|
server: FastMCP1x,
|
|
202
202
|
host: str | None = None,
|
|
203
203
|
port: int | None = None,
|
|
204
204
|
transport: TransportType | None = None,
|
|
205
205
|
) -> None:
|
|
206
|
-
|
|
206
|
+
"""Run a FastMCP 1.x server using async methods.
|
|
207
207
|
|
|
208
|
+
Args:
|
|
209
|
+
server: FastMCP 1.x server instance
|
|
210
|
+
host: Host to bind to
|
|
211
|
+
port: Port to bind to
|
|
212
|
+
transport: Transport protocol to use
|
|
213
|
+
"""
|
|
208
214
|
if host:
|
|
209
215
|
server.settings.host = host
|
|
210
216
|
if port:
|
|
211
217
|
server.settings.port = port
|
|
218
|
+
|
|
212
219
|
match transport:
|
|
213
220
|
case "stdio":
|
|
214
|
-
|
|
221
|
+
await server.run_stdio_async()
|
|
215
222
|
case "http" | "streamable-http" | None:
|
|
216
|
-
|
|
223
|
+
await server.run_streamable_http_async()
|
|
217
224
|
case "sse":
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
runner()
|
|
225
|
+
await server.run_sse_async()
|
fastmcp/cli/tasks.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""FastMCP tasks CLI for Docket task management."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import cyclopts
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from fastmcp.utilities.cli import load_and_merge_config
|
|
11
|
+
from fastmcp.utilities.logging import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger("cli.tasks")
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
tasks_app = cyclopts.App(
|
|
17
|
+
name="tasks",
|
|
18
|
+
help="Manage FastMCP background tasks using Docket",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check_distributed_backend() -> None:
|
|
23
|
+
"""Check if Docket is configured with a distributed backend.
|
|
24
|
+
|
|
25
|
+
The CLI worker runs as a separate process, so it needs Redis/Valkey
|
|
26
|
+
to coordinate with the main server process.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
SystemExit: If using memory:// URL
|
|
30
|
+
"""
|
|
31
|
+
import fastmcp
|
|
32
|
+
|
|
33
|
+
docket_url = fastmcp.settings.docket.url
|
|
34
|
+
|
|
35
|
+
# Check for memory:// URL and provide helpful error
|
|
36
|
+
if docket_url.startswith("memory://"):
|
|
37
|
+
console.print(
|
|
38
|
+
"[bold red]✗ In-memory backend not supported by CLI[/bold red]\n\n"
|
|
39
|
+
"Your Docket configuration uses an in-memory backend (memory://) which\n"
|
|
40
|
+
"only works within a single process.\n\n"
|
|
41
|
+
"To use [cyan]fastmcp tasks[/cyan] CLI commands (which run in separate\n"
|
|
42
|
+
"processes), you need a distributed backend:\n\n"
|
|
43
|
+
"[bold]1. Install Redis or Valkey:[/bold]\n"
|
|
44
|
+
" [dim]macOS:[/dim] brew install redis\n"
|
|
45
|
+
" [dim]Ubuntu:[/dim] apt install redis-server\n"
|
|
46
|
+
" [dim]Valkey:[/dim] See https://valkey.io/\n\n"
|
|
47
|
+
"[bold]2. Start the service:[/bold]\n"
|
|
48
|
+
" redis-server\n\n"
|
|
49
|
+
"[bold]3. Configure Docket URL:[/bold]\n"
|
|
50
|
+
" [dim]Environment variable:[/dim]\n"
|
|
51
|
+
" export FASTMCP_DOCKET_URL=redis://localhost:6379/0\n\n"
|
|
52
|
+
"[bold]4. Try again[/bold]\n\n"
|
|
53
|
+
"The memory backend works great for single-process servers, but the CLI\n"
|
|
54
|
+
"commands need a distributed backend to coordinate across processes.\n\n"
|
|
55
|
+
"Need help? See: [cyan]https://gofastmcp.com/docs/tasks[/cyan]"
|
|
56
|
+
)
|
|
57
|
+
sys.exit(1)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@tasks_app.command
|
|
61
|
+
def worker(
|
|
62
|
+
server_spec: Annotated[
|
|
63
|
+
str | None,
|
|
64
|
+
cyclopts.Parameter(
|
|
65
|
+
help="Python file to run, optionally with :object suffix, or None to auto-detect fastmcp.json"
|
|
66
|
+
),
|
|
67
|
+
] = None,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Start an additional worker to process background tasks.
|
|
70
|
+
|
|
71
|
+
Connects to your Docket backend and processes tasks in parallel with
|
|
72
|
+
any other running workers. Configure via environment variables
|
|
73
|
+
(FASTMCP_DOCKET_*).
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
fastmcp tasks worker server.py
|
|
77
|
+
fastmcp tasks worker examples/tasks/server.py
|
|
78
|
+
"""
|
|
79
|
+
import fastmcp
|
|
80
|
+
|
|
81
|
+
check_distributed_backend()
|
|
82
|
+
|
|
83
|
+
# Load server to get task functions
|
|
84
|
+
try:
|
|
85
|
+
config, _resolved_spec = load_and_merge_config(server_spec)
|
|
86
|
+
except FileNotFoundError:
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
|
|
89
|
+
# Load the server
|
|
90
|
+
server = asyncio.run(config.source.load_server())
|
|
91
|
+
|
|
92
|
+
async def run_worker():
|
|
93
|
+
"""Enter server lifespan and camp forever."""
|
|
94
|
+
async with server._lifespan_manager():
|
|
95
|
+
console.print(
|
|
96
|
+
f"[bold green]✓[/bold green] Starting worker for [cyan]{server.name}[/cyan]"
|
|
97
|
+
)
|
|
98
|
+
console.print(f" Docket: {fastmcp.settings.docket.name}")
|
|
99
|
+
console.print(f" Backend: {fastmcp.settings.docket.url}")
|
|
100
|
+
console.print(f" Concurrency: {fastmcp.settings.docket.concurrency}")
|
|
101
|
+
|
|
102
|
+
# Server's lifespan has started its worker - just camp here forever
|
|
103
|
+
while True:
|
|
104
|
+
await asyncio.sleep(3600)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
asyncio.run(run_worker())
|
|
108
|
+
except KeyboardInterrupt:
|
|
109
|
+
console.print("\n[yellow]Worker stopped[/yellow]")
|
|
110
|
+
sys.exit(0)
|
fastmcp/client/__init__.py
CHANGED
|
@@ -15,18 +15,18 @@ from .transports import (
|
|
|
15
15
|
from .auth import OAuth, BearerAuth
|
|
16
16
|
|
|
17
17
|
__all__ = [
|
|
18
|
+
"BearerAuth",
|
|
18
19
|
"Client",
|
|
19
20
|
"ClientTransport",
|
|
20
|
-
"
|
|
21
|
-
"SSETransport",
|
|
22
|
-
"StdioTransport",
|
|
23
|
-
"PythonStdioTransport",
|
|
21
|
+
"FastMCPTransport",
|
|
24
22
|
"NodeStdioTransport",
|
|
25
|
-
"UvxStdioTransport",
|
|
26
|
-
"UvStdioTransport",
|
|
27
23
|
"NpxStdioTransport",
|
|
28
|
-
"FastMCPTransport",
|
|
29
|
-
"StreamableHttpTransport",
|
|
30
24
|
"OAuth",
|
|
31
|
-
"
|
|
25
|
+
"PythonStdioTransport",
|
|
26
|
+
"SSETransport",
|
|
27
|
+
"StdioTransport",
|
|
28
|
+
"StreamableHttpTransport",
|
|
29
|
+
"UvStdioTransport",
|
|
30
|
+
"UvxStdioTransport",
|
|
31
|
+
"WSTransport",
|
|
32
32
|
]
|