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.
Files changed (133) hide show
  1. fastmcp/__init__.py +2 -23
  2. fastmcp/cli/__init__.py +0 -3
  3. fastmcp/cli/__main__.py +5 -0
  4. fastmcp/cli/cli.py +19 -33
  5. fastmcp/cli/install/claude_code.py +6 -6
  6. fastmcp/cli/install/claude_desktop.py +3 -3
  7. fastmcp/cli/install/cursor.py +18 -12
  8. fastmcp/cli/install/gemini_cli.py +3 -3
  9. fastmcp/cli/install/mcp_json.py +3 -3
  10. fastmcp/cli/install/shared.py +0 -15
  11. fastmcp/cli/run.py +13 -8
  12. fastmcp/cli/tasks.py +110 -0
  13. fastmcp/client/__init__.py +9 -9
  14. fastmcp/client/auth/oauth.py +123 -225
  15. fastmcp/client/client.py +697 -95
  16. fastmcp/client/elicitation.py +11 -5
  17. fastmcp/client/logging.py +18 -14
  18. fastmcp/client/messages.py +7 -5
  19. fastmcp/client/oauth_callback.py +85 -171
  20. fastmcp/client/roots.py +2 -1
  21. fastmcp/client/sampling.py +1 -1
  22. fastmcp/client/tasks.py +614 -0
  23. fastmcp/client/transports.py +117 -30
  24. fastmcp/contrib/component_manager/__init__.py +1 -1
  25. fastmcp/contrib/component_manager/component_manager.py +2 -2
  26. fastmcp/contrib/component_manager/component_service.py +10 -26
  27. fastmcp/contrib/mcp_mixin/README.md +32 -1
  28. fastmcp/contrib/mcp_mixin/__init__.py +2 -2
  29. fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -2
  30. fastmcp/dependencies.py +25 -0
  31. fastmcp/experimental/sampling/handlers/openai.py +3 -3
  32. fastmcp/experimental/server/openapi/__init__.py +20 -21
  33. fastmcp/experimental/utilities/openapi/__init__.py +16 -47
  34. fastmcp/mcp_config.py +3 -4
  35. fastmcp/prompts/__init__.py +1 -1
  36. fastmcp/prompts/prompt.py +54 -51
  37. fastmcp/prompts/prompt_manager.py +16 -101
  38. fastmcp/resources/__init__.py +5 -5
  39. fastmcp/resources/resource.py +43 -21
  40. fastmcp/resources/resource_manager.py +9 -168
  41. fastmcp/resources/template.py +161 -61
  42. fastmcp/resources/types.py +30 -24
  43. fastmcp/server/__init__.py +1 -1
  44. fastmcp/server/auth/__init__.py +9 -14
  45. fastmcp/server/auth/auth.py +197 -46
  46. fastmcp/server/auth/handlers/authorize.py +326 -0
  47. fastmcp/server/auth/jwt_issuer.py +236 -0
  48. fastmcp/server/auth/middleware.py +96 -0
  49. fastmcp/server/auth/oauth_proxy.py +1469 -298
  50. fastmcp/server/auth/oidc_proxy.py +91 -20
  51. fastmcp/server/auth/providers/auth0.py +40 -21
  52. fastmcp/server/auth/providers/aws.py +29 -3
  53. fastmcp/server/auth/providers/azure.py +312 -131
  54. fastmcp/server/auth/providers/debug.py +114 -0
  55. fastmcp/server/auth/providers/descope.py +86 -29
  56. fastmcp/server/auth/providers/discord.py +308 -0
  57. fastmcp/server/auth/providers/github.py +29 -8
  58. fastmcp/server/auth/providers/google.py +48 -9
  59. fastmcp/server/auth/providers/in_memory.py +29 -5
  60. fastmcp/server/auth/providers/introspection.py +281 -0
  61. fastmcp/server/auth/providers/jwt.py +48 -31
  62. fastmcp/server/auth/providers/oci.py +233 -0
  63. fastmcp/server/auth/providers/scalekit.py +238 -0
  64. fastmcp/server/auth/providers/supabase.py +188 -0
  65. fastmcp/server/auth/providers/workos.py +35 -17
  66. fastmcp/server/context.py +236 -116
  67. fastmcp/server/dependencies.py +503 -18
  68. fastmcp/server/elicitation.py +286 -48
  69. fastmcp/server/event_store.py +177 -0
  70. fastmcp/server/http.py +71 -20
  71. fastmcp/server/low_level.py +165 -2
  72. fastmcp/server/middleware/__init__.py +1 -1
  73. fastmcp/server/middleware/caching.py +476 -0
  74. fastmcp/server/middleware/error_handling.py +14 -10
  75. fastmcp/server/middleware/logging.py +50 -39
  76. fastmcp/server/middleware/middleware.py +29 -16
  77. fastmcp/server/middleware/rate_limiting.py +3 -3
  78. fastmcp/server/middleware/tool_injection.py +116 -0
  79. fastmcp/server/openapi/__init__.py +35 -0
  80. fastmcp/{experimental/server → server}/openapi/components.py +15 -10
  81. fastmcp/{experimental/server → server}/openapi/routing.py +3 -3
  82. fastmcp/{experimental/server → server}/openapi/server.py +6 -5
  83. fastmcp/server/proxy.py +72 -48
  84. fastmcp/server/server.py +1415 -733
  85. fastmcp/server/tasks/__init__.py +21 -0
  86. fastmcp/server/tasks/capabilities.py +22 -0
  87. fastmcp/server/tasks/config.py +89 -0
  88. fastmcp/server/tasks/converters.py +205 -0
  89. fastmcp/server/tasks/handlers.py +356 -0
  90. fastmcp/server/tasks/keys.py +93 -0
  91. fastmcp/server/tasks/protocol.py +355 -0
  92. fastmcp/server/tasks/subscriptions.py +205 -0
  93. fastmcp/settings.py +125 -113
  94. fastmcp/tools/__init__.py +1 -1
  95. fastmcp/tools/tool.py +138 -55
  96. fastmcp/tools/tool_manager.py +30 -112
  97. fastmcp/tools/tool_transform.py +12 -21
  98. fastmcp/utilities/cli.py +67 -28
  99. fastmcp/utilities/components.py +10 -5
  100. fastmcp/utilities/inspect.py +79 -23
  101. fastmcp/utilities/json_schema.py +4 -4
  102. fastmcp/utilities/json_schema_type.py +8 -8
  103. fastmcp/utilities/logging.py +118 -8
  104. fastmcp/utilities/mcp_config.py +1 -2
  105. fastmcp/utilities/mcp_server_config/__init__.py +3 -3
  106. fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
  107. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +6 -6
  108. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +5 -5
  109. fastmcp/utilities/mcp_server_config/v1/schema.json +3 -0
  110. fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
  111. fastmcp/{experimental/utilities → utilities}/openapi/README.md +7 -35
  112. fastmcp/utilities/openapi/__init__.py +63 -0
  113. fastmcp/{experimental/utilities → utilities}/openapi/director.py +14 -15
  114. fastmcp/{experimental/utilities → utilities}/openapi/formatters.py +5 -5
  115. fastmcp/{experimental/utilities → utilities}/openapi/json_schema_converter.py +7 -3
  116. fastmcp/{experimental/utilities → utilities}/openapi/parser.py +37 -16
  117. fastmcp/utilities/tests.py +92 -5
  118. fastmcp/utilities/types.py +86 -16
  119. fastmcp/utilities/ui.py +626 -0
  120. {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/METADATA +24 -15
  121. fastmcp-2.14.0.dist-info/RECORD +156 -0
  122. {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/WHEEL +1 -1
  123. fastmcp/cli/claude.py +0 -135
  124. fastmcp/server/auth/providers/bearer.py +0 -25
  125. fastmcp/server/openapi.py +0 -1083
  126. fastmcp/utilities/openapi.py +0 -1568
  127. fastmcp/utilities/storage.py +0 -204
  128. fastmcp-2.12.5.dist-info/RECORD +0 -134
  129. fastmcp/{experimental/server → server}/openapi/README.md +0 -0
  130. fastmcp/{experimental/utilities → utilities}/openapi/models.py +3 -3
  131. fastmcp/{experimental/utilities → utilities}/openapi/schemas.py +2 -2
  132. {fastmcp-2.12.5.dist-info → fastmcp-2.14.0.dist-info}/entry_points.txt +0 -0
  133. {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
@@ -1,6 +1,3 @@
1
1
  """FastMCP CLI package."""
2
2
 
3
3
  from .cli import app
4
-
5
- if __name__ == "__main__":
6
- app()
@@ -0,0 +1,5 @@
1
+ """FastMCP CLI as a runnable package"""
2
+
3
+ from .cli import app
4
+
5
+ app()
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.server.server import 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]] + args
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
- server: FastMCP = await config.source.load_server()
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 with shell=True on Windows
281
- shell = sys.platform == "win32"
259
+ # Run the MCP Inspector command
282
260
  process = subprocess.run(
283
- [npx_cmd, inspector_cmd] + uv_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.error(
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.error(
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.error(
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=str(with_requirements) if with_requirements else None,
114
- project=str(project) if project else None,
115
- editable=[str(p) for p in with_editable] if with_editable else None,
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 (before the name and command)
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.extend([name, "--"])
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=str(with_requirements) if with_requirements else None,
80
- project=str(project) if project else None,
81
- editable=[str(p) for p in with_editable] if with_editable else None,
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:
@@ -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
- deeplink = f"cursor://anysphere.cursor-deeplink/mcp/install?name={server_name}&config={config_b64}"
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
- subprocess.run(
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=str(with_requirements.resolve()) if with_requirements else None,
114
- project=str(project.resolve()) if project else None,
115
- editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
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=str(with_requirements.resolve()) if with_requirements else None,
184
- project=str(project.resolve()) if project else None,
185
- editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
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=str(with_requirements) if with_requirements else None,
111
- project=str(project) if project else None,
112
- editable=[str(p) for p in with_editable] if with_editable else None,
110
+ requirements=with_requirements,
111
+ project=project,
112
+ editable=with_editable,
113
113
  )
114
114
 
115
115
  # Build server spec from parsed components
@@ -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=str(with_requirements) if with_requirements else None,
55
- project=str(project) if project else None,
56
- editable=[str(p) for p in with_editable] if with_editable else None,
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:
@@ -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
- run_v1_server(server, host=host, port=port, transport=transport)
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 run_v1_server(
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
- from functools import partial
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
- runner = partial(server.run)
221
+ await server.run_stdio_async()
215
222
  case "http" | "streamable-http" | None:
216
- runner = partial(server.run, transport="streamable-http")
223
+ await server.run_streamable_http_async()
217
224
  case "sse":
218
- runner = partial(server.run, transport="sse")
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)
@@ -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
- "WSTransport",
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
- "BearerAuth",
25
+ "PythonStdioTransport",
26
+ "SSETransport",
27
+ "StdioTransport",
28
+ "StreamableHttpTransport",
29
+ "UvStdioTransport",
30
+ "UvxStdioTransport",
31
+ "WSTransport",
32
32
  ]