fastmcp 2.9.2__py3-none-any.whl → 2.10.1__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/client/auth/oauth.py +5 -82
- fastmcp/client/client.py +114 -24
- fastmcp/client/elicitation.py +63 -0
- fastmcp/client/transports.py +50 -36
- fastmcp/contrib/component_manager/README.md +170 -0
- fastmcp/contrib/component_manager/__init__.py +4 -0
- fastmcp/contrib/component_manager/component_manager.py +186 -0
- fastmcp/contrib/component_manager/component_service.py +225 -0
- fastmcp/contrib/component_manager/example.py +59 -0
- fastmcp/prompts/prompt.py +12 -4
- fastmcp/resources/resource.py +8 -3
- fastmcp/resources/template.py +5 -0
- fastmcp/server/auth/auth.py +15 -0
- fastmcp/server/auth/providers/bearer.py +41 -3
- fastmcp/server/auth/providers/bearer_env.py +4 -0
- fastmcp/server/auth/providers/in_memory.py +15 -0
- fastmcp/server/context.py +144 -4
- fastmcp/server/elicitation.py +160 -0
- fastmcp/server/http.py +1 -9
- fastmcp/server/low_level.py +4 -2
- fastmcp/server/middleware/__init__.py +14 -1
- fastmcp/server/middleware/logging.py +11 -0
- fastmcp/server/middleware/middleware.py +10 -6
- fastmcp/server/openapi.py +19 -77
- fastmcp/server/proxy.py +13 -6
- fastmcp/server/server.py +27 -7
- fastmcp/settings.py +0 -17
- fastmcp/tools/tool.py +209 -57
- fastmcp/tools/tool_manager.py +2 -3
- fastmcp/tools/tool_transform.py +125 -26
- fastmcp/utilities/components.py +5 -1
- fastmcp/utilities/json_schema_type.py +648 -0
- fastmcp/utilities/openapi.py +69 -0
- fastmcp/utilities/types.py +50 -19
- {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/METADATA +3 -2
- {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/RECORD +39 -31
- {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/WHEEL +0 -0
- {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/licenses/LICENSE +0 -0
fastmcp/tools/tool_transform.py
CHANGED
|
@@ -4,20 +4,17 @@ import inspect
|
|
|
4
4
|
from collections.abc import Callable
|
|
5
5
|
from contextvars import ContextVar
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from types import EllipsisType
|
|
8
7
|
from typing import Any, Literal
|
|
9
8
|
|
|
10
9
|
from mcp.types import ToolAnnotations
|
|
11
10
|
from pydantic import ConfigDict
|
|
12
11
|
|
|
13
|
-
from fastmcp.tools.tool import ParsedFunction, Tool
|
|
12
|
+
from fastmcp.tools.tool import ParsedFunction, Tool, ToolResult
|
|
14
13
|
from fastmcp.utilities.logging import get_logger
|
|
15
|
-
from fastmcp.utilities.types import
|
|
14
|
+
from fastmcp.utilities.types import NotSet, NotSetT, get_cached_typeadapter
|
|
16
15
|
|
|
17
16
|
logger = get_logger(__name__)
|
|
18
17
|
|
|
19
|
-
NotSet = ...
|
|
20
|
-
|
|
21
18
|
|
|
22
19
|
# Context variable to store current transformed tool
|
|
23
20
|
_current_tool: ContextVar[TransformedTool | None] = ContextVar(
|
|
@@ -25,7 +22,7 @@ _current_tool: ContextVar[TransformedTool | None] = ContextVar(
|
|
|
25
22
|
)
|
|
26
23
|
|
|
27
24
|
|
|
28
|
-
async def forward(**kwargs) ->
|
|
25
|
+
async def forward(**kwargs) -> ToolResult:
|
|
29
26
|
"""Forward to parent tool with argument transformation applied.
|
|
30
27
|
|
|
31
28
|
This function can only be called from within a transformed tool's custom
|
|
@@ -41,7 +38,7 @@ async def forward(**kwargs) -> Any:
|
|
|
41
38
|
**kwargs: Arguments to forward to the parent tool (using transformed names).
|
|
42
39
|
|
|
43
40
|
Returns:
|
|
44
|
-
The
|
|
41
|
+
The ToolResult from the parent tool execution.
|
|
45
42
|
|
|
46
43
|
Raises:
|
|
47
44
|
RuntimeError: If called outside a transformed tool context.
|
|
@@ -55,7 +52,7 @@ async def forward(**kwargs) -> Any:
|
|
|
55
52
|
return await tool.forwarding_fn(**kwargs)
|
|
56
53
|
|
|
57
54
|
|
|
58
|
-
async def forward_raw(**kwargs) ->
|
|
55
|
+
async def forward_raw(**kwargs) -> ToolResult:
|
|
59
56
|
"""Forward directly to parent tool without transformation.
|
|
60
57
|
|
|
61
58
|
This function bypasses all argument transformation and validation, calling the parent
|
|
@@ -69,7 +66,7 @@ async def forward_raw(**kwargs) -> Any:
|
|
|
69
66
|
**kwargs: Arguments to pass directly to the parent tool (using original names).
|
|
70
67
|
|
|
71
68
|
Returns:
|
|
72
|
-
The
|
|
69
|
+
The ToolResult from the parent tool execution.
|
|
73
70
|
|
|
74
71
|
Raises:
|
|
75
72
|
RuntimeError: If called outside a transformed tool context.
|
|
@@ -151,14 +148,14 @@ class ArgTransform:
|
|
|
151
148
|
```
|
|
152
149
|
"""
|
|
153
150
|
|
|
154
|
-
name: str |
|
|
155
|
-
description: str |
|
|
156
|
-
default: Any |
|
|
157
|
-
default_factory: Callable[[], Any] |
|
|
158
|
-
type: Any |
|
|
151
|
+
name: str | NotSetT = NotSet
|
|
152
|
+
description: str | NotSetT = NotSet
|
|
153
|
+
default: Any | NotSetT = NotSet
|
|
154
|
+
default_factory: Callable[[], Any] | NotSetT = NotSet
|
|
155
|
+
type: Any | NotSetT = NotSet
|
|
159
156
|
hide: bool = False
|
|
160
|
-
required: Literal[True] |
|
|
161
|
-
examples: Any |
|
|
157
|
+
required: Literal[True] | NotSetT = NotSet
|
|
158
|
+
examples: Any | NotSetT = NotSet
|
|
162
159
|
|
|
163
160
|
def __post_init__(self):
|
|
164
161
|
"""Validate that only one of default or default_factory is provided."""
|
|
@@ -201,11 +198,12 @@ class TransformedTool(Tool):
|
|
|
201
198
|
|
|
202
199
|
This class represents a tool that has been created by transforming another tool.
|
|
203
200
|
It supports argument renaming, schema modification, custom function injection,
|
|
204
|
-
and provides context for the forward() and forward_raw() functions.
|
|
201
|
+
structured output control, and provides context for the forward() and forward_raw() functions.
|
|
205
202
|
|
|
206
203
|
The transformation can be purely schema-based (argument renaming, dropping, etc.)
|
|
207
204
|
or can include a custom function that uses forward() to call the parent tool
|
|
208
|
-
with transformed arguments.
|
|
205
|
+
with transformed arguments. Output schemas and structured outputs are automatically
|
|
206
|
+
inherited from the parent tool but can be overridden or disabled.
|
|
209
207
|
|
|
210
208
|
Attributes:
|
|
211
209
|
parent_tool: The original tool that this tool was transformed from.
|
|
@@ -222,7 +220,7 @@ class TransformedTool(Tool):
|
|
|
222
220
|
forwarding_fn: Callable[..., Any] # Always present, handles arg transformation
|
|
223
221
|
transform_args: dict[str, ArgTransform]
|
|
224
222
|
|
|
225
|
-
async def run(self, arguments: dict[str, Any]) ->
|
|
223
|
+
async def run(self, arguments: dict[str, Any]) -> ToolResult:
|
|
226
224
|
"""Run the tool with context set for forward() functions.
|
|
227
225
|
|
|
228
226
|
This method executes the tool's function while setting up the context
|
|
@@ -233,8 +231,7 @@ class TransformedTool(Tool):
|
|
|
233
231
|
arguments: Dictionary of arguments to pass to the tool's function.
|
|
234
232
|
|
|
235
233
|
Returns:
|
|
236
|
-
|
|
237
|
-
the tool's output.
|
|
234
|
+
ToolResult object containing content and optional structured output.
|
|
238
235
|
"""
|
|
239
236
|
from fastmcp.tools.tool import _convert_to_content
|
|
240
237
|
|
|
@@ -272,7 +269,57 @@ class TransformedTool(Tool):
|
|
|
272
269
|
token = _current_tool.set(self)
|
|
273
270
|
try:
|
|
274
271
|
result = await self.fn(**arguments)
|
|
275
|
-
|
|
272
|
+
|
|
273
|
+
# If transform function returns ToolResult, respect our output_schema setting
|
|
274
|
+
if isinstance(result, ToolResult):
|
|
275
|
+
if self.output_schema is None:
|
|
276
|
+
# Check if this is from a custom function that returns ToolResult
|
|
277
|
+
import inspect
|
|
278
|
+
|
|
279
|
+
return_annotation = inspect.signature(self.fn).return_annotation
|
|
280
|
+
if return_annotation is ToolResult:
|
|
281
|
+
# Custom function returns ToolResult - preserve its content
|
|
282
|
+
return result
|
|
283
|
+
else:
|
|
284
|
+
# Forwarded call with disabled schema - strip structured content
|
|
285
|
+
return ToolResult(
|
|
286
|
+
content=result.content,
|
|
287
|
+
structured_content=None,
|
|
288
|
+
)
|
|
289
|
+
elif self.output_schema.get(
|
|
290
|
+
"type"
|
|
291
|
+
) != "object" and not self.output_schema.get("x-fastmcp-wrap-result"):
|
|
292
|
+
# Non-object explicit schemas disable structured content
|
|
293
|
+
return ToolResult(
|
|
294
|
+
content=result.content,
|
|
295
|
+
structured_content=None,
|
|
296
|
+
)
|
|
297
|
+
else:
|
|
298
|
+
return result
|
|
299
|
+
|
|
300
|
+
# Otherwise convert to content and create ToolResult with proper structured content
|
|
301
|
+
from fastmcp.tools.tool import _convert_to_content
|
|
302
|
+
|
|
303
|
+
unstructured_result = _convert_to_content(
|
|
304
|
+
result, serializer=self.serializer
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Handle structured content based on output schema
|
|
308
|
+
if self.output_schema is not None:
|
|
309
|
+
if self.output_schema.get("x-fastmcp-wrap-result"):
|
|
310
|
+
# Schema says wrap - always wrap in result key
|
|
311
|
+
structured_output = {"result": result}
|
|
312
|
+
else:
|
|
313
|
+
# Object schemas - use result directly
|
|
314
|
+
# User is responsible for returning dict-compatible data
|
|
315
|
+
structured_output = result
|
|
316
|
+
else:
|
|
317
|
+
structured_output = None
|
|
318
|
+
|
|
319
|
+
return ToolResult(
|
|
320
|
+
content=unstructured_result,
|
|
321
|
+
structured_content=structured_output,
|
|
322
|
+
)
|
|
276
323
|
finally:
|
|
277
324
|
_current_tool.reset(token)
|
|
278
325
|
|
|
@@ -286,6 +333,7 @@ class TransformedTool(Tool):
|
|
|
286
333
|
transform_fn: Callable[..., Any] | None = None,
|
|
287
334
|
transform_args: dict[str, ArgTransform] | None = None,
|
|
288
335
|
annotations: ToolAnnotations | None = None,
|
|
336
|
+
output_schema: dict[str, Any] | None | Literal[False] = None,
|
|
289
337
|
serializer: Callable[[Any], str] | None = None,
|
|
290
338
|
enabled: bool | None = None,
|
|
291
339
|
) -> TransformedTool:
|
|
@@ -305,6 +353,10 @@ class TransformedTool(Tool):
|
|
|
305
353
|
description: New description. Defaults to parent's description.
|
|
306
354
|
tags: New tags. Defaults to parent's tags.
|
|
307
355
|
annotations: New annotations. Defaults to parent's annotations.
|
|
356
|
+
output_schema: Control output schema for structured outputs:
|
|
357
|
+
- None (default): Inherit from transform_fn if available, then parent tool
|
|
358
|
+
- dict: Use custom output schema
|
|
359
|
+
- False: Disable output schema and structured outputs
|
|
308
360
|
serializer: New serializer. Defaults to parent's serializer.
|
|
309
361
|
|
|
310
362
|
Returns:
|
|
@@ -333,6 +385,26 @@ class TransformedTool(Tool):
|
|
|
333
385
|
|
|
334
386
|
Tool.from_tool(parent, transform_fn=flexible, transform_args={"a": "x"})
|
|
335
387
|
```
|
|
388
|
+
|
|
389
|
+
# Control structured outputs and schemas
|
|
390
|
+
```python
|
|
391
|
+
# Custom output schema
|
|
392
|
+
Tool.from_tool(parent, output_schema={
|
|
393
|
+
"type": "object",
|
|
394
|
+
"properties": {"status": {"type": "string"}}
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
# Disable structured outputs
|
|
398
|
+
Tool.from_tool(parent, output_schema=False)
|
|
399
|
+
|
|
400
|
+
# Return ToolResult for full control
|
|
401
|
+
async def custom_output(**kwargs) -> ToolResult:
|
|
402
|
+
result = await forward(**kwargs)
|
|
403
|
+
return ToolResult(
|
|
404
|
+
content=[TextContent(text="Summary")],
|
|
405
|
+
structured_content={"processed": True}
|
|
406
|
+
)
|
|
407
|
+
```
|
|
336
408
|
"""
|
|
337
409
|
transform_args = transform_args or {}
|
|
338
410
|
|
|
@@ -348,19 +420,45 @@ class TransformedTool(Tool):
|
|
|
348
420
|
# Always create the forwarding transform
|
|
349
421
|
schema, forwarding_fn = cls._create_forwarding_transform(tool, transform_args)
|
|
350
422
|
|
|
423
|
+
# Handle output schema with smart fallback
|
|
424
|
+
if output_schema is False:
|
|
425
|
+
final_output_schema = None
|
|
426
|
+
elif output_schema is not None:
|
|
427
|
+
# Explicit schema provided - use as-is
|
|
428
|
+
final_output_schema = output_schema
|
|
429
|
+
else:
|
|
430
|
+
# Smart fallback: try custom function, then parent, then None
|
|
431
|
+
if transform_fn is not None:
|
|
432
|
+
parsed_fn = ParsedFunction.from_function(transform_fn, validate=False)
|
|
433
|
+
final_output_schema = parsed_fn.output_schema
|
|
434
|
+
if final_output_schema is None:
|
|
435
|
+
# Check if function returns ToolResult - if so, don't fall back to parent
|
|
436
|
+
import inspect
|
|
437
|
+
|
|
438
|
+
return_annotation = inspect.signature(
|
|
439
|
+
transform_fn
|
|
440
|
+
).return_annotation
|
|
441
|
+
if return_annotation is ToolResult:
|
|
442
|
+
final_output_schema = None
|
|
443
|
+
else:
|
|
444
|
+
final_output_schema = tool.output_schema
|
|
445
|
+
else:
|
|
446
|
+
final_output_schema = tool.output_schema
|
|
447
|
+
|
|
351
448
|
if transform_fn is None:
|
|
352
449
|
# User wants pure transformation - use forwarding_fn as the main function
|
|
353
450
|
final_fn = forwarding_fn
|
|
354
451
|
final_schema = schema
|
|
355
452
|
else:
|
|
356
453
|
# User provided custom function - merge schemas
|
|
357
|
-
parsed_fn
|
|
454
|
+
if "parsed_fn" not in locals():
|
|
455
|
+
parsed_fn = ParsedFunction.from_function(transform_fn, validate=False)
|
|
358
456
|
final_fn = transform_fn
|
|
359
457
|
|
|
360
458
|
has_kwargs = cls._function_has_kwargs(transform_fn)
|
|
361
459
|
|
|
362
460
|
# Validate function parameters against transformed schema
|
|
363
|
-
fn_params = set(parsed_fn.
|
|
461
|
+
fn_params = set(parsed_fn.input_schema.get("properties", {}).keys())
|
|
364
462
|
transformed_params = set(schema.get("properties", {}).keys())
|
|
365
463
|
|
|
366
464
|
if not has_kwargs:
|
|
@@ -377,7 +475,7 @@ class TransformedTool(Tool):
|
|
|
377
475
|
# ArgTransform takes precedence over function signature
|
|
378
476
|
# Start with function schema as base, then override with transformed schema
|
|
379
477
|
final_schema = cls._merge_schema_with_precedence(
|
|
380
|
-
parsed_fn.
|
|
478
|
+
parsed_fn.input_schema, schema
|
|
381
479
|
)
|
|
382
480
|
else:
|
|
383
481
|
# With **kwargs, function can access all transformed params
|
|
@@ -386,7 +484,7 @@ class TransformedTool(Tool):
|
|
|
386
484
|
|
|
387
485
|
# Start with function schema as base, then override with transformed schema
|
|
388
486
|
final_schema = cls._merge_schema_with_precedence(
|
|
389
|
-
parsed_fn.
|
|
487
|
+
parsed_fn.input_schema, schema
|
|
390
488
|
)
|
|
391
489
|
|
|
392
490
|
# Additional validation: check for naming conflicts after transformation
|
|
@@ -422,6 +520,7 @@ class TransformedTool(Tool):
|
|
|
422
520
|
name=name or tool.name,
|
|
423
521
|
description=final_description,
|
|
424
522
|
parameters=final_schema,
|
|
523
|
+
output_schema=final_output_schema,
|
|
425
524
|
tags=tags or tool.tags,
|
|
426
525
|
annotations=annotations or tool.annotations,
|
|
427
526
|
serializer=serializer or tool.serializer,
|
fastmcp/utilities/components.py
CHANGED
|
@@ -24,6 +24,10 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
|
24
24
|
name: str = Field(
|
|
25
25
|
description="The name of the component.",
|
|
26
26
|
)
|
|
27
|
+
title: str | None = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="The title of the component for display purposes.",
|
|
30
|
+
)
|
|
27
31
|
description: str | None = Field(
|
|
28
32
|
default=None,
|
|
29
33
|
description="The description of the component.",
|
|
@@ -64,7 +68,7 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
|
64
68
|
return self.model_dump() == other.model_dump()
|
|
65
69
|
|
|
66
70
|
def __repr__(self) -> str:
|
|
67
|
-
return f"{self.__class__.__name__}(name={self.name!r}, description={self.description!r}, tags={self.tags}, enabled={self.enabled})"
|
|
71
|
+
return f"{self.__class__.__name__}(name={self.name!r}, title={self.title!r}, description={self.description!r}, tags={self.tags}, enabled={self.enabled})"
|
|
68
72
|
|
|
69
73
|
def enable(self) -> None:
|
|
70
74
|
"""Enable the component."""
|