fastmcp 2.11.0__py3-none-any.whl → 2.11.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.
fastmcp/cli/cli.py CHANGED
@@ -138,7 +138,7 @@ def version(
138
138
 
139
139
 
140
140
  @app.command
141
- def dev(
141
+ async def dev(
142
142
  server_spec: str,
143
143
  *,
144
144
  with_editable: Annotated[
@@ -220,7 +220,7 @@ def dev(
220
220
 
221
221
  try:
222
222
  # Import server to get dependencies
223
- server: FastMCP = run_module.import_server(file, server_object)
223
+ server: FastMCP = await run_module.import_server(file, server_object)
224
224
  if server.dependencies is not None:
225
225
  with_packages = list(set(with_packages + server.dependencies))
226
226
 
@@ -283,7 +283,7 @@ def dev(
283
283
 
284
284
 
285
285
  @app.command
286
- def run(
286
+ async def run(
287
287
  server_spec: str,
288
288
  *server_args: str,
289
289
  transport: Annotated[
@@ -414,7 +414,7 @@ def run(
414
414
  else:
415
415
  # Use direct import for backwards compatibility
416
416
  try:
417
- run_module.run_command(
417
+ await run_module.run_command(
418
418
  server_spec=server_spec,
419
419
  transport=transport,
420
420
  host=host,
@@ -476,7 +476,7 @@ async def inspect(
476
476
 
477
477
  try:
478
478
  # Import the server
479
- server = run_module.import_server(file, server_object)
479
+ server = await run_module.import_server(file, server_object)
480
480
 
481
481
  # Get server information - using native async support
482
482
  info = await inspect_fastmcp(server)
@@ -167,7 +167,7 @@ def install_claude_code(
167
167
  return False
168
168
 
169
169
 
170
- def claude_code_command(
170
+ async def claude_code_command(
171
171
  server_spec: str,
172
172
  *,
173
173
  server_name: Annotated[
@@ -234,7 +234,7 @@ def claude_code_command(
234
234
  Args:
235
235
  server_spec: Python file to install, optionally with :object suffix
236
236
  """
237
- file, server_object, name, packages, env_dict = process_common_args(
237
+ file, server_object, name, packages, env_dict = await process_common_args(
238
238
  server_spec, server_name, with_packages, env_vars, env_file
239
239
  )
240
240
 
@@ -140,7 +140,7 @@ def install_claude_desktop(
140
140
  return False
141
141
 
142
142
 
143
- def claude_desktop_command(
143
+ async def claude_desktop_command(
144
144
  server_spec: str,
145
145
  *,
146
146
  server_name: Annotated[
@@ -207,7 +207,7 @@ def claude_desktop_command(
207
207
  Args:
208
208
  server_spec: Python file to install, optionally with :object suffix
209
209
  """
210
- file, server_object, name, with_packages, env_dict = process_common_args(
210
+ file, server_object, name, with_packages, env_dict = await process_common_args(
211
211
  server_spec, server_name, with_packages, env_vars, env_file
212
212
  )
213
213
 
@@ -150,7 +150,7 @@ def install_cursor(
150
150
  return False
151
151
 
152
152
 
153
- def cursor_command(
153
+ async def cursor_command(
154
154
  server_spec: str,
155
155
  *,
156
156
  server_name: Annotated[
@@ -217,7 +217,7 @@ def cursor_command(
217
217
  Args:
218
218
  server_spec: Python file to install, optionally with :object suffix
219
219
  """
220
- file, server_object, name, with_packages, env_dict = process_common_args(
220
+ file, server_object, name, with_packages, env_dict = await process_common_args(
221
221
  server_spec, server_name, with_packages, env_vars, env_file
222
222
  )
223
223
 
@@ -113,7 +113,7 @@ def install_mcp_json(
113
113
  return False
114
114
 
115
115
 
116
- def mcp_json_command(
116
+ async def mcp_json_command(
117
117
  server_spec: str,
118
118
  *,
119
119
  server_name: Annotated[
@@ -188,7 +188,7 @@ def mcp_json_command(
188
188
  Args:
189
189
  server_spec: Python file to install, optionally with :object suffix
190
190
  """
191
- file, server_object, name, packages, env_dict = process_common_args(
191
+ file, server_object, name, packages, env_dict = await process_common_args(
192
192
  server_spec, server_name, with_packages, env_vars, env_file
193
193
  )
194
194
 
@@ -23,7 +23,7 @@ def parse_env_var(env_var: str) -> tuple[str, str]:
23
23
  return key.strip(), value.strip()
24
24
 
25
25
 
26
- def process_common_args(
26
+ async def process_common_args(
27
27
  server_spec: str,
28
28
  server_name: str | None,
29
29
  with_packages: list[str],
@@ -49,7 +49,7 @@ def process_common_args(
49
49
  server = None
50
50
  if not name:
51
51
  try:
52
- server = import_server(file, server_object)
52
+ server = await import_server(file, server_object)
53
53
  name = server.name
54
54
  except (ImportError, ModuleNotFoundError) as e:
55
55
  logger.debug(
fastmcp/cli/run.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """FastMCP run command implementation with enhanced type hints."""
2
2
 
3
3
  import importlib.util
4
+ import inspect
4
5
  import json
5
6
  import re
6
7
  import subprocess
@@ -58,15 +59,15 @@ def parse_file_path(server_spec: str) -> tuple[Path, str | None]:
58
59
  return file_path, server_object
59
60
 
60
61
 
61
- def import_server(file: Path, server_object: str | None = None) -> Any:
62
+ async def import_server(file: Path, server_or_factory: str | None = None) -> Any:
62
63
  """Import a MCP server from a file.
63
64
 
64
65
  Args:
65
66
  file: Path to the file
66
- server_object: Optional object name in format "module:object" or just "object"
67
+ server_or_factory: Optional object name in format "module:object" or just "object"
67
68
 
68
69
  Returns:
69
- The server object
70
+ The server object (or result of calling a factory function)
70
71
  """
71
72
  # Add parent directory to Python path so imports can be resolved
72
73
  file_dir = str(file.parent)
@@ -86,11 +87,12 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
86
87
  spec.loader.exec_module(module)
87
88
 
88
89
  # If no object specified, try common server names
89
- if not server_object:
90
- # Look for the most common server object names
90
+ if not server_or_factory:
91
+ # Look for common server instance names
91
92
  for name in ["mcp", "server", "app"]:
92
93
  if hasattr(module, name):
93
- return getattr(module, name)
94
+ obj = getattr(module, name)
95
+ return await _resolve_server_or_factory(obj, file, name)
94
96
 
95
97
  logger.error(
96
98
  f"No server object found in {file}. Please either:\n"
@@ -100,14 +102,14 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
100
102
  )
101
103
  sys.exit(1)
102
104
 
103
- assert server_object is not None
105
+ assert server_or_factory is not None
104
106
 
105
107
  # Handle module:object syntax
106
- if ":" in server_object:
107
- module_name, object_name = server_object.split(":", 1)
108
+ if ":" in server_or_factory:
109
+ module_name, object_name = server_or_factory.split(":", 1)
108
110
  try:
109
111
  server_module = importlib.import_module(module_name)
110
- server = getattr(server_module, object_name, None)
112
+ obj = getattr(server_module, object_name, None)
111
113
  except ImportError:
112
114
  logger.error(
113
115
  f"Could not import module '{module_name}'",
@@ -116,16 +118,62 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
116
118
  sys.exit(1)
117
119
  else:
118
120
  # Just object name
119
- server = getattr(module, server_object, None)
121
+ obj = getattr(module, server_or_factory, None)
120
122
 
121
- if server is None:
123
+ if obj is None:
122
124
  logger.error(
123
- f"Server object '{server_object}' not found",
125
+ f"Server object '{server_or_factory}' not found",
124
126
  extra={"file": str(file)},
125
127
  )
126
128
  sys.exit(1)
127
129
 
128
- return server
130
+ return await _resolve_server_or_factory(obj, file, server_or_factory)
131
+
132
+
133
+ async def _resolve_server_or_factory(obj: Any, file: Path, name: str) -> Any:
134
+ """Resolve a server object or factory function to a server instance.
135
+
136
+ Args:
137
+ obj: The object that might be a server or factory function
138
+ file: Path to the file for error messages
139
+ name: Name of the object for error messages
140
+
141
+ Returns:
142
+ A server instance
143
+ """
144
+ # Check if it's a function or coroutine function
145
+ if inspect.isfunction(obj) or inspect.iscoroutinefunction(obj):
146
+ logger.debug(f"Found factory function '{name}' in {file}")
147
+
148
+ try:
149
+ if inspect.iscoroutinefunction(obj):
150
+ # Async factory function
151
+ server = await obj()
152
+ else:
153
+ # Sync factory function
154
+ server = obj()
155
+
156
+ # Validate the result is a FastMCP server
157
+ if not isinstance(server, FastMCP | FastMCP1x):
158
+ logger.error(
159
+ f"Factory function '{name}' must return a FastMCP server instance, "
160
+ f"got {type(server).__name__}",
161
+ extra={"file": str(file)},
162
+ )
163
+ sys.exit(1)
164
+
165
+ logger.debug(f"Factory function '{name}' created server: {server.name}")
166
+ return server
167
+
168
+ except Exception as e:
169
+ logger.error(
170
+ f"Failed to call factory function '{name}': {e}",
171
+ extra={"file": str(file)},
172
+ )
173
+ sys.exit(1)
174
+
175
+ # Not a function, return as-is (should be a server instance)
176
+ return obj
129
177
 
130
178
 
131
179
  def run_with_uv(
@@ -219,7 +267,7 @@ def create_client_server(url: str) -> Any:
219
267
  import fastmcp
220
268
 
221
269
  client = fastmcp.Client(url)
222
- server = fastmcp.FastMCP.from_client(client)
270
+ server = fastmcp.FastMCP.as_proxy(client)
223
271
  return server
224
272
  except Exception as e:
225
273
  logger.error(f"Failed to create client for URL {url}: {e}")
@@ -237,14 +285,16 @@ def create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]:
237
285
  return server
238
286
 
239
287
 
240
- def import_server_with_args(
241
- file: Path, server_object: str | None = None, server_args: list[str] | None = None
288
+ async def import_server_with_args(
289
+ file: Path,
290
+ server_or_factory: str | None = None,
291
+ server_args: list[str] | None = None,
242
292
  ) -> Any:
243
293
  """Import a server with optional command line arguments.
244
294
 
245
295
  Args:
246
296
  file: Path to the server file
247
- server_object: Optional server object name
297
+ server_or_factory: Optional server object or factory function name
248
298
  server_args: Optional command line arguments to inject
249
299
 
250
300
  Returns:
@@ -254,14 +304,14 @@ def import_server_with_args(
254
304
  original_argv = sys.argv[:]
255
305
  try:
256
306
  sys.argv = [str(file)] + server_args
257
- return import_server(file, server_object)
307
+ return await import_server(file, server_or_factory)
258
308
  finally:
259
309
  sys.argv = original_argv
260
310
  else:
261
- return import_server(file, server_object)
311
+ return await import_server(file, server_or_factory)
262
312
 
263
313
 
264
- def run_command(
314
+ async def run_command(
265
315
  server_spec: str,
266
316
  transport: TransportType | None = None,
267
317
  host: str | None = None,
@@ -293,8 +343,8 @@ def run_command(
293
343
  server = create_mcp_config_server(Path(server_spec))
294
344
  else:
295
345
  # Handle file case
296
- file, server_object = parse_file_path(server_spec)
297
- server = import_server_with_args(file, server_object, server_args)
346
+ file, server_or_factory = parse_file_path(server_spec)
347
+ server = await import_server_with_args(file, server_or_factory, server_args)
298
348
  logger.debug(f'Found server "{server.name}" in {file}')
299
349
 
300
350
  # Run the server
@@ -320,7 +370,7 @@ def run_command(
320
370
  kwargs["show_banner"] = False
321
371
 
322
372
  try:
323
- server.run(**kwargs)
373
+ await server.run_async(**kwargs)
324
374
  except Exception as e:
325
375
  logger.error(f"Failed to run server: {e}")
326
376
  sys.exit(1)
@@ -1,4 +1,4 @@
1
- from .auth import OAuthProvider, TokenVerifier
1
+ from .auth import OAuthProvider, TokenVerifier, RemoteAuthProvider
2
2
  from .providers.jwt import JWTVerifier, StaticTokenVerifier
3
3
 
4
4
 
@@ -7,6 +7,7 @@ __all__ = [
7
7
  "TokenVerifier",
8
8
  "JWTVerifier",
9
9
  "StaticTokenVerifier",
10
+ "RemoteAuthProvider",
10
11
  ]
11
12
 
12
13
 
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
4
-
5
3
  from mcp.server.auth.provider import (
6
4
  AccessToken,
7
5
  AuthorizationCode,
@@ -11,6 +9,10 @@ from mcp.server.auth.provider import (
11
9
  from mcp.server.auth.provider import (
12
10
  TokenVerifier as TokenVerifierProtocol,
13
11
  )
12
+ from mcp.server.auth.routes import (
13
+ create_auth_routes,
14
+ create_protected_resource_routes,
15
+ )
14
16
  from mcp.server.auth.settings import (
15
17
  ClientRegistrationOptions,
16
18
  RevocationOptions,
@@ -18,11 +20,8 @@ from mcp.server.auth.settings import (
18
20
  from pydantic import AnyHttpUrl
19
21
  from starlette.routing import Route
20
22
 
21
- if TYPE_CHECKING:
22
- pass
23
-
24
23
 
25
- class AuthProvider:
24
+ class AuthProvider(TokenVerifierProtocol):
26
25
  """Base class for all FastMCP authentication providers.
27
26
 
28
27
  This class provides a unified interface for all authentication providers,
@@ -31,9 +30,18 @@ class AuthProvider:
31
30
  custom authentication routes.
32
31
  """
33
32
 
34
- def __init__(self, required_scopes: list[str] | None = None):
35
- """Initialize the auth provider."""
36
- self.required_scopes: list[str] = required_scopes or []
33
+ def __init__(self, resource_server_url: AnyHttpUrl | str | None = None):
34
+ """
35
+ Initialize the auth provider.
36
+
37
+ Args:
38
+ resource_server_url: The URL of this resource server. This is used
39
+ for RFC 8707 resource indicators, including creating the WWW-Authenticate
40
+ header.
41
+ """
42
+ if isinstance(resource_server_url, str):
43
+ resource_server_url = AnyHttpUrl(resource_server_url)
44
+ self.resource_server_url = resource_server_url
37
45
 
38
46
  async def verify_token(self, token: str) -> AccessToken | None:
39
47
  """Verify a bearer token and return access info if valid.
@@ -48,22 +56,34 @@ class AuthProvider:
48
56
  """
49
57
  raise NotImplementedError("Subclasses must implement verify_token")
50
58
 
51
- def customize_auth_routes(self, routes: list[Route]) -> list[Route]:
52
- """Customize authentication routes after standard creation.
59
+ def get_routes(self) -> list[Route]:
60
+ """Get the routes for this authentication provider.
53
61
 
54
- This method allows providers to modify or add to the standard OAuth routes.
55
- The default implementation returns the routes unchanged.
56
-
57
- Args:
58
- routes: List of standard routes (may be empty for token-only providers)
62
+ Each provider is responsible for creating whatever routes it needs:
63
+ - TokenVerifier: typically no routes (default implementation)
64
+ - RemoteAuthProvider: protected resource metadata routes
65
+ - OAuthProvider: full OAuth authorization server routes
66
+ - Custom providers: whatever routes they need
59
67
 
60
68
  Returns:
61
- List of routes (potentially modified or extended)
69
+ List of routes for this provider
62
70
  """
63
- return routes
71
+ return []
72
+
73
+ def get_resource_metadata_url(self) -> AnyHttpUrl | None:
74
+ """Get the resource metadata URL for RFC 9728 compliance."""
75
+ if self.resource_server_url is None:
76
+ return None
64
77
 
78
+ # Add .well-known path for RFC 9728 compliance
79
+ resource_metadata_url = AnyHttpUrl(
80
+ str(self.resource_server_url).rstrip("/")
81
+ + "/.well-known/oauth-protected-resource"
82
+ )
83
+ return resource_metadata_url
65
84
 
66
- class TokenVerifier(AuthProvider, TokenVerifierProtocol):
85
+
86
+ class TokenVerifier(AuthProvider):
67
87
  """Base class for token verifiers (Resource Servers).
68
88
 
69
89
  This class provides token verification capability without OAuth server functionality.
@@ -79,26 +99,71 @@ class TokenVerifier(AuthProvider, TokenVerifierProtocol):
79
99
  Initialize the token verifier.
80
100
 
81
101
  Args:
82
- resource_server_url: The URL of this resource server (for RFC 8707 resource indicators)
102
+ resource_server_url: The URL of this resource server. This is used
103
+ for RFC 8707 resource indicators, including creating the WWW-Authenticate
104
+ header.
83
105
  required_scopes: Scopes that are required for all requests
84
106
  """
85
- # Initialize AuthProvider (no args needed)
86
- AuthProvider.__init__(self, required_scopes=required_scopes)
87
-
88
- # Handle our own resource_server_url and required_scopes
89
- self.resource_server_url: AnyHttpUrl | None
90
- if resource_server_url is None:
91
- self.resource_server_url = None
92
- elif isinstance(resource_server_url, str):
93
- self.resource_server_url = AnyHttpUrl(resource_server_url)
94
- else:
95
- self.resource_server_url = resource_server_url
107
+ super().__init__(resource_server_url=resource_server_url)
108
+ self.required_scopes = required_scopes or []
96
109
 
97
110
  async def verify_token(self, token: str) -> AccessToken | None:
98
111
  """Verify a bearer token and return access info if valid."""
99
112
  raise NotImplementedError("Subclasses must implement verify_token")
100
113
 
101
114
 
115
+ class RemoteAuthProvider(AuthProvider):
116
+ """Authentication provider for resource servers that verify tokens from known authorization servers.
117
+
118
+ This provider composes a TokenVerifier with authorization server metadata to create
119
+ standardized OAuth 2.0 Protected Resource endpoints (RFC 9728). Perfect for:
120
+ - JWT verification with known issuers
121
+ - Remote token introspection services
122
+ - Any resource server that knows where its tokens come from
123
+
124
+ Use this when you have token verification logic and want to advertise
125
+ the authorization servers that issue valid tokens.
126
+ """
127
+
128
+ def __init__(
129
+ self,
130
+ token_verifier: TokenVerifier,
131
+ authorization_servers: list[AnyHttpUrl],
132
+ resource_server_url: AnyHttpUrl | str,
133
+ ):
134
+ """Initialize the remote auth provider.
135
+
136
+ Args:
137
+ token_verifier: TokenVerifier instance for token validation
138
+ authorization_servers: List of authorization servers that issue valid tokens
139
+ resource_server_url: URL of this resource server. This is used
140
+ for RFC 8707 resource indicators, including creating the WWW-Authenticate
141
+ header.
142
+ """
143
+ super().__init__(resource_server_url=resource_server_url)
144
+ self.token_verifier = token_verifier
145
+ self.authorization_servers = authorization_servers
146
+
147
+ async def verify_token(self, token: str) -> AccessToken | None:
148
+ """Verify token using the configured token verifier."""
149
+ return await self.token_verifier.verify_token(token)
150
+
151
+ def get_routes(self) -> list[Route]:
152
+ """Get OAuth routes for this provider.
153
+
154
+ By default, returns only the standardized OAuth 2.0 Protected Resource routes.
155
+ Subclasses can override this method to add additional routes by calling
156
+ super().get_routes() and extending the returned list.
157
+ """
158
+ assert self.resource_server_url is not None
159
+
160
+ return create_protected_resource_routes(
161
+ resource_url=self.resource_server_url,
162
+ authorization_servers=self.authorization_servers,
163
+ scopes_supported=self.token_verifier.required_scopes,
164
+ )
165
+
166
+
102
167
  class OAuthProvider(
103
168
  AuthProvider,
104
169
  OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken],
@@ -181,17 +246,33 @@ class OAuthProvider(
181
246
  """
182
247
  return await self.load_access_token(token)
183
248
 
184
- def customize_auth_routes(self, routes: list[Route]) -> list[Route]:
185
- """Customize OAuth authentication routes after standard creation.
249
+ def get_routes(self) -> list[Route]:
250
+ """Get OAuth authorization server routes and optional protected resource routes.
186
251
 
187
- This method allows providers to modify the standard OAuth routes
188
- returned by create_auth_routes. The default implementation returns
189
- the routes unchanged.
190
-
191
- Args:
192
- routes: List of standard OAuth routes from create_auth_routes
252
+ This method creates the full set of OAuth routes including:
253
+ - Standard OAuth authorization server routes (/.well-known/oauth-authorization-server, /authorize, /token, etc.)
254
+ - Optional protected resource routes if resource_server_url is configured
193
255
 
194
256
  Returns:
195
- List of routes (potentially modified)
257
+ List of OAuth routes
196
258
  """
197
- return routes
259
+
260
+ # Create standard OAuth authorization server routes
261
+ oauth_routes = create_auth_routes(
262
+ provider=self,
263
+ issuer_url=self.issuer_url,
264
+ service_documentation_url=self.service_documentation_url,
265
+ client_registration_options=self.client_registration_options,
266
+ revocation_options=self.revocation_options,
267
+ )
268
+
269
+ # Add protected resource routes if this server is also acting as a resource server
270
+ if self.resource_server_url:
271
+ protected_routes = create_protected_resource_routes(
272
+ resource_url=self.resource_server_url,
273
+ authorization_servers=[self.issuer_url],
274
+ scopes_supported=self.required_scopes,
275
+ )
276
+ oauth_routes.extend(protected_routes)
277
+
278
+ return oauth_routes
@@ -16,7 +16,7 @@ from pydantic import AnyHttpUrl, SecretStr
16
16
  from pydantic_settings import BaseSettings, SettingsConfigDict
17
17
  from typing_extensions import TypedDict
18
18
 
19
- from fastmcp.server.auth.auth import TokenVerifier
19
+ from fastmcp.server.auth import TokenVerifier
20
20
  from fastmcp.server.auth.registry import register_provider
21
21
  from fastmcp.utilities.logging import get_logger
22
22
  from fastmcp.utilities.types import NotSet, NotSetT
@@ -1,15 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import httpx
4
- from mcp.server.auth.provider import (
5
- AccessToken,
6
- )
7
4
  from pydantic import AnyHttpUrl
8
5
  from pydantic_settings import BaseSettings, SettingsConfigDict
9
6
  from starlette.responses import JSONResponse
10
- from starlette.routing import BaseRoute, Route
7
+ from starlette.routing import Route
11
8
 
12
- from fastmcp.server.auth.auth import AuthProvider, TokenVerifier
9
+ from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
13
10
  from fastmcp.server.auth.providers.jwt import JWTVerifier
14
11
  from fastmcp.server.auth.registry import register_provider
15
12
  from fastmcp.utilities.logging import get_logger
@@ -31,10 +28,10 @@ class AuthKitProviderSettings(BaseSettings):
31
28
 
32
29
 
33
30
  @register_provider("AUTHKIT")
34
- class AuthKitProvider(AuthProvider):
35
- """WorkOS AuthKit metadata provider for DCR (Dynamic Client Registration).
31
+ class AuthKitProvider(RemoteAuthProvider):
32
+ """AuthKit metadata provider for DCR (Dynamic Client Registration).
36
33
 
37
- This provider implements WorkOS AuthKit integration using metadata forwarding
34
+ This provider implements AuthKit integration using metadata forwarding
38
35
  instead of OAuth proxying. This is the recommended approach for WorkOS DCR
39
36
  as it allows WorkOS to handle the OAuth flow directly while FastMCP acts
40
37
  as a resource server.
@@ -56,7 +53,7 @@ class AuthKitProvider(AuthProvider):
56
53
  ```python
57
54
  from fastmcp.server.auth.providers.workos import AuthKitProvider
58
55
 
59
- # Create WorkOS metadata provider (JWT verifier created automatically)
56
+ # Create AuthKit metadata provider (JWT verifier created automatically)
60
57
  workos_auth = AuthKitProvider(
61
58
  authkit_domain="https://your-workos-domain.authkit.app",
62
59
  base_url="https://your-fastmcp-server.com",
@@ -75,16 +72,14 @@ class AuthKitProvider(AuthProvider):
75
72
  required_scopes: list[str] | None | NotSetT = NotSet,
76
73
  token_verifier: TokenVerifier | None = None,
77
74
  ):
78
- """Initialize WorkOS metadata provider.
75
+ """Initialize AuthKit metadata provider.
79
76
 
80
77
  Args:
81
- authkit_domain: Your WorkOS AuthKit domain (e.g., "https://your-app.authkit.app")
78
+ authkit_domain: Your AuthKit domain (e.g., "https://your-app.authkit.app")
82
79
  base_url: Public URL of this FastMCP server
83
80
  required_scopes: Optional list of scopes to require for all requests
84
- token_verifier: Optional token verifier. If None, creates JWT verifier for WorkOS
81
+ token_verifier: Optional token verifier. If None, creates JWT verifier for AuthKit
85
82
  """
86
- super().__init__()
87
-
88
83
  settings = AuthKitProviderSettings.model_validate(
89
84
  {
90
85
  k: v
@@ -109,19 +104,21 @@ class AuthKitProvider(AuthProvider):
109
104
  required_scopes=settings.required_scopes,
110
105
  )
111
106
 
112
- self.token_verifier = token_verifier
113
-
114
- async def verify_token(self, token: str) -> AccessToken | None:
115
- """Verify a WorkOS token using the configured token verifier."""
116
- return await self.token_verifier.verify_token(token)
107
+ # Initialize RemoteAuthProvider with AuthKit as the authorization server
108
+ super().__init__(
109
+ token_verifier=token_verifier,
110
+ authorization_servers=[AnyHttpUrl(self.authkit_domain)],
111
+ resource_server_url=self.base_url,
112
+ )
117
113
 
118
- def customize_auth_routes(self, routes: list[BaseRoute]) -> list[BaseRoute]:
119
- """Add AuthKit metadata endpoints.
114
+ def get_routes(self) -> list[Route]:
115
+ """Get OAuth routes including AuthKit authorization server metadata forwarding.
120
116
 
121
- This adds:
122
- - /.well-known/oauth-authorization-server (forwards AuthKit metadata)
123
- - /.well-known/oauth-protected-resource (returns FastMCP resource info)
117
+ This returns the standard protected resource routes plus an authorization server
118
+ metadata endpoint that forwards AuthKit's OAuth metadata to clients.
124
119
  """
120
+ # Get the standard protected resource routes from RemoteAuthProvider
121
+ routes = super().get_routes()
125
122
 
126
123
  async def oauth_authorization_server_metadata(request):
127
124
  """Forward AuthKit OAuth authorization server metadata with FastMCP customizations."""
@@ -142,29 +139,13 @@ class AuthKitProvider(AuthProvider):
142
139
  status_code=500,
143
140
  )
144
141
 
145
- async def oauth_protected_resource_metadata(request):
146
- """Return FastMCP resource server metadata."""
147
- return JSONResponse(
148
- {
149
- "resource": self.base_url,
150
- "authorization_servers": [self.authkit_domain],
151
- "bearer_methods_supported": ["header"],
152
- }
142
+ # Add AuthKit authorization server metadata forwarding
143
+ routes.append(
144
+ Route(
145
+ "/.well-known/oauth-authorization-server",
146
+ endpoint=oauth_authorization_server_metadata,
147
+ methods=["GET"],
153
148
  )
154
-
155
- routes.extend(
156
- [
157
- Route(
158
- "/.well-known/oauth-authorization-server",
159
- endpoint=oauth_authorization_server_metadata,
160
- methods=["GET"],
161
- ),
162
- Route(
163
- "/.well-known/oauth-protected-resource",
164
- endpoint=oauth_protected_resource_metadata,
165
- methods=["GET"],
166
- ),
167
- ]
168
149
  )
169
150
 
170
151
  return routes
fastmcp/server/http.py CHANGED
@@ -11,12 +11,10 @@ from mcp.server.auth.middleware.bearer_auth import (
11
11
  RequireAuthMiddleware,
12
12
  )
13
13
  from mcp.server.auth.provider import TokenVerifier as TokenVerifierProtocol
14
- from mcp.server.auth.routes import create_auth_routes
15
14
  from mcp.server.lowlevel.server import LifespanResultT
16
15
  from mcp.server.sse import SseServerTransport
17
16
  from mcp.server.streamable_http import EventStore
18
17
  from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
19
- from pydantic import AnyHttpUrl
20
18
  from starlette.applications import Starlette
21
19
  from starlette.middleware import Middleware
22
20
  from starlette.middleware.authentication import AuthenticationMiddleware
@@ -25,7 +23,7 @@ from starlette.responses import Response
25
23
  from starlette.routing import BaseRoute, Mount, Route
26
24
  from starlette.types import Lifespan, Receive, Scope, Send
27
25
 
28
- from fastmcp.server.auth.auth import AuthProvider, OAuthProvider, TokenVerifier
26
+ from fastmcp.server.auth.auth import AuthProvider
29
27
  from fastmcp.utilities.logging import get_logger
30
28
 
31
29
  if TYPE_CHECKING:
@@ -71,51 +69,6 @@ class RequestContextMiddleware:
71
69
  await self.app(scope, receive, send)
72
70
 
73
71
 
74
- def setup_auth_middleware_and_routes(
75
- auth: AuthProvider,
76
- ) -> tuple[list[Middleware], list[Route], list[str]]:
77
- """Set up authentication middleware and routes if auth is enabled.
78
-
79
- Args:
80
- auth: An AuthProvider for authentication (TokenVerifier or OAuthProvider)
81
-
82
- Returns:
83
- Tuple of (middleware, auth_routes, required_scopes)
84
- """
85
- middleware: list[Middleware] = [
86
- Middleware(
87
- AuthenticationMiddleware,
88
- backend=BearerAuthBackend(cast(TokenVerifierProtocol, auth)),
89
- ),
90
- Middleware(AuthContextMiddleware),
91
- ]
92
-
93
- auth_routes: list[Route] = []
94
- required_scopes: list[str] = auth.required_scopes or []
95
-
96
- # Check if it's an OAuthProvider (has OAuth server capability)
97
- if isinstance(auth, OAuthProvider):
98
- # OAuthProvider: create standard OAuth routes first
99
- standard_routes = list(
100
- create_auth_routes(
101
- provider=auth,
102
- issuer_url=auth.issuer_url,
103
- service_documentation_url=auth.service_documentation_url,
104
- client_registration_options=auth.client_registration_options,
105
- revocation_options=auth.revocation_options,
106
- )
107
- )
108
-
109
- # Allow provider to customize routes (e.g., for proxy behavior or metadata endpoints)
110
- auth_routes = auth.customize_auth_routes(standard_routes)
111
- else:
112
- # Simple AuthProvider or TokenVerifier: start with empty routes
113
- # Allow provider to add custom routes (e.g., metadata endpoints)
114
- auth_routes = auth.customize_auth_routes([])
115
-
116
- return middleware, auth_routes, required_scopes
117
-
118
-
119
72
  def create_base_app(
120
73
  routes: list[BaseRoute],
121
74
  middleware: list[Middleware],
@@ -183,24 +136,27 @@ def create_sse_app(
183
136
  )
184
137
  return Response()
185
138
 
186
- # Get auth middleware and routes
139
+ # Set up auth if enabled
187
140
  if auth:
188
- auth_middleware, auth_routes, required_scopes = (
189
- setup_auth_middleware_and_routes(auth)
190
- )
141
+ # Create auth middleware
142
+ auth_middleware = [
143
+ Middleware(
144
+ AuthenticationMiddleware,
145
+ backend=BearerAuthBackend(auth),
146
+ ),
147
+ Middleware(AuthContextMiddleware),
148
+ ]
149
+
150
+ # Get auth routes and scopes
151
+ auth_routes = auth.get_routes()
152
+ required_scopes = getattr(auth, "required_scopes", None) or []
153
+
154
+ # Get resource metadata URL for WWW-Authenticate header
155
+ resource_metadata_url = auth.get_resource_metadata_url()
191
156
 
192
157
  server_routes.extend(auth_routes)
193
158
  server_middleware.extend(auth_middleware)
194
159
 
195
- # Determine resource_metadata_url for TokenVerifier
196
- resource_metadata_url = None
197
- if isinstance(auth, TokenVerifier) and auth.resource_server_url:
198
- # Add .well-known path for RFC 9728 compliance
199
- resource_metadata_url = AnyHttpUrl(
200
- str(auth.resource_server_url).rstrip("/")
201
- + "/.well-known/oauth-protected-resource"
202
- )
203
-
204
160
  # Auth is enabled, wrap endpoints with RequireAuthMiddleware
205
161
  server_routes.append(
206
162
  Route(
@@ -328,22 +284,25 @@ def create_streamable_http_app(
328
284
 
329
285
  # Add StreamableHTTP routes with or without auth
330
286
  if auth:
331
- auth_middleware, auth_routes, required_scopes = (
332
- setup_auth_middleware_and_routes(auth)
333
- )
287
+ # Create auth middleware
288
+ auth_middleware = [
289
+ Middleware(
290
+ AuthenticationMiddleware,
291
+ backend=BearerAuthBackend(cast(TokenVerifierProtocol, auth)),
292
+ ),
293
+ Middleware(AuthContextMiddleware),
294
+ ]
295
+
296
+ # Get auth routes and scopes
297
+ auth_routes = auth.get_routes()
298
+ required_scopes = getattr(auth, "required_scopes", None) or []
299
+
300
+ # Get resource metadata URL for WWW-Authenticate header
301
+ resource_metadata_url = auth.get_resource_metadata_url()
334
302
 
335
303
  server_routes.extend(auth_routes)
336
304
  server_middleware.extend(auth_middleware)
337
305
 
338
- # Determine resource_metadata_url for TokenVerifier
339
- resource_metadata_url = None
340
- if isinstance(auth, TokenVerifier) and auth.resource_server_url:
341
- # Add .well-known path for RFC 9728 compliance
342
- resource_metadata_url = AnyHttpUrl(
343
- str(auth.resource_server_url).rstrip("/")
344
- + "/.well-known/oauth-protected-resource"
345
- )
346
-
347
306
  # Auth is enabled, wrap endpoint with RequireAuthMiddleware
348
307
  server_routes.append(
349
308
  Mount(
fastmcp/server/proxy.py CHANGED
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import inspect
3
4
  import warnings
4
- from collections.abc import Callable
5
+ from collections.abc import Awaitable, Callable
5
6
  from pathlib import Path
6
7
  from typing import TYPE_CHECKING, Any, cast
7
8
  from urllib.parse import quote
@@ -48,11 +49,27 @@ if TYPE_CHECKING:
48
49
 
49
50
  logger = get_logger(__name__)
50
51
 
52
+ # Type alias for client factory functions
53
+ ClientFactoryT = Callable[[], Client] | Callable[[], Awaitable[Client]]
51
54
 
52
- class ProxyToolManager(ToolManager):
55
+
56
+ class ProxyManagerMixin:
57
+ """A mixin for proxy managers to provide a unified client retrieval method."""
58
+
59
+ client_factory: ClientFactoryT
60
+
61
+ async def _get_client(self) -> Client:
62
+ """Gets a client instance by calling the sync or async factory."""
63
+ client = self.client_factory()
64
+ if inspect.isawaitable(client):
65
+ client = await client
66
+ return client
67
+
68
+
69
+ class ProxyToolManager(ToolManager, ProxyManagerMixin):
53
70
  """A ToolManager that sources its tools from a remote client in addition to local and mounted tools."""
54
71
 
55
- def __init__(self, client_factory: Callable[[], Client], **kwargs):
72
+ def __init__(self, client_factory: ClientFactoryT, **kwargs):
56
73
  super().__init__(**kwargs)
57
74
  self.client_factory = client_factory
58
75
 
@@ -63,7 +80,7 @@ class ProxyToolManager(ToolManager):
63
80
 
64
81
  # Then add proxy tools, but don't overwrite existing ones
65
82
  try:
66
- client = self.client_factory()
83
+ client = await self._get_client()
67
84
  async with client:
68
85
  client_tools = await client.list_tools()
69
86
  for tool in client_tools:
@@ -94,7 +111,7 @@ class ProxyToolManager(ToolManager):
94
111
  return await super().call_tool(key, arguments)
95
112
  except NotFoundError:
96
113
  # If not found locally, try proxy
97
- client = self.client_factory()
114
+ client = await self._get_client()
98
115
  async with client:
99
116
  result = await client.call_tool(key, arguments)
100
117
  return ToolResult(
@@ -103,10 +120,10 @@ class ProxyToolManager(ToolManager):
103
120
  )
104
121
 
105
122
 
106
- class ProxyResourceManager(ResourceManager):
123
+ class ProxyResourceManager(ResourceManager, ProxyManagerMixin):
107
124
  """A ResourceManager that sources its resources from a remote client in addition to local and mounted resources."""
108
125
 
109
- def __init__(self, client_factory: Callable[[], Client], **kwargs):
126
+ def __init__(self, client_factory: ClientFactoryT, **kwargs):
110
127
  super().__init__(**kwargs)
111
128
  self.client_factory = client_factory
112
129
 
@@ -117,7 +134,7 @@ class ProxyResourceManager(ResourceManager):
117
134
 
118
135
  # Then add proxy resources, but don't overwrite existing ones
119
136
  try:
120
- client = self.client_factory()
137
+ client = await self._get_client()
121
138
  async with client:
122
139
  client_resources = await client.list_resources()
123
140
  for resource in client_resources:
@@ -140,7 +157,7 @@ class ProxyResourceManager(ResourceManager):
140
157
 
141
158
  # Then add proxy templates, but don't overwrite existing ones
142
159
  try:
143
- client = self.client_factory()
160
+ client = await self._get_client()
144
161
  async with client:
145
162
  client_templates = await client.list_resource_templates()
146
163
  for template in client_templates:
@@ -173,7 +190,7 @@ class ProxyResourceManager(ResourceManager):
173
190
  return await super().read_resource(uri)
174
191
  except NotFoundError:
175
192
  # If not found locally, try proxy
176
- client = self.client_factory()
193
+ client = await self._get_client()
177
194
  async with client:
178
195
  result = await client.read_resource(uri)
179
196
  if isinstance(result[0], TextResourceContents):
@@ -184,10 +201,10 @@ class ProxyResourceManager(ResourceManager):
184
201
  raise ResourceError(f"Unsupported content type: {type(result[0])}")
185
202
 
186
203
 
187
- class ProxyPromptManager(PromptManager):
204
+ class ProxyPromptManager(PromptManager, ProxyManagerMixin):
188
205
  """A PromptManager that sources its prompts from a remote client in addition to local and mounted prompts."""
189
206
 
190
- def __init__(self, client_factory: Callable[[], Client], **kwargs):
207
+ def __init__(self, client_factory: ClientFactoryT, **kwargs):
191
208
  super().__init__(**kwargs)
192
209
  self.client_factory = client_factory
193
210
 
@@ -198,7 +215,7 @@ class ProxyPromptManager(PromptManager):
198
215
 
199
216
  # Then add proxy prompts, but don't overwrite existing ones
200
217
  try:
201
- client = self.client_factory()
218
+ client = await self._get_client()
202
219
  async with client:
203
220
  client_prompts = await client.list_prompts()
204
221
  for prompt in client_prompts:
@@ -230,7 +247,7 @@ class ProxyPromptManager(PromptManager):
230
247
  return await super().render_prompt(name, arguments)
231
248
  except NotFoundError:
232
249
  # If not found locally, try proxy
233
- client = self.client_factory()
250
+ client = await self._get_client()
234
251
  async with client:
235
252
  result = await client.get_prompt(name, arguments)
236
253
  return result
@@ -444,7 +461,7 @@ class FastMCPProxy(FastMCP):
444
461
  self,
445
462
  client: Client | None = None,
446
463
  *,
447
- client_factory: Callable[[], Client] | None = None,
464
+ client_factory: ClientFactoryT | None = None,
448
465
  **kwargs,
449
466
  ):
450
467
  """
@@ -459,6 +476,7 @@ class FastMCPProxy(FastMCP):
459
476
  created that provides session isolation for backwards compatibility.
460
477
  client_factory: A callable that returns a Client instance when called.
461
478
  This gives you full control over session creation and reuse.
479
+ Can be either a synchronous or asynchronous function.
462
480
  **kwargs: Additional settings for the FastMCP server.
463
481
  """
464
482
 
fastmcp/server/server.py CHANGED
@@ -140,19 +140,18 @@ class FastMCP(Generic[LifespanResultT]):
140
140
  ]
141
141
  | None
142
142
  ) = None,
143
- tool_serializer: Callable[[Any], str] | None = None,
144
- cache_expiration_seconds: float | None = None,
145
- on_duplicate_tools: DuplicateBehavior | None = None,
146
- on_duplicate_resources: DuplicateBehavior | None = None,
147
- on_duplicate_prompts: DuplicateBehavior | None = None,
143
+ dependencies: list[str] | None = None,
148
144
  resource_prefix_format: Literal["protocol", "path"] | None = None,
149
145
  mask_error_details: bool | None = None,
150
146
  tools: list[Tool | Callable[..., Any]] | None = None,
151
147
  tool_transformations: dict[str, ToolTransformConfig] | None = None,
152
- dependencies: list[str] | None = None,
148
+ tool_serializer: Callable[[Any], str] | None = None,
153
149
  include_tags: set[str] | None = None,
154
150
  exclude_tags: set[str] | None = None,
155
151
  include_fastmcp_meta: bool | None = None,
152
+ on_duplicate_tools: DuplicateBehavior | None = None,
153
+ on_duplicate_resources: DuplicateBehavior | None = None,
154
+ on_duplicate_prompts: DuplicateBehavior | None = None,
156
155
  # ---
157
156
  # ---
158
157
  # --- The following arguments are DEPRECATED ---
@@ -304,6 +303,10 @@ class FastMCP(Generic[LifespanResultT]):
304
303
  def instructions(self) -> str | None:
305
304
  return self._mcp_server.instructions
306
305
 
306
+ @property
307
+ def version(self) -> str | None:
308
+ return self._mcp_server.version
309
+
307
310
  async def run_async(
308
311
  self,
309
312
  transport: Transport | None = None,
fastmcp/settings.py CHANGED
@@ -244,13 +244,10 @@ class Settings(BaseSettings):
244
244
  ),
245
245
  ] = False
246
246
 
247
- server_dependencies: Annotated[
248
- list[str],
249
- Field(
250
- default_factory=list,
251
- description="List of dependencies to install in the server environment",
252
- ),
253
- ] = []
247
+ server_dependencies: list[str] = Field(
248
+ default_factory=list,
249
+ description="List of dependencies to install in the server environment",
250
+ )
254
251
 
255
252
  # StreamableHTTP settings
256
253
  json_response: bool = False
@@ -92,7 +92,12 @@ class FastMCPComponent(FastMCPBaseModel):
92
92
  return meta or None
93
93
 
94
94
  def with_key(self, key: str) -> Self:
95
- return self.model_copy(update={"_key": key})
95
+ # `model_copy` has an `update` parameter but it doesn't work for certain private attributes
96
+ # https://github.com/pydantic/pydantic/issues/12116
97
+ # So we manually set the private attribute here instead
98
+ copy = self.model_copy()
99
+ copy._key = key
100
+ return copy
96
101
 
97
102
  def __eq__(self, other: object) -> bool:
98
103
  if type(self) is not type(other):
@@ -9,6 +9,7 @@ from typing import Any
9
9
  from mcp.server.fastmcp import FastMCP as FastMCP1x
10
10
 
11
11
  import fastmcp
12
+ from fastmcp import Client
12
13
  from fastmcp.server.server import FastMCP
13
14
 
14
15
 
@@ -71,7 +72,7 @@ class FastMCPInfo:
71
72
  instructions: str | None
72
73
  fastmcp_version: str
73
74
  mcp_version: str
74
- server_version: str
75
+ server_version: str | None
75
76
  tools: list[ToolInfo]
76
77
  prompts: list[PromptInfo]
77
78
  resources: list[ResourceInfo]
@@ -170,7 +171,9 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
170
171
  instructions=mcp.instructions,
171
172
  fastmcp_version=fastmcp.__version__,
172
173
  mcp_version=importlib.metadata.version("mcp"),
173
- server_version=fastmcp.__version__, # v2.x uses FastMCP version
174
+ server_version=(
175
+ mcp.version if hasattr(mcp, "version") else mcp._mcp_server.version
176
+ ),
174
177
  tools=tool_infos,
175
178
  prompts=prompt_infos,
176
179
  resources=resource_infos,
@@ -179,7 +182,7 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
179
182
  )
180
183
 
181
184
 
182
- async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo:
185
+ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
183
186
  """Extract information from a FastMCP v1.x instance using a Client.
184
187
 
185
188
  Args:
@@ -188,7 +191,6 @@ async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo:
188
191
  Returns:
189
192
  FastMCPInfo dataclass containing the extracted information
190
193
  """
191
- from fastmcp import Client
192
194
 
193
195
  # Use a client to interact with the FastMCP1x server
194
196
  async with Client(mcp) as client:
@@ -288,11 +290,11 @@ async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo:
288
290
  }
289
291
 
290
292
  return FastMCPInfo(
291
- name=mcp.name,
292
- instructions=getattr(mcp, "instructions", None),
293
- fastmcp_version=fastmcp.__version__, # Report current fastmcp version
293
+ name=mcp._mcp_server.name,
294
+ instructions=mcp._mcp_server.instructions,
295
+ fastmcp_version=importlib.metadata.version("mcp"),
294
296
  mcp_version=importlib.metadata.version("mcp"),
295
- server_version="1.0", # FastMCP 1.x version
297
+ server_version=mcp._mcp_server.version,
296
298
  tools=tool_infos,
297
299
  prompts=prompt_infos,
298
300
  resources=resource_infos,
@@ -301,14 +303,7 @@ async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo:
301
303
  )
302
304
 
303
305
 
304
- def _is_fastmcp_v1(mcp: Any) -> bool:
305
- """Check if the given instance is a FastMCP v1.x instance."""
306
-
307
- # Check if it's an instance of FastMCP1x and not FastMCP2
308
- return isinstance(mcp, FastMCP1x) and not isinstance(mcp, FastMCP)
309
-
310
-
311
- async def inspect_fastmcp(mcp: FastMCP[Any] | Any) -> FastMCPInfo:
306
+ async def inspect_fastmcp(mcp: FastMCP[Any] | FastMCP1x) -> FastMCPInfo:
312
307
  """Extract information from a FastMCP instance into a dataclass.
313
308
 
314
309
  This function automatically detects whether the instance is FastMCP v1.x or v2.x
@@ -320,7 +315,7 @@ async def inspect_fastmcp(mcp: FastMCP[Any] | Any) -> FastMCPInfo:
320
315
  Returns:
321
316
  FastMCPInfo dataclass containing the extracted information
322
317
  """
323
- if _is_fastmcp_v1(mcp):
318
+ if isinstance(mcp, FastMCP1x):
324
319
  return await inspect_fastmcp_v1(mcp)
325
320
  else:
326
321
  return await inspect_fastmcp_v2(mcp)
@@ -82,13 +82,9 @@ def format_array_parameter(
82
82
  return values
83
83
  else:
84
84
  # For path parameters, fallback to string representation without Python syntax
85
- str_value = (
86
- str(values)
87
- .replace("[", "")
88
- .replace("]", "")
89
- .replace("'", "")
90
- .replace('"', "")
91
- )
85
+ # Use str.translate() for efficient character removal
86
+ translation_table = str.maketrans("", "", "[]'\"")
87
+ str_value = str(values).translate(translation_table)
92
88
  return str_value
93
89
 
94
90
 
@@ -101,7 +101,12 @@ def get_cached_typeadapter(cls: T) -> TypeAdapter[T]:
101
101
  new_func.__module__ = cls.__module__
102
102
  new_func.__qualname__ = getattr(cls, "__qualname__", cls.__name__)
103
103
  new_func.__annotations__ = processed_hints
104
- return TypeAdapter(new_func)
104
+
105
+ if inspect.ismethod(cls):
106
+ new_method = types.MethodType(new_func, cls.__self__)
107
+ return TypeAdapter(new_method)
108
+ else:
109
+ return TypeAdapter(new_func)
105
110
 
106
111
  return TypeAdapter(cls)
107
112
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.11.0
3
+ Version: 2.11.2
4
4
  Summary: The fast, Pythonic way to build MCP servers and clients.
5
5
  Project-URL: Homepage, https://gofastmcp.com
6
6
  Project-URL: Repository, https://github.com/jlowin/fastmcp
@@ -2,17 +2,17 @@ fastmcp/__init__.py,sha256=B_FAqsxbTJmwvJKyIDMOZWpUUdmO806bKo8RR32oZL0,1503
2
2
  fastmcp/exceptions.py,sha256=-krEavxwddQau6T7MESCR4VjKNLfP9KHJrU1p3y72FU,744
3
3
  fastmcp/mcp_config.py,sha256=jf6VyGHli3GcZNg4spdV1L_lPOeNCKTJh-nGIBaMgn4,10324
4
4
  fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- fastmcp/settings.py,sha256=70cxWrYktvHSOHCgj2rKLJ09HU-pHnfs0jCxxv2Rf80,11131
5
+ fastmcp/settings.py,sha256=LxSDlUmWX2SJLZRjX5ih07jAVIHaFZFRK7RCQPPKAGI,11081
6
6
  fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
7
7
  fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
8
- fastmcp/cli/cli.py,sha256=ovUWG-E3lAfIfKegn2zfYM9oiB0yyPkLViQZd12mYKw,15669
9
- fastmcp/cli/run.py,sha256=OOdau2sNQsgan4NXucY-3LWOWn6cKjmdALfzht2RXQ0,10508
8
+ fastmcp/cli/cli.py,sha256=32OKZ49JKq3INqIlHXnZfFNTxQ2XYE1UkW3x5iTvtlI,15699
9
+ fastmcp/cli/run.py,sha256=12I_JP4NSbmNqfQT2u14wvl4Uq2Rr_RvlK1z6bgAfOQ,12383
10
10
  fastmcp/cli/install/__init__.py,sha256=cDEc0hhuf_xwGpI6ghpqlvlMdBMJUGHq_rs-tgmOJZ8,695
11
- fastmcp/cli/install/claude_code.py,sha256=ayTZtudjNWHE8MIGeJfb_TmJI3F5K_DHD1X3qEjxEWg,7712
12
- fastmcp/cli/install/claude_desktop.py,sha256=y8ha2OKM8IuvQbmGNq842q-m-vs_ts2P9cMxAhbBvs4,6872
13
- fastmcp/cli/install/cursor.py,sha256=q5jXBzpxvoWrgP9pItwNlyl13XpYbtIlZS_dRBkBSdc,6837
14
- fastmcp/cli/install/mcp_json.py,sha256=uxCN8fsIjJStWtL0J7kfQ9haIJU0SYF8CXYT-crl_o0,5923
15
- fastmcp/cli/install/shared.py,sha256=Y0YZei1YemVCkg0ieUgfRse-lqSlIn5Ho8t6pB9nDa4,2683
11
+ fastmcp/cli/install/claude_code.py,sha256=1aSO02cJGGWAVl_iVY_dWpShz9Df5ndrAGA_qEj5rbc,7724
12
+ fastmcp/cli/install/claude_desktop.py,sha256=4ErROs-wxdLWr-bCMvL3r9-UIYMETfAghzHTNzhLx5E,6884
13
+ fastmcp/cli/install/cursor.py,sha256=KkRzIBiqaJhmT1wfl3IzprgTnIiAIioTcveq-oDHELE,6849
14
+ fastmcp/cli/install/mcp_json.py,sha256=RUlg7rQiwBysgTOoJfjvVwgz9MFXT-u7aleBDzK1eVQ,5935
15
+ fastmcp/cli/install/shared.py,sha256=T-0R3-WMlmq5uRHYMEVbdItvm6zThzFET4AlI_foG7s,2695
16
16
  fastmcp/client/__init__.py,sha256=J-RcLU2WcnYnstXWoW01itGtAg7DEjvCsWyqQKQljoo,663
17
17
  fastmcp/client/client.py,sha256=GniETS28L8B-ahzztU2ZLI_XSCcEib-miGzE2ZnG4Xc,34054
18
18
  fastmcp/client/elicitation.py,sha256=Jf9yqna8R7r1hqedXAyh9a2-QNVzbCSKUDZhkFHqHqg,2403
@@ -65,19 +65,19 @@ fastmcp/server/__init__.py,sha256=bMD4aQD4yJqLz7-mudoNsyeV8UgQfRAg3PRwPvwTEds,11
65
65
  fastmcp/server/context.py,sha256=ooVwF4RoIbLB2S043E5HtK_dCyAWQkDm1YWCkqfUPzs,22638
66
66
  fastmcp/server/dependencies.py,sha256=gfb1l3KD2m3h7BpN8lgKWrHY3_OJdDrlnJXCvzmU5YM,2487
67
67
  fastmcp/server/elicitation.py,sha256=jZIHjV4NjhYbT-w8pBArwd0vNzP8OYwzmsnWDdk6Bd0,6136
68
- fastmcp/server/http.py,sha256=aFWWPB_l9HPFnqTh_RMdrDGFMUETGkiQIeXqSzf7ZoA,13396
68
+ fastmcp/server/http.py,sha256=idZGs4kRm_eLMscMrlEBIBpX21aiuTlvon9Fizmjswo,11648
69
69
  fastmcp/server/low_level.py,sha256=LNmc_nU_wx-fRG8OEHdLPKopZpovcrWlyAxJzKss3TA,1239
70
70
  fastmcp/server/openapi.py,sha256=-7-pKwQ1hT-UV9OnLlWrjbbXXRfZld8YJqa4Duybhtw,42102
71
- fastmcp/server/proxy.py,sha256=4eHW2Vgwe7zvd0g-ozsvYRyIoengyGyhlyMcSPhcaIU,24975
72
- fastmcp/server/server.py,sha256=hmNiwYFnWhERY-WhxAvS9jIvXOx8_g8ylX-Ju91Y4qk,87597
73
- fastmcp/server/auth/__init__.py,sha256=_9t3pctON0W55jLFTZp06oS6BLiRUQhmX5LgtA-ya48,504
74
- fastmcp/server/auth/auth.py,sha256=TZ_XBJ66gR5vb4BdIpe-EkUa1pRR03AA-oZj-YiAtsA,7035
71
+ fastmcp/server/proxy.py,sha256=soKobVa-c-1Vex7IYnz-jAT526Do2Z4Rs1FaT5ruLro,25651
72
+ fastmcp/server/server.py,sha256=PBDHMx2N2Zr6qVeEs7XEcZrCNObnsAoIrPbK9DlArGk,87634
73
+ fastmcp/server/auth/__init__.py,sha256=ldv13Dsxr2r-VdUV6af6P5qa3wMPQ-MP_yKxsWdlb-4,550
74
+ fastmcp/server/auth/auth.py,sha256=FMkCcht4BMEqKx-SwvfzVC5l7X2moiIzS_ZevaHBq08,10536
75
75
  fastmcp/server/auth/registry.py,sha256=4ftVbbuyAi-8zBiJL9-dYIKLU_EOpxY-pFdzHMrmjV8,1306
76
76
  fastmcp/server/auth/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
77
  fastmcp/server/auth/providers/bearer.py,sha256=iu4pUj7TF5pT1wPuAGzDuM6lt5WtzenBN3c0otUleQY,923
78
78
  fastmcp/server/auth/providers/in_memory.py,sha256=tlUwbUZK5stUC_wjezcDs61PxjaHjykXbd8aH0cnW6k,14359
79
- fastmcp/server/auth/providers/jwt.py,sha256=oHwtnQ8hUBwV9ZZZsCBNCj9s4yUYUaweULQYl6MMHgk,18887
80
- fastmcp/server/auth/providers/workos.py,sha256=fIMmTFJpX6QyYsetxtPO87QJH8Aom9UmHSgedo0DMY4,6131
79
+ fastmcp/server/auth/providers/jwt.py,sha256=7pAH8kuudl_lbY2skeVDlLkxFMhs5-3axpAoGVVp_MU,18882
80
+ fastmcp/server/auth/providers/workos.py,sha256=jedSq4uhttDxYDYSZFrb4u_q1X9JeHgvKupJPpu0tSM,5590
81
81
  fastmcp/server/middleware/__init__.py,sha256=vh5C9ubN6q-y5QND32P4mQ4zDT89C7XYK39yqhELNAk,155
82
82
  fastmcp/server/middleware/error_handling.py,sha256=SoDatr9i3T2qSIUbSEGWrOnu4WPPyMDymnsF5GR_BiE,7572
83
83
  fastmcp/server/middleware/logging.py,sha256=UIAoafnKRGWpQa7OX5nzChep-9EhKdyTDBmmcRcEVdo,6239
@@ -90,19 +90,19 @@ fastmcp/tools/tool_manager.py,sha256=Pr6BI7IInvhkTw2S4ENWj8qMewdYi2CpzbOBiJIk40c
90
90
  fastmcp/tools/tool_transform.py,sha256=f8Bt79qdmOM8Zietm7lXb5_5DlJLu02ovGmQBQJ6orw,36671
91
91
  fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
92
92
  fastmcp/utilities/cli.py,sha256=TXuSyALFAGJwi7tWEBwBmaGhYZBdF1aG6dLgl3zjM1w,3272
93
- fastmcp/utilities/components.py,sha256=WbmAn925ESRsMc4jm3gNQghvAkfeCjSXquOxU4ENZc4,5085
93
+ fastmcp/utilities/components.py,sha256=wmo1XZBqf_tcOMMN1a-r8d1pT7g--Zmf75f5-vXx5FA,5333
94
94
  fastmcp/utilities/exceptions.py,sha256=7Z9j5IzM5rT27BC1Mcn8tkS-bjqCYqMKwb2MMTaxJYU,1350
95
95
  fastmcp/utilities/http.py,sha256=1ns1ymBS-WSxbZjGP6JYjSO52Wa_ls4j4WbnXiupoa4,245
96
- fastmcp/utilities/inspect.py,sha256=XNA0dfYM5G-FVbJaVJO8loSUUCNypyLA-QjqTOneJyU,10833
96
+ fastmcp/utilities/inspect.py,sha256=VLCrh8oMnIg49uuwVO899rUcHhxszPJ1nU0RZ8q2HX8,10639
97
97
  fastmcp/utilities/json_schema.py,sha256=Nk6qQKtp0-MlMbRQmx8ps8SJ7SW9CpuWhSes1VASFPQ,8141
98
98
  fastmcp/utilities/json_schema_type.py,sha256=fSG-af3OPGgOhuhY_xb0-JsTu5tqi275zXlUw4ItjNo,22287
99
99
  fastmcp/utilities/logging.py,sha256=1y7oNmy8WrR0NsfNVw1LPoKu92OFdmzIO65syOKi_BI,1388
100
100
  fastmcp/utilities/mcp_config.py,sha256=zzs4VWHqG0eWEnEUwVve7mef_JFThwfvqBYt7nx3jXc,871
101
- fastmcp/utilities/openapi.py,sha256=URb_OG3D1HQ2KW38rqeyy_fHzpthf-RYlGhDm_7KDjE,63310
101
+ fastmcp/utilities/openapi.py,sha256=Jcxu0s9VdA8RSkx1iyoTQwvsny4YCusuuJahyy-TvE8,63300
102
102
  fastmcp/utilities/tests.py,sha256=9FVLmGYfUjqfn0pPH33awlTgvg-JCNbnZnraJHNSYTg,6156
103
- fastmcp/utilities/types.py,sha256=JmIgOGlFDf3Ksde9p22M8S60AlO6YJywNGtLeQMwwyo,13790
104
- fastmcp-2.11.0.dist-info/METADATA,sha256=azUYPj5I8ygluPs1Ix-pCrHi7NjCLF50twldOVe1eE4,17839
105
- fastmcp-2.11.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
106
- fastmcp-2.11.0.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
107
- fastmcp-2.11.0.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
108
- fastmcp-2.11.0.dist-info/RECORD,,
103
+ fastmcp/utilities/types.py,sha256=zrF8Oc_L_BdzGj0pqU5ZalM02BisC0_D-d8kcRGccS0,13984
104
+ fastmcp-2.11.2.dist-info/METADATA,sha256=6OE28R9_gx-82MA3st0SR6dCxsfoH4oftu0jhSjbL10,17839
105
+ fastmcp-2.11.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
106
+ fastmcp-2.11.2.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
107
+ fastmcp-2.11.2.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
108
+ fastmcp-2.11.2.dist-info/RECORD,,