fastmcp 2.10.6__py3-none-any.whl → 2.11.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. fastmcp/cli/cli.py +128 -33
  2. fastmcp/cli/install/claude_code.py +42 -1
  3. fastmcp/cli/install/claude_desktop.py +42 -1
  4. fastmcp/cli/install/cursor.py +42 -1
  5. fastmcp/cli/install/mcp_json.py +41 -0
  6. fastmcp/cli/run.py +127 -1
  7. fastmcp/client/__init__.py +2 -0
  8. fastmcp/client/auth/oauth.py +68 -99
  9. fastmcp/client/oauth_callback.py +18 -0
  10. fastmcp/client/transports.py +69 -15
  11. fastmcp/contrib/component_manager/example.py +2 -2
  12. fastmcp/experimental/server/openapi/README.md +266 -0
  13. fastmcp/experimental/server/openapi/__init__.py +38 -0
  14. fastmcp/experimental/server/openapi/components.py +348 -0
  15. fastmcp/experimental/server/openapi/routing.py +132 -0
  16. fastmcp/experimental/server/openapi/server.py +466 -0
  17. fastmcp/experimental/utilities/openapi/README.md +239 -0
  18. fastmcp/experimental/utilities/openapi/__init__.py +68 -0
  19. fastmcp/experimental/utilities/openapi/director.py +208 -0
  20. fastmcp/experimental/utilities/openapi/formatters.py +355 -0
  21. fastmcp/experimental/utilities/openapi/json_schema_converter.py +340 -0
  22. fastmcp/experimental/utilities/openapi/models.py +85 -0
  23. fastmcp/experimental/utilities/openapi/parser.py +618 -0
  24. fastmcp/experimental/utilities/openapi/schemas.py +538 -0
  25. fastmcp/mcp_config.py +125 -88
  26. fastmcp/prompts/prompt.py +11 -1
  27. fastmcp/resources/resource.py +21 -1
  28. fastmcp/resources/template.py +20 -1
  29. fastmcp/server/auth/__init__.py +17 -2
  30. fastmcp/server/auth/auth.py +144 -7
  31. fastmcp/server/auth/providers/bearer.py +25 -473
  32. fastmcp/server/auth/providers/in_memory.py +4 -2
  33. fastmcp/server/auth/providers/jwt.py +538 -0
  34. fastmcp/server/auth/providers/workos.py +170 -0
  35. fastmcp/server/auth/registry.py +52 -0
  36. fastmcp/server/context.py +107 -26
  37. fastmcp/server/dependencies.py +9 -2
  38. fastmcp/server/http.py +62 -30
  39. fastmcp/server/middleware/middleware.py +3 -23
  40. fastmcp/server/openapi.py +1 -1
  41. fastmcp/server/proxy.py +50 -11
  42. fastmcp/server/server.py +168 -59
  43. fastmcp/settings.py +73 -6
  44. fastmcp/tools/tool.py +36 -3
  45. fastmcp/tools/tool_manager.py +38 -2
  46. fastmcp/tools/tool_transform.py +112 -3
  47. fastmcp/utilities/components.py +35 -2
  48. fastmcp/utilities/json_schema.py +136 -98
  49. fastmcp/utilities/json_schema_type.py +1 -3
  50. fastmcp/utilities/mcp_config.py +28 -0
  51. fastmcp/utilities/openapi.py +240 -50
  52. fastmcp/utilities/tests.py +54 -6
  53. fastmcp/utilities/types.py +89 -11
  54. {fastmcp-2.10.6.dist-info → fastmcp-2.11.0.dist-info}/METADATA +4 -3
  55. fastmcp-2.11.0.dist-info/RECORD +108 -0
  56. fastmcp/server/auth/providers/bearer_env.py +0 -63
  57. fastmcp/utilities/cache.py +0 -26
  58. fastmcp-2.10.6.dist-info/RECORD +0 -93
  59. {fastmcp-2.10.6.dist-info → fastmcp-2.11.0.dist-info}/WHEEL +0 -0
  60. {fastmcp-2.10.6.dist-info → fastmcp-2.11.0.dist-info}/entry_points.txt +0 -0
  61. {fastmcp-2.10.6.dist-info → fastmcp-2.11.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/cli/run.py CHANGED
@@ -1,11 +1,17 @@
1
1
  """FastMCP run command implementation with enhanced type hints."""
2
2
 
3
3
  import importlib.util
4
+ import json
4
5
  import re
6
+ import subprocess
5
7
  import sys
8
+ from functools import partial
6
9
  from pathlib import Path
7
10
  from typing import Any, Literal
8
11
 
12
+ from mcp.server.fastmcp import FastMCP as FastMCP1x
13
+
14
+ from fastmcp.server.server import FastMCP
9
15
  from fastmcp.utilities.logging import get_logger
10
16
 
11
17
  logger = get_logger("cli.run")
@@ -122,6 +128,84 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
122
128
  return server
123
129
 
124
130
 
131
+ def run_with_uv(
132
+ server_spec: str,
133
+ python_version: str | None = None,
134
+ with_packages: list[str] | None = None,
135
+ with_requirements: Path | None = None,
136
+ project: Path | None = None,
137
+ transport: TransportType | None = None,
138
+ host: str | None = None,
139
+ port: int | None = None,
140
+ path: str | None = None,
141
+ log_level: LogLevelType | None = None,
142
+ show_banner: bool = True,
143
+ ) -> None:
144
+ """Run a MCP server using uv run subprocess.
145
+
146
+ Args:
147
+ server_spec: Python file, object specification (file:obj), or URL
148
+ python_version: Python version to use (e.g. "3.10")
149
+ with_packages: Additional packages to install
150
+ with_requirements: Requirements file to use
151
+ project: Run the command within the given project directory
152
+ transport: Transport protocol to use
153
+ host: Host to bind to when using http transport
154
+ port: Port to bind to when using http transport
155
+ path: Path to bind to when using http transport
156
+ log_level: Log level
157
+ show_banner: Whether to show the server banner
158
+ """
159
+ cmd = ["uv", "run"]
160
+
161
+ # Add Python version if specified
162
+ if python_version:
163
+ cmd.extend(["--python", python_version])
164
+
165
+ # Add project if specified
166
+ if project:
167
+ cmd.extend(["--project", str(project)])
168
+
169
+ # Add fastmcp package
170
+ cmd.extend(["--with", "fastmcp"])
171
+
172
+ # Add additional packages
173
+ if with_packages:
174
+ for pkg in with_packages:
175
+ if pkg:
176
+ cmd.extend(["--with", pkg])
177
+
178
+ # Add requirements file
179
+ if with_requirements:
180
+ cmd.extend(["--with-requirements", str(with_requirements)])
181
+
182
+ # Add fastmcp run command
183
+ cmd.extend(["fastmcp", "run", server_spec])
184
+
185
+ # Add transport options
186
+ if transport:
187
+ cmd.extend(["--transport", transport])
188
+ if host:
189
+ cmd.extend(["--host", host])
190
+ if port:
191
+ cmd.extend(["--port", str(port)])
192
+ if path:
193
+ cmd.extend(["--path", path])
194
+ if log_level:
195
+ cmd.extend(["--log-level", log_level])
196
+ if not show_banner:
197
+ cmd.append("--no-banner")
198
+
199
+ # Run the command
200
+ logger.debug(f"Running command: {' '.join(cmd)}")
201
+ try:
202
+ process = subprocess.run(cmd, check=True)
203
+ sys.exit(process.returncode)
204
+ except subprocess.CalledProcessError as e:
205
+ logger.error(f"Failed to run server: {e}")
206
+ sys.exit(e.returncode)
207
+
208
+
125
209
  def create_client_server(url: str) -> Any:
126
210
  """Create a FastMCP server from a client URL.
127
211
 
@@ -142,6 +226,17 @@ def create_client_server(url: str) -> Any:
142
226
  sys.exit(1)
143
227
 
144
228
 
229
+ def create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]:
230
+ """Create a FastMCP server from a MCPConfig."""
231
+ from fastmcp import FastMCP
232
+
233
+ with mcp_config_path.open() as src:
234
+ mcp_config = json.load(src)
235
+
236
+ server = FastMCP.as_proxy(mcp_config)
237
+ return server
238
+
239
+
145
240
  def import_server_with_args(
146
241
  file: Path, server_object: str | None = None, server_args: list[str] | None = None
147
242
  ) -> Any:
@@ -175,11 +270,12 @@ def run_command(
175
270
  log_level: LogLevelType | None = None,
176
271
  server_args: list[str] | None = None,
177
272
  show_banner: bool = True,
273
+ use_direct_import: bool = False,
178
274
  ) -> None:
179
275
  """Run a MCP server or connect to a remote one.
180
276
 
181
277
  Args:
182
- server_spec: Python file, object specification (file:obj), or URL
278
+ server_spec: Python file, object specification (file:obj), MCPConfig file, or URL
183
279
  transport: Transport protocol to use
184
280
  host: Host to bind to when using http transport
185
281
  port: Port to bind to when using http transport
@@ -187,11 +283,14 @@ def run_command(
187
283
  log_level: Log level
188
284
  server_args: Additional arguments to pass to the server
189
285
  show_banner: Whether to show the server banner
286
+ use_direct_import: Whether to use direct import instead of subprocess
190
287
  """
191
288
  if is_url(server_spec):
192
289
  # Handle URL case
193
290
  server = create_client_server(server_spec)
194
291
  logger.debug(f"Created client proxy server for {server_spec}")
292
+ elif server_spec.endswith(".json"):
293
+ server = create_mcp_config_server(Path(server_spec))
195
294
  else:
196
295
  # Handle file case
197
296
  file, server_object = parse_file_path(server_spec)
@@ -199,6 +298,12 @@ def run_command(
199
298
  logger.debug(f'Found server "{server.name}" in {file}')
200
299
 
201
300
  # Run the server
301
+
302
+ # handle v1 servers
303
+ if isinstance(server, FastMCP1x):
304
+ run_v1_server(server, host=host, port=port, transport=transport)
305
+ return
306
+
202
307
  kwargs = {}
203
308
  if transport:
204
309
  kwargs["transport"] = transport
@@ -219,3 +324,24 @@ def run_command(
219
324
  except Exception as e:
220
325
  logger.error(f"Failed to run server: {e}")
221
326
  sys.exit(1)
327
+
328
+
329
+ def run_v1_server(
330
+ server: FastMCP1x,
331
+ host: str | None = None,
332
+ port: int | None = None,
333
+ transport: TransportType | None = None,
334
+ ) -> None:
335
+ if host:
336
+ server.settings.host = host
337
+ if port:
338
+ server.settings.port = port
339
+ match transport:
340
+ case "stdio":
341
+ runner = partial(server.run)
342
+ case "http" | "streamable-http" | None:
343
+ runner = partial(server.run, transport="streamable-http")
344
+ case "sse":
345
+ runner = partial(server.run, transport="sse")
346
+
347
+ runner()
@@ -7,6 +7,7 @@ from .transports import (
7
7
  PythonStdioTransport,
8
8
  NodeStdioTransport,
9
9
  UvxStdioTransport,
10
+ UvStdioTransport,
10
11
  NpxStdioTransport,
11
12
  FastMCPTransport,
12
13
  StreamableHttpTransport,
@@ -22,6 +23,7 @@ __all__ = [
22
23
  "PythonStdioTransport",
23
24
  "NodeStdioTransport",
24
25
  "UvxStdioTransport",
26
+ "UvStdioTransport",
25
27
  "NpxStdioTransport",
26
28
  "FastMCPTransport",
27
29
  "StreamableHttpTransport",
@@ -5,7 +5,7 @@ import json
5
5
  import webbrowser
6
6
  from pathlib import Path
7
7
  from typing import Any, Literal
8
- from urllib.parse import urljoin, urlparse
8
+ from urllib.parse import urlparse
9
9
 
10
10
  import anyio
11
11
  import httpx
@@ -13,7 +13,6 @@ from mcp.client.auth import OAuthClientProvider, TokenStorage
13
13
  from mcp.shared.auth import (
14
14
  OAuthClientInformationFull,
15
15
  OAuthClientMetadata,
16
- OAuthMetadata,
17
16
  )
18
17
  from mcp.shared.auth import (
19
18
  OAuthToken as OAuthToken,
@@ -150,41 +149,6 @@ class FileTokenStorage(TokenStorage):
150
149
  logger.info("Cleared all OAuth client cache data.")
151
150
 
152
151
 
153
- async def discover_oauth_metadata(
154
- server_base_url: str, httpx_kwargs: dict[str, Any] | None = None
155
- ) -> OAuthMetadata | None:
156
- """
157
- Discover OAuth metadata from the server using RFC 8414 well-known endpoint.
158
-
159
- Args:
160
- server_base_url: Base URL of the OAuth server (e.g., "https://example.com")
161
- httpx_kwargs: Additional kwargs for httpx client
162
-
163
- Returns:
164
- OAuth metadata if found, None otherwise
165
- """
166
- well_known_url = urljoin(server_base_url, "/.well-known/oauth-authorization-server")
167
- logger.debug(f"Discovering OAuth metadata from: {well_known_url}")
168
-
169
- async with httpx.AsyncClient(**(httpx_kwargs or {})) as client:
170
- try:
171
- response = await client.get(well_known_url, timeout=10.0)
172
- if response.status_code == 200:
173
- logger.debug("Successfully discovered OAuth metadata")
174
- return OAuthMetadata.model_validate(response.json())
175
- elif response.status_code == 404:
176
- logger.debug(
177
- "OAuth metadata not found (404) - server may not require auth"
178
- )
179
- return None
180
- else:
181
- logger.warning(f"OAuth metadata request failed: {response.status_code}")
182
- return None
183
- except (httpx.RequestError, json.JSONDecodeError, ValidationError) as e:
184
- logger.debug(f"OAuth metadata discovery failed: {e}")
185
- return None
186
-
187
-
188
152
  async def check_if_auth_required(
189
153
  mcp_url: str, httpx_kwargs: dict[str, Any] | None = None
190
154
  ) -> bool:
@@ -215,70 +179,86 @@ async def check_if_auth_required(
215
179
  return True
216
180
 
217
181
 
218
- def OAuth(
219
- mcp_url: str,
220
- scopes: str | list[str] | None = None,
221
- client_name: str = "FastMCP Client",
222
- token_storage_cache_dir: Path | None = None,
223
- additional_client_metadata: dict[str, Any] | None = None,
224
- ) -> OAuthClientProvider:
182
+ class OAuth(OAuthClientProvider):
225
183
  """
226
- Create an OAuthClientProvider for an MCP server.
184
+ OAuth client provider for MCP servers with browser-based authentication.
227
185
 
228
- This is intended to be provided to the `auth` parameter of an
229
- httpx.AsyncClient (or appropriate FastMCP client/transport instance)
186
+ This class provides OAuth authentication for FastMCP clients by opening
187
+ a browser for user authorization and running a local callback server.
188
+ """
230
189
 
231
- Args:
232
- mcp_url: Full URL to the MCP endpoint (e.g. "http://host/mcp/sse/")
233
- scopes: OAuth scopes to request. Can be a
234
- space-separated string or a list of strings.
235
- client_name: Name for this client during registration
236
- token_storage_cache_dir: Directory for FileTokenStorage
237
- additional_client_metadata: Extra fields for OAuthClientMetadata
190
+ def __init__(
191
+ self,
192
+ mcp_url: str,
193
+ scopes: str | list[str] | None = None,
194
+ client_name: str = "FastMCP Client",
195
+ token_storage_cache_dir: Path | None = None,
196
+ additional_client_metadata: dict[str, Any] | None = None,
197
+ callback_port: int | None = None,
198
+ ):
199
+ """
200
+ Initialize OAuth client provider for an MCP server.
201
+
202
+ Args:
203
+ mcp_url: Full URL to the MCP endpoint (e.g. "http://host/mcp/sse/")
204
+ scopes: OAuth scopes to request. Can be a
205
+ space-separated string or a list of strings.
206
+ client_name: Name for this client during registration
207
+ token_storage_cache_dir: Directory for FileTokenStorage
208
+ additional_client_metadata: Extra fields for OAuthClientMetadata
209
+ callback_port: Fixed port for OAuth callback (default: random available port)
210
+ """
211
+ parsed_url = urlparse(mcp_url)
212
+ server_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
213
+
214
+ # Setup OAuth client
215
+ self.redirect_port = callback_port or find_available_port()
216
+ redirect_uri = f"http://localhost:{self.redirect_port}/callback"
217
+
218
+ if isinstance(scopes, list):
219
+ scopes = " ".join(scopes)
220
+
221
+ client_metadata = OAuthClientMetadata(
222
+ client_name=client_name,
223
+ redirect_uris=[AnyHttpUrl(redirect_uri)],
224
+ grant_types=["authorization_code", "refresh_token"],
225
+ response_types=["code"],
226
+ # token_endpoint_auth_method="client_secret_post",
227
+ scope=scopes,
228
+ **(additional_client_metadata or {}),
229
+ )
238
230
 
239
- Returns:
240
- OAuthClientProvider
241
- """
242
- parsed_url = urlparse(mcp_url)
243
- server_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
244
-
245
- # Setup OAuth client
246
- redirect_port = find_available_port()
247
- redirect_uri = f"http://127.0.0.1:{redirect_port}/callback"
248
-
249
- if isinstance(scopes, list):
250
- scopes = " ".join(scopes)
251
-
252
- client_metadata = OAuthClientMetadata(
253
- client_name=client_name,
254
- redirect_uris=[AnyHttpUrl(redirect_uri)],
255
- grant_types=["authorization_code", "refresh_token"],
256
- response_types=["code"],
257
- token_endpoint_auth_method="client_secret_post",
258
- scope=scopes,
259
- **(additional_client_metadata or {}),
260
- )
261
-
262
- # Create server-specific token storage
263
- storage = FileTokenStorage(
264
- server_url=server_base_url, cache_dir=token_storage_cache_dir
265
- )
266
-
267
- # Define OAuth handlers
268
- async def redirect_handler(authorization_url: str) -> None:
231
+ # Create server-specific token storage
232
+ storage = FileTokenStorage(
233
+ server_url=server_base_url, cache_dir=token_storage_cache_dir
234
+ )
235
+
236
+ # Store server_base_url for use in callback_handler
237
+ self.server_base_url = server_base_url
238
+
239
+ # Initialize parent class
240
+ super().__init__(
241
+ server_url=server_base_url,
242
+ client_metadata=client_metadata,
243
+ storage=storage,
244
+ redirect_handler=self.redirect_handler,
245
+ callback_handler=self.callback_handler,
246
+ )
247
+
248
+ async def redirect_handler(self, authorization_url: str) -> None:
269
249
  """Open browser for authorization."""
270
250
  logger.info(f"OAuth authorization URL: {authorization_url}")
271
251
  webbrowser.open(authorization_url)
272
252
 
273
- async def callback_handler() -> tuple[str, str | None]:
253
+ async def callback_handler(self) -> tuple[str, str | None]:
274
254
  """Handle OAuth callback and return (auth_code, state)."""
275
255
  # Create a future to capture the OAuth response
276
256
  response_future = asyncio.get_running_loop().create_future()
277
257
 
278
258
  # Create server with the future
279
259
  server = create_oauth_callback_server(
280
- port=redirect_port,
281
- server_url=server_base_url,
260
+ port=self.redirect_port,
261
+ server_url=self.server_base_url,
282
262
  response_future=response_future,
283
263
  )
284
264
 
@@ -286,7 +266,7 @@ def OAuth(
286
266
  async with anyio.create_task_group() as tg:
287
267
  tg.start_soon(server.serve)
288
268
  logger.info(
289
- f"🎧 OAuth callback server started on http://127.0.0.1:{redirect_port}"
269
+ f"🎧 OAuth callback server started on http://localhost:{self.redirect_port}"
290
270
  )
291
271
 
292
272
  TIMEOUT = 300.0 # 5 minute timeout
@@ -300,14 +280,3 @@ def OAuth(
300
280
  server.should_exit = True
301
281
  await asyncio.sleep(0.1) # Allow server to shutdown gracefully
302
282
  tg.cancel_scope.cancel()
303
-
304
- # Create OAuth provider
305
- oauth_provider = OAuthClientProvider(
306
- server_url=server_base_url,
307
- client_metadata=client_metadata,
308
- storage=storage,
309
- redirect_handler=redirect_handler,
310
- callback_handler=callback_handler,
311
- )
312
-
313
- return oauth_provider
@@ -252,6 +252,24 @@ def create_oauth_callback_server(
252
252
  status_code=400,
253
253
  )
254
254
 
255
+ # Check for missing state parameter (indicates OAuth flow issue)
256
+ if callback_response.state is None:
257
+ # Resolve future with exception if provided
258
+ if response_future and not response_future.done():
259
+ response_future.set_exception(
260
+ RuntimeError(
261
+ "OAuth server did not return state parameter - authentication failed"
262
+ )
263
+ )
264
+
265
+ return HTMLResponse(
266
+ create_callback_html(
267
+ "FastMCP OAuth Error: Authentication failed<br>The OAuth server did not return the expected state parameter",
268
+ is_success=False,
269
+ ),
270
+ status_code=400,
271
+ )
272
+
255
273
  # Success case
256
274
  if response_future and not response_future.done():
257
275
  response_future.set_result(
@@ -48,6 +48,7 @@ __all__ = [
48
48
  "FastMCPStdioTransport",
49
49
  "NodeStdioTransport",
50
50
  "UvxStdioTransport",
51
+ "UvStdioTransport",
51
52
  "NpxStdioTransport",
52
53
  "FastMCPTransport",
53
54
  "infer_transport",
@@ -542,6 +543,63 @@ class NodeStdioTransport(StdioTransport):
542
543
  self.script_path = script_path
543
544
 
544
545
 
546
+ class UvStdioTransport(StdioTransport):
547
+ """Transport for running commands via the uv tool."""
548
+
549
+ def __init__(
550
+ self,
551
+ command: str,
552
+ args: list[str] | None = None,
553
+ module: bool = False,
554
+ project_directory: str | None = None,
555
+ python_version: str | None = None,
556
+ with_packages: list[str] | None = None,
557
+ with_requirements: str | None = None,
558
+ env_vars: dict[str, str] | None = None,
559
+ keep_alive: bool | None = None,
560
+ ):
561
+ # Basic validation
562
+ if project_directory and not Path(project_directory).exists():
563
+ raise NotADirectoryError(
564
+ f"Project directory not found: {project_directory}"
565
+ )
566
+
567
+ # Build uv arguments
568
+ uv_args: list[str] = ["run"]
569
+ if project_directory:
570
+ uv_args.extend(["--directory", str(project_directory)])
571
+ if python_version:
572
+ uv_args.extend(["--python", python_version])
573
+ for pkg in with_packages or []:
574
+ uv_args.extend(["--with", pkg])
575
+ if with_requirements:
576
+ uv_args.extend(["--with-requirements", str(with_requirements)])
577
+ if module:
578
+ uv_args.append("--module")
579
+
580
+ if not args:
581
+ args = []
582
+
583
+ uv_args.extend([command, *args])
584
+
585
+ # Get environment with any additional variables
586
+ env: dict[str, str] | None = None
587
+ if env_vars or project_directory:
588
+ env = os.environ.copy()
589
+ if project_directory:
590
+ env["UV_PROJECT_DIR"] = str(project_directory)
591
+ if env_vars:
592
+ env.update(env_vars)
593
+
594
+ super().__init__(
595
+ command="uv",
596
+ args=uv_args,
597
+ env=env,
598
+ cwd=None, # Use --directory flag instead of cwd
599
+ keep_alive=keep_alive,
600
+ )
601
+
602
+
545
603
  class UvxStdioTransport(StdioTransport):
546
604
  """Transport for running commands via the uvx tool."""
547
605
 
@@ -579,7 +637,7 @@ class UvxStdioTransport(StdioTransport):
579
637
  )
580
638
 
581
639
  # Build uvx arguments
582
- uvx_args = []
640
+ uvx_args: list[str] = []
583
641
  if python_version:
584
642
  uvx_args.extend(["--python", python_version])
585
643
  if from_package:
@@ -592,11 +650,9 @@ class UvxStdioTransport(StdioTransport):
592
650
  if tool_args:
593
651
  uvx_args.extend(tool_args)
594
652
 
595
- # Get environment with any additional variables
596
- env = None
653
+ env: dict[str, str] | None = None
597
654
  if env_vars:
598
655
  env = os.environ.copy()
599
- env.update(env_vars)
600
656
 
601
657
  super().__init__(
602
658
  command="uvx",
@@ -605,7 +661,7 @@ class UvxStdioTransport(StdioTransport):
605
661
  cwd=project_directory,
606
662
  keep_alive=keep_alive,
607
663
  )
608
- self.tool_name = tool_name
664
+ self.tool_name: str = tool_name
609
665
 
610
666
 
611
667
  class NpxStdioTransport(StdioTransport):
@@ -732,7 +788,7 @@ class MCPConfigTransport(ClientTransport):
732
788
 
733
789
  1. If the MCPConfig contains exactly one server, it creates a direct transport to that server.
734
790
  2. If the MCPConfig contains multiple servers, it creates a composite client by mounting
735
- all servers on a single FastMCP instance, with each server's name used as its mounting prefix.
791
+ all servers on a single FastMCP instance, with each server's name, by default, used as its mounting prefix.
736
792
 
737
793
  In the multi-server case, tools are accessible with the prefix pattern `{server_name}_{tool_name}`
738
794
  and resources with the pattern `protocol://{server_name}/path/to/resource`.
@@ -772,7 +828,9 @@ class MCPConfigTransport(ClientTransport):
772
828
  ```
773
829
  """
774
830
 
775
- def __init__(self, config: MCPConfig | dict):
831
+ def __init__(self, config: MCPConfig | dict, name_as_prefix: bool = True):
832
+ from fastmcp.utilities.mcp_config import composite_server_from_mcp_config
833
+
776
834
  if isinstance(config, dict):
777
835
  config = MCPConfig.from_dict(config)
778
836
  self.config = config
@@ -787,15 +845,11 @@ class MCPConfigTransport(ClientTransport):
787
845
 
788
846
  # otherwise create a composite client
789
847
  else:
790
- composite_server = FastMCP()
791
-
792
- for name, server in self.config.mcpServers.items():
793
- composite_server.mount(
794
- prefix=name,
795
- server=FastMCP.as_proxy(backend=server.to_transport()),
848
+ self.transport = FastMCPTransport(
849
+ mcp=composite_server_from_mcp_config(
850
+ self.config, name_as_prefix=name_as_prefix
796
851
  )
797
-
798
- self.transport = FastMCPTransport(mcp=composite_server)
852
+ )
799
853
 
800
854
  @contextlib.asynccontextmanager
801
855
  async def connect_session(
@@ -1,10 +1,10 @@
1
1
  from fastmcp import FastMCP
2
2
  from fastmcp.contrib.component_manager import set_up_component_manager
3
- from fastmcp.server.auth.providers.bearer import BearerAuthProvider, RSAKeyPair
3
+ from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair
4
4
 
5
5
  key_pair = RSAKeyPair.generate()
6
6
 
7
- auth = BearerAuthProvider(
7
+ auth = JWTVerifier(
8
8
  public_key=key_pair.public_key,
9
9
  issuer="https://dev.example.com",
10
10
  audience="my-dev-server",