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
fastmcp/cli/run.py CHANGED
@@ -1,14 +1,18 @@
1
1
  """FastMCP run command implementation with enhanced type hints."""
2
2
 
3
+ import asyncio
4
+ import contextlib
3
5
  import json
4
6
  import re
7
+ import signal
5
8
  import sys
6
9
  from pathlib import Path
7
10
  from typing import Any, Literal
8
11
 
9
12
  from mcp.server.fastmcp import FastMCP as FastMCP1x
13
+ from watchfiles import Change, awatch
10
14
 
11
- from fastmcp.server.server import FastMCP
15
+ from fastmcp.server.server import FastMCP, create_proxy
12
16
  from fastmcp.utilities.logging import get_logger
13
17
  from fastmcp.utilities.mcp_server_config import (
14
18
  MCPServerConfig,
@@ -41,7 +45,7 @@ def create_client_server(url: str) -> Any:
41
45
  import fastmcp
42
46
 
43
47
  client = fastmcp.Client(url)
44
- server = fastmcp.FastMCP.as_proxy(client)
48
+ server = create_proxy(client)
45
49
  return server
46
50
  except Exception as e:
47
51
  logger.error(f"Failed to create client for URL {url}: {e}")
@@ -50,12 +54,10 @@ def create_client_server(url: str) -> Any:
50
54
 
51
55
  def create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]:
52
56
  """Create a FastMCP server from a MCPConfig."""
53
- from fastmcp import FastMCP
54
-
55
57
  with mcp_config_path.open() as src:
56
58
  mcp_config = json.load(src)
57
59
 
58
- server = FastMCP.as_proxy(mcp_config)
60
+ server = create_proxy(mcp_config)
59
61
  return server
60
62
 
61
63
 
@@ -87,6 +89,7 @@ async def run_command(
87
89
  show_banner: bool = True,
88
90
  use_direct_import: bool = False,
89
91
  skip_source: bool = False,
92
+ stateless: bool = False,
90
93
  ) -> None:
91
94
  """Run a MCP server or connect to a remote one.
92
95
 
@@ -101,6 +104,7 @@ async def run_command(
101
104
  show_banner: Whether to show the server banner
102
105
  use_direct_import: Whether to use direct import instead of subprocess
103
106
  skip_source: Whether to skip source preparation step
107
+ stateless: Whether to run in stateless mode (no session)
104
108
  """
105
109
  # Special case: URLs
106
110
  if is_url(server_spec):
@@ -186,6 +190,8 @@ async def run_command(
186
190
  kwargs["path"] = path
187
191
  if log_level:
188
192
  kwargs["log_level"] = log_level
193
+ if stateless:
194
+ kwargs["stateless"] = True
189
195
 
190
196
  if not show_banner:
191
197
  kwargs["show_banner"] = False
@@ -223,3 +229,138 @@ async def run_v1_server_async(
223
229
  await server.run_streamable_http_async()
224
230
  case "sse":
225
231
  await server.run_sse_async()
232
+
233
+
234
+ def _python_file_filter(change: Change, path: str) -> bool:
235
+ """Filter for Python files only."""
236
+ return path.endswith(".py")
237
+
238
+
239
+ async def _terminate_process(process: asyncio.subprocess.Process) -> None:
240
+ """Terminate a subprocess immediately."""
241
+ if process.returncode is not None:
242
+ return
243
+ process.kill()
244
+ await process.wait()
245
+
246
+
247
+ async def run_with_reload(
248
+ cmd: list[str],
249
+ reload_dirs: list[Path] | None = None,
250
+ is_stdio: bool = False,
251
+ ) -> None:
252
+ """Run a command with file watching and auto-reload.
253
+
254
+ Args:
255
+ cmd: Command to run as subprocess (should include --no-reload)
256
+ reload_dirs: Directories to watch for changes (default: cwd)
257
+ is_stdio: Whether this is stdio transport
258
+ """
259
+ watch_paths = reload_dirs or [Path.cwd()]
260
+ process: asyncio.subprocess.Process | None = None
261
+ first_run = True
262
+
263
+ if is_stdio:
264
+ logger.info("Reload mode enabled (using stateless sessions)")
265
+ else:
266
+ logger.info(
267
+ "Reload mode enabled (using stateless HTTP). "
268
+ "Some features requiring bidirectional communication "
269
+ "(like elicitation) are not available."
270
+ )
271
+
272
+ # Handle SIGTERM/SIGINT gracefully with proper asyncio integration
273
+ shutdown_event = asyncio.Event()
274
+ loop = asyncio.get_running_loop()
275
+
276
+ def signal_handler() -> None:
277
+ logger.info("Received shutdown signal, stopping...")
278
+ shutdown_event.set()
279
+
280
+ # Windows doesn't support add_signal_handler
281
+ if sys.platform != "win32":
282
+ loop.add_signal_handler(signal.SIGTERM, signal_handler)
283
+ loop.add_signal_handler(signal.SIGINT, signal_handler)
284
+
285
+ try:
286
+ while not shutdown_event.is_set():
287
+ # Build command - add --no-banner on restarts to reduce noise
288
+ if first_run or "--no-banner" in cmd:
289
+ run_cmd = cmd
290
+ else:
291
+ run_cmd = [*cmd, "--no-banner"]
292
+ first_run = False
293
+
294
+ process = await asyncio.create_subprocess_exec(
295
+ *run_cmd,
296
+ stdin=None,
297
+ stdout=None,
298
+ stderr=None,
299
+ )
300
+
301
+ # Watch for either: file changes OR process death
302
+ watch_task = asyncio.create_task(
303
+ anext(aiter(awatch(*watch_paths, watch_filter=_python_file_filter)))
304
+ )
305
+ wait_task = asyncio.create_task(process.wait())
306
+ shutdown_task = asyncio.create_task(shutdown_event.wait())
307
+
308
+ done, pending = await asyncio.wait(
309
+ [watch_task, wait_task, shutdown_task],
310
+ return_when=asyncio.FIRST_COMPLETED,
311
+ )
312
+
313
+ for task in pending:
314
+ task.cancel()
315
+ with contextlib.suppress(asyncio.CancelledError):
316
+ await task
317
+
318
+ if shutdown_task in done:
319
+ # User requested shutdown
320
+ break
321
+
322
+ if wait_task in done:
323
+ # Server died on its own - wait for file change before restart
324
+ code = wait_task.result()
325
+ if code != 0:
326
+ logger.error(
327
+ f"Server exited with code {code}, waiting for file change..."
328
+ )
329
+ else:
330
+ logger.info("Server exited, waiting for file change...")
331
+
332
+ # Wait for file change or shutdown (avoid hot loop on crash)
333
+ watch_task = asyncio.create_task(
334
+ anext(aiter(awatch(*watch_paths, watch_filter=_python_file_filter)))
335
+ )
336
+ shutdown_task = asyncio.create_task(shutdown_event.wait())
337
+ done, pending = await asyncio.wait(
338
+ [watch_task, shutdown_task],
339
+ return_when=asyncio.FIRST_COMPLETED,
340
+ )
341
+ for task in pending:
342
+ task.cancel()
343
+ with contextlib.suppress(asyncio.CancelledError):
344
+ await task
345
+ if shutdown_task in done:
346
+ break
347
+ logger.info("Detected changes, restarting...")
348
+ else:
349
+ # File changed - restart server
350
+ changes = watch_task.result()
351
+ logger.info(
352
+ f"Detected changes in {len(changes)} file(s), restarting..."
353
+ )
354
+ await _terminate_process(process)
355
+
356
+ except KeyboardInterrupt:
357
+ # Handle Ctrl+C on Windows (where add_signal_handler isn't available)
358
+ logger.info("Received shutdown signal, stopping...")
359
+
360
+ finally:
361
+ # Clean up signal handlers
362
+ if sys.platform != "win32":
363
+ loop.remove_signal_handler(signal.SIGTERM)
364
+ loop.remove_signal_handler(signal.SIGINT)
365
+ if process and process.returncode is None:
366
+ await _terminate_process(process)
@@ -1,18 +1,17 @@
1
+ from .auth import OAuth, BearerAuth
1
2
  from .client import Client
2
3
  from .transports import (
3
4
  ClientTransport,
4
- WSTransport,
5
- SSETransport,
6
- StdioTransport,
7
- PythonStdioTransport,
5
+ FastMCPTransport,
8
6
  NodeStdioTransport,
9
- UvxStdioTransport,
10
- UvStdioTransport,
11
7
  NpxStdioTransport,
12
- FastMCPTransport,
8
+ PythonStdioTransport,
9
+ SSETransport,
10
+ StdioTransport,
13
11
  StreamableHttpTransport,
12
+ UvStdioTransport,
13
+ UvxStdioTransport,
14
14
  )
15
- from .auth import OAuth, BearerAuth
16
15
 
17
16
  __all__ = [
18
17
  "BearerAuth",
@@ -28,5 +27,4 @@ __all__ = [
28
27
  "StreamableHttpTransport",
29
28
  "UvStdioTransport",
30
29
  "UvxStdioTransport",
31
- "WSTransport",
32
30
  ]
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import time
4
4
  import webbrowser
5
5
  from collections.abc import AsyncGenerator
6
+ from contextlib import aclosing
6
7
  from typing import Any
7
8
 
8
9
  import anyio
@@ -299,15 +300,15 @@ class OAuth(OAuthClientProvider):
299
300
  """
300
301
  try:
301
302
  # First attempt with potentially cached credentials
302
- gen = super().async_auth_flow(request)
303
- response = None
304
- while True:
305
- try:
306
- # First iteration sends None, subsequent iterations send response
307
- yielded_request = await gen.asend(response) # ty: ignore[invalid-argument-type]
308
- response = yield yielded_request
309
- except StopAsyncIteration:
310
- break
303
+ async with aclosing(super().async_auth_flow(request)) as gen:
304
+ response = None
305
+ while True:
306
+ try:
307
+ # First iteration sends None, subsequent iterations send response
308
+ yielded_request = await gen.asend(response) # ty: ignore[invalid-argument-type]
309
+ response = yield yielded_request
310
+ except StopAsyncIteration:
311
+ break
311
312
 
312
313
  except ClientNotFoundError:
313
314
  logger.debug(
@@ -318,11 +319,11 @@ class OAuth(OAuthClientProvider):
318
319
  await self.token_storage_adapter.clear()
319
320
 
320
321
  # Retry with fresh registration
321
- gen = super().async_auth_flow(request)
322
- response = None
323
- while True:
324
- try:
325
- yielded_request = await gen.asend(response) # ty: ignore[invalid-argument-type]
326
- response = yield yielded_request
327
- except StopAsyncIteration:
328
- break
322
+ async with aclosing(super().async_auth_flow(request)) as gen:
323
+ response = None
324
+ while True:
325
+ try:
326
+ yielded_request = await gen.asend(response) # ty: ignore[invalid-argument-type]
327
+ response = yield yielded_request
328
+ except StopAsyncIteration:
329
+ break