fastmcp 2.12.5__py3-none-any.whl → 2.13.2__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 (108) hide show
  1. fastmcp/__init__.py +2 -2
  2. fastmcp/cli/cli.py +11 -11
  3. fastmcp/cli/install/claude_code.py +6 -6
  4. fastmcp/cli/install/claude_desktop.py +3 -3
  5. fastmcp/cli/install/cursor.py +18 -12
  6. fastmcp/cli/install/gemini_cli.py +3 -3
  7. fastmcp/cli/install/mcp_json.py +3 -3
  8. fastmcp/cli/run.py +13 -8
  9. fastmcp/client/__init__.py +9 -9
  10. fastmcp/client/auth/oauth.py +115 -217
  11. fastmcp/client/client.py +105 -39
  12. fastmcp/client/logging.py +18 -14
  13. fastmcp/client/oauth_callback.py +85 -171
  14. fastmcp/client/sampling.py +1 -1
  15. fastmcp/client/transports.py +80 -25
  16. fastmcp/contrib/component_manager/__init__.py +1 -1
  17. fastmcp/contrib/component_manager/component_manager.py +2 -2
  18. fastmcp/contrib/component_manager/component_service.py +6 -6
  19. fastmcp/contrib/mcp_mixin/README.md +32 -1
  20. fastmcp/contrib/mcp_mixin/__init__.py +2 -2
  21. fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -2
  22. fastmcp/experimental/sampling/handlers/openai.py +2 -2
  23. fastmcp/experimental/server/openapi/__init__.py +5 -8
  24. fastmcp/experimental/server/openapi/components.py +11 -7
  25. fastmcp/experimental/server/openapi/routing.py +2 -2
  26. fastmcp/experimental/utilities/openapi/__init__.py +10 -15
  27. fastmcp/experimental/utilities/openapi/director.py +14 -15
  28. fastmcp/experimental/utilities/openapi/json_schema_converter.py +6 -2
  29. fastmcp/experimental/utilities/openapi/models.py +3 -3
  30. fastmcp/experimental/utilities/openapi/parser.py +37 -16
  31. fastmcp/experimental/utilities/openapi/schemas.py +2 -2
  32. fastmcp/mcp_config.py +3 -4
  33. fastmcp/prompts/__init__.py +1 -1
  34. fastmcp/prompts/prompt.py +22 -19
  35. fastmcp/prompts/prompt_manager.py +16 -101
  36. fastmcp/resources/__init__.py +5 -5
  37. fastmcp/resources/resource.py +14 -9
  38. fastmcp/resources/resource_manager.py +9 -168
  39. fastmcp/resources/template.py +107 -17
  40. fastmcp/resources/types.py +30 -24
  41. fastmcp/server/__init__.py +1 -1
  42. fastmcp/server/auth/__init__.py +9 -5
  43. fastmcp/server/auth/auth.py +70 -43
  44. fastmcp/server/auth/handlers/authorize.py +326 -0
  45. fastmcp/server/auth/jwt_issuer.py +236 -0
  46. fastmcp/server/auth/middleware.py +96 -0
  47. fastmcp/server/auth/oauth_proxy.py +1510 -289
  48. fastmcp/server/auth/oidc_proxy.py +84 -20
  49. fastmcp/server/auth/providers/auth0.py +40 -21
  50. fastmcp/server/auth/providers/aws.py +29 -3
  51. fastmcp/server/auth/providers/azure.py +312 -131
  52. fastmcp/server/auth/providers/bearer.py +1 -1
  53. fastmcp/server/auth/providers/debug.py +114 -0
  54. fastmcp/server/auth/providers/descope.py +86 -29
  55. fastmcp/server/auth/providers/discord.py +308 -0
  56. fastmcp/server/auth/providers/github.py +29 -8
  57. fastmcp/server/auth/providers/google.py +48 -9
  58. fastmcp/server/auth/providers/in_memory.py +27 -3
  59. fastmcp/server/auth/providers/introspection.py +281 -0
  60. fastmcp/server/auth/providers/jwt.py +48 -31
  61. fastmcp/server/auth/providers/oci.py +233 -0
  62. fastmcp/server/auth/providers/scalekit.py +238 -0
  63. fastmcp/server/auth/providers/supabase.py +188 -0
  64. fastmcp/server/auth/providers/workos.py +35 -17
  65. fastmcp/server/context.py +177 -51
  66. fastmcp/server/dependencies.py +39 -12
  67. fastmcp/server/elicitation.py +1 -1
  68. fastmcp/server/http.py +56 -17
  69. fastmcp/server/low_level.py +121 -2
  70. fastmcp/server/middleware/__init__.py +1 -1
  71. fastmcp/server/middleware/caching.py +476 -0
  72. fastmcp/server/middleware/error_handling.py +14 -10
  73. fastmcp/server/middleware/logging.py +50 -39
  74. fastmcp/server/middleware/middleware.py +29 -16
  75. fastmcp/server/middleware/rate_limiting.py +3 -3
  76. fastmcp/server/middleware/tool_injection.py +116 -0
  77. fastmcp/server/openapi.py +10 -6
  78. fastmcp/server/proxy.py +22 -11
  79. fastmcp/server/server.py +725 -242
  80. fastmcp/settings.py +24 -10
  81. fastmcp/tools/__init__.py +1 -1
  82. fastmcp/tools/tool.py +70 -23
  83. fastmcp/tools/tool_manager.py +30 -112
  84. fastmcp/tools/tool_transform.py +12 -10
  85. fastmcp/utilities/cli.py +67 -28
  86. fastmcp/utilities/components.py +7 -2
  87. fastmcp/utilities/inspect.py +79 -23
  88. fastmcp/utilities/json_schema.py +4 -4
  89. fastmcp/utilities/json_schema_type.py +4 -4
  90. fastmcp/utilities/logging.py +118 -8
  91. fastmcp/utilities/mcp_server_config/__init__.py +3 -3
  92. fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
  93. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +6 -6
  94. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +4 -4
  95. fastmcp/utilities/mcp_server_config/v1/schema.json +3 -0
  96. fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
  97. fastmcp/utilities/openapi.py +11 -11
  98. fastmcp/utilities/tests.py +85 -4
  99. fastmcp/utilities/types.py +78 -16
  100. fastmcp/utilities/ui.py +626 -0
  101. {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/METADATA +22 -14
  102. fastmcp-2.13.2.dist-info/RECORD +144 -0
  103. {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/WHEEL +1 -1
  104. fastmcp/cli/claude.py +0 -135
  105. fastmcp/utilities/storage.py +0 -204
  106. fastmcp-2.12.5.dist-info/RECORD +0 -134
  107. {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/entry_points.txt +0 -0
  108. {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/licenses/LICENSE +0 -0
fastmcp/client/client.py CHANGED
@@ -61,15 +61,15 @@ from .transports import (
61
61
 
62
62
  __all__ = [
63
63
  "Client",
64
- "SessionKwargs",
65
- "RootsHandler",
66
- "RootsList",
67
- "LogHandler",
68
- "MessageHandler",
69
64
  "ClientSamplingHandler",
70
- "SamplingHandler",
71
65
  "ElicitationHandler",
66
+ "LogHandler",
67
+ "MessageHandler",
72
68
  "ProgressHandler",
69
+ "RootsHandler",
70
+ "RootsList",
71
+ "SamplingHandler",
72
+ "SessionKwargs",
73
73
  ]
74
74
 
75
75
  logger = get_logger(__name__)
@@ -77,6 +77,16 @@ logger = get_logger(__name__)
77
77
  T = TypeVar("T", bound="ClientTransport")
78
78
 
79
79
 
80
+ def _timeout_to_seconds(
81
+ timeout: datetime.timedelta | float | int | None,
82
+ ) -> float | None:
83
+ if timeout is None:
84
+ return None
85
+ if isinstance(timeout, datetime.timedelta):
86
+ return timeout.total_seconds()
87
+ return float(timeout)
88
+
89
+
80
90
  @dataclass
81
91
  class ClientSessionState:
82
92
  """Holds all session-related state for a Client instance.
@@ -155,38 +165,38 @@ class Client(Generic[ClientTransportT]):
155
165
  """
156
166
 
157
167
  @overload
158
- def __init__(self: Client[T], transport: T, *args, **kwargs) -> None: ...
168
+ def __init__(self: Client[T], transport: T, *args: Any, **kwargs: Any) -> None: ...
159
169
 
160
170
  @overload
161
171
  def __init__(
162
172
  self: Client[SSETransport | StreamableHttpTransport],
163
173
  transport: AnyUrl,
164
- *args,
165
- **kwargs,
174
+ *args: Any,
175
+ **kwargs: Any,
166
176
  ) -> None: ...
167
177
 
168
178
  @overload
169
179
  def __init__(
170
180
  self: Client[FastMCPTransport],
171
181
  transport: FastMCP | FastMCP1Server,
172
- *args,
173
- **kwargs,
182
+ *args: Any,
183
+ **kwargs: Any,
174
184
  ) -> None: ...
175
185
 
176
186
  @overload
177
187
  def __init__(
178
188
  self: Client[PythonStdioTransport | NodeStdioTransport],
179
189
  transport: Path,
180
- *args,
181
- **kwargs,
190
+ *args: Any,
191
+ **kwargs: Any,
182
192
  ) -> None: ...
183
193
 
184
194
  @overload
185
195
  def __init__(
186
196
  self: Client[MCPConfigTransport],
187
197
  transport: MCPConfig | dict[str, Any],
188
- *args,
189
- **kwargs,
198
+ *args: Any,
199
+ **kwargs: Any,
190
200
  ) -> None: ...
191
201
 
192
202
  @overload
@@ -198,8 +208,8 @@ class Client(Generic[ClientTransportT]):
198
208
  | StreamableHttpTransport
199
209
  ],
200
210
  transport: str,
201
- *args,
202
- **kwargs,
211
+ *args: Any,
212
+ **kwargs: Any,
203
213
  ) -> None: ...
204
214
 
205
215
  def __init__(
@@ -222,6 +232,7 @@ class Client(Generic[ClientTransportT]):
222
232
  message_handler: MessageHandlerT | MessageHandler | None = None,
223
233
  progress_handler: ProgressHandler | None = None,
224
234
  timeout: datetime.timedelta | float | int | None = None,
235
+ auto_initialize: bool = True,
225
236
  init_timeout: datetime.timedelta | float | int | None = None,
226
237
  client_info: mcp.types.Implementation | None = None,
227
238
  auth: httpx.Auth | Literal["oauth"] | str | None = None,
@@ -240,26 +251,23 @@ class Client(Generic[ClientTransportT]):
240
251
 
241
252
  self._progress_handler = progress_handler
242
253
 
254
+ # Convert timeout to timedelta if needed
243
255
  if isinstance(timeout, int | float):
244
256
  timeout = datetime.timedelta(seconds=float(timeout))
245
257
 
246
258
  # handle init handshake timeout
247
259
  if init_timeout is None:
248
260
  init_timeout = fastmcp.settings.client_init_timeout
249
- if isinstance(init_timeout, datetime.timedelta):
250
- init_timeout = init_timeout.total_seconds()
251
- elif not init_timeout:
252
- init_timeout = None
253
- else:
254
- init_timeout = float(init_timeout)
255
- self._init_timeout = init_timeout
261
+ self._init_timeout = _timeout_to_seconds(init_timeout)
262
+
263
+ self.auto_initialize = auto_initialize
256
264
 
257
265
  self._session_kwargs: SessionKwargs = {
258
266
  "sampling_callback": None,
259
267
  "list_roots_callback": None,
260
268
  "logging_callback": create_log_callback(log_handler),
261
269
  "message_handler": message_handler,
262
- "read_timeout_seconds": timeout,
270
+ "read_timeout_seconds": timeout, # ty: ignore[invalid-argument-type]
263
271
  "client_info": client_info,
264
272
  }
265
273
 
@@ -290,12 +298,8 @@ class Client(Generic[ClientTransportT]):
290
298
  return self._session_state.session
291
299
 
292
300
  @property
293
- def initialize_result(self) -> mcp.types.InitializeResult:
301
+ def initialize_result(self) -> mcp.types.InitializeResult | None:
294
302
  """Get the result of the initialization request."""
295
- if self._session_state.initialize_result is None:
296
- raise RuntimeError(
297
- "Client is not connected. Use the 'async with client:' context manager first."
298
- )
299
303
  return self._session_state.initialize_result
300
304
 
301
305
  def set_roots(self, roots: RootsList | RootsHandler) -> None:
@@ -357,15 +361,11 @@ class Client(Generic[ClientTransportT]):
357
361
  self._session_state.session = session
358
362
  # Initialize the session
359
363
  try:
360
- with anyio.fail_after(self._init_timeout):
361
- self._session_state.initialize_result = (
362
- await self._session_state.session.initialize()
363
- )
364
+ if self.auto_initialize:
365
+ await self.initialize()
364
366
  yield
365
- except anyio.ClosedResourceError:
366
- raise RuntimeError("Server session was closed unexpectedly")
367
- except TimeoutError:
368
- raise RuntimeError("Failed to initialize server session")
367
+ except anyio.ClosedResourceError as e:
368
+ raise RuntimeError("Server session was closed unexpectedly") from e
369
369
  finally:
370
370
  self._session_state.session = None
371
371
  self._session_state.initialize_result = None
@@ -493,6 +493,55 @@ class Client(Generic[ClientTransportT]):
493
493
 
494
494
  # --- MCP Client Methods ---
495
495
 
496
+ async def initialize(
497
+ self,
498
+ timeout: datetime.timedelta | float | int | None = None,
499
+ ) -> mcp.types.InitializeResult:
500
+ """Send an initialize request to the server.
501
+
502
+ This method performs the MCP initialization handshake with the server,
503
+ exchanging capabilities and server information. It is idempotent - calling
504
+ it multiple times returns the cached result from the first call.
505
+
506
+ The initialization happens automatically when entering the client context
507
+ manager unless `auto_initialize=False` was set during client construction.
508
+ Manual calls to this method are only needed when auto-initialization is disabled.
509
+
510
+ Args:
511
+ timeout: Optional timeout for the initialization request (seconds or timedelta).
512
+ If None, uses the client's init_timeout setting.
513
+
514
+ Returns:
515
+ InitializeResult: The server's initialization response containing server info,
516
+ capabilities, protocol version, and optional instructions.
517
+
518
+ Raises:
519
+ RuntimeError: If the client is not connected or initialization times out.
520
+
521
+ Example:
522
+ ```python
523
+ # With auto-initialization disabled
524
+ client = Client(server, auto_initialize=False)
525
+ async with client:
526
+ result = await client.initialize()
527
+ print(f"Server: {result.serverInfo.name}")
528
+ print(f"Instructions: {result.instructions}")
529
+ ```
530
+ """
531
+
532
+ if self.initialize_result is not None:
533
+ return self.initialize_result
534
+
535
+ if timeout is None:
536
+ timeout = self._init_timeout
537
+ try:
538
+ with anyio.fail_after(_timeout_to_seconds(timeout)):
539
+ initialize_result = await self.session.initialize()
540
+ self._session_state.initialize_result = initialize_result
541
+ return initialize_result
542
+ except TimeoutError as e:
543
+ raise RuntimeError("Failed to initialize server session") from e
544
+
496
545
  async def ping(self) -> bool:
497
546
  """Send a ping request."""
498
547
  result = await self.session.send_ping()
@@ -831,6 +880,7 @@ class Client(Generic[ClientTransportT]):
831
880
  arguments: dict[str, Any],
832
881
  progress_handler: ProgressHandler | None = None,
833
882
  timeout: datetime.timedelta | float | int | None = None,
883
+ meta: dict[str, Any] | None = None,
834
884
  ) -> mcp.types.CallToolResult:
835
885
  """Send a tools/call request and return the complete MCP protocol result.
836
886
 
@@ -842,6 +892,10 @@ class Client(Generic[ClientTransportT]):
842
892
  arguments (dict[str, Any]): Arguments to pass to the tool.
843
893
  timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
844
894
  progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
895
+ meta (dict[str, Any] | None, optional): Additional metadata to include with the request.
896
+ This is useful for passing contextual information (like user IDs, trace IDs, or preferences)
897
+ that shouldn't be tool arguments but may influence server-side processing. The server
898
+ can access this via `context.request_context.meta`. Defaults to None.
845
899
 
846
900
  Returns:
847
901
  mcp.types.CallToolResult: The complete response object from the protocol,
@@ -852,13 +906,16 @@ class Client(Generic[ClientTransportT]):
852
906
  """
853
907
  logger.debug(f"[{self.name}] called call_tool: {name}")
854
908
 
909
+ # Convert timeout to timedelta if needed
855
910
  if isinstance(timeout, int | float):
856
911
  timeout = datetime.timedelta(seconds=float(timeout))
912
+
857
913
  result = await self.session.call_tool(
858
914
  name=name,
859
915
  arguments=arguments,
860
- read_timeout_seconds=timeout,
916
+ read_timeout_seconds=timeout, # ty: ignore[invalid-argument-type]
861
917
  progress_callback=progress_handler or self._progress_handler,
918
+ meta=meta,
862
919
  )
863
920
  return result
864
921
 
@@ -869,6 +926,7 @@ class Client(Generic[ClientTransportT]):
869
926
  timeout: datetime.timedelta | float | int | None = None,
870
927
  progress_handler: ProgressHandler | None = None,
871
928
  raise_on_error: bool = True,
929
+ meta: dict[str, Any] | None = None,
872
930
  ) -> CallToolResult:
873
931
  """Call a tool on the server.
874
932
 
@@ -879,6 +937,11 @@ class Client(Generic[ClientTransportT]):
879
937
  arguments (dict[str, Any] | None, optional): Arguments to pass to the tool. Defaults to None.
880
938
  timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
881
939
  progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
940
+ raise_on_error (bool, optional): Whether to raise a ToolError if the tool call results in an error. Defaults to True.
941
+ meta (dict[str, Any] | None, optional): Additional metadata to include with the request.
942
+ This is useful for passing contextual information (like user IDs, trace IDs, or preferences)
943
+ that shouldn't be tool arguments but may influence server-side processing. The server
944
+ can access this via `context.request_context.meta`. Defaults to None.
882
945
 
883
946
  Returns:
884
947
  CallToolResult:
@@ -898,6 +961,7 @@ class Client(Generic[ClientTransportT]):
898
961
  arguments=arguments or {},
899
962
  timeout=timeout,
900
963
  progress_handler=progress_handler,
964
+ meta=meta,
901
965
  )
902
966
  data = None
903
967
  if result.isError and raise_on_error:
@@ -928,6 +992,7 @@ class Client(Generic[ClientTransportT]):
928
992
  return CallToolResult(
929
993
  content=result.content,
930
994
  structured_content=result.structuredContent,
995
+ meta=result.meta,
931
996
  data=data,
932
997
  is_error=result.isError,
933
998
  )
@@ -945,5 +1010,6 @@ class Client(Generic[ClientTransportT]):
945
1010
  class CallToolResult:
946
1011
  content: list[mcp.types.ContentBlock]
947
1012
  structured_content: dict[str, Any] | None
1013
+ meta: dict[str, Any] | None
948
1014
  data: Any = None
949
1015
  is_error: bool = False
fastmcp/client/logging.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from collections.abc import Awaitable, Callable
2
+ from logging import Logger
2
3
  from typing import TypeAlias
3
4
 
4
5
  from mcp.client.session import LoggingFnT
@@ -6,7 +7,8 @@ from mcp.types import LoggingMessageNotificationParams
6
7
 
7
8
  from fastmcp.utilities.logging import get_logger
8
9
 
9
- logger = get_logger(__name__)
10
+ logger: Logger = get_logger(name=__name__)
11
+ from_server_logger: Logger = get_logger(name="fastmcp.client.from_server")
10
12
 
11
13
  LogMessage: TypeAlias = LoggingMessageNotificationParams
12
14
  LogHandler: TypeAlias = Callable[[LogMessage], Awaitable[None]]
@@ -14,30 +16,32 @@ LogHandler: TypeAlias = Callable[[LogMessage], Awaitable[None]]
14
16
 
15
17
  async def default_log_handler(message: LogMessage) -> None:
16
18
  """Default handler that properly routes server log messages to appropriate log levels."""
17
- msg = message.data.get("msg", str(message))
18
- extra = message.data.get("extra", {})
19
+ # data can be any JSON-serializable type, not just a dict
20
+ data = message.data
19
21
 
20
22
  # Map MCP log levels to Python logging levels
21
23
  level_map = {
22
- "debug": logger.debug,
23
- "info": logger.info,
24
- "notice": logger.info, # Python doesn't have 'notice', map to info
25
- "warning": logger.warning,
26
- "error": logger.error,
27
- "critical": logger.critical,
28
- "alert": logger.critical, # Map alert to critical
29
- "emergency": logger.critical, # Map emergency to critical
24
+ "debug": from_server_logger.debug,
25
+ "info": from_server_logger.info,
26
+ "notice": from_server_logger.info, # Python doesn't have 'notice', map to info
27
+ "warning": from_server_logger.warning,
28
+ "error": from_server_logger.error,
29
+ "critical": from_server_logger.critical,
30
+ "alert": from_server_logger.critical, # Map alert to critical
31
+ "emergency": from_server_logger.critical, # Map emergency to critical
30
32
  }
31
33
 
32
34
  # Get the appropriate logging function based on the message level
33
35
  log_fn = level_map.get(message.level.lower(), logger.info)
34
36
 
35
37
  # Include logger name if available
38
+ msg_prefix: str = f"Received {message.level.upper()} from server"
39
+
36
40
  if message.logger:
37
- msg = f"[{message.logger}] {msg}"
41
+ msg_prefix += f" ({message.logger})"
38
42
 
39
- # Log with appropriate level and extra data
40
- log_fn(f"Server log: {msg}", extra=extra)
43
+ # Log with appropriate level and data
44
+ log_fn(msg=f"{msg_prefix}: {data}")
41
45
 
42
46
 
43
47
  def create_log_callback(handler: LogHandler | None = None) -> LoggingFnT: