fastmcp 2.14.4__py3-none-any.whl → 3.0.0b1__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/_vendor/__init__.py +1 -0
- fastmcp/_vendor/docket_di/README.md +7 -0
- fastmcp/_vendor/docket_di/__init__.py +163 -0
- fastmcp/cli/cli.py +112 -28
- fastmcp/cli/install/claude_code.py +1 -5
- fastmcp/cli/install/claude_desktop.py +1 -5
- fastmcp/cli/install/cursor.py +1 -5
- fastmcp/cli/install/gemini_cli.py +1 -5
- fastmcp/cli/install/mcp_json.py +1 -6
- fastmcp/cli/run.py +146 -5
- fastmcp/client/__init__.py +7 -9
- fastmcp/client/auth/oauth.py +18 -17
- fastmcp/client/client.py +100 -870
- fastmcp/client/elicitation.py +1 -1
- fastmcp/client/mixins/__init__.py +13 -0
- fastmcp/client/mixins/prompts.py +295 -0
- fastmcp/client/mixins/resources.py +325 -0
- fastmcp/client/mixins/task_management.py +157 -0
- fastmcp/client/mixins/tools.py +397 -0
- fastmcp/client/sampling/handlers/anthropic.py +2 -2
- fastmcp/client/sampling/handlers/openai.py +1 -1
- fastmcp/client/tasks.py +3 -3
- fastmcp/client/telemetry.py +47 -0
- fastmcp/client/transports/__init__.py +38 -0
- fastmcp/client/transports/base.py +82 -0
- fastmcp/client/transports/config.py +170 -0
- fastmcp/client/transports/http.py +145 -0
- fastmcp/client/transports/inference.py +154 -0
- fastmcp/client/transports/memory.py +90 -0
- fastmcp/client/transports/sse.py +89 -0
- fastmcp/client/transports/stdio.py +543 -0
- fastmcp/contrib/component_manager/README.md +4 -10
- fastmcp/contrib/component_manager/__init__.py +1 -2
- fastmcp/contrib/component_manager/component_manager.py +95 -160
- fastmcp/contrib/component_manager/example.py +1 -1
- fastmcp/contrib/mcp_mixin/example.py +4 -4
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
- fastmcp/decorators.py +41 -0
- fastmcp/dependencies.py +12 -1
- fastmcp/exceptions.py +4 -0
- fastmcp/experimental/server/openapi/__init__.py +18 -15
- fastmcp/mcp_config.py +13 -4
- fastmcp/prompts/__init__.py +6 -3
- fastmcp/prompts/function_prompt.py +465 -0
- fastmcp/prompts/prompt.py +321 -271
- fastmcp/resources/__init__.py +5 -3
- fastmcp/resources/function_resource.py +335 -0
- fastmcp/resources/resource.py +325 -115
- fastmcp/resources/template.py +215 -43
- fastmcp/resources/types.py +27 -12
- fastmcp/server/__init__.py +2 -2
- fastmcp/server/auth/__init__.py +14 -0
- fastmcp/server/auth/auth.py +30 -10
- fastmcp/server/auth/authorization.py +190 -0
- fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
- fastmcp/server/auth/oauth_proxy/consent.py +361 -0
- fastmcp/server/auth/oauth_proxy/models.py +178 -0
- fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
- fastmcp/server/auth/oauth_proxy/ui.py +277 -0
- fastmcp/server/auth/oidc_proxy.py +2 -2
- fastmcp/server/auth/providers/auth0.py +24 -94
- fastmcp/server/auth/providers/aws.py +26 -95
- fastmcp/server/auth/providers/azure.py +41 -129
- fastmcp/server/auth/providers/descope.py +18 -49
- fastmcp/server/auth/providers/discord.py +25 -86
- fastmcp/server/auth/providers/github.py +23 -87
- fastmcp/server/auth/providers/google.py +24 -87
- fastmcp/server/auth/providers/introspection.py +60 -79
- fastmcp/server/auth/providers/jwt.py +30 -67
- fastmcp/server/auth/providers/oci.py +47 -110
- fastmcp/server/auth/providers/scalekit.py +23 -61
- fastmcp/server/auth/providers/supabase.py +18 -47
- fastmcp/server/auth/providers/workos.py +34 -127
- fastmcp/server/context.py +372 -419
- fastmcp/server/dependencies.py +541 -251
- fastmcp/server/elicitation.py +20 -18
- fastmcp/server/event_store.py +3 -3
- fastmcp/server/http.py +16 -6
- fastmcp/server/lifespan.py +198 -0
- fastmcp/server/low_level.py +92 -2
- fastmcp/server/middleware/__init__.py +5 -1
- fastmcp/server/middleware/authorization.py +312 -0
- fastmcp/server/middleware/caching.py +101 -54
- fastmcp/server/middleware/middleware.py +6 -9
- fastmcp/server/middleware/ping.py +70 -0
- fastmcp/server/middleware/tool_injection.py +2 -2
- fastmcp/server/mixins/__init__.py +7 -0
- fastmcp/server/mixins/lifespan.py +217 -0
- fastmcp/server/mixins/mcp_operations.py +392 -0
- fastmcp/server/mixins/transport.py +342 -0
- fastmcp/server/openapi/__init__.py +41 -21
- fastmcp/server/openapi/components.py +16 -339
- fastmcp/server/openapi/routing.py +34 -118
- fastmcp/server/openapi/server.py +67 -392
- fastmcp/server/providers/__init__.py +71 -0
- fastmcp/server/providers/aggregate.py +261 -0
- fastmcp/server/providers/base.py +578 -0
- fastmcp/server/providers/fastmcp_provider.py +674 -0
- fastmcp/server/providers/filesystem.py +226 -0
- fastmcp/server/providers/filesystem_discovery.py +327 -0
- fastmcp/server/providers/local_provider/__init__.py +11 -0
- fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
- fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
- fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
- fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
- fastmcp/server/providers/local_provider/local_provider.py +465 -0
- fastmcp/server/providers/openapi/__init__.py +39 -0
- fastmcp/server/providers/openapi/components.py +332 -0
- fastmcp/server/providers/openapi/provider.py +405 -0
- fastmcp/server/providers/openapi/routing.py +109 -0
- fastmcp/server/providers/proxy.py +867 -0
- fastmcp/server/providers/skills/__init__.py +59 -0
- fastmcp/server/providers/skills/_common.py +101 -0
- fastmcp/server/providers/skills/claude_provider.py +44 -0
- fastmcp/server/providers/skills/directory_provider.py +153 -0
- fastmcp/server/providers/skills/skill_provider.py +432 -0
- fastmcp/server/providers/skills/vendor_providers.py +142 -0
- fastmcp/server/providers/wrapped_provider.py +140 -0
- fastmcp/server/proxy.py +34 -700
- fastmcp/server/sampling/run.py +341 -2
- fastmcp/server/sampling/sampling_tool.py +4 -3
- fastmcp/server/server.py +1214 -2171
- fastmcp/server/tasks/__init__.py +2 -1
- fastmcp/server/tasks/capabilities.py +13 -1
- fastmcp/server/tasks/config.py +66 -3
- fastmcp/server/tasks/handlers.py +65 -273
- fastmcp/server/tasks/keys.py +4 -6
- fastmcp/server/tasks/requests.py +474 -0
- fastmcp/server/tasks/routing.py +76 -0
- fastmcp/server/tasks/subscriptions.py +20 -11
- fastmcp/server/telemetry.py +131 -0
- fastmcp/server/transforms/__init__.py +244 -0
- fastmcp/server/transforms/namespace.py +193 -0
- fastmcp/server/transforms/prompts_as_tools.py +175 -0
- fastmcp/server/transforms/resources_as_tools.py +190 -0
- fastmcp/server/transforms/tool_transform.py +96 -0
- fastmcp/server/transforms/version_filter.py +124 -0
- fastmcp/server/transforms/visibility.py +526 -0
- fastmcp/settings.py +34 -96
- fastmcp/telemetry.py +122 -0
- fastmcp/tools/__init__.py +10 -3
- fastmcp/tools/function_parsing.py +201 -0
- fastmcp/tools/function_tool.py +467 -0
- fastmcp/tools/tool.py +215 -362
- fastmcp/tools/tool_transform.py +38 -21
- fastmcp/utilities/async_utils.py +69 -0
- fastmcp/utilities/components.py +152 -91
- fastmcp/utilities/inspect.py +8 -20
- fastmcp/utilities/json_schema.py +12 -5
- fastmcp/utilities/json_schema_type.py +17 -15
- fastmcp/utilities/lifespan.py +56 -0
- fastmcp/utilities/logging.py +12 -4
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
- fastmcp/utilities/openapi/parser.py +3 -3
- fastmcp/utilities/pagination.py +80 -0
- fastmcp/utilities/skills.py +253 -0
- fastmcp/utilities/tests.py +0 -16
- fastmcp/utilities/timeout.py +47 -0
- fastmcp/utilities/types.py +1 -1
- fastmcp/utilities/versions.py +285 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
- fastmcp-3.0.0b1.dist-info/RECORD +228 -0
- fastmcp/client/transports.py +0 -1170
- fastmcp/contrib/component_manager/component_service.py +0 -209
- fastmcp/prompts/prompt_manager.py +0 -117
- fastmcp/resources/resource_manager.py +0 -338
- fastmcp/server/tasks/converters.py +0 -206
- fastmcp/server/tasks/protocol.py +0 -359
- fastmcp/tools/tool_manager.py +0 -170
- fastmcp/utilities/mcp_config.py +0 -56
- fastmcp-2.14.4.dist-info/RECORD +0 -161
- /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
"""Standalone @tool decorator for FastMCP."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import warnings
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import (
|
|
10
|
+
TYPE_CHECKING,
|
|
11
|
+
Any,
|
|
12
|
+
Literal,
|
|
13
|
+
Protocol,
|
|
14
|
+
TypeVar,
|
|
15
|
+
overload,
|
|
16
|
+
runtime_checkable,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
import anyio
|
|
20
|
+
import mcp.types
|
|
21
|
+
from mcp.shared.exceptions import McpError
|
|
22
|
+
from mcp.types import ErrorData, Icon, ToolAnnotations, ToolExecution
|
|
23
|
+
|
|
24
|
+
import fastmcp
|
|
25
|
+
from fastmcp.decorators import resolve_task_config
|
|
26
|
+
from fastmcp.server.dependencies import without_injected_parameters
|
|
27
|
+
from fastmcp.server.tasks.config import TaskConfig
|
|
28
|
+
from fastmcp.tools.function_parsing import ParsedFunction, _is_object_schema
|
|
29
|
+
from fastmcp.tools.tool import (
|
|
30
|
+
AuthCheckCallable,
|
|
31
|
+
Tool,
|
|
32
|
+
ToolResult,
|
|
33
|
+
ToolResultSerializerType,
|
|
34
|
+
)
|
|
35
|
+
from fastmcp.utilities.async_utils import call_sync_fn_in_threadpool
|
|
36
|
+
from fastmcp.utilities.logging import get_logger
|
|
37
|
+
from fastmcp.utilities.types import (
|
|
38
|
+
NotSet,
|
|
39
|
+
NotSetT,
|
|
40
|
+
get_cached_typeadapter,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
logger = get_logger(__name__)
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from docket import Docket
|
|
47
|
+
from docket.execution import Execution
|
|
48
|
+
|
|
49
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@runtime_checkable
|
|
53
|
+
class DecoratedTool(Protocol):
|
|
54
|
+
"""Protocol for functions decorated with @tool."""
|
|
55
|
+
|
|
56
|
+
__fastmcp__: ToolMeta
|
|
57
|
+
|
|
58
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True, kw_only=True)
|
|
62
|
+
class ToolMeta:
|
|
63
|
+
"""Metadata attached to functions by the @tool decorator."""
|
|
64
|
+
|
|
65
|
+
type: Literal["tool"] = field(default="tool", init=False)
|
|
66
|
+
name: str | None = None
|
|
67
|
+
version: str | int | None = None
|
|
68
|
+
title: str | None = None
|
|
69
|
+
description: str | None = None
|
|
70
|
+
icons: list[Icon] | None = None
|
|
71
|
+
tags: set[str] | None = None
|
|
72
|
+
output_schema: dict[str, Any] | NotSetT | None = NotSet
|
|
73
|
+
annotations: ToolAnnotations | None = None
|
|
74
|
+
meta: dict[str, Any] | None = None
|
|
75
|
+
task: bool | TaskConfig | None = None
|
|
76
|
+
exclude_args: list[str] | None = None
|
|
77
|
+
serializer: Any | None = None
|
|
78
|
+
timeout: float | None = None
|
|
79
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None
|
|
80
|
+
enabled: bool = True
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class FunctionTool(Tool):
|
|
84
|
+
fn: Callable[..., Any]
|
|
85
|
+
|
|
86
|
+
def to_mcp_tool(
|
|
87
|
+
self,
|
|
88
|
+
**overrides: Any,
|
|
89
|
+
) -> mcp.types.Tool:
|
|
90
|
+
"""Convert the FastMCP tool to an MCP tool.
|
|
91
|
+
|
|
92
|
+
Extends the base implementation to add task execution mode if enabled.
|
|
93
|
+
"""
|
|
94
|
+
# Get base MCP tool from parent
|
|
95
|
+
mcp_tool = super().to_mcp_tool(**overrides)
|
|
96
|
+
|
|
97
|
+
# Add task execution mode per SEP-1686
|
|
98
|
+
# Only set execution if not overridden and task execution is supported
|
|
99
|
+
if self.task_config.supports_tasks() and "execution" not in overrides:
|
|
100
|
+
mcp_tool.execution = ToolExecution(taskSupport=self.task_config.mode)
|
|
101
|
+
|
|
102
|
+
return mcp_tool
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def from_function(
|
|
106
|
+
cls,
|
|
107
|
+
fn: Callable[..., Any],
|
|
108
|
+
*,
|
|
109
|
+
metadata: ToolMeta | None = None,
|
|
110
|
+
# Keep individual params for backwards compat
|
|
111
|
+
name: str | None = None,
|
|
112
|
+
version: str | int | None = None,
|
|
113
|
+
title: str | None = None,
|
|
114
|
+
description: str | None = None,
|
|
115
|
+
icons: list[Icon] | None = None,
|
|
116
|
+
tags: set[str] | None = None,
|
|
117
|
+
annotations: ToolAnnotations | None = None,
|
|
118
|
+
exclude_args: list[str] | None = None,
|
|
119
|
+
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
|
120
|
+
serializer: ToolResultSerializerType | None = None,
|
|
121
|
+
meta: dict[str, Any] | None = None,
|
|
122
|
+
task: bool | TaskConfig | None = None,
|
|
123
|
+
timeout: float | None = None,
|
|
124
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
|
|
125
|
+
) -> FunctionTool:
|
|
126
|
+
"""Create a FunctionTool from a function.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
fn: The function to wrap
|
|
130
|
+
metadata: ToolMeta object with all configuration. If provided,
|
|
131
|
+
individual parameters must not be passed.
|
|
132
|
+
name, title, etc.: Individual parameters for backwards compatibility.
|
|
133
|
+
Cannot be used together with metadata parameter.
|
|
134
|
+
"""
|
|
135
|
+
# Check mutual exclusion
|
|
136
|
+
individual_params_provided = (
|
|
137
|
+
any(
|
|
138
|
+
x is not None and x is not NotSet
|
|
139
|
+
for x in [
|
|
140
|
+
name,
|
|
141
|
+
version,
|
|
142
|
+
title,
|
|
143
|
+
description,
|
|
144
|
+
icons,
|
|
145
|
+
tags,
|
|
146
|
+
annotations,
|
|
147
|
+
meta,
|
|
148
|
+
task,
|
|
149
|
+
serializer,
|
|
150
|
+
timeout,
|
|
151
|
+
auth,
|
|
152
|
+
]
|
|
153
|
+
)
|
|
154
|
+
or output_schema is not NotSet
|
|
155
|
+
or exclude_args is not None
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if metadata is not None and individual_params_provided:
|
|
159
|
+
raise TypeError(
|
|
160
|
+
"Cannot pass both 'metadata' and individual parameters to from_function(). "
|
|
161
|
+
"Use metadata alone or individual parameters alone."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Build metadata from kwargs if not provided
|
|
165
|
+
if metadata is None:
|
|
166
|
+
metadata = ToolMeta(
|
|
167
|
+
name=name,
|
|
168
|
+
version=version,
|
|
169
|
+
title=title,
|
|
170
|
+
description=description,
|
|
171
|
+
icons=icons,
|
|
172
|
+
tags=tags,
|
|
173
|
+
output_schema=output_schema,
|
|
174
|
+
annotations=annotations,
|
|
175
|
+
meta=meta,
|
|
176
|
+
task=task,
|
|
177
|
+
exclude_args=exclude_args,
|
|
178
|
+
serializer=serializer,
|
|
179
|
+
timeout=timeout,
|
|
180
|
+
auth=auth,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if metadata.serializer is not None and fastmcp.settings.deprecation_warnings:
|
|
184
|
+
warnings.warn(
|
|
185
|
+
"The `serializer` parameter is deprecated. "
|
|
186
|
+
"Return ToolResult from your tools for full control over serialization. "
|
|
187
|
+
"See https://gofastmcp.com/servers/tools#custom-serialization for migration examples.",
|
|
188
|
+
DeprecationWarning,
|
|
189
|
+
stacklevel=2,
|
|
190
|
+
)
|
|
191
|
+
if metadata.exclude_args and fastmcp.settings.deprecation_warnings:
|
|
192
|
+
warnings.warn(
|
|
193
|
+
"The `exclude_args` parameter is deprecated as of FastMCP 2.14. "
|
|
194
|
+
"Use dependency injection with `Depends()` instead for better lifecycle management. "
|
|
195
|
+
"See https://gofastmcp.com/servers/dependencies for examples.",
|
|
196
|
+
DeprecationWarning,
|
|
197
|
+
stacklevel=2,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
parsed_fn = ParsedFunction.from_function(fn, exclude_args=metadata.exclude_args)
|
|
201
|
+
func_name = metadata.name or parsed_fn.name
|
|
202
|
+
|
|
203
|
+
if func_name == "<lambda>":
|
|
204
|
+
raise ValueError("You must provide a name for lambda functions")
|
|
205
|
+
|
|
206
|
+
# Normalize task to TaskConfig
|
|
207
|
+
task_value = metadata.task
|
|
208
|
+
if task_value is None:
|
|
209
|
+
task_config = TaskConfig(mode="forbidden")
|
|
210
|
+
elif isinstance(task_value, bool):
|
|
211
|
+
task_config = TaskConfig.from_bool(task_value)
|
|
212
|
+
else:
|
|
213
|
+
task_config = task_value
|
|
214
|
+
task_config.validate_function(fn, func_name)
|
|
215
|
+
|
|
216
|
+
# Handle output_schema
|
|
217
|
+
if isinstance(metadata.output_schema, NotSetT):
|
|
218
|
+
final_output_schema = parsed_fn.output_schema
|
|
219
|
+
else:
|
|
220
|
+
final_output_schema = metadata.output_schema
|
|
221
|
+
|
|
222
|
+
if final_output_schema is not None and isinstance(final_output_schema, dict):
|
|
223
|
+
if not _is_object_schema(final_output_schema):
|
|
224
|
+
raise ValueError(
|
|
225
|
+
f"Output schemas must represent object types due to MCP spec limitations. "
|
|
226
|
+
f"Received: {final_output_schema!r}"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return cls(
|
|
230
|
+
fn=parsed_fn.fn,
|
|
231
|
+
name=metadata.name or parsed_fn.name,
|
|
232
|
+
version=str(metadata.version) if metadata.version is not None else None,
|
|
233
|
+
title=metadata.title,
|
|
234
|
+
description=metadata.description or parsed_fn.description,
|
|
235
|
+
icons=metadata.icons,
|
|
236
|
+
parameters=parsed_fn.input_schema,
|
|
237
|
+
output_schema=final_output_schema,
|
|
238
|
+
annotations=metadata.annotations,
|
|
239
|
+
tags=metadata.tags or set(),
|
|
240
|
+
serializer=metadata.serializer,
|
|
241
|
+
meta=metadata.meta,
|
|
242
|
+
task_config=task_config,
|
|
243
|
+
timeout=metadata.timeout,
|
|
244
|
+
auth=metadata.auth,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
async def run(self, arguments: dict[str, Any]) -> ToolResult:
|
|
248
|
+
"""Run the tool with arguments."""
|
|
249
|
+
wrapper_fn = without_injected_parameters(self.fn)
|
|
250
|
+
type_adapter = get_cached_typeadapter(wrapper_fn)
|
|
251
|
+
|
|
252
|
+
# Apply timeout if configured
|
|
253
|
+
if self.timeout is not None:
|
|
254
|
+
try:
|
|
255
|
+
with anyio.fail_after(self.timeout):
|
|
256
|
+
# Thread pool execution for sync functions, direct await for async
|
|
257
|
+
if inspect.iscoroutinefunction(wrapper_fn):
|
|
258
|
+
result = await type_adapter.validate_python(arguments)
|
|
259
|
+
else:
|
|
260
|
+
# Sync function: run in threadpool to avoid blocking
|
|
261
|
+
result = await call_sync_fn_in_threadpool(
|
|
262
|
+
type_adapter.validate_python, arguments
|
|
263
|
+
)
|
|
264
|
+
# Handle sync wrappers that return awaitables
|
|
265
|
+
if inspect.isawaitable(result):
|
|
266
|
+
result = await result
|
|
267
|
+
except TimeoutError:
|
|
268
|
+
logger.warning(
|
|
269
|
+
f"Tool '{self.name}' timed out after {self.timeout}s. "
|
|
270
|
+
f"Consider using task=True for long-running operations. "
|
|
271
|
+
f"See https://gofastmcp.com/servers/tasks"
|
|
272
|
+
)
|
|
273
|
+
raise McpError(
|
|
274
|
+
ErrorData(
|
|
275
|
+
code=-32000,
|
|
276
|
+
message=f"Tool '{self.name}' execution timed out after {self.timeout}s",
|
|
277
|
+
)
|
|
278
|
+
) from None
|
|
279
|
+
else:
|
|
280
|
+
# No timeout: use existing execution path
|
|
281
|
+
if inspect.iscoroutinefunction(wrapper_fn):
|
|
282
|
+
result = await type_adapter.validate_python(arguments)
|
|
283
|
+
else:
|
|
284
|
+
result = await call_sync_fn_in_threadpool(
|
|
285
|
+
type_adapter.validate_python, arguments
|
|
286
|
+
)
|
|
287
|
+
if inspect.isawaitable(result):
|
|
288
|
+
result = await result
|
|
289
|
+
|
|
290
|
+
return self.convert_result(result)
|
|
291
|
+
|
|
292
|
+
def register_with_docket(self, docket: Docket) -> None:
|
|
293
|
+
"""Register this tool with docket for background execution.
|
|
294
|
+
|
|
295
|
+
FunctionTool registers the underlying function, which has the user's
|
|
296
|
+
Depends parameters for docket to resolve.
|
|
297
|
+
"""
|
|
298
|
+
if not self.task_config.supports_tasks():
|
|
299
|
+
return
|
|
300
|
+
docket.register(self.fn, names=[self.key])
|
|
301
|
+
|
|
302
|
+
async def add_to_docket(
|
|
303
|
+
self,
|
|
304
|
+
docket: Docket,
|
|
305
|
+
arguments: dict[str, Any],
|
|
306
|
+
*,
|
|
307
|
+
fn_key: str | None = None,
|
|
308
|
+
task_key: str | None = None,
|
|
309
|
+
**kwargs: Any,
|
|
310
|
+
) -> Execution:
|
|
311
|
+
"""Schedule this tool for background execution via docket.
|
|
312
|
+
|
|
313
|
+
FunctionTool splats the arguments dict since .fn expects **kwargs.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
docket: The Docket instance
|
|
317
|
+
arguments: Tool arguments
|
|
318
|
+
fn_key: Function lookup key in Docket registry (defaults to self.key)
|
|
319
|
+
task_key: Redis storage key for the result
|
|
320
|
+
**kwargs: Additional kwargs passed to docket.add()
|
|
321
|
+
"""
|
|
322
|
+
lookup_key = fn_key or self.key
|
|
323
|
+
if task_key:
|
|
324
|
+
kwargs["key"] = task_key
|
|
325
|
+
return await docket.add(lookup_key, **kwargs)(**arguments)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@overload
|
|
329
|
+
def tool(fn: F) -> F: ...
|
|
330
|
+
@overload
|
|
331
|
+
def tool(
|
|
332
|
+
name_or_fn: str,
|
|
333
|
+
*,
|
|
334
|
+
version: str | int | None = None,
|
|
335
|
+
title: str | None = None,
|
|
336
|
+
description: str | None = None,
|
|
337
|
+
icons: list[Icon] | None = None,
|
|
338
|
+
tags: set[str] | None = None,
|
|
339
|
+
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
|
340
|
+
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
341
|
+
meta: dict[str, Any] | None = None,
|
|
342
|
+
task: bool | TaskConfig | None = None,
|
|
343
|
+
exclude_args: list[str] | None = None,
|
|
344
|
+
serializer: Any | None = None,
|
|
345
|
+
timeout: float | None = None,
|
|
346
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
|
|
347
|
+
) -> Callable[[F], F]: ...
|
|
348
|
+
@overload
|
|
349
|
+
def tool(
|
|
350
|
+
name_or_fn: None = None,
|
|
351
|
+
*,
|
|
352
|
+
name: str | None = None,
|
|
353
|
+
version: str | int | None = None,
|
|
354
|
+
title: str | None = None,
|
|
355
|
+
description: str | None = None,
|
|
356
|
+
icons: list[Icon] | None = None,
|
|
357
|
+
tags: set[str] | None = None,
|
|
358
|
+
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
|
359
|
+
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
360
|
+
meta: dict[str, Any] | None = None,
|
|
361
|
+
task: bool | TaskConfig | None = None,
|
|
362
|
+
exclude_args: list[str] | None = None,
|
|
363
|
+
serializer: Any | None = None,
|
|
364
|
+
timeout: float | None = None,
|
|
365
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
|
|
366
|
+
) -> Callable[[F], F]: ...
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def tool(
|
|
370
|
+
name_or_fn: str | Callable[..., Any] | None = None,
|
|
371
|
+
*,
|
|
372
|
+
name: str | None = None,
|
|
373
|
+
version: str | int | None = None,
|
|
374
|
+
title: str | None = None,
|
|
375
|
+
description: str | None = None,
|
|
376
|
+
icons: list[Icon] | None = None,
|
|
377
|
+
tags: set[str] | None = None,
|
|
378
|
+
output_schema: dict[str, Any] | NotSetT | None = NotSet,
|
|
379
|
+
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
380
|
+
meta: dict[str, Any] | None = None,
|
|
381
|
+
task: bool | TaskConfig | None = None,
|
|
382
|
+
exclude_args: list[str] | None = None,
|
|
383
|
+
serializer: Any | None = None,
|
|
384
|
+
timeout: float | None = None,
|
|
385
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
|
|
386
|
+
) -> Any:
|
|
387
|
+
"""Standalone decorator to mark a function as an MCP tool.
|
|
388
|
+
|
|
389
|
+
Returns the original function with metadata attached. Register with a server
|
|
390
|
+
using mcp.add_tool().
|
|
391
|
+
"""
|
|
392
|
+
if isinstance(annotations, dict):
|
|
393
|
+
annotations = ToolAnnotations(**annotations)
|
|
394
|
+
|
|
395
|
+
if isinstance(name_or_fn, classmethod):
|
|
396
|
+
raise TypeError(
|
|
397
|
+
"To decorate a classmethod, use @classmethod above @tool. "
|
|
398
|
+
"See https://gofastmcp.com/servers/tools#using-with-methods"
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
def create_tool(fn: Callable[..., Any], tool_name: str | None) -> FunctionTool:
|
|
402
|
+
# Create metadata first, then pass it
|
|
403
|
+
tool_meta = ToolMeta(
|
|
404
|
+
name=tool_name,
|
|
405
|
+
version=version,
|
|
406
|
+
title=title,
|
|
407
|
+
description=description,
|
|
408
|
+
icons=icons,
|
|
409
|
+
tags=tags,
|
|
410
|
+
output_schema=output_schema,
|
|
411
|
+
annotations=annotations,
|
|
412
|
+
meta=meta,
|
|
413
|
+
task=resolve_task_config(task),
|
|
414
|
+
exclude_args=exclude_args,
|
|
415
|
+
serializer=serializer,
|
|
416
|
+
timeout=timeout,
|
|
417
|
+
auth=auth,
|
|
418
|
+
)
|
|
419
|
+
return FunctionTool.from_function(fn, metadata=tool_meta)
|
|
420
|
+
|
|
421
|
+
def attach_metadata(fn: F, tool_name: str | None) -> F:
|
|
422
|
+
metadata = ToolMeta(
|
|
423
|
+
name=tool_name,
|
|
424
|
+
version=version,
|
|
425
|
+
title=title,
|
|
426
|
+
description=description,
|
|
427
|
+
icons=icons,
|
|
428
|
+
tags=tags,
|
|
429
|
+
output_schema=output_schema,
|
|
430
|
+
annotations=annotations,
|
|
431
|
+
meta=meta,
|
|
432
|
+
task=task,
|
|
433
|
+
exclude_args=exclude_args,
|
|
434
|
+
serializer=serializer,
|
|
435
|
+
timeout=timeout,
|
|
436
|
+
auth=auth,
|
|
437
|
+
)
|
|
438
|
+
target = fn.__func__ if hasattr(fn, "__func__") else fn
|
|
439
|
+
target.__fastmcp__ = metadata
|
|
440
|
+
return fn
|
|
441
|
+
|
|
442
|
+
def decorator(fn: F, tool_name: str | None) -> F:
|
|
443
|
+
if fastmcp.settings.decorator_mode == "object":
|
|
444
|
+
warnings.warn(
|
|
445
|
+
"decorator_mode='object' is deprecated and will be removed in a future version. "
|
|
446
|
+
"Decorators now return the original function with metadata attached.",
|
|
447
|
+
DeprecationWarning,
|
|
448
|
+
stacklevel=4,
|
|
449
|
+
)
|
|
450
|
+
return create_tool(fn, tool_name) # type: ignore[return-value]
|
|
451
|
+
return attach_metadata(fn, tool_name)
|
|
452
|
+
|
|
453
|
+
if inspect.isroutine(name_or_fn):
|
|
454
|
+
return decorator(name_or_fn, name)
|
|
455
|
+
elif isinstance(name_or_fn, str):
|
|
456
|
+
if name is not None:
|
|
457
|
+
raise TypeError("Cannot specify name both as first argument and keyword")
|
|
458
|
+
tool_name = name_or_fn
|
|
459
|
+
elif name_or_fn is None:
|
|
460
|
+
tool_name = name
|
|
461
|
+
else:
|
|
462
|
+
raise TypeError(f"Invalid first argument: {type(name_or_fn)}")
|
|
463
|
+
|
|
464
|
+
def wrapper(fn: F) -> F:
|
|
465
|
+
return decorator(fn, tool_name)
|
|
466
|
+
|
|
467
|
+
return wrapper
|