fastmcp 2.13.2__py3-none-any.whl → 2.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. fastmcp/__init__.py +0 -21
  2. fastmcp/cli/__init__.py +0 -3
  3. fastmcp/cli/__main__.py +5 -0
  4. fastmcp/cli/cli.py +8 -22
  5. fastmcp/cli/install/shared.py +0 -15
  6. fastmcp/cli/tasks.py +110 -0
  7. fastmcp/client/auth/oauth.py +9 -9
  8. fastmcp/client/client.py +665 -129
  9. fastmcp/client/elicitation.py +11 -5
  10. fastmcp/client/messages.py +7 -5
  11. fastmcp/client/roots.py +2 -1
  12. fastmcp/client/tasks.py +614 -0
  13. fastmcp/client/transports.py +37 -5
  14. fastmcp/contrib/component_manager/component_service.py +4 -20
  15. fastmcp/dependencies.py +25 -0
  16. fastmcp/experimental/sampling/handlers/openai.py +1 -1
  17. fastmcp/experimental/server/openapi/__init__.py +15 -13
  18. fastmcp/experimental/utilities/openapi/__init__.py +12 -38
  19. fastmcp/prompts/prompt.py +33 -33
  20. fastmcp/resources/resource.py +29 -12
  21. fastmcp/resources/template.py +64 -54
  22. fastmcp/server/auth/__init__.py +0 -9
  23. fastmcp/server/auth/auth.py +127 -3
  24. fastmcp/server/auth/oauth_proxy.py +47 -97
  25. fastmcp/server/auth/oidc_proxy.py +7 -0
  26. fastmcp/server/auth/providers/in_memory.py +2 -2
  27. fastmcp/server/auth/providers/oci.py +2 -2
  28. fastmcp/server/context.py +66 -72
  29. fastmcp/server/dependencies.py +464 -6
  30. fastmcp/server/elicitation.py +285 -47
  31. fastmcp/server/event_store.py +177 -0
  32. fastmcp/server/http.py +15 -3
  33. fastmcp/server/low_level.py +56 -12
  34. fastmcp/server/middleware/middleware.py +2 -2
  35. fastmcp/server/openapi/__init__.py +35 -0
  36. fastmcp/{experimental/server → server}/openapi/components.py +4 -3
  37. fastmcp/{experimental/server → server}/openapi/routing.py +1 -1
  38. fastmcp/{experimental/server → server}/openapi/server.py +6 -5
  39. fastmcp/server/proxy.py +50 -37
  40. fastmcp/server/server.py +731 -532
  41. fastmcp/server/tasks/__init__.py +21 -0
  42. fastmcp/server/tasks/capabilities.py +22 -0
  43. fastmcp/server/tasks/config.py +89 -0
  44. fastmcp/server/tasks/converters.py +205 -0
  45. fastmcp/server/tasks/handlers.py +356 -0
  46. fastmcp/server/tasks/keys.py +93 -0
  47. fastmcp/server/tasks/protocol.py +355 -0
  48. fastmcp/server/tasks/subscriptions.py +205 -0
  49. fastmcp/settings.py +101 -103
  50. fastmcp/tools/tool.py +80 -44
  51. fastmcp/tools/tool_transform.py +1 -12
  52. fastmcp/utilities/components.py +3 -3
  53. fastmcp/utilities/json_schema_type.py +4 -4
  54. fastmcp/utilities/mcp_config.py +1 -2
  55. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +1 -1
  56. fastmcp/{experimental/utilities → utilities}/openapi/README.md +7 -35
  57. fastmcp/utilities/openapi/__init__.py +63 -0
  58. fastmcp/{experimental/utilities → utilities}/openapi/formatters.py +5 -5
  59. fastmcp/{experimental/utilities → utilities}/openapi/json_schema_converter.py +1 -1
  60. fastmcp/utilities/tests.py +11 -5
  61. fastmcp/utilities/types.py +8 -0
  62. {fastmcp-2.13.2.dist-info → fastmcp-2.14.0.dist-info}/METADATA +5 -4
  63. {fastmcp-2.13.2.dist-info → fastmcp-2.14.0.dist-info}/RECORD +71 -59
  64. fastmcp/server/auth/providers/bearer.py +0 -25
  65. fastmcp/server/openapi.py +0 -1087
  66. fastmcp/utilities/openapi.py +0 -1568
  67. /fastmcp/{experimental/server → server}/openapi/README.md +0 -0
  68. /fastmcp/{experimental/utilities → utilities}/openapi/director.py +0 -0
  69. /fastmcp/{experimental/utilities → utilities}/openapi/models.py +0 -0
  70. /fastmcp/{experimental/utilities → utilities}/openapi/parser.py +0 -0
  71. /fastmcp/{experimental/utilities → utilities}/openapi/schemas.py +0 -0
  72. {fastmcp-2.13.2.dist-info → fastmcp-2.14.0.dist-info}/WHEEL +0 -0
  73. {fastmcp-2.13.2.dist-info → fastmcp-2.14.0.dist-info}/entry_points.txt +0 -0
  74. {fastmcp-2.13.2.dist-info → fastmcp-2.14.0.dist-info}/licenses/LICENSE +0 -0
fastmcp/settings.py CHANGED
@@ -3,19 +3,16 @@ from __future__ import annotations as _annotations
3
3
  import inspect
4
4
  import os
5
5
  import warnings
6
+ from datetime import timedelta
6
7
  from pathlib import Path
7
8
  from typing import TYPE_CHECKING, Annotated, Any, Literal
8
9
 
9
10
  from platformdirs import user_data_dir
10
11
  from pydantic import Field, ImportString, field_validator
11
- from pydantic.fields import FieldInfo
12
12
  from pydantic_settings import (
13
13
  BaseSettings,
14
- EnvSettingsSource,
15
- PydanticBaseSettingsSource,
16
14
  SettingsConfigDict,
17
15
  )
18
- from typing_extensions import Self
19
16
 
20
17
  from fastmcp.utilities.logging import get_logger
21
18
 
@@ -33,61 +30,123 @@ if TYPE_CHECKING:
33
30
  from fastmcp.server.auth.auth import AuthProvider
34
31
 
35
32
 
36
- class ExtendedEnvSettingsSource(EnvSettingsSource):
37
- """
38
- A special EnvSettingsSource that allows for multiple env var prefixes to be used.
33
+ class DocketSettings(BaseSettings):
34
+ """Docket worker configuration."""
39
35
 
40
- Raises a deprecation warning if the old `FASTMCP_SERVER_` prefix is used.
41
- """
36
+ model_config = SettingsConfigDict(
37
+ env_prefix="FASTMCP_DOCKET_",
38
+ extra="ignore",
39
+ )
42
40
 
43
- def get_field_value(
44
- self, field: FieldInfo, field_name: str
45
- ) -> tuple[Any, str, bool]:
46
- if prefixes := self.config.get("env_prefixes"):
47
- for prefix in prefixes:
48
- self.env_prefix = prefix
49
- env_val, field_key, value_is_complex = super().get_field_value(
50
- field, field_name
51
- )
52
- if env_val is not None:
53
- if prefix == "FASTMCP_SERVER_":
54
- # Deprecated in 2.8.0
55
- logger.warning(
56
- "Using `FASTMCP_SERVER_` environment variables is deprecated. Use `FASTMCP_` instead.",
57
- )
58
- return env_val, field_key, value_is_complex
41
+ name: Annotated[
42
+ str,
43
+ Field(
44
+ description=inspect.cleandoc(
45
+ """
46
+ Name for the Docket queue. All servers/workers sharing the same name
47
+ and backend URL will share a task queue.
48
+ """
49
+ ),
50
+ ),
51
+ ] = "fastmcp"
59
52
 
60
- return super().get_field_value(field, field_name)
53
+ url: Annotated[
54
+ str,
55
+ Field(
56
+ description=inspect.cleandoc(
57
+ """
58
+ URL for the Docket backend. Supports:
59
+ - memory:// - In-memory backend (single process only)
60
+ - redis://host:port/db - Redis/Valkey backend (distributed, multi-process)
61
61
 
62
+ Example: redis://localhost:6379/0
62
63
 
63
- class ExtendedSettingsConfigDict(SettingsConfigDict, total=False):
64
- env_prefixes: list[str] | None
64
+ Default is memory:// for single-process scenarios. Use Redis or Valkey
65
+ when coordinating tasks across multiple processes (e.g., additional
66
+ workers via the fastmcp tasks CLI).
67
+ """
68
+ ),
69
+ ),
70
+ ] = "memory://"
65
71
 
72
+ worker_name: Annotated[
73
+ str | None,
74
+ Field(
75
+ description=inspect.cleandoc(
76
+ """
77
+ Name for the Docket worker. If None, Docket will auto-generate
78
+ a unique worker name.
79
+ """
80
+ ),
81
+ ),
82
+ ] = None
66
83
 
67
- class ExperimentalSettings(BaseSettings):
68
- model_config = SettingsConfigDict(
69
- env_prefix="FASTMCP_EXPERIMENTAL_",
70
- extra="ignore",
71
- )
84
+ concurrency: Annotated[
85
+ int,
86
+ Field(
87
+ description=inspect.cleandoc(
88
+ """
89
+ Maximum number of tasks the worker can process concurrently.
90
+ """
91
+ ),
92
+ ),
93
+ ] = 10
72
94
 
73
- enable_new_openapi_parser: Annotated[
74
- bool,
95
+ redelivery_timeout: Annotated[
96
+ timedelta,
75
97
  Field(
76
98
  description=inspect.cleandoc(
77
99
  """
78
- Whether to use the new OpenAPI parser. This parser was introduced
79
- for testing in 2.11 and will become the default soon.
100
+ Task redelivery timeout. If a worker doesn't complete
101
+ a task within this time, the task will be redelivered to another
102
+ worker.
80
103
  """
81
104
  ),
82
105
  ),
83
- ] = False
106
+ ] = timedelta(seconds=300)
107
+
108
+ reconnection_delay: Annotated[
109
+ timedelta,
110
+ Field(
111
+ description=inspect.cleandoc(
112
+ """
113
+ Delay between reconnection attempts when the worker
114
+ loses connection to the Docket backend.
115
+ """
116
+ ),
117
+ ),
118
+ ] = timedelta(seconds=5)
119
+
120
+
121
+ class ExperimentalSettings(BaseSettings):
122
+ model_config = SettingsConfigDict(
123
+ env_prefix="FASTMCP_EXPERIMENTAL_",
124
+ extra="ignore",
125
+ validate_assignment=True,
126
+ )
127
+
128
+ # Deprecated in 2.14 - the new OpenAPI parser is now the default and only parser
129
+ enable_new_openapi_parser: bool = False
130
+
131
+ @field_validator("enable_new_openapi_parser", mode="after")
132
+ @classmethod
133
+ def _warn_openapi_parser_deprecated(cls, v: bool) -> bool:
134
+ if v:
135
+ warnings.warn(
136
+ "enable_new_openapi_parser is deprecated. "
137
+ "The new OpenAPI parser is now the default (and only) parser. "
138
+ "You can remove this setting.",
139
+ DeprecationWarning,
140
+ stacklevel=2,
141
+ )
142
+ return v
84
143
 
85
144
 
86
145
  class Settings(BaseSettings):
87
146
  """FastMCP settings."""
88
147
 
89
- model_config = ExtendedSettingsConfigDict(
90
- env_prefixes=["FASTMCP_", "FASTMCP_SERVER_"],
148
+ model_config = SettingsConfigDict(
149
+ env_prefix="FASTMCP_",
91
150
  env_file=ENV_FILE,
92
151
  extra="ignore",
93
152
  env_nested_delimiter="__",
@@ -121,36 +180,6 @@ class Settings(BaseSettings):
121
180
  settings = getattr(settings, parent_attr)
122
181
  setattr(settings, attr, value)
123
182
 
124
- @classmethod
125
- def settings_customise_sources(
126
- cls,
127
- settings_cls: type[BaseSettings],
128
- init_settings: PydanticBaseSettingsSource,
129
- env_settings: PydanticBaseSettingsSource,
130
- dotenv_settings: PydanticBaseSettingsSource,
131
- file_secret_settings: PydanticBaseSettingsSource,
132
- ) -> tuple[PydanticBaseSettingsSource, ...]:
133
- # can remove this classmethod after deprecated FASTMCP_SERVER_ prefix is
134
- # removed
135
- return (
136
- init_settings,
137
- ExtendedEnvSettingsSource(settings_cls),
138
- dotenv_settings,
139
- file_secret_settings,
140
- )
141
-
142
- @property
143
- def settings(self) -> Self:
144
- """
145
- This property is for backwards compatibility with FastMCP < 2.8.0,
146
- which accessed fastmcp.settings.settings
147
- """
148
- # Deprecated in 2.8.0
149
- logger.warning(
150
- "Using fastmcp.settings.settings is deprecated. Use fastmcp.settings instead.",
151
- )
152
- return self
153
-
154
183
  home: Path = Path(user_data_dir("fastmcp", appauthor=False))
155
184
 
156
185
  test_mode: bool = False
@@ -167,6 +196,8 @@ class Settings(BaseSettings):
167
196
 
168
197
  experimental: ExperimentalSettings = ExperimentalSettings()
169
198
 
199
+ docket: DocketSettings = DocketSettings()
200
+
170
201
  enable_rich_tracebacks: Annotated[
171
202
  bool,
172
203
  Field(
@@ -207,19 +238,6 @@ class Settings(BaseSettings):
207
238
  ),
208
239
  ] = True
209
240
 
210
- resource_prefix_format: Annotated[
211
- Literal["protocol", "path"],
212
- Field(
213
- description=inspect.cleandoc(
214
- """
215
- When perfixing a resource URI, either use path formatting (resource://prefix/path)
216
- or protocol formatting (prefix+resource://path). Protocol formatting was the default in FastMCP < 2.4;
217
- path formatting is current default.
218
- """
219
- ),
220
- ),
221
- ] = "path"
222
-
223
241
  client_init_timeout: Annotated[
224
242
  float | None,
225
243
  Field(
@@ -392,23 +410,3 @@ class Settings(BaseSettings):
392
410
  auth_class = type_adapter.validate_python(self.server_auth)
393
411
 
394
412
  return auth_class
395
-
396
-
397
- def __getattr__(name: str):
398
- """
399
- Used to deprecate the module-level Image class; can be removed once it is no longer imported to root.
400
- """
401
- if name == "settings":
402
- import fastmcp
403
-
404
- settings = fastmcp.settings
405
- # Deprecated in 2.10.2
406
- if settings.deprecation_warnings:
407
- warnings.warn(
408
- "`from fastmcp.settings import settings` is deprecated. use `fastmcp.settings` instead.",
409
- DeprecationWarning,
410
- stacklevel=2,
411
- )
412
- return settings
413
-
414
- raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
fastmcp/tools/tool.py CHANGED
@@ -9,20 +9,28 @@ from typing import (
9
9
  Annotated,
10
10
  Any,
11
11
  Generic,
12
- Literal,
13
12
  TypeAlias,
14
13
  get_type_hints,
15
14
  )
16
15
 
17
16
  import mcp.types
18
17
  import pydantic_core
19
- from mcp.types import CallToolResult, ContentBlock, Icon, TextContent, ToolAnnotations
18
+ from mcp.shared.tool_name_validation import validate_and_warn_tool_name
19
+ from mcp.types import (
20
+ CallToolResult,
21
+ ContentBlock,
22
+ Icon,
23
+ TextContent,
24
+ ToolAnnotations,
25
+ ToolExecution,
26
+ )
20
27
  from mcp.types import Tool as MCPTool
21
- from pydantic import Field, PydanticSchemaGenerationError
28
+ from pydantic import Field, PydanticSchemaGenerationError, model_validator
22
29
  from typing_extensions import TypeVar
23
30
 
24
31
  import fastmcp
25
- from fastmcp.server.dependencies import get_context
32
+ from fastmcp.server.dependencies import get_context, without_injected_parameters
33
+ from fastmcp.server.tasks.config import TaskConfig
26
34
  from fastmcp.utilities.components import FastMCPComponent
27
35
  from fastmcp.utilities.json_schema import compress_schema
28
36
  from fastmcp.utilities.logging import get_logger
@@ -33,7 +41,6 @@ from fastmcp.utilities.types import (
33
41
  NotSet,
34
42
  NotSetT,
35
43
  create_function_without_params,
36
- find_kwarg_by_type,
37
44
  get_cached_typeadapter,
38
45
  replace_type,
39
46
  )
@@ -126,11 +133,21 @@ class Tool(FastMCPComponent):
126
133
  ToolAnnotations | None,
127
134
  Field(description="Additional annotations about the tool"),
128
135
  ] = None
136
+ execution: Annotated[
137
+ ToolExecution | None,
138
+ Field(description="Task execution configuration (SEP-1686)"),
139
+ ] = None
129
140
  serializer: Annotated[
130
141
  ToolResultSerializerType | None,
131
142
  Field(description="Optional custom serializer for tool results"),
132
143
  ] = None
133
144
 
145
+ @model_validator(mode="after")
146
+ def _validate_tool_name(self) -> Tool:
147
+ """Validate tool name according to MCP specification (SEP-986)."""
148
+ validate_and_warn_tool_name(self.name)
149
+ return self
150
+
134
151
  def enable(self) -> None:
135
152
  super().enable()
136
153
  try:
@@ -169,6 +186,7 @@ class Tool(FastMCPComponent):
169
186
  outputSchema=overrides.get("outputSchema", self.output_schema),
170
187
  icons=overrides.get("icons", self.icons),
171
188
  annotations=overrides.get("annotations", self.annotations),
189
+ execution=overrides.get("execution", self.execution),
172
190
  _meta=overrides.get(
173
191
  "_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
174
192
  ),
@@ -184,10 +202,11 @@ class Tool(FastMCPComponent):
184
202
  tags: set[str] | None = None,
185
203
  annotations: ToolAnnotations | None = None,
186
204
  exclude_args: list[str] | None = None,
187
- output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
205
+ output_schema: dict[str, Any] | NotSetT | None = NotSet,
188
206
  serializer: ToolResultSerializerType | None = None,
189
207
  meta: dict[str, Any] | None = None,
190
208
  enabled: bool | None = None,
209
+ task: bool | TaskConfig | None = None,
191
210
  ) -> FunctionTool:
192
211
  """Create a Tool from a function."""
193
212
  return FunctionTool.from_function(
@@ -203,6 +222,7 @@ class Tool(FastMCPComponent):
203
222
  serializer=serializer,
204
223
  meta=meta,
205
224
  enabled=enabled,
225
+ task=task,
206
226
  )
207
227
 
208
228
  async def run(self, arguments: dict[str, Any]) -> ToolResult:
@@ -227,7 +247,7 @@ class Tool(FastMCPComponent):
227
247
  description: str | NotSetT | None = NotSet,
228
248
  tags: set[str] | None = None,
229
249
  annotations: ToolAnnotations | NotSetT | None = NotSet,
230
- output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
250
+ output_schema: dict[str, Any] | NotSetT | None = NotSet,
231
251
  serializer: ToolResultSerializerType | None = None,
232
252
  meta: dict[str, Any] | NotSetT | None = NotSet,
233
253
  transform_args: dict[str, ArgTransform] | None = None,
@@ -254,6 +274,32 @@ class Tool(FastMCPComponent):
254
274
 
255
275
  class FunctionTool(Tool):
256
276
  fn: Callable[..., Any]
277
+ task_config: Annotated[
278
+ TaskConfig,
279
+ Field(description="Background task execution configuration (SEP-1686)."),
280
+ ] = Field(default_factory=lambda: TaskConfig(mode="forbidden"))
281
+
282
+ def to_mcp_tool(
283
+ self,
284
+ *,
285
+ include_fastmcp_meta: bool | None = None,
286
+ **overrides: Any,
287
+ ) -> MCPTool:
288
+ """Convert the FastMCP tool to an MCP tool.
289
+
290
+ Extends the base implementation to add task execution mode if enabled.
291
+ """
292
+ # Get base MCP tool from parent
293
+ mcp_tool = super().to_mcp_tool(
294
+ include_fastmcp_meta=include_fastmcp_meta, **overrides
295
+ )
296
+
297
+ # Add task execution mode per SEP-1686
298
+ # Only set execution if not overridden and mode is not "forbidden"
299
+ if self.task_config.mode != "forbidden" and "execution" not in overrides:
300
+ mcp_tool.execution = ToolExecution(taskSupport=self.task_config.mode)
301
+
302
+ return mcp_tool
257
303
 
258
304
  @classmethod
259
305
  def from_function(
@@ -266,10 +312,11 @@ class FunctionTool(Tool):
266
312
  tags: set[str] | None = None,
267
313
  annotations: ToolAnnotations | None = None,
268
314
  exclude_args: list[str] | None = None,
269
- output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
315
+ output_schema: dict[str, Any] | NotSetT | None = NotSet,
270
316
  serializer: ToolResultSerializerType | None = None,
271
317
  meta: dict[str, Any] | None = None,
272
318
  enabled: bool | None = None,
319
+ task: bool | TaskConfig | None = None,
273
320
  ) -> FunctionTool:
274
321
  """Create a Tool from a function."""
275
322
  if exclude_args and fastmcp.settings.deprecation_warnings:
@@ -284,23 +331,24 @@ class FunctionTool(Tool):
284
331
  )
285
332
 
286
333
  parsed_fn = ParsedFunction.from_function(fn, exclude_args=exclude_args)
334
+ func_name = name or parsed_fn.name
287
335
 
288
- if name is None and parsed_fn.name == "<lambda>":
336
+ if func_name == "<lambda>":
289
337
  raise ValueError("You must provide a name for lambda functions")
290
338
 
339
+ # Normalize task to TaskConfig and validate
340
+ if task is None:
341
+ task_config = TaskConfig(mode="forbidden")
342
+ elif isinstance(task, bool):
343
+ task_config = TaskConfig.from_bool(task)
344
+ else:
345
+ task_config = task
346
+ task_config.validate_function(fn, func_name)
347
+
291
348
  if isinstance(output_schema, NotSetT):
292
349
  final_output_schema = parsed_fn.output_schema
293
- elif output_schema is False:
294
- # Handle False as deprecated synonym for None (deprecated in 2.11.4)
295
- if fastmcp.settings.deprecation_warnings:
296
- warnings.warn(
297
- "Passing output_schema=False is deprecated. Use output_schema=None instead.",
298
- DeprecationWarning,
299
- stacklevel=2,
300
- )
301
- final_output_schema = None
302
350
  else:
303
- # At this point output_schema is not NotSetT and not False, so it must be dict | None
351
+ # At this point output_schema is not NotSetT, so it must be dict | None
304
352
  final_output_schema = output_schema
305
353
  # Note: explicit schemas (dict) are used as-is without auto-wrapping
306
354
 
@@ -325,21 +373,14 @@ class FunctionTool(Tool):
325
373
  serializer=serializer,
326
374
  meta=meta,
327
375
  enabled=enabled if enabled is not None else True,
376
+ task_config=task_config,
328
377
  )
329
378
 
330
379
  async def run(self, arguments: dict[str, Any]) -> ToolResult:
331
380
  """Run the tool with arguments."""
332
- from fastmcp.server.context import Context
333
-
334
- arguments = arguments.copy()
335
-
336
- context_kwarg = find_kwarg_by_type(self.fn, kwarg_type=Context)
337
- if context_kwarg and context_kwarg not in arguments:
338
- arguments[context_kwarg] = get_context()
339
-
340
- type_adapter = get_cached_typeadapter(self.fn)
381
+ wrapper_fn = without_injected_parameters(self.fn)
382
+ type_adapter = get_cached_typeadapter(wrapper_fn)
341
383
  result = type_adapter.validate_python(arguments)
342
-
343
384
  if inspect.isawaitable(result):
344
385
  result = await result
345
386
 
@@ -409,8 +450,6 @@ class ParsedFunction:
409
450
  validate: bool = True,
410
451
  wrap_non_object_output_schema: bool = True,
411
452
  ) -> ParsedFunction:
412
- from fastmcp.server.context import Context
413
-
414
453
  if validate:
415
454
  sig = inspect.signature(fn)
416
455
  # Reject functions with *args or **kwargs
@@ -446,22 +485,19 @@ class ParsedFunction:
446
485
  if isinstance(fn, staticmethod):
447
486
  fn = fn.__func__
448
487
 
449
- prune_params: list[str] = []
450
- context_kwarg = find_kwarg_by_type(fn, kwarg_type=Context)
451
- if context_kwarg:
452
- prune_params.append(context_kwarg)
453
- if exclude_args:
454
- prune_params.extend(exclude_args)
488
+ # Handle injected parameters (Context, Docket dependencies)
489
+ wrapper_fn = without_injected_parameters(fn)
455
490
 
456
- # Create a function without excluded parameters in annotations
457
- # This prevents Pydantic from trying to serialize non-serializable types
458
- # before we can exclude them in compress_schema
459
- fn_for_typeadapter = fn
460
- if prune_params:
461
- fn_for_typeadapter = create_function_without_params(fn, prune_params)
491
+ # Also handle exclude_args with non-serializable types (issue #2431)
492
+ # This must happen before Pydantic tries to serialize the parameters
493
+ if exclude_args:
494
+ wrapper_fn = create_function_without_params(wrapper_fn, list(exclude_args))
462
495
 
463
- input_type_adapter = get_cached_typeadapter(fn_for_typeadapter)
496
+ input_type_adapter = get_cached_typeadapter(wrapper_fn)
464
497
  input_schema = input_type_adapter.json_schema()
498
+
499
+ # Compress and handle exclude_args
500
+ prune_params = list(exclude_args) if exclude_args else None
465
501
  input_schema = compress_schema(
466
502
  input_schema, prune_params=prune_params, prune_titles=True
467
503
  )
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import inspect
4
- import warnings
5
4
  from collections.abc import Callable
6
5
  from contextvars import ContextVar
7
6
  from copy import deepcopy
@@ -14,7 +13,6 @@ from pydantic import ConfigDict
14
13
  from pydantic.fields import Field
15
14
  from pydantic.functional_validators import BeforeValidator
16
15
 
17
- import fastmcp
18
16
  from fastmcp.tools.tool import ParsedFunction, Tool, ToolResult, _convert_to_content
19
17
  from fastmcp.utilities.components import _convert_set_default_none
20
18
  from fastmcp.utilities.json_schema import compress_schema
@@ -372,7 +370,7 @@ class TransformedTool(Tool):
372
370
  transform_fn: Callable[..., Any] | None = None,
373
371
  transform_args: dict[str, ArgTransform] | None = None,
374
372
  annotations: ToolAnnotations | NotSetT | None = NotSet,
375
- output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet,
373
+ output_schema: dict[str, Any] | NotSetT | None = NotSet,
376
374
  serializer: Callable[[Any], str] | NotSetT | None = NotSet,
377
375
  meta: dict[str, Any] | NotSetT | None = NotSet,
378
376
  enabled: bool | None = None,
@@ -487,15 +485,6 @@ class TransformedTool(Tool):
487
485
  final_output_schema = tool.output_schema
488
486
  else:
489
487
  final_output_schema = tool.output_schema
490
- elif output_schema is False:
491
- # Handle False as deprecated synonym for None (deprecated in 2.11.4)
492
- if fastmcp.settings.deprecation_warnings:
493
- warnings.warn(
494
- "Passing output_schema=False is deprecated. Use output_schema=None instead.",
495
- DeprecationWarning,
496
- stacklevel=2,
497
- )
498
- final_output_schema = None
499
488
  else:
500
489
  final_output_schema = cast(dict | None, output_schema)
501
490
 
@@ -96,7 +96,7 @@ class FastMCPComponent(FastMCPBaseModel):
96
96
 
97
97
  return meta or None
98
98
 
99
- def model_copy(
99
+ def model_copy( # type: ignore[override]
100
100
  self,
101
101
  *,
102
102
  update: dict[str, Any] | None = None,
@@ -137,7 +137,7 @@ class FastMCPComponent(FastMCPBaseModel):
137
137
  """Disable the component."""
138
138
  self.enabled = False
139
139
 
140
- def copy(self) -> Self:
140
+ def copy(self) -> Self: # type: ignore[override]
141
141
  """Create a copy of the component."""
142
142
  return self.model_copy()
143
143
 
@@ -173,7 +173,7 @@ class MirroredComponent(FastMCPComponent):
173
173
  )
174
174
  super().disable()
175
175
 
176
- def copy(self) -> Self:
176
+ def copy(self) -> Self: # type: ignore[override]
177
177
  """Create a copy of the component that can be modified."""
178
178
  # Create a copy and mark it as not mirrored
179
179
  copied = self.model_copy()
@@ -248,7 +248,7 @@ def _create_numeric_type(
248
248
  if v is not None
249
249
  }
250
250
 
251
- return Annotated[base, Field(**constraints)] if constraints else base
251
+ return Annotated[base, Field(**constraints)] if constraints else base # type: ignore[return-value]
252
252
 
253
253
 
254
254
  def _create_enum(name: str, values: list[Any]) -> type:
@@ -265,8 +265,8 @@ def _create_array_type(
265
265
  if isinstance(items, list):
266
266
  # Handle positional item schemas
267
267
  item_types = [_schema_to_type(s, schemas) for s in items]
268
- combined = Union[tuple(item_types)] # type: ignore # noqa: UP007
269
- base = list[combined]
268
+ combined = Union[tuple(item_types)] # type: ignore[arg-type] # noqa: UP007
269
+ base = list[combined] # type: ignore[valid-type]
270
270
  else:
271
271
  # Handle single item schema
272
272
  item_type = _schema_to_type(items, schemas)
@@ -282,7 +282,7 @@ def _create_array_type(
282
282
  if v is not None
283
283
  }
284
284
 
285
- return Annotated[base, Field(**constraints)] if constraints else base
285
+ return Annotated[base, Field(**constraints)] if constraints else base # type: ignore[return-value]
286
286
 
287
287
 
288
288
  def _return_Any() -> Any:
@@ -43,8 +43,7 @@ def mcp_server_type_to_servers_and_transports(
43
43
 
44
44
  if isinstance(mcp_server, TransformingRemoteMCPServer | TransformingStdioMCPServer):
45
45
  server, transport = mcp_server._to_server_and_underlying_transport(
46
- server_name=server_name,
47
- client_name=client_name,
46
+ server_name=server_name, client_name=client_name
48
47
  )
49
48
  else:
50
49
  transport = mcp_server.to_transport()
@@ -192,7 +192,7 @@ class MCPServerConfig(BaseModel):
192
192
  """
193
193
  if isinstance(v, dict):
194
194
  return FileSystemSource(**v)
195
- return v
195
+ return v # type: ignore[return-value]
196
196
 
197
197
  @field_validator("environment", mode="before")
198
198
  @classmethod