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.
Files changed (39) hide show
  1. fastmcp/client/auth/oauth.py +5 -82
  2. fastmcp/client/client.py +114 -24
  3. fastmcp/client/elicitation.py +63 -0
  4. fastmcp/client/transports.py +50 -36
  5. fastmcp/contrib/component_manager/README.md +170 -0
  6. fastmcp/contrib/component_manager/__init__.py +4 -0
  7. fastmcp/contrib/component_manager/component_manager.py +186 -0
  8. fastmcp/contrib/component_manager/component_service.py +225 -0
  9. fastmcp/contrib/component_manager/example.py +59 -0
  10. fastmcp/prompts/prompt.py +12 -4
  11. fastmcp/resources/resource.py +8 -3
  12. fastmcp/resources/template.py +5 -0
  13. fastmcp/server/auth/auth.py +15 -0
  14. fastmcp/server/auth/providers/bearer.py +41 -3
  15. fastmcp/server/auth/providers/bearer_env.py +4 -0
  16. fastmcp/server/auth/providers/in_memory.py +15 -0
  17. fastmcp/server/context.py +144 -4
  18. fastmcp/server/elicitation.py +160 -0
  19. fastmcp/server/http.py +1 -9
  20. fastmcp/server/low_level.py +4 -2
  21. fastmcp/server/middleware/__init__.py +14 -1
  22. fastmcp/server/middleware/logging.py +11 -0
  23. fastmcp/server/middleware/middleware.py +10 -6
  24. fastmcp/server/openapi.py +19 -77
  25. fastmcp/server/proxy.py +13 -6
  26. fastmcp/server/server.py +27 -7
  27. fastmcp/settings.py +0 -17
  28. fastmcp/tools/tool.py +209 -57
  29. fastmcp/tools/tool_manager.py +2 -3
  30. fastmcp/tools/tool_transform.py +125 -26
  31. fastmcp/utilities/components.py +5 -1
  32. fastmcp/utilities/json_schema_type.py +648 -0
  33. fastmcp/utilities/openapi.py +69 -0
  34. fastmcp/utilities/types.py +50 -19
  35. {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/METADATA +3 -2
  36. {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/RECORD +39 -31
  37. {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/WHEEL +0 -0
  38. {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/entry_points.txt +0 -0
  39. {fastmcp-2.9.2.dist-info → fastmcp-2.10.1.dist-info}/licenses/LICENSE +0 -0
@@ -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 MCPContent, get_cached_typeadapter
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) -> Any:
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 result from the parent tool execution.
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) -> Any:
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 result from the parent tool execution.
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 | EllipsisType = NotSet
155
- description: str | EllipsisType = NotSet
156
- default: Any | EllipsisType = NotSet
157
- default_factory: Callable[[], Any] | EllipsisType = NotSet
158
- type: Any | EllipsisType = NotSet
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] | EllipsisType = NotSet
161
- examples: Any | EllipsisType = NotSet
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]) -> list[MCPContent]:
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
- List of content objects (text, image, or embedded resources) representing
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
- return _convert_to_content(result, serializer=self.serializer)
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 = ParsedFunction.from_function(transform_fn, validate=False)
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.parameters.get("properties", {}).keys())
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.parameters, schema
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.parameters, schema
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,
@@ -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."""