fastmcp 2.11.2__py3-none-any.whl → 2.12.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 (77) hide show
  1. fastmcp/__init__.py +5 -4
  2. fastmcp/cli/claude.py +22 -18
  3. fastmcp/cli/cli.py +472 -136
  4. fastmcp/cli/install/claude_code.py +37 -40
  5. fastmcp/cli/install/claude_desktop.py +37 -42
  6. fastmcp/cli/install/cursor.py +148 -38
  7. fastmcp/cli/install/mcp_json.py +38 -43
  8. fastmcp/cli/install/shared.py +64 -7
  9. fastmcp/cli/run.py +122 -215
  10. fastmcp/client/auth/oauth.py +69 -13
  11. fastmcp/client/client.py +46 -9
  12. fastmcp/client/logging.py +25 -1
  13. fastmcp/client/oauth_callback.py +91 -91
  14. fastmcp/client/sampling.py +12 -4
  15. fastmcp/client/transports.py +143 -67
  16. fastmcp/experimental/sampling/__init__.py +0 -0
  17. fastmcp/experimental/sampling/handlers/__init__.py +3 -0
  18. fastmcp/experimental/sampling/handlers/base.py +21 -0
  19. fastmcp/experimental/sampling/handlers/openai.py +163 -0
  20. fastmcp/experimental/server/openapi/routing.py +1 -3
  21. fastmcp/experimental/server/openapi/server.py +10 -25
  22. fastmcp/experimental/utilities/openapi/__init__.py +2 -2
  23. fastmcp/experimental/utilities/openapi/formatters.py +34 -0
  24. fastmcp/experimental/utilities/openapi/models.py +5 -2
  25. fastmcp/experimental/utilities/openapi/parser.py +252 -70
  26. fastmcp/experimental/utilities/openapi/schemas.py +135 -106
  27. fastmcp/mcp_config.py +40 -20
  28. fastmcp/prompts/prompt_manager.py +4 -2
  29. fastmcp/resources/resource_manager.py +16 -6
  30. fastmcp/server/auth/__init__.py +11 -1
  31. fastmcp/server/auth/auth.py +19 -2
  32. fastmcp/server/auth/oauth_proxy.py +1047 -0
  33. fastmcp/server/auth/providers/azure.py +270 -0
  34. fastmcp/server/auth/providers/github.py +287 -0
  35. fastmcp/server/auth/providers/google.py +305 -0
  36. fastmcp/server/auth/providers/jwt.py +27 -16
  37. fastmcp/server/auth/providers/workos.py +256 -2
  38. fastmcp/server/auth/redirect_validation.py +65 -0
  39. fastmcp/server/auth/registry.py +1 -1
  40. fastmcp/server/context.py +91 -41
  41. fastmcp/server/dependencies.py +32 -2
  42. fastmcp/server/elicitation.py +60 -1
  43. fastmcp/server/http.py +44 -37
  44. fastmcp/server/middleware/logging.py +66 -28
  45. fastmcp/server/proxy.py +2 -0
  46. fastmcp/server/sampling/handler.py +19 -0
  47. fastmcp/server/server.py +85 -20
  48. fastmcp/settings.py +18 -3
  49. fastmcp/tools/tool.py +23 -10
  50. fastmcp/tools/tool_manager.py +5 -1
  51. fastmcp/tools/tool_transform.py +75 -32
  52. fastmcp/utilities/auth.py +34 -0
  53. fastmcp/utilities/cli.py +148 -15
  54. fastmcp/utilities/components.py +21 -5
  55. fastmcp/utilities/inspect.py +166 -37
  56. fastmcp/utilities/json_schema_type.py +4 -2
  57. fastmcp/utilities/logging.py +4 -1
  58. fastmcp/utilities/mcp_config.py +47 -18
  59. fastmcp/utilities/mcp_server_config/__init__.py +25 -0
  60. fastmcp/utilities/mcp_server_config/v1/__init__.py +0 -0
  61. fastmcp/utilities/mcp_server_config/v1/environments/__init__.py +6 -0
  62. fastmcp/utilities/mcp_server_config/v1/environments/base.py +30 -0
  63. fastmcp/utilities/mcp_server_config/v1/environments/uv.py +306 -0
  64. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +446 -0
  65. fastmcp/utilities/mcp_server_config/v1/schema.json +361 -0
  66. fastmcp/utilities/mcp_server_config/v1/sources/__init__.py +0 -0
  67. fastmcp/utilities/mcp_server_config/v1/sources/base.py +30 -0
  68. fastmcp/utilities/mcp_server_config/v1/sources/filesystem.py +216 -0
  69. fastmcp/utilities/openapi.py +4 -4
  70. fastmcp/utilities/tests.py +7 -2
  71. fastmcp/utilities/types.py +15 -2
  72. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/METADATA +3 -2
  73. fastmcp-2.12.0.dist-info/RECORD +129 -0
  74. fastmcp-2.11.2.dist-info/RECORD +0 -108
  75. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/WHEEL +0 -0
  76. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/entry_points.txt +0 -0
  77. {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -4,8 +4,10 @@ from __future__ import annotations
4
4
 
5
5
  import importlib.metadata
6
6
  from dataclasses import dataclass
7
- from typing import Any
7
+ from enum import Enum
8
+ from typing import Any, Literal, cast
8
9
 
10
+ import pydantic_core
9
11
  from mcp.server.fastmcp import FastMCP as FastMCP1x
10
12
 
11
13
  import fastmcp
@@ -21,9 +23,12 @@ class ToolInfo:
21
23
  name: str
22
24
  description: str | None
23
25
  input_schema: dict[str, Any]
26
+ output_schema: dict[str, Any] | None = None
24
27
  annotations: dict[str, Any] | None = None
25
28
  tags: list[str] | None = None
26
29
  enabled: bool | None = None
30
+ title: str | None = None
31
+ meta: dict[str, Any] | None = None
27
32
 
28
33
 
29
34
  @dataclass
@@ -36,6 +41,8 @@ class PromptInfo:
36
41
  arguments: list[dict[str, Any]] | None = None
37
42
  tags: list[str] | None = None
38
43
  enabled: bool | None = None
44
+ title: str | None = None
45
+ meta: dict[str, Any] | None = None
39
46
 
40
47
 
41
48
  @dataclass
@@ -47,8 +54,11 @@ class ResourceInfo:
47
54
  name: str | None
48
55
  description: str | None
49
56
  mime_type: str | None = None
57
+ annotations: dict[str, Any] | None = None
50
58
  tags: list[str] | None = None
51
59
  enabled: bool | None = None
60
+ title: str | None = None
61
+ meta: dict[str, Any] | None = None
52
62
 
53
63
 
54
64
  @dataclass
@@ -60,8 +70,12 @@ class TemplateInfo:
60
70
  name: str | None
61
71
  description: str | None
62
72
  mime_type: str | None = None
73
+ parameters: dict[str, Any] | None = None
74
+ annotations: dict[str, Any] | None = None
63
75
  tags: list[str] | None = None
64
76
  enabled: bool | None = None
77
+ title: str | None = None
78
+ meta: dict[str, Any] | None = None
65
79
 
66
80
 
67
81
  @dataclass
@@ -70,9 +84,10 @@ class FastMCPInfo:
70
84
 
71
85
  name: str
72
86
  instructions: str | None
73
- fastmcp_version: str
74
- mcp_version: str
75
- server_version: str | None
87
+ version: str | None # The server's own version string (if specified)
88
+ fastmcp_version: str # Version of FastMCP generating this manifest
89
+ mcp_version: str # Version of MCP protocol library
90
+ server_generation: int # Server generation: 1 (mcp package) or 2 (fastmcp)
76
91
  tools: list[ToolInfo]
77
92
  prompts: list[PromptInfo]
78
93
  resources: list[ResourceInfo]
@@ -106,9 +121,12 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
106
121
  name=tool.name or key,
107
122
  description=tool.description,
108
123
  input_schema=mcp_tool.inputSchema if mcp_tool.inputSchema else {},
124
+ output_schema=tool.output_schema,
109
125
  annotations=tool.annotations.model_dump() if tool.annotations else None,
110
126
  tags=list(tool.tags) if tool.tags else None,
111
127
  enabled=tool.enabled,
128
+ title=tool.title,
129
+ meta=tool.meta,
112
130
  )
113
131
  )
114
132
 
@@ -125,6 +143,8 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
125
143
  else None,
126
144
  tags=list(prompt.tags) if prompt.tags else None,
127
145
  enabled=prompt.enabled,
146
+ title=prompt.title,
147
+ meta=prompt.meta,
128
148
  )
129
149
  )
130
150
 
@@ -138,8 +158,13 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
138
158
  name=resource.name,
139
159
  description=resource.description,
140
160
  mime_type=resource.mime_type,
161
+ annotations=resource.annotations.model_dump()
162
+ if resource.annotations
163
+ else None,
141
164
  tags=list(resource.tags) if resource.tags else None,
142
165
  enabled=resource.enabled,
166
+ title=resource.title,
167
+ meta=resource.meta,
143
168
  )
144
169
  )
145
170
 
@@ -153,8 +178,14 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
153
178
  name=template.name,
154
179
  description=template.description,
155
180
  mime_type=template.mime_type,
181
+ parameters=template.parameters,
182
+ annotations=template.annotations.model_dump()
183
+ if template.annotations
184
+ else None,
156
185
  tags=list(template.tags) if template.tags else None,
157
186
  enabled=template.enabled,
187
+ title=template.title,
188
+ meta=template.meta,
158
189
  )
159
190
  )
160
191
 
@@ -171,9 +202,8 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
171
202
  instructions=mcp.instructions,
172
203
  fastmcp_version=fastmcp.__version__,
173
204
  mcp_version=importlib.metadata.version("mcp"),
174
- server_version=(
175
- mcp.version if hasattr(mcp, "version") else mcp._mcp_server.version
176
- ),
205
+ server_generation=2, # FastMCP v2
206
+ version=(mcp.version if hasattr(mcp, "version") else mcp._mcp_server.version),
177
207
  tools=tool_infos,
178
208
  prompts=prompt_infos,
179
209
  resources=resource_infos,
@@ -191,7 +221,6 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
191
221
  Returns:
192
222
  FastMCPInfo dataclass containing the extracted information
193
223
  """
194
-
195
224
  # Use a client to interact with the FastMCP1x server
196
225
  async with Client(mcp) as client:
197
226
  # Get components via client calls (these return MCP objects)
@@ -208,25 +237,18 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
208
237
  # Extract detailed tool information from MCP Tool objects
209
238
  tool_infos = []
210
239
  for mcp_tool in mcp_tools:
211
- # Extract annotations if they exist
212
- annotations = None
213
- if hasattr(mcp_tool, "annotations") and mcp_tool.annotations:
214
- if hasattr(mcp_tool.annotations, "model_dump"):
215
- annotations = mcp_tool.annotations.model_dump()
216
- elif isinstance(mcp_tool.annotations, dict):
217
- annotations = mcp_tool.annotations
218
- else:
219
- annotations = None
220
-
221
240
  tool_infos.append(
222
241
  ToolInfo(
223
- key=mcp_tool.name, # For 1.x, key and name are the same
242
+ key=mcp_tool.name,
224
243
  name=mcp_tool.name,
225
244
  description=mcp_tool.description,
226
245
  input_schema=mcp_tool.inputSchema if mcp_tool.inputSchema else {},
227
- annotations=annotations,
228
- tags=None, # 1.x doesn't have tags
229
- enabled=None, # 1.x doesn't have enabled field
246
+ output_schema=None, # v1 doesn't have output_schema
247
+ annotations=None, # v1 doesn't have annotations
248
+ tags=None, # v1 doesn't have tags
249
+ enabled=None, # v1 doesn't have enabled field
250
+ title=None, # v1 doesn't have title
251
+ meta=None, # v1 doesn't have meta field
230
252
  )
231
253
  )
232
254
 
@@ -240,12 +262,14 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
240
262
 
241
263
  prompt_infos.append(
242
264
  PromptInfo(
243
- key=mcp_prompt.name, # For 1.x, key and name are the same
265
+ key=mcp_prompt.name,
244
266
  name=mcp_prompt.name,
245
267
  description=mcp_prompt.description,
246
268
  arguments=arguments,
247
- tags=None, # 1.x doesn't have tags
248
- enabled=None, # 1.x doesn't have enabled field
269
+ tags=None, # v1 doesn't have tags
270
+ enabled=None, # v1 doesn't have enabled field
271
+ title=None, # v1 doesn't have title
272
+ meta=None, # v1 doesn't have meta field
249
273
  )
250
274
  )
251
275
 
@@ -254,13 +278,16 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
254
278
  for mcp_resource in mcp_resources:
255
279
  resource_infos.append(
256
280
  ResourceInfo(
257
- key=str(mcp_resource.uri), # For 1.x, key and uri are the same
281
+ key=str(mcp_resource.uri),
258
282
  uri=str(mcp_resource.uri),
259
283
  name=mcp_resource.name,
260
284
  description=mcp_resource.description,
261
285
  mime_type=mcp_resource.mimeType,
262
- tags=None, # 1.x doesn't have tags
263
- enabled=None, # 1.x doesn't have enabled field
286
+ annotations=None, # v1 doesn't have annotations
287
+ tags=None, # v1 doesn't have tags
288
+ enabled=None, # v1 doesn't have enabled field
289
+ title=None, # v1 doesn't have title
290
+ meta=None, # v1 doesn't have meta field
264
291
  )
265
292
  )
266
293
 
@@ -269,15 +296,17 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
269
296
  for mcp_template in mcp_templates:
270
297
  template_infos.append(
271
298
  TemplateInfo(
272
- key=str(
273
- mcp_template.uriTemplate
274
- ), # For 1.x, key and uriTemplate are the same
299
+ key=str(mcp_template.uriTemplate),
275
300
  uri_template=str(mcp_template.uriTemplate),
276
301
  name=mcp_template.name,
277
302
  description=mcp_template.description,
278
303
  mime_type=mcp_template.mimeType,
279
- tags=None, # 1.x doesn't have tags
280
- enabled=None, # 1.x doesn't have enabled field
304
+ parameters=None, # v1 doesn't expose template parameters
305
+ annotations=None, # v1 doesn't have annotations
306
+ tags=None, # v1 doesn't have tags
307
+ enabled=None, # v1 doesn't have enabled field
308
+ title=None, # v1 doesn't have title
309
+ meta=None, # v1 doesn't have meta field
281
310
  )
282
311
  )
283
312
 
@@ -292,13 +321,14 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
292
321
  return FastMCPInfo(
293
322
  name=mcp._mcp_server.name,
294
323
  instructions=mcp._mcp_server.instructions,
295
- fastmcp_version=importlib.metadata.version("mcp"),
324
+ fastmcp_version=fastmcp.__version__, # Version generating this manifest
296
325
  mcp_version=importlib.metadata.version("mcp"),
297
- server_version=mcp._mcp_server.version,
326
+ server_generation=1, # MCP v1
327
+ version=mcp._mcp_server.version,
298
328
  tools=tool_infos,
299
329
  prompts=prompt_infos,
300
330
  resources=resource_infos,
301
- templates=template_infos, # FastMCP1x does have templates
331
+ templates=template_infos,
302
332
  capabilities=capabilities,
303
333
  )
304
334
 
@@ -318,4 +348,103 @@ async def inspect_fastmcp(mcp: FastMCP[Any] | FastMCP1x) -> FastMCPInfo:
318
348
  if isinstance(mcp, FastMCP1x):
319
349
  return await inspect_fastmcp_v1(mcp)
320
350
  else:
321
- return await inspect_fastmcp_v2(mcp)
351
+ return await inspect_fastmcp_v2(cast(FastMCP[Any], mcp))
352
+
353
+
354
+ class InspectFormat(str, Enum):
355
+ """Output format for inspect command."""
356
+
357
+ FASTMCP = "fastmcp"
358
+ MCP = "mcp"
359
+
360
+
361
+ async def format_fastmcp_info(info: FastMCPInfo) -> bytes:
362
+ """Format FastMCPInfo as FastMCP-specific JSON.
363
+
364
+ This includes FastMCP-specific fields like tags, enabled, annotations, etc.
365
+ """
366
+ # Build the output dict with nested structure
367
+ result = {
368
+ "server": {
369
+ "name": info.name,
370
+ "instructions": info.instructions,
371
+ "version": info.version,
372
+ "generation": info.server_generation,
373
+ "capabilities": info.capabilities,
374
+ },
375
+ "environment": {
376
+ "fastmcp": info.fastmcp_version,
377
+ "mcp": info.mcp_version,
378
+ },
379
+ "tools": info.tools,
380
+ "prompts": info.prompts,
381
+ "resources": info.resources,
382
+ "templates": info.templates,
383
+ }
384
+
385
+ return pydantic_core.to_json(result, indent=2)
386
+
387
+
388
+ async def format_mcp_info(mcp: FastMCP[Any] | FastMCP1x) -> bytes:
389
+ """Format server info as standard MCP protocol JSON.
390
+
391
+ Uses Client to get the standard MCP protocol format with camelCase fields.
392
+ Includes version metadata at the top level.
393
+ """
394
+ async with Client(mcp) as client:
395
+ # Get all the MCP protocol objects
396
+ tools_result = await client.list_tools_mcp()
397
+ prompts_result = await client.list_prompts_mcp()
398
+ resources_result = await client.list_resources_mcp()
399
+ templates_result = await client.list_resource_templates_mcp()
400
+
401
+ # Get server info from the initialize result
402
+ server_info = client.initialize_result.serverInfo
403
+
404
+ # Combine into MCP protocol structure with environment metadata
405
+ result = {
406
+ "environment": {
407
+ "fastmcp": fastmcp.__version__, # Version generating this manifest
408
+ "mcp": importlib.metadata.version("mcp"), # MCP protocol version
409
+ },
410
+ "serverInfo": server_info,
411
+ "capabilities": {}, # MCP format doesn't include capabilities at top level
412
+ "tools": tools_result.tools,
413
+ "prompts": prompts_result.prompts,
414
+ "resources": resources_result.resources,
415
+ "resourceTemplates": templates_result.resourceTemplates,
416
+ }
417
+
418
+ return pydantic_core.to_json(result, indent=2)
419
+
420
+
421
+ async def format_info(
422
+ mcp: FastMCP[Any] | FastMCP1x,
423
+ format: InspectFormat | Literal["fastmcp", "mcp"],
424
+ info: FastMCPInfo | None = None,
425
+ ) -> bytes:
426
+ """Format server information according to the specified format.
427
+
428
+ Args:
429
+ mcp: The FastMCP instance
430
+ format: Output format ("fastmcp" or "mcp")
431
+ info: Pre-extracted FastMCPInfo (optional, will be extracted if not provided)
432
+
433
+ Returns:
434
+ JSON bytes in the requested format
435
+ """
436
+ # Convert string to enum if needed
437
+ if isinstance(format, str):
438
+ format = InspectFormat(format)
439
+
440
+ if format == InspectFormat.MCP:
441
+ # MCP format doesn't need FastMCPInfo, it uses Client directly
442
+ return await format_mcp_info(mcp)
443
+ elif format == InspectFormat.FASTMCP:
444
+ # For FastMCP format, we need the FastMCPInfo
445
+ # This works for both v1 and v2 servers
446
+ if info is None:
447
+ info = await inspect_fastmcp(mcp)
448
+ return await format_fastmcp_info(info)
449
+ else:
450
+ raise ValueError(f"Unknown format: {format}")
@@ -449,7 +449,8 @@ def _create_pydantic_model(
449
449
  ) -> type:
450
450
  """Create Pydantic BaseModel from object schema with additionalProperties."""
451
451
  name = name or schema.get("title", "Root")
452
- assert name is not None # Should not be None after the or operation
452
+ if name is None:
453
+ raise ValueError("Name is required")
453
454
  sanitized_name = _sanitize_name(name)
454
455
  schema_hash = _hash_schema(schema)
455
456
  cache_key = (schema_hash, sanitized_name)
@@ -507,7 +508,8 @@ def _create_dataclass(
507
508
  """Create dataclass from object schema."""
508
509
  name = name or schema.get("title", "Root")
509
510
  # Sanitize name for class creation
510
- assert name is not None # Should not be None after the or operation
511
+ if name is None:
512
+ raise ValueError("Name is required")
511
513
  sanitized_name = _sanitize_name(name)
512
514
  schema_hash = _hash_schema(schema)
513
515
  cache_key = (schema_hash, sanitized_name)
@@ -1,7 +1,7 @@
1
1
  """Logging utilities for FastMCP."""
2
2
 
3
3
  import logging
4
- from typing import Literal
4
+ from typing import Any, Literal
5
5
 
6
6
  from rich.console import Console
7
7
  from rich.logging import RichHandler
@@ -23,6 +23,7 @@ def configure_logging(
23
23
  level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | int = "INFO",
24
24
  logger: logging.Logger | None = None,
25
25
  enable_rich_tracebacks: bool = True,
26
+ **rich_kwargs: Any,
26
27
  ) -> None:
27
28
  """
28
29
  Configure logging for FastMCP.
@@ -30,6 +31,7 @@ def configure_logging(
30
31
  Args:
31
32
  logger: the logger to configure
32
33
  level: the log level to use
34
+ rich_kwargs: the parameters to use for creating RichHandler
33
35
  """
34
36
 
35
37
  if logger is None:
@@ -39,6 +41,7 @@ def configure_logging(
39
41
  handler = RichHandler(
40
42
  console=Console(stderr=True),
41
43
  rich_tracebacks=enable_rich_tracebacks,
44
+ **rich_kwargs,
42
45
  )
43
46
  formatter = logging.Formatter("%(message)s")
44
47
  handler.setFormatter(formatter)
@@ -1,28 +1,57 @@
1
1
  from typing import Any
2
2
 
3
- from fastmcp.mcp_config import MCPConfig
3
+ from fastmcp.client.transports import (
4
+ ClientTransport,
5
+ SSETransport,
6
+ StdioTransport,
7
+ StreamableHttpTransport,
8
+ )
9
+ from fastmcp.mcp_config import (
10
+ MCPConfig,
11
+ MCPServerTypes,
12
+ )
13
+ from fastmcp.server.proxy import FastMCPProxy, ProxyClient
4
14
  from fastmcp.server.server import FastMCP
5
15
 
6
16
 
7
- def composite_server_from_mcp_config(
8
- config: MCPConfig, name_as_prefix: bool = True
9
- ) -> FastMCP[None]:
10
- """A utility function to create a composite server from an MCPConfig."""
11
- composite_server = FastMCP[None]()
17
+ def mcp_config_to_servers_and_transports(
18
+ config: MCPConfig,
19
+ ) -> list[tuple[str, FastMCP[Any], ClientTransport]]:
20
+ """A utility function to convert each entry of an MCP Config into a transport and server."""
21
+ return [
22
+ mcp_server_type_to_servers_and_transports(name, mcp_server)
23
+ for name, mcp_server in config.mcpServers.items()
24
+ ]
12
25
 
13
- mount_mcp_config_into_server(config, composite_server, name_as_prefix)
14
26
 
15
- return composite_server
27
+ def mcp_server_type_to_servers_and_transports(
28
+ name: str,
29
+ mcp_server: MCPServerTypes,
30
+ ) -> tuple[str, FastMCP[Any], ClientTransport]:
31
+ """A utility function to convert each entry of an MCP Config into a transport and server."""
16
32
 
33
+ from fastmcp.mcp_config import (
34
+ TransformingRemoteMCPServer,
35
+ TransformingStdioMCPServer,
36
+ )
17
37
 
18
- def mount_mcp_config_into_server(
19
- config: MCPConfig,
20
- server: FastMCP[Any],
21
- name_as_prefix: bool = True,
22
- ) -> None:
23
- """A utility function to mount the servers from an MCPConfig into a FastMCP server."""
24
- for name, mcp_server in config.mcpServers.items():
25
- server.mount(
26
- prefix=name if name_as_prefix else None,
27
- server=FastMCP.as_proxy(backend=mcp_server.to_transport()),
38
+ server: FastMCP[Any]
39
+ transport: ClientTransport
40
+
41
+ client_name = ProxyClient.generate_name(f"MCP_{name}")
42
+ server_name = FastMCPProxy.generate_name(f"MCP_{name}")
43
+
44
+ if isinstance(mcp_server, TransformingRemoteMCPServer | TransformingStdioMCPServer):
45
+ server, transport = mcp_server._to_server_and_underlying_transport(
46
+ server_name=server_name,
47
+ client_name=client_name,
28
48
  )
49
+ else:
50
+ transport = mcp_server.to_transport()
51
+ client: ProxyClient[StreamableHttpTransport | SSETransport | StdioTransport] = (
52
+ ProxyClient(transport=transport, name=client_name)
53
+ )
54
+
55
+ server = FastMCP.as_proxy(name=server_name, backend=client)
56
+
57
+ return name, server, transport
@@ -0,0 +1,25 @@
1
+ """FastMCP Configuration module.
2
+
3
+ This module provides versioned configuration support for FastMCP servers.
4
+ The current version is v1, which is re-exported here for convenience.
5
+ """
6
+
7
+ from fastmcp.utilities.mcp_server_config.v1.environments.base import Environment
8
+ from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
9
+ from fastmcp.utilities.mcp_server_config.v1.mcp_server_config import (
10
+ Deployment,
11
+ MCPServerConfig,
12
+ generate_schema,
13
+ )
14
+ from fastmcp.utilities.mcp_server_config.v1.sources.base import Source
15
+ from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource
16
+
17
+ __all__ = [
18
+ "Source",
19
+ "Deployment",
20
+ "Environment",
21
+ "UVEnvironment",
22
+ "MCPServerConfig",
23
+ "FileSystemSource",
24
+ "generate_schema",
25
+ ]
File without changes
@@ -0,0 +1,6 @@
1
+ """Environment configuration for MCP servers."""
2
+
3
+ from fastmcp.utilities.mcp_server_config.v1.environments.base import Environment
4
+ from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
5
+
6
+ __all__ = ["Environment", "UVEnvironment"]
@@ -0,0 +1,30 @@
1
+ from abc import ABC, abstractmethod
2
+ from pathlib import Path
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class Environment(BaseModel, ABC):
8
+ """Base class for environment configuration."""
9
+
10
+ type: str = Field(description="Environment type identifier")
11
+
12
+ @abstractmethod
13
+ def build_command(self, command: list[str]) -> list[str]:
14
+ """Build the full command with environment setup.
15
+
16
+ Args:
17
+ command: Base command to wrap with environment setup
18
+
19
+ Returns:
20
+ Full command ready for subprocess execution
21
+ """
22
+ pass
23
+
24
+ async def prepare(self, output_dir: Path | None = None) -> None:
25
+ """Prepare the environment (optional, can be no-op).
26
+
27
+ Args:
28
+ output_dir: Directory for persistent environment setup
29
+ """
30
+ pass # Default no-op implementation