fastmcp 2.13.3__py3-none-any.whl → 2.14.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/__init__.py +0 -21
- fastmcp/cli/__init__.py +0 -3
- fastmcp/cli/__main__.py +5 -0
- fastmcp/cli/cli.py +8 -22
- fastmcp/cli/install/shared.py +0 -15
- fastmcp/cli/tasks.py +110 -0
- fastmcp/client/auth/oauth.py +9 -9
- fastmcp/client/client.py +739 -136
- fastmcp/client/elicitation.py +11 -5
- fastmcp/client/messages.py +7 -5
- fastmcp/client/roots.py +2 -1
- fastmcp/client/sampling/__init__.py +69 -0
- fastmcp/client/sampling/handlers/__init__.py +0 -0
- fastmcp/client/sampling/handlers/anthropic.py +387 -0
- fastmcp/client/sampling/handlers/openai.py +399 -0
- fastmcp/client/tasks.py +551 -0
- fastmcp/client/transports.py +72 -21
- fastmcp/contrib/component_manager/component_service.py +4 -20
- fastmcp/dependencies.py +25 -0
- fastmcp/experimental/sampling/handlers/__init__.py +5 -0
- fastmcp/experimental/sampling/handlers/openai.py +4 -169
- fastmcp/experimental/server/openapi/__init__.py +15 -13
- fastmcp/experimental/utilities/openapi/__init__.py +12 -38
- fastmcp/prompts/prompt.py +38 -38
- fastmcp/resources/resource.py +33 -16
- fastmcp/resources/template.py +69 -59
- fastmcp/server/auth/__init__.py +0 -9
- fastmcp/server/auth/auth.py +127 -3
- fastmcp/server/auth/oauth_proxy.py +47 -97
- fastmcp/server/auth/oidc_proxy.py +7 -0
- fastmcp/server/auth/providers/in_memory.py +2 -2
- fastmcp/server/auth/providers/oci.py +2 -2
- fastmcp/server/context.py +509 -180
- fastmcp/server/dependencies.py +464 -6
- fastmcp/server/elicitation.py +285 -47
- fastmcp/server/event_store.py +177 -0
- fastmcp/server/http.py +15 -3
- fastmcp/server/low_level.py +56 -12
- fastmcp/server/middleware/middleware.py +2 -2
- fastmcp/server/openapi/__init__.py +35 -0
- fastmcp/{experimental/server → server}/openapi/components.py +4 -3
- fastmcp/{experimental/server → server}/openapi/routing.py +1 -1
- fastmcp/{experimental/server → server}/openapi/server.py +6 -5
- fastmcp/server/proxy.py +53 -40
- fastmcp/server/sampling/__init__.py +10 -0
- fastmcp/server/sampling/run.py +301 -0
- fastmcp/server/sampling/sampling_tool.py +108 -0
- fastmcp/server/server.py +793 -552
- fastmcp/server/tasks/__init__.py +21 -0
- fastmcp/server/tasks/capabilities.py +22 -0
- fastmcp/server/tasks/config.py +89 -0
- fastmcp/server/tasks/converters.py +206 -0
- fastmcp/server/tasks/handlers.py +356 -0
- fastmcp/server/tasks/keys.py +93 -0
- fastmcp/server/tasks/protocol.py +355 -0
- fastmcp/server/tasks/subscriptions.py +205 -0
- fastmcp/settings.py +101 -103
- fastmcp/tools/tool.py +83 -49
- fastmcp/tools/tool_transform.py +1 -12
- fastmcp/utilities/components.py +3 -3
- fastmcp/utilities/json_schema_type.py +4 -4
- fastmcp/utilities/mcp_config.py +1 -2
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +1 -1
- fastmcp/{experimental/utilities → utilities}/openapi/README.md +7 -35
- fastmcp/utilities/openapi/__init__.py +63 -0
- fastmcp/{experimental/utilities → utilities}/openapi/formatters.py +5 -5
- fastmcp/{experimental/utilities → utilities}/openapi/json_schema_converter.py +1 -1
- fastmcp/utilities/tests.py +11 -5
- fastmcp/utilities/types.py +8 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/METADATA +7 -4
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/RECORD +79 -63
- fastmcp/client/sampling.py +0 -56
- fastmcp/experimental/sampling/handlers/base.py +0 -21
- fastmcp/server/auth/providers/bearer.py +0 -25
- fastmcp/server/openapi.py +0 -1087
- fastmcp/server/sampling/handler.py +0 -19
- fastmcp/utilities/openapi.py +0 -1568
- /fastmcp/{experimental/server → server}/openapi/README.md +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/director.py +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/models.py +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/parser.py +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/schemas.py +0 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/WHEEL +0 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.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
|
|
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
|
-
|
|
41
|
-
|
|
36
|
+
model_config = SettingsConfigDict(
|
|
37
|
+
env_prefix="FASTMCP_DOCKET_",
|
|
38
|
+
extra="ignore",
|
|
39
|
+
)
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
95
|
+
redelivery_timeout: Annotated[
|
|
96
|
+
timedelta,
|
|
75
97
|
Field(
|
|
76
98
|
description=inspect.cleandoc(
|
|
77
99
|
"""
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
] =
|
|
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 =
|
|
90
|
-
|
|
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.
|
|
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] |
|
|
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] |
|
|
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,41 +312,41 @@ 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] |
|
|
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:
|
|
276
323
|
warnings.warn(
|
|
277
|
-
"The `exclude_args` parameter
|
|
278
|
-
"
|
|
279
|
-
"
|
|
280
|
-
"`exclude_args` will continue to work until then. "
|
|
281
|
-
"See https://gofastmcp.com/docs/servers/tools for examples.",
|
|
324
|
+
"The `exclude_args` parameter is deprecated as of FastMCP 2.14. "
|
|
325
|
+
"Use dependency injection with `Depends()` instead for better lifecycle management. "
|
|
326
|
+
"See https://gofastmcp.com/servers/dependencies for examples.",
|
|
282
327
|
DeprecationWarning,
|
|
283
328
|
stacklevel=2,
|
|
284
329
|
)
|
|
285
330
|
|
|
286
331
|
parsed_fn = ParsedFunction.from_function(fn, exclude_args=exclude_args)
|
|
332
|
+
func_name = name or parsed_fn.name
|
|
287
333
|
|
|
288
|
-
if
|
|
334
|
+
if func_name == "<lambda>":
|
|
289
335
|
raise ValueError("You must provide a name for lambda functions")
|
|
290
336
|
|
|
337
|
+
# Normalize task to TaskConfig and validate
|
|
338
|
+
if task is None:
|
|
339
|
+
task_config = TaskConfig(mode="forbidden")
|
|
340
|
+
elif isinstance(task, bool):
|
|
341
|
+
task_config = TaskConfig.from_bool(task)
|
|
342
|
+
else:
|
|
343
|
+
task_config = task
|
|
344
|
+
task_config.validate_function(fn, func_name)
|
|
345
|
+
|
|
291
346
|
if isinstance(output_schema, NotSetT):
|
|
292
347
|
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
348
|
else:
|
|
303
|
-
# At this point output_schema is not NotSetT
|
|
349
|
+
# At this point output_schema is not NotSetT, so it must be dict | None
|
|
304
350
|
final_output_schema = output_schema
|
|
305
351
|
# Note: explicit schemas (dict) are used as-is without auto-wrapping
|
|
306
352
|
|
|
@@ -325,21 +371,14 @@ class FunctionTool(Tool):
|
|
|
325
371
|
serializer=serializer,
|
|
326
372
|
meta=meta,
|
|
327
373
|
enabled=enabled if enabled is not None else True,
|
|
374
|
+
task_config=task_config,
|
|
328
375
|
)
|
|
329
376
|
|
|
330
377
|
async def run(self, arguments: dict[str, Any]) -> ToolResult:
|
|
331
378
|
"""Run the tool with arguments."""
|
|
332
|
-
|
|
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)
|
|
379
|
+
wrapper_fn = without_injected_parameters(self.fn)
|
|
380
|
+
type_adapter = get_cached_typeadapter(wrapper_fn)
|
|
341
381
|
result = type_adapter.validate_python(arguments)
|
|
342
|
-
|
|
343
382
|
if inspect.isawaitable(result):
|
|
344
383
|
result = await result
|
|
345
384
|
|
|
@@ -409,8 +448,6 @@ class ParsedFunction:
|
|
|
409
448
|
validate: bool = True,
|
|
410
449
|
wrap_non_object_output_schema: bool = True,
|
|
411
450
|
) -> ParsedFunction:
|
|
412
|
-
from fastmcp.server.context import Context
|
|
413
|
-
|
|
414
451
|
if validate:
|
|
415
452
|
sig = inspect.signature(fn)
|
|
416
453
|
# Reject functions with *args or **kwargs
|
|
@@ -446,22 +483,19 @@ class ParsedFunction:
|
|
|
446
483
|
if isinstance(fn, staticmethod):
|
|
447
484
|
fn = fn.__func__
|
|
448
485
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if context_kwarg:
|
|
452
|
-
prune_params.append(context_kwarg)
|
|
453
|
-
if exclude_args:
|
|
454
|
-
prune_params.extend(exclude_args)
|
|
486
|
+
# Handle injected parameters (Context, Docket dependencies)
|
|
487
|
+
wrapper_fn = without_injected_parameters(fn)
|
|
455
488
|
|
|
456
|
-
#
|
|
457
|
-
# This
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if prune_params:
|
|
461
|
-
fn_for_typeadapter = create_function_without_params(fn, prune_params)
|
|
489
|
+
# Also handle exclude_args with non-serializable types (issue #2431)
|
|
490
|
+
# This must happen before Pydantic tries to serialize the parameters
|
|
491
|
+
if exclude_args:
|
|
492
|
+
wrapper_fn = create_function_without_params(wrapper_fn, list(exclude_args))
|
|
462
493
|
|
|
463
|
-
input_type_adapter = get_cached_typeadapter(
|
|
494
|
+
input_type_adapter = get_cached_typeadapter(wrapper_fn)
|
|
464
495
|
input_schema = input_type_adapter.json_schema()
|
|
496
|
+
|
|
497
|
+
# Compress and handle exclude_args
|
|
498
|
+
prune_params = list(exclude_args) if exclude_args else None
|
|
465
499
|
input_schema = compress_schema(
|
|
466
500
|
input_schema, prune_params=prune_params, prune_titles=True
|
|
467
501
|
)
|
fastmcp/tools/tool_transform.py
CHANGED
|
@@ -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] |
|
|
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
|
|
fastmcp/utilities/components.py
CHANGED
|
@@ -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:
|
fastmcp/utilities/mcp_config.py
CHANGED
|
@@ -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()
|