fastmcp 2.10.3__py3-none-any.whl → 2.10.5__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 +1 -1
- fastmcp/cli/install/claude_code.py +35 -6
- fastmcp/cli/run.py +1 -1
- fastmcp/server/middleware/__init__.py +0 -8
- fastmcp/server/middleware/middleware.py +8 -34
- fastmcp/server/openapi.py +125 -51
- fastmcp/server/proxy.py +9 -4
- fastmcp/server/server.py +24 -5
- fastmcp/utilities/cli.py +6 -6
- fastmcp/utilities/components.py +43 -0
- fastmcp/utilities/openapi.py +106 -11
- {fastmcp-2.10.3.dist-info → fastmcp-2.10.5.dist-info}/METADATA +1 -1
- {fastmcp-2.10.3.dist-info → fastmcp-2.10.5.dist-info}/RECORD +16 -16
- {fastmcp-2.10.3.dist-info → fastmcp-2.10.5.dist-info}/WHEEL +0 -0
- {fastmcp-2.10.3.dist-info → fastmcp-2.10.5.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.10.3.dist-info → fastmcp-2.10.5.dist-info}/licenses/LICENSE +0 -0
fastmcp/cli/cli.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Claude Code integration for FastMCP install using Cyclopts."""
|
|
2
2
|
|
|
3
|
+
import shutil
|
|
3
4
|
import subprocess
|
|
4
5
|
import sys
|
|
5
6
|
from pathlib import Path
|
|
@@ -16,22 +17,50 @@ logger = get_logger(__name__)
|
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def find_claude_command() -> str | None:
|
|
19
|
-
"""Find the Claude Code CLI command.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
"""Find the Claude Code CLI command.
|
|
21
|
+
|
|
22
|
+
Checks common installation locations since 'claude' is often a shell alias
|
|
23
|
+
that doesn't work with subprocess calls.
|
|
24
|
+
"""
|
|
25
|
+
# First try shutil.which() in case it's a real executable in PATH
|
|
26
|
+
claude_in_path = shutil.which("claude")
|
|
27
|
+
if claude_in_path:
|
|
23
28
|
try:
|
|
24
29
|
result = subprocess.run(
|
|
25
|
-
[
|
|
30
|
+
[claude_in_path, "--version"],
|
|
26
31
|
check=True,
|
|
27
32
|
capture_output=True,
|
|
28
33
|
text=True,
|
|
29
34
|
)
|
|
30
35
|
if "Claude Code" in result.stdout:
|
|
31
|
-
return
|
|
36
|
+
return claude_in_path
|
|
32
37
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
33
38
|
pass
|
|
34
39
|
|
|
40
|
+
# Check common installation locations (aliases don't work with subprocess)
|
|
41
|
+
potential_paths = [
|
|
42
|
+
# Default Claude Code installation location (after migration)
|
|
43
|
+
Path.home() / ".claude" / "local" / "claude",
|
|
44
|
+
# npm global installation on macOS/Linux (default)
|
|
45
|
+
Path("/usr/local/bin/claude"),
|
|
46
|
+
# npm global installation with custom prefix
|
|
47
|
+
Path.home() / ".npm-global" / "bin" / "claude",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
for path in potential_paths:
|
|
51
|
+
if path.exists():
|
|
52
|
+
try:
|
|
53
|
+
result = subprocess.run(
|
|
54
|
+
[str(path), "--version"],
|
|
55
|
+
check=True,
|
|
56
|
+
capture_output=True,
|
|
57
|
+
text=True,
|
|
58
|
+
)
|
|
59
|
+
if "Claude Code" in result.stdout:
|
|
60
|
+
return str(path)
|
|
61
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
62
|
+
continue
|
|
63
|
+
|
|
35
64
|
return None
|
|
36
65
|
|
|
37
66
|
|
fastmcp/cli/run.py
CHANGED
|
@@ -11,7 +11,7 @@ from fastmcp.utilities.logging import get_logger
|
|
|
11
11
|
logger = get_logger("cli.run")
|
|
12
12
|
|
|
13
13
|
# Type aliases for better type safety
|
|
14
|
-
TransportType = Literal["stdio", "http", "sse"]
|
|
14
|
+
TransportType = Literal["stdio", "http", "sse", "streamable-http"]
|
|
15
15
|
LogLevelType = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
16
16
|
|
|
17
17
|
|
|
@@ -2,18 +2,10 @@ from .middleware import (
|
|
|
2
2
|
Middleware,
|
|
3
3
|
MiddlewareContext,
|
|
4
4
|
CallNext,
|
|
5
|
-
ListToolsResult,
|
|
6
|
-
ListResourcesResult,
|
|
7
|
-
ListResourceTemplatesResult,
|
|
8
|
-
ListPromptsResult,
|
|
9
5
|
)
|
|
10
6
|
|
|
11
7
|
__all__ = [
|
|
12
8
|
"Middleware",
|
|
13
9
|
"MiddlewareContext",
|
|
14
10
|
"CallNext",
|
|
15
|
-
"ListToolsResult",
|
|
16
|
-
"ListResourcesResult",
|
|
17
|
-
"ListResourceTemplatesResult",
|
|
18
|
-
"ListPromptsResult",
|
|
19
11
|
]
|
|
@@ -29,10 +29,6 @@ __all__ = [
|
|
|
29
29
|
"Middleware",
|
|
30
30
|
"MiddlewareContext",
|
|
31
31
|
"CallNext",
|
|
32
|
-
"ListToolsResult",
|
|
33
|
-
"ListResourcesResult",
|
|
34
|
-
"ListResourceTemplatesResult",
|
|
35
|
-
"ListPromptsResult",
|
|
36
32
|
]
|
|
37
33
|
|
|
38
34
|
logger = logging.getLogger(__name__)
|
|
@@ -62,26 +58,6 @@ ServerResultT = TypeVar(
|
|
|
62
58
|
)
|
|
63
59
|
|
|
64
60
|
|
|
65
|
-
@dataclass(kw_only=True)
|
|
66
|
-
class ListToolsResult:
|
|
67
|
-
tools: dict[str, Tool]
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@dataclass(kw_only=True)
|
|
71
|
-
class ListResourcesResult:
|
|
72
|
-
resources: list[Resource]
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@dataclass(kw_only=True)
|
|
76
|
-
class ListResourceTemplatesResult:
|
|
77
|
-
resource_templates: list[ResourceTemplate]
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@dataclass(kw_only=True)
|
|
81
|
-
class ListPromptsResult:
|
|
82
|
-
prompts: list[Prompt]
|
|
83
|
-
|
|
84
|
-
|
|
85
61
|
@runtime_checkable
|
|
86
62
|
class ServerResultProtocol(Protocol[ServerResultT]):
|
|
87
63
|
root: ServerResultT
|
|
@@ -212,29 +188,27 @@ class Middleware:
|
|
|
212
188
|
async def on_list_tools(
|
|
213
189
|
self,
|
|
214
190
|
context: MiddlewareContext[mt.ListToolsRequest],
|
|
215
|
-
call_next: CallNext[mt.ListToolsRequest,
|
|
216
|
-
) ->
|
|
191
|
+
call_next: CallNext[mt.ListToolsRequest, list[Tool]],
|
|
192
|
+
) -> list[Tool]:
|
|
217
193
|
return await call_next(context)
|
|
218
194
|
|
|
219
195
|
async def on_list_resources(
|
|
220
196
|
self,
|
|
221
197
|
context: MiddlewareContext[mt.ListResourcesRequest],
|
|
222
|
-
call_next: CallNext[mt.ListResourcesRequest,
|
|
223
|
-
) ->
|
|
198
|
+
call_next: CallNext[mt.ListResourcesRequest, list[Resource]],
|
|
199
|
+
) -> list[Resource]:
|
|
224
200
|
return await call_next(context)
|
|
225
201
|
|
|
226
202
|
async def on_list_resource_templates(
|
|
227
203
|
self,
|
|
228
204
|
context: MiddlewareContext[mt.ListResourceTemplatesRequest],
|
|
229
|
-
call_next: CallNext[
|
|
230
|
-
|
|
231
|
-
],
|
|
232
|
-
) -> ListResourceTemplatesResult:
|
|
205
|
+
call_next: CallNext[mt.ListResourceTemplatesRequest, list[ResourceTemplate]],
|
|
206
|
+
) -> list[ResourceTemplate]:
|
|
233
207
|
return await call_next(context)
|
|
234
208
|
|
|
235
209
|
async def on_list_prompts(
|
|
236
210
|
self,
|
|
237
211
|
context: MiddlewareContext[mt.ListPromptsRequest],
|
|
238
|
-
call_next: CallNext[mt.ListPromptsRequest,
|
|
239
|
-
) ->
|
|
212
|
+
call_next: CallNext[mt.ListPromptsRequest, list[Prompt]],
|
|
213
|
+
) -> list[Prompt]:
|
|
240
214
|
return await call_next(context)
|
fastmcp/server/openapi.py
CHANGED
|
@@ -29,6 +29,7 @@ from fastmcp.utilities.openapi import (
|
|
|
29
29
|
_combine_schemas,
|
|
30
30
|
extract_output_schema_from_responses,
|
|
31
31
|
format_array_parameter,
|
|
32
|
+
format_deep_object_parameter,
|
|
32
33
|
format_description_with_responses,
|
|
33
34
|
)
|
|
34
35
|
|
|
@@ -261,19 +262,45 @@ class OpenAPITool(Tool):
|
|
|
261
262
|
async def run(self, arguments: dict[str, Any]) -> ToolResult:
|
|
262
263
|
"""Execute the HTTP request based on the route configuration."""
|
|
263
264
|
|
|
265
|
+
# Create mapping from suffixed parameter names back to original names and locations
|
|
266
|
+
# This handles parameter collisions where suffixes were added during schema generation
|
|
267
|
+
param_mapping = {} # suffixed_name -> (original_name, location)
|
|
268
|
+
|
|
269
|
+
# First, check if we have request body properties to detect collisions
|
|
270
|
+
body_props = set()
|
|
271
|
+
if self._route.request_body and self._route.request_body.content_schema:
|
|
272
|
+
content_type = next(iter(self._route.request_body.content_schema))
|
|
273
|
+
body_schema = self._route.request_body.content_schema[content_type]
|
|
274
|
+
body_props = set(body_schema.get("properties", {}).keys())
|
|
275
|
+
|
|
276
|
+
# Build parameter mapping for potentially suffixed parameters
|
|
277
|
+
for param in self._route.parameters:
|
|
278
|
+
original_name = param.name
|
|
279
|
+
suffixed_name = f"{param.name}__{param.location}"
|
|
280
|
+
|
|
281
|
+
# If parameter name collides with body property, it would have been suffixed
|
|
282
|
+
if param.name in body_props:
|
|
283
|
+
param_mapping[suffixed_name] = (original_name, param.location)
|
|
284
|
+
# Also map original name for backward compatibility when no collision
|
|
285
|
+
param_mapping[original_name] = (original_name, param.location)
|
|
286
|
+
|
|
264
287
|
# Prepare URL
|
|
265
288
|
path = self._route.path
|
|
266
289
|
|
|
267
|
-
# Replace path parameters with values from
|
|
268
|
-
#
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
p.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
290
|
+
# Replace path parameters with values from arguments
|
|
291
|
+
# Look for both original and suffixed parameter names
|
|
292
|
+
path_params = {}
|
|
293
|
+
for p in self._route.parameters:
|
|
294
|
+
if p.location == "path":
|
|
295
|
+
# Try suffixed name first, then original name
|
|
296
|
+
suffixed_name = f"{p.name}__{p.location}"
|
|
297
|
+
if (
|
|
298
|
+
suffixed_name in arguments
|
|
299
|
+
and arguments.get(suffixed_name) is not None
|
|
300
|
+
):
|
|
301
|
+
path_params[p.name] = arguments[suffixed_name]
|
|
302
|
+
elif p.name in arguments and arguments.get(p.name) is not None:
|
|
303
|
+
path_params[p.name] = arguments[p.name]
|
|
277
304
|
|
|
278
305
|
# Ensure all path parameters are provided
|
|
279
306
|
required_path_params = {
|
|
@@ -312,35 +339,67 @@ class OpenAPITool(Tool):
|
|
|
312
339
|
# Prepare query parameters - filter out None and empty strings
|
|
313
340
|
query_params = {}
|
|
314
341
|
for p in self._route.parameters:
|
|
315
|
-
if
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
342
|
+
if p.location == "query":
|
|
343
|
+
# Try suffixed name first, then original name
|
|
344
|
+
suffixed_name = f"{p.name}__{p.location}"
|
|
345
|
+
param_value = None
|
|
346
|
+
|
|
347
|
+
if (
|
|
348
|
+
suffixed_name in arguments
|
|
349
|
+
and arguments.get(suffixed_name) is not None
|
|
350
|
+
and arguments.get(suffixed_name) != ""
|
|
351
|
+
):
|
|
352
|
+
param_value = arguments[suffixed_name]
|
|
353
|
+
elif (
|
|
354
|
+
p.name in arguments
|
|
355
|
+
and arguments.get(p.name) is not None
|
|
356
|
+
and arguments.get(p.name) != ""
|
|
357
|
+
):
|
|
358
|
+
param_value = arguments[p.name]
|
|
359
|
+
|
|
360
|
+
if param_value is not None:
|
|
361
|
+
# Handle different parameter styles and types
|
|
362
|
+
param_style = (
|
|
363
|
+
p.style or "form"
|
|
364
|
+
) # Default style for query parameters is "form"
|
|
365
|
+
param_explode = (
|
|
366
|
+
p.explode if p.explode is not None else True
|
|
367
|
+
) # Default explode for query is True
|
|
368
|
+
|
|
369
|
+
# Handle deepObject style for object parameters
|
|
370
|
+
if param_style == "deepObject" and isinstance(param_value, dict):
|
|
371
|
+
if param_explode:
|
|
372
|
+
# deepObject with explode=true: object properties become separate parameters
|
|
373
|
+
# e.g., target[id]=123&target[type]=user
|
|
374
|
+
deep_obj_params = format_deep_object_parameter(
|
|
375
|
+
param_value, p.name
|
|
376
|
+
)
|
|
377
|
+
query_params.update(deep_obj_params)
|
|
378
|
+
else:
|
|
379
|
+
# deepObject with explode=false is not commonly used, fallback to JSON
|
|
380
|
+
logger.warning(
|
|
381
|
+
f"deepObject style with explode=false for parameter '{p.name}' is not standard. "
|
|
382
|
+
f"Using JSON serialization fallback."
|
|
383
|
+
)
|
|
384
|
+
query_params[p.name] = json.dumps(param_value)
|
|
385
|
+
# Handle array parameters with form style (default)
|
|
386
|
+
elif (
|
|
387
|
+
isinstance(param_value, list)
|
|
388
|
+
and p.schema_.get("type") == "array"
|
|
389
|
+
):
|
|
390
|
+
if param_explode:
|
|
391
|
+
# When explode=True, we pass the array directly, which HTTPX will serialize
|
|
392
|
+
# as multiple parameters with the same name
|
|
393
|
+
query_params[p.name] = param_value
|
|
394
|
+
else:
|
|
395
|
+
# Format array as comma-separated string when explode=False
|
|
396
|
+
formatted_value = format_array_parameter(
|
|
397
|
+
param_value, p.name, is_query_parameter=True
|
|
398
|
+
)
|
|
399
|
+
query_params[p.name] = formatted_value
|
|
335
400
|
else:
|
|
336
|
-
#
|
|
337
|
-
|
|
338
|
-
param_value, p.name, is_query_parameter=True
|
|
339
|
-
)
|
|
340
|
-
query_params[p.name] = formatted_value
|
|
341
|
-
else:
|
|
342
|
-
# Non-array parameters are passed as is
|
|
343
|
-
query_params[p.name] = param_value
|
|
401
|
+
# Non-array, non-deepObject parameters are passed as is
|
|
402
|
+
query_params[p.name] = param_value
|
|
344
403
|
|
|
345
404
|
# Prepare headers - fix typing by ensuring all values are strings
|
|
346
405
|
headers = {}
|
|
@@ -348,12 +407,21 @@ class OpenAPITool(Tool):
|
|
|
348
407
|
# Start with OpenAPI-defined header parameters
|
|
349
408
|
openapi_headers = {}
|
|
350
409
|
for p in self._route.parameters:
|
|
351
|
-
if
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
410
|
+
if p.location == "header":
|
|
411
|
+
# Try suffixed name first, then original name
|
|
412
|
+
suffixed_name = f"{p.name}__{p.location}"
|
|
413
|
+
param_value = None
|
|
414
|
+
|
|
415
|
+
if (
|
|
416
|
+
suffixed_name in arguments
|
|
417
|
+
and arguments.get(suffixed_name) is not None
|
|
418
|
+
):
|
|
419
|
+
param_value = arguments[suffixed_name]
|
|
420
|
+
elif p.name in arguments and arguments.get(p.name) is not None:
|
|
421
|
+
param_value = arguments[p.name]
|
|
422
|
+
|
|
423
|
+
if param_value is not None:
|
|
424
|
+
openapi_headers[p.name.lower()] = str(param_value)
|
|
357
425
|
headers.update(openapi_headers)
|
|
358
426
|
|
|
359
427
|
# Add headers from the current MCP client HTTP request (these take precedence)
|
|
@@ -363,16 +431,22 @@ class OpenAPITool(Tool):
|
|
|
363
431
|
# Prepare request body
|
|
364
432
|
json_data = None
|
|
365
433
|
if self._route.request_body and self._route.request_body.content_schema:
|
|
366
|
-
# Extract body parameters
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
434
|
+
# Extract body parameters with collision-aware logic
|
|
435
|
+
# Exclude all parameter names that belong to path/query/header locations
|
|
436
|
+
params_to_exclude = set()
|
|
437
|
+
|
|
438
|
+
for p in self._route.parameters:
|
|
439
|
+
if (
|
|
440
|
+
p.name in body_props
|
|
441
|
+
): # This parameter had a collision, so it was suffixed
|
|
442
|
+
params_to_exclude.add(f"{p.name}__{p.location}")
|
|
443
|
+
else: # No collision, parameter keeps original name but should still be excluded from body
|
|
444
|
+
params_to_exclude.add(p.name)
|
|
445
|
+
|
|
372
446
|
body_params = {
|
|
373
447
|
k: v
|
|
374
448
|
for k, v in arguments.items()
|
|
375
|
-
if k not in
|
|
449
|
+
if k not in params_to_exclude and k != "context"
|
|
376
450
|
}
|
|
377
451
|
|
|
378
452
|
if body_params:
|
fastmcp/server/proxy.py
CHANGED
|
@@ -36,6 +36,7 @@ from fastmcp.server.dependencies import get_context
|
|
|
36
36
|
from fastmcp.server.server import FastMCP
|
|
37
37
|
from fastmcp.tools.tool import Tool, ToolResult
|
|
38
38
|
from fastmcp.tools.tool_manager import ToolManager
|
|
39
|
+
from fastmcp.utilities.components import MirroredComponent
|
|
39
40
|
from fastmcp.utilities.logging import get_logger
|
|
40
41
|
|
|
41
42
|
if TYPE_CHECKING:
|
|
@@ -226,7 +227,7 @@ class ProxyPromptManager(PromptManager):
|
|
|
226
227
|
return result
|
|
227
228
|
|
|
228
229
|
|
|
229
|
-
class ProxyTool(Tool):
|
|
230
|
+
class ProxyTool(Tool, MirroredComponent):
|
|
230
231
|
"""
|
|
231
232
|
A Tool that represents and executes a tool on a remote server.
|
|
232
233
|
"""
|
|
@@ -245,6 +246,7 @@ class ProxyTool(Tool):
|
|
|
245
246
|
parameters=mcp_tool.inputSchema,
|
|
246
247
|
annotations=mcp_tool.annotations,
|
|
247
248
|
output_schema=mcp_tool.outputSchema,
|
|
249
|
+
_mirrored=True,
|
|
248
250
|
)
|
|
249
251
|
|
|
250
252
|
async def run(
|
|
@@ -266,7 +268,7 @@ class ProxyTool(Tool):
|
|
|
266
268
|
)
|
|
267
269
|
|
|
268
270
|
|
|
269
|
-
class ProxyResource(Resource):
|
|
271
|
+
class ProxyResource(Resource, MirroredComponent):
|
|
270
272
|
"""
|
|
271
273
|
A Resource that represents and reads a resource from a remote server.
|
|
272
274
|
"""
|
|
@@ -298,6 +300,7 @@ class ProxyResource(Resource):
|
|
|
298
300
|
name=mcp_resource.name,
|
|
299
301
|
description=mcp_resource.description,
|
|
300
302
|
mime_type=mcp_resource.mimeType or "text/plain",
|
|
303
|
+
_mirrored=True,
|
|
301
304
|
)
|
|
302
305
|
|
|
303
306
|
async def read(self) -> str | bytes:
|
|
@@ -315,7 +318,7 @@ class ProxyResource(Resource):
|
|
|
315
318
|
raise ResourceError(f"Unsupported content type: {type(result[0])}")
|
|
316
319
|
|
|
317
320
|
|
|
318
|
-
class ProxyTemplate(ResourceTemplate):
|
|
321
|
+
class ProxyTemplate(ResourceTemplate, MirroredComponent):
|
|
319
322
|
"""
|
|
320
323
|
A ResourceTemplate that represents and creates resources from a remote server template.
|
|
321
324
|
"""
|
|
@@ -336,6 +339,7 @@ class ProxyTemplate(ResourceTemplate):
|
|
|
336
339
|
description=mcp_template.description,
|
|
337
340
|
mime_type=mcp_template.mimeType or "text/plain",
|
|
338
341
|
parameters={}, # Remote templates don't have local parameters
|
|
342
|
+
_mirrored=True,
|
|
339
343
|
)
|
|
340
344
|
|
|
341
345
|
async def create_resource(
|
|
@@ -371,7 +375,7 @@ class ProxyTemplate(ResourceTemplate):
|
|
|
371
375
|
)
|
|
372
376
|
|
|
373
377
|
|
|
374
|
-
class ProxyPrompt(Prompt):
|
|
378
|
+
class ProxyPrompt(Prompt, MirroredComponent):
|
|
375
379
|
"""
|
|
376
380
|
A Prompt that represents and renders a prompt from a remote server.
|
|
377
381
|
"""
|
|
@@ -400,6 +404,7 @@ class ProxyPrompt(Prompt):
|
|
|
400
404
|
name=mcp_prompt.name,
|
|
401
405
|
description=mcp_prompt.description,
|
|
402
406
|
arguments=arguments,
|
|
407
|
+
_mirrored=True,
|
|
403
408
|
)
|
|
404
409
|
|
|
405
410
|
async def render(self, arguments: dict[str, Any]) -> list[PromptMessage]:
|
fastmcp/server/server.py
CHANGED
|
@@ -759,7 +759,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
759
759
|
)
|
|
760
760
|
return await self._apply_middleware(mw_context, _handler)
|
|
761
761
|
|
|
762
|
-
def add_tool(self, tool: Tool) ->
|
|
762
|
+
def add_tool(self, tool: Tool) -> Tool:
|
|
763
763
|
"""Add a tool to the server.
|
|
764
764
|
|
|
765
765
|
The tool function can optionally request a Context object by adding a parameter
|
|
@@ -767,6 +767,9 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
767
767
|
|
|
768
768
|
Args:
|
|
769
769
|
tool: The Tool instance to register
|
|
770
|
+
|
|
771
|
+
Returns:
|
|
772
|
+
The tool instance that was added to the server.
|
|
770
773
|
"""
|
|
771
774
|
self._tool_manager.add_tool(tool)
|
|
772
775
|
self._cache.clear()
|
|
@@ -780,6 +783,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
780
783
|
except RuntimeError:
|
|
781
784
|
pass # No context available
|
|
782
785
|
|
|
786
|
+
return tool
|
|
787
|
+
|
|
783
788
|
def remove_tool(self, name: str) -> None:
|
|
784
789
|
"""Remove a tool from the server.
|
|
785
790
|
|
|
@@ -958,13 +963,15 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
958
963
|
enabled=enabled,
|
|
959
964
|
)
|
|
960
965
|
|
|
961
|
-
def add_resource(self, resource: Resource) ->
|
|
966
|
+
def add_resource(self, resource: Resource) -> Resource:
|
|
962
967
|
"""Add a resource to the server.
|
|
963
968
|
|
|
964
969
|
Args:
|
|
965
970
|
resource: A Resource instance to add
|
|
966
|
-
"""
|
|
967
971
|
|
|
972
|
+
Returns:
|
|
973
|
+
The resource instance that was added to the server.
|
|
974
|
+
"""
|
|
968
975
|
self._resource_manager.add_resource(resource)
|
|
969
976
|
self._cache.clear()
|
|
970
977
|
|
|
@@ -977,11 +984,16 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
977
984
|
except RuntimeError:
|
|
978
985
|
pass # No context available
|
|
979
986
|
|
|
980
|
-
|
|
987
|
+
return resource
|
|
988
|
+
|
|
989
|
+
def add_template(self, template: ResourceTemplate) -> ResourceTemplate:
|
|
981
990
|
"""Add a resource template to the server.
|
|
982
991
|
|
|
983
992
|
Args:
|
|
984
993
|
template: A ResourceTemplate instance to add
|
|
994
|
+
|
|
995
|
+
Returns:
|
|
996
|
+
The template instance that was added to the server.
|
|
985
997
|
"""
|
|
986
998
|
self._resource_manager.add_template(template)
|
|
987
999
|
|
|
@@ -994,6 +1006,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
994
1006
|
except RuntimeError:
|
|
995
1007
|
pass # No context available
|
|
996
1008
|
|
|
1009
|
+
return template
|
|
1010
|
+
|
|
997
1011
|
def add_resource_fn(
|
|
998
1012
|
self,
|
|
999
1013
|
fn: AnyFunction,
|
|
@@ -1159,11 +1173,14 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1159
1173
|
|
|
1160
1174
|
return decorator
|
|
1161
1175
|
|
|
1162
|
-
def add_prompt(self, prompt: Prompt) ->
|
|
1176
|
+
def add_prompt(self, prompt: Prompt) -> Prompt:
|
|
1163
1177
|
"""Add a prompt to the server.
|
|
1164
1178
|
|
|
1165
1179
|
Args:
|
|
1166
1180
|
prompt: A Prompt instance to add
|
|
1181
|
+
|
|
1182
|
+
Returns:
|
|
1183
|
+
The prompt instance that was added to the server.
|
|
1167
1184
|
"""
|
|
1168
1185
|
self._prompt_manager.add_prompt(prompt)
|
|
1169
1186
|
self._cache.clear()
|
|
@@ -1177,6 +1194,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
1177
1194
|
except RuntimeError:
|
|
1178
1195
|
pass # No context available
|
|
1179
1196
|
|
|
1197
|
+
return prompt
|
|
1198
|
+
|
|
1180
1199
|
@overload
|
|
1181
1200
|
def prompt(
|
|
1182
1201
|
self,
|
fastmcp/utilities/cli.py
CHANGED
|
@@ -14,11 +14,11 @@ if TYPE_CHECKING:
|
|
|
14
14
|
from fastmcp import FastMCP
|
|
15
15
|
|
|
16
16
|
LOGO_ASCII = r"""
|
|
17
|
-
_ __ ___ ______ __ __ _____________
|
|
18
|
-
_ __ ___ / ____/___ ______/ /_/ |/ / ____/ __ \
|
|
19
|
-
_ __ ___ / /_ / __ `/ ___/ __/ /|_/ / / / /_/ /
|
|
20
|
-
_ __ ___ / __/ / /_/ (__ ) /_/ / / / /___/ ____/
|
|
21
|
-
_ __ ___ /_/ \__,_/____/\__/_/ /_/\____/_/
|
|
17
|
+
_ __ ___ ______ __ __ _____________ ____ ____
|
|
18
|
+
_ __ ___ / ____/___ ______/ /_/ |/ / ____/ __ \ |___ \ / __ \
|
|
19
|
+
_ __ ___ / /_ / __ `/ ___/ __/ /|_/ / / / /_/ / ___/ / / / / /
|
|
20
|
+
_ __ ___ / __/ / /_/ (__ ) /_/ / / / /___/ ____/ / __/_/ /_/ /
|
|
21
|
+
_ __ ___ /_/ \__,_/____/\__/_/ /_/\____/_/ /_____(_)____/
|
|
22
22
|
|
|
23
23
|
""".lstrip("\n")
|
|
24
24
|
|
|
@@ -94,7 +94,7 @@ def log_server_banner(
|
|
|
94
94
|
title="FastMCP 2.0",
|
|
95
95
|
title_align="left",
|
|
96
96
|
border_style="dim",
|
|
97
|
-
padding=(
|
|
97
|
+
padding=(1, 4),
|
|
98
98
|
expand=False,
|
|
99
99
|
)
|
|
100
100
|
|
fastmcp/utilities/components.py
CHANGED
|
@@ -77,3 +77,46 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
|
77
77
|
def disable(self) -> None:
|
|
78
78
|
"""Disable the component."""
|
|
79
79
|
self.enabled = False
|
|
80
|
+
|
|
81
|
+
def copy(self) -> Self:
|
|
82
|
+
"""Create a copy of the component."""
|
|
83
|
+
return self.model_copy()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class MirroredComponent(FastMCPComponent):
|
|
87
|
+
"""Base class for components that are mirrored from a remote server.
|
|
88
|
+
|
|
89
|
+
Mirrored components cannot be enabled or disabled directly. Call copy() first
|
|
90
|
+
to create a local version you can modify.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
_mirrored: bool = PrivateAttr(default=False)
|
|
94
|
+
|
|
95
|
+
def __init__(self, *, _mirrored: bool = False, **kwargs: Any) -> None:
|
|
96
|
+
super().__init__(**kwargs)
|
|
97
|
+
self._mirrored = _mirrored
|
|
98
|
+
|
|
99
|
+
def enable(self) -> None:
|
|
100
|
+
"""Enable the component."""
|
|
101
|
+
if self._mirrored:
|
|
102
|
+
raise RuntimeError(
|
|
103
|
+
f"Cannot enable mirrored component '{self.name}'. "
|
|
104
|
+
f"Create a local copy first with {self.name}.copy() and add it to your server."
|
|
105
|
+
)
|
|
106
|
+
super().enable()
|
|
107
|
+
|
|
108
|
+
def disable(self) -> None:
|
|
109
|
+
"""Disable the component."""
|
|
110
|
+
if self._mirrored:
|
|
111
|
+
raise RuntimeError(
|
|
112
|
+
f"Cannot disable mirrored component '{self.name}'. "
|
|
113
|
+
f"Create a local copy first with {self.name}.copy() and add it to your server."
|
|
114
|
+
)
|
|
115
|
+
super().disable()
|
|
116
|
+
|
|
117
|
+
def copy(self) -> Self:
|
|
118
|
+
"""Create a copy of the component that can be modified."""
|
|
119
|
+
# Create a copy and mark it as not mirrored
|
|
120
|
+
copied = self.model_copy()
|
|
121
|
+
copied._mirrored = False
|
|
122
|
+
return copied
|
fastmcp/utilities/openapi.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
-
from typing import Any, Generic, Literal, TypeVar
|
|
3
|
+
from typing import Any, Generic, Literal, TypeVar, cast
|
|
4
4
|
|
|
5
5
|
from openapi_pydantic import (
|
|
6
6
|
OpenAPI,
|
|
@@ -93,6 +93,40 @@ def format_array_parameter(
|
|
|
93
93
|
return str_value
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
def format_deep_object_parameter(
|
|
97
|
+
param_value: dict, parameter_name: str
|
|
98
|
+
) -> dict[str, str]:
|
|
99
|
+
"""
|
|
100
|
+
Format a dictionary parameter for deepObject style serialization.
|
|
101
|
+
|
|
102
|
+
According to OpenAPI 3.0 spec, deepObject style with explode=true serializes
|
|
103
|
+
object properties as separate query parameters with bracket notation.
|
|
104
|
+
|
|
105
|
+
For example: {"id": "123", "type": "user"} becomes:
|
|
106
|
+
param[id]=123¶m[type]=user
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
param_value: Dictionary value to format
|
|
110
|
+
parameter_name: Name of the parameter
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Dictionary with bracketed parameter names as keys
|
|
114
|
+
"""
|
|
115
|
+
if not isinstance(param_value, dict):
|
|
116
|
+
logger.warning(
|
|
117
|
+
f"deepObject style parameter '{parameter_name}' expected dict, got {type(param_value)}"
|
|
118
|
+
)
|
|
119
|
+
return {}
|
|
120
|
+
|
|
121
|
+
result = {}
|
|
122
|
+
for key, value in param_value.items():
|
|
123
|
+
# Format as param[key]=value
|
|
124
|
+
bracketed_key = f"{parameter_name}[{key}]"
|
|
125
|
+
result[bracketed_key] = str(value)
|
|
126
|
+
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
|
|
96
130
|
class ParameterInfo(FastMCPBaseModel):
|
|
97
131
|
"""Represents a single parameter for an HTTP operation in our IR."""
|
|
98
132
|
|
|
@@ -102,6 +136,7 @@ class ParameterInfo(FastMCPBaseModel):
|
|
|
102
136
|
schema_: JsonSchema = Field(..., alias="schema") # Target name in IR
|
|
103
137
|
description: str | None = None
|
|
104
138
|
explode: bool | None = None # OpenAPI explode property for array parameters
|
|
139
|
+
style: str | None = None # OpenAPI style property for parameter serialization
|
|
105
140
|
|
|
106
141
|
|
|
107
142
|
class RequestBodyInfo(FastMCPBaseModel):
|
|
@@ -153,6 +188,7 @@ __all__ = [
|
|
|
153
188
|
"JsonSchema",
|
|
154
189
|
"parse_openapi_to_http_routes",
|
|
155
190
|
"extract_output_schema_from_responses",
|
|
191
|
+
"format_deep_object_parameter",
|
|
156
192
|
]
|
|
157
193
|
|
|
158
194
|
# Type variables for generic parser
|
|
@@ -415,8 +451,9 @@ class OpenAPIParser(
|
|
|
415
451
|
):
|
|
416
452
|
param_schema_dict["default"] = resolved_media_schema.default
|
|
417
453
|
|
|
418
|
-
# Extract explode
|
|
454
|
+
# Extract explode and style properties if present
|
|
419
455
|
explode = getattr(parameter, "explode", None)
|
|
456
|
+
style = getattr(parameter, "style", None)
|
|
420
457
|
|
|
421
458
|
# Create parameter info object
|
|
422
459
|
param_info = ParameterInfo(
|
|
@@ -426,6 +463,7 @@ class OpenAPIParser(
|
|
|
426
463
|
schema=param_schema_dict,
|
|
427
464
|
description=parameter.description,
|
|
428
465
|
explode=explode,
|
|
466
|
+
style=style,
|
|
429
467
|
)
|
|
430
468
|
extracted_params.append(param_info)
|
|
431
469
|
except Exception as e:
|
|
@@ -1060,6 +1098,7 @@ def _replace_ref_with_defs(
|
|
|
1060
1098
|
def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
|
|
1061
1099
|
"""
|
|
1062
1100
|
Combines parameter and request body schemas into a single schema.
|
|
1101
|
+
Handles parameter name collisions by adding location suffixes.
|
|
1063
1102
|
|
|
1064
1103
|
Args:
|
|
1065
1104
|
route: HTTPRoute object
|
|
@@ -1070,17 +1109,19 @@ def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
|
|
|
1070
1109
|
properties = {}
|
|
1071
1110
|
required = []
|
|
1072
1111
|
|
|
1073
|
-
#
|
|
1112
|
+
# First pass: collect parameter names by location and body properties
|
|
1113
|
+
param_names_by_location = {
|
|
1114
|
+
"path": set(),
|
|
1115
|
+
"query": set(),
|
|
1116
|
+
"header": set(),
|
|
1117
|
+
"cookie": set(),
|
|
1118
|
+
}
|
|
1119
|
+
body_props = {}
|
|
1120
|
+
|
|
1074
1121
|
for param in route.parameters:
|
|
1075
|
-
|
|
1076
|
-
required.append(param.name)
|
|
1077
|
-
properties[param.name] = _replace_ref_with_defs(
|
|
1078
|
-
param.schema_.copy(), param.description
|
|
1079
|
-
)
|
|
1122
|
+
param_names_by_location[param.location].add(param.name)
|
|
1080
1123
|
|
|
1081
|
-
# Add request body if it exists
|
|
1082
1124
|
if route.request_body and route.request_body.content_schema:
|
|
1083
|
-
# For now, just use the first content type's schema
|
|
1084
1125
|
content_type = next(iter(route.request_body.content_schema))
|
|
1085
1126
|
body_schema = _replace_ref_with_defs(
|
|
1086
1127
|
route.request_body.content_schema[content_type].copy(),
|
|
@@ -1088,7 +1129,44 @@ def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
|
|
|
1088
1129
|
)
|
|
1089
1130
|
body_props = body_schema.get("properties", {})
|
|
1090
1131
|
|
|
1091
|
-
|
|
1132
|
+
# Detect collisions: parameters that exist in both body and path/query/header
|
|
1133
|
+
all_non_body_params = set()
|
|
1134
|
+
for location_params in param_names_by_location.values():
|
|
1135
|
+
all_non_body_params.update(location_params)
|
|
1136
|
+
|
|
1137
|
+
body_param_names = set(body_props.keys())
|
|
1138
|
+
colliding_params = all_non_body_params & body_param_names
|
|
1139
|
+
|
|
1140
|
+
# Add parameters with suffixes for collisions
|
|
1141
|
+
for param in route.parameters:
|
|
1142
|
+
if param.name in colliding_params:
|
|
1143
|
+
# Add suffix for non-body parameters when collision detected
|
|
1144
|
+
suffixed_name = f"{param.name}__{param.location}"
|
|
1145
|
+
if param.required:
|
|
1146
|
+
required.append(suffixed_name)
|
|
1147
|
+
|
|
1148
|
+
# Add location info to description
|
|
1149
|
+
param_schema = _replace_ref_with_defs(
|
|
1150
|
+
param.schema_.copy(), param.description
|
|
1151
|
+
)
|
|
1152
|
+
original_desc = param_schema.get("description", "")
|
|
1153
|
+
location_desc = f"({param.location.capitalize()} parameter)"
|
|
1154
|
+
if original_desc:
|
|
1155
|
+
param_schema["description"] = f"{original_desc} {location_desc}"
|
|
1156
|
+
else:
|
|
1157
|
+
param_schema["description"] = location_desc
|
|
1158
|
+
|
|
1159
|
+
properties[suffixed_name] = param_schema
|
|
1160
|
+
else:
|
|
1161
|
+
# No collision, use original name
|
|
1162
|
+
if param.required:
|
|
1163
|
+
required.append(param.name)
|
|
1164
|
+
properties[param.name] = _replace_ref_with_defs(
|
|
1165
|
+
param.schema_.copy(), param.description
|
|
1166
|
+
)
|
|
1167
|
+
|
|
1168
|
+
# Add request body properties (no suffixes for body parameters)
|
|
1169
|
+
if route.request_body and route.request_body.content_schema:
|
|
1092
1170
|
for prop_name, prop_schema in body_props.items():
|
|
1093
1171
|
properties[prop_name] = prop_schema
|
|
1094
1172
|
|
|
@@ -1110,6 +1188,20 @@ def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
|
|
|
1110
1188
|
return result
|
|
1111
1189
|
|
|
1112
1190
|
|
|
1191
|
+
def _adjust_union_types(
|
|
1192
|
+
schema: dict[str, Any] | list[Any],
|
|
1193
|
+
) -> dict[str, Any] | list[Any]:
|
|
1194
|
+
"""Recursively replace 'oneOf' with 'anyOf' in schema to handle overlapping unions."""
|
|
1195
|
+
if isinstance(schema, dict):
|
|
1196
|
+
if "oneOf" in schema:
|
|
1197
|
+
schema["anyOf"] = schema.pop("oneOf")
|
|
1198
|
+
for k, v in schema.items():
|
|
1199
|
+
schema[k] = _adjust_union_types(v)
|
|
1200
|
+
elif isinstance(schema, list):
|
|
1201
|
+
return [_adjust_union_types(item) for item in schema]
|
|
1202
|
+
return schema
|
|
1203
|
+
|
|
1204
|
+
|
|
1113
1205
|
def extract_output_schema_from_responses(
|
|
1114
1206
|
responses: dict[str, ResponseInfo], schema_definitions: dict[str, Any] | None = None
|
|
1115
1207
|
) -> dict[str, Any] | None:
|
|
@@ -1198,4 +1290,7 @@ def extract_output_schema_from_responses(
|
|
|
1198
1290
|
# Use compress_schema to remove unused definitions
|
|
1199
1291
|
output_schema = compress_schema(output_schema)
|
|
1200
1292
|
|
|
1293
|
+
# Adjust union types to handle overlapping unions
|
|
1294
|
+
output_schema = cast(dict[str, Any], _adjust_union_types(output_schema))
|
|
1295
|
+
|
|
1201
1296
|
return output_schema
|
|
@@ -5,10 +5,10 @@ fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
5
5
|
fastmcp/settings.py,sha256=Ta0TKA75xda9sNkIOpPVIEEk4W9jf_2gwcmO26uDQpg,8946
|
|
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=
|
|
9
|
-
fastmcp/cli/run.py,sha256=
|
|
8
|
+
fastmcp/cli/cli.py,sha256=Q4HDVDSty1Gx6qN_M4FDx8RYat34vhWT25338X-quNs,12672
|
|
9
|
+
fastmcp/cli/run.py,sha256=V268Lf7LXdeMZ4_D4fKdFST7cOs8pGgLXZTxtcEJRWg,6715
|
|
10
10
|
fastmcp/cli/install/__init__.py,sha256=afaNANIhivfKi4QhJdYUP56Q2XZ4da4dUbDq9HAMJqo,701
|
|
11
|
-
fastmcp/cli/install/claude_code.py,sha256=
|
|
11
|
+
fastmcp/cli/install/claude_code.py,sha256=VlFVGKKRppkmp42io6VPTQrQHgNww4H2ppa6mAWM-Ao,6430
|
|
12
12
|
fastmcp/cli/install/claude_desktop.py,sha256=o3p3KiW0QzsrzOEa3OU_gkvzTuFSkabDwEqwwgp1mYU,5590
|
|
13
13
|
fastmcp/cli/install/cursor.py,sha256=296a22T3fdaC8deCwh3TreVFoJ0QpjLxRF1gNsp3Wjg,5555
|
|
14
14
|
fastmcp/cli/install/mcp_config.py,sha256=D1vkZj13ofNFbFhdn8eAuEx-CHTr3nGiVkjSZTdnetY,4559
|
|
@@ -54,19 +54,19 @@ fastmcp/server/dependencies.py,sha256=iKJdz1XsVJcrfHo_reXj9ZSldw-HeAwsp9S6lAgfGA
|
|
|
54
54
|
fastmcp/server/elicitation.py,sha256=jZIHjV4NjhYbT-w8pBArwd0vNzP8OYwzmsnWDdk6Bd0,6136
|
|
55
55
|
fastmcp/server/http.py,sha256=d0Jij4HVTaAohluRXArSniXLb1HcHP3ytbe-mMHg6nE,11678
|
|
56
56
|
fastmcp/server/low_level.py,sha256=LNmc_nU_wx-fRG8OEHdLPKopZpovcrWlyAxJzKss3TA,1239
|
|
57
|
-
fastmcp/server/openapi.py,sha256=
|
|
58
|
-
fastmcp/server/proxy.py,sha256=
|
|
59
|
-
fastmcp/server/server.py,sha256=
|
|
57
|
+
fastmcp/server/openapi.py,sha256=LWT5rI8TN90MCppuo-LWkYM_ZVnxT6xtS-7Ny8sJIFI,41531
|
|
58
|
+
fastmcp/server/proxy.py,sha256=3cABSJyOalxnqmHGaS8kb6jyaJEAXshQcOh8XihY7Kk,21936
|
|
59
|
+
fastmcp/server/server.py,sha256=8ah5ZYgKLFT2sHzPRT3NUKBrQWi4nny_LPx6cN8EhQs,82816
|
|
60
60
|
fastmcp/server/auth/__init__.py,sha256=doHCLwOIElvH1NrTdpeP9JKfnNf3MDYPSpQfdsQ-uI0,84
|
|
61
61
|
fastmcp/server/auth/auth.py,sha256=A00OKxglEMrGMMIiMbc6UmpGc2VoWDkEVU5g2pIzDIg,2119
|
|
62
62
|
fastmcp/server/auth/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
63
|
fastmcp/server/auth/providers/bearer.py,sha256=rjwuKFgCm4fLsAQzUaIaE8OD6bml3sBaO7NYq3Wgs24,16718
|
|
64
64
|
fastmcp/server/auth/providers/bearer_env.py,sha256=NYPCW363Q8u8BdiPPz1FdB3_kwmbCaWT5yKdAO-ZgwA,2081
|
|
65
65
|
fastmcp/server/auth/providers/in_memory.py,sha256=Sb3GOtLL2bWbm8z-T8cEsMz1qcQUSHpPEEgYRvTOQi4,14251
|
|
66
|
-
fastmcp/server/middleware/__init__.py,sha256=
|
|
66
|
+
fastmcp/server/middleware/__init__.py,sha256=vh5C9ubN6q-y5QND32P4mQ4zDT89C7XYK39yqhELNAk,155
|
|
67
67
|
fastmcp/server/middleware/error_handling.py,sha256=SoDatr9i3T2qSIUbSEGWrOnu4WPPyMDymnsF5GR_BiE,7572
|
|
68
68
|
fastmcp/server/middleware/logging.py,sha256=UIAoafnKRGWpQa7OX5nzChep-9EhKdyTDBmmcRcEVdo,6239
|
|
69
|
-
fastmcp/server/middleware/middleware.py,sha256=
|
|
69
|
+
fastmcp/server/middleware/middleware.py,sha256=jFy7NmLHvGmrGkKViS1PvR_7oGM1EklxNx66EJtUzYU,6441
|
|
70
70
|
fastmcp/server/middleware/rate_limiting.py,sha256=VTrCoQFmWCm0BxwOrNfG21CBFDDOKJT7IiSEjpJgmPA,7921
|
|
71
71
|
fastmcp/server/middleware/timing.py,sha256=lL_xc-ErLD5lplfvd5-HIyWEbZhgNBYkcQ74KFXAMkA,5591
|
|
72
72
|
fastmcp/tools/__init__.py,sha256=vzqb-Y7Kf0d5T0aOsld-O-FA8kD7-4uFExChewFHEzY,201
|
|
@@ -75,19 +75,19 @@ fastmcp/tools/tool_manager.py,sha256=Sm_tOO-SY0m7tEN_dofP-tvBnC2HroPRKLU6sp8gnUw
|
|
|
75
75
|
fastmcp/tools/tool_transform.py,sha256=nGvxxKsyfp3gYl_nkYFHlnb8Fc4Jtw6t7WL291S4Vh4,32558
|
|
76
76
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
77
77
|
fastmcp/utilities/cache.py,sha256=aV3oZ-ZhMgLSM9iAotlUlEy5jFvGXrVo0Y5Bj4PBtqY,707
|
|
78
|
-
fastmcp/utilities/cli.py,sha256=
|
|
79
|
-
fastmcp/utilities/components.py,sha256=
|
|
78
|
+
fastmcp/utilities/cli.py,sha256=TXuSyALFAGJwi7tWEBwBmaGhYZBdF1aG6dLgl3zjM1w,3272
|
|
79
|
+
fastmcp/utilities/components.py,sha256=0eldO8mqG0o0sLlMK5heTOFaQdLzscn1Mup-AR-MnTA,3994
|
|
80
80
|
fastmcp/utilities/exceptions.py,sha256=7Z9j5IzM5rT27BC1Mcn8tkS-bjqCYqMKwb2MMTaxJYU,1350
|
|
81
81
|
fastmcp/utilities/http.py,sha256=1ns1ymBS-WSxbZjGP6JYjSO52Wa_ls4j4WbnXiupoa4,245
|
|
82
82
|
fastmcp/utilities/inspect.py,sha256=XNA0dfYM5G-FVbJaVJO8loSUUCNypyLA-QjqTOneJyU,10833
|
|
83
83
|
fastmcp/utilities/json_schema.py,sha256=8qyb3qOvk1gc3p63uP6LGyKdOANkNdD9YA32OiBxyNw,5495
|
|
84
84
|
fastmcp/utilities/json_schema_type.py,sha256=Sml03nJGOnUfxCGrHWRMwZMultV0X5JThMepUnHIUiA,22377
|
|
85
85
|
fastmcp/utilities/logging.py,sha256=1y7oNmy8WrR0NsfNVw1LPoKu92OFdmzIO65syOKi_BI,1388
|
|
86
|
-
fastmcp/utilities/openapi.py,sha256=
|
|
86
|
+
fastmcp/utilities/openapi.py,sha256=Fvn6M_deDdm3qHGMLXClpIi5ZBp9bM31lvZ9XSnQkH4,52047
|
|
87
87
|
fastmcp/utilities/tests.py,sha256=kZH8HQAC702a5vNJb4K0tO1ll9CZADWQ_P-5ERWSvSA,4242
|
|
88
88
|
fastmcp/utilities/types.py,sha256=c6HPvHCpkq8EXh0hWjaUlj9aCZklmxzAQHCXZy7llNo,10636
|
|
89
|
-
fastmcp-2.10.
|
|
90
|
-
fastmcp-2.10.
|
|
91
|
-
fastmcp-2.10.
|
|
92
|
-
fastmcp-2.10.
|
|
93
|
-
fastmcp-2.10.
|
|
89
|
+
fastmcp-2.10.5.dist-info/METADATA,sha256=T3PxSdKmyz51Fpby151d0mvdIQX-HZK0ns0aXlyIql0,17842
|
|
90
|
+
fastmcp-2.10.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
91
|
+
fastmcp-2.10.5.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
92
|
+
fastmcp-2.10.5.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
93
|
+
fastmcp-2.10.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|