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.
- fastmcp/__init__.py +5 -4
- fastmcp/cli/claude.py +22 -18
- fastmcp/cli/cli.py +472 -136
- fastmcp/cli/install/claude_code.py +37 -40
- fastmcp/cli/install/claude_desktop.py +37 -42
- fastmcp/cli/install/cursor.py +148 -38
- fastmcp/cli/install/mcp_json.py +38 -43
- fastmcp/cli/install/shared.py +64 -7
- fastmcp/cli/run.py +122 -215
- fastmcp/client/auth/oauth.py +69 -13
- fastmcp/client/client.py +46 -9
- fastmcp/client/logging.py +25 -1
- fastmcp/client/oauth_callback.py +91 -91
- fastmcp/client/sampling.py +12 -4
- fastmcp/client/transports.py +143 -67
- fastmcp/experimental/sampling/__init__.py +0 -0
- fastmcp/experimental/sampling/handlers/__init__.py +3 -0
- fastmcp/experimental/sampling/handlers/base.py +21 -0
- fastmcp/experimental/sampling/handlers/openai.py +163 -0
- fastmcp/experimental/server/openapi/routing.py +1 -3
- fastmcp/experimental/server/openapi/server.py +10 -25
- fastmcp/experimental/utilities/openapi/__init__.py +2 -2
- fastmcp/experimental/utilities/openapi/formatters.py +34 -0
- fastmcp/experimental/utilities/openapi/models.py +5 -2
- fastmcp/experimental/utilities/openapi/parser.py +252 -70
- fastmcp/experimental/utilities/openapi/schemas.py +135 -106
- fastmcp/mcp_config.py +40 -20
- fastmcp/prompts/prompt_manager.py +4 -2
- fastmcp/resources/resource_manager.py +16 -6
- fastmcp/server/auth/__init__.py +11 -1
- fastmcp/server/auth/auth.py +19 -2
- fastmcp/server/auth/oauth_proxy.py +1047 -0
- fastmcp/server/auth/providers/azure.py +270 -0
- fastmcp/server/auth/providers/github.py +287 -0
- fastmcp/server/auth/providers/google.py +305 -0
- fastmcp/server/auth/providers/jwt.py +27 -16
- fastmcp/server/auth/providers/workos.py +256 -2
- fastmcp/server/auth/redirect_validation.py +65 -0
- fastmcp/server/auth/registry.py +1 -1
- fastmcp/server/context.py +91 -41
- fastmcp/server/dependencies.py +32 -2
- fastmcp/server/elicitation.py +60 -1
- fastmcp/server/http.py +44 -37
- fastmcp/server/middleware/logging.py +66 -28
- fastmcp/server/proxy.py +2 -0
- fastmcp/server/sampling/handler.py +19 -0
- fastmcp/server/server.py +85 -20
- fastmcp/settings.py +18 -3
- fastmcp/tools/tool.py +23 -10
- fastmcp/tools/tool_manager.py +5 -1
- fastmcp/tools/tool_transform.py +75 -32
- fastmcp/utilities/auth.py +34 -0
- fastmcp/utilities/cli.py +148 -15
- fastmcp/utilities/components.py +21 -5
- fastmcp/utilities/inspect.py +166 -37
- fastmcp/utilities/json_schema_type.py +4 -2
- fastmcp/utilities/logging.py +4 -1
- fastmcp/utilities/mcp_config.py +47 -18
- fastmcp/utilities/mcp_server_config/__init__.py +25 -0
- fastmcp/utilities/mcp_server_config/v1/__init__.py +0 -0
- fastmcp/utilities/mcp_server_config/v1/environments/__init__.py +6 -0
- fastmcp/utilities/mcp_server_config/v1/environments/base.py +30 -0
- fastmcp/utilities/mcp_server_config/v1/environments/uv.py +306 -0
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +446 -0
- fastmcp/utilities/mcp_server_config/v1/schema.json +361 -0
- fastmcp/utilities/mcp_server_config/v1/sources/__init__.py +0 -0
- fastmcp/utilities/mcp_server_config/v1/sources/base.py +30 -0
- fastmcp/utilities/mcp_server_config/v1/sources/filesystem.py +216 -0
- fastmcp/utilities/openapi.py +4 -4
- fastmcp/utilities/tests.py +7 -2
- fastmcp/utilities/types.py +15 -2
- {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/METADATA +3 -2
- fastmcp-2.12.0.dist-info/RECORD +129 -0
- fastmcp-2.11.2.dist-info/RECORD +0 -108
- {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/WHEEL +0 -0
- {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.11.2.dist-info → fastmcp-2.12.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/utilities/inspect.py
CHANGED
|
@@ -4,8 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import importlib.metadata
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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,
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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,
|
|
265
|
+
key=mcp_prompt.name,
|
|
244
266
|
name=mcp_prompt.name,
|
|
245
267
|
description=mcp_prompt.description,
|
|
246
268
|
arguments=arguments,
|
|
247
|
-
tags=None, #
|
|
248
|
-
enabled=None, #
|
|
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),
|
|
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
|
-
|
|
263
|
-
|
|
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
|
-
|
|
280
|
-
|
|
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=
|
|
324
|
+
fastmcp_version=fastmcp.__version__, # Version generating this manifest
|
|
296
325
|
mcp_version=importlib.metadata.version("mcp"),
|
|
297
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
fastmcp/utilities/logging.py
CHANGED
|
@@ -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)
|
fastmcp/utilities/mcp_config.py
CHANGED
|
@@ -1,28 +1,57 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
from fastmcp.
|
|
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
|
|
8
|
-
config: MCPConfig,
|
|
9
|
-
) -> FastMCP[
|
|
10
|
-
"""A utility function to
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
server.
|
|
26
|
-
|
|
27
|
-
|
|
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,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
|