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.
Files changed (175) hide show
  1. fastmcp/_vendor/__init__.py +1 -0
  2. fastmcp/_vendor/docket_di/README.md +7 -0
  3. fastmcp/_vendor/docket_di/__init__.py +163 -0
  4. fastmcp/cli/cli.py +112 -28
  5. fastmcp/cli/install/claude_code.py +1 -5
  6. fastmcp/cli/install/claude_desktop.py +1 -5
  7. fastmcp/cli/install/cursor.py +1 -5
  8. fastmcp/cli/install/gemini_cli.py +1 -5
  9. fastmcp/cli/install/mcp_json.py +1 -6
  10. fastmcp/cli/run.py +146 -5
  11. fastmcp/client/__init__.py +7 -9
  12. fastmcp/client/auth/oauth.py +18 -17
  13. fastmcp/client/client.py +100 -870
  14. fastmcp/client/elicitation.py +1 -1
  15. fastmcp/client/mixins/__init__.py +13 -0
  16. fastmcp/client/mixins/prompts.py +295 -0
  17. fastmcp/client/mixins/resources.py +325 -0
  18. fastmcp/client/mixins/task_management.py +157 -0
  19. fastmcp/client/mixins/tools.py +397 -0
  20. fastmcp/client/sampling/handlers/anthropic.py +2 -2
  21. fastmcp/client/sampling/handlers/openai.py +1 -1
  22. fastmcp/client/tasks.py +3 -3
  23. fastmcp/client/telemetry.py +47 -0
  24. fastmcp/client/transports/__init__.py +38 -0
  25. fastmcp/client/transports/base.py +82 -0
  26. fastmcp/client/transports/config.py +170 -0
  27. fastmcp/client/transports/http.py +145 -0
  28. fastmcp/client/transports/inference.py +154 -0
  29. fastmcp/client/transports/memory.py +90 -0
  30. fastmcp/client/transports/sse.py +89 -0
  31. fastmcp/client/transports/stdio.py +543 -0
  32. fastmcp/contrib/component_manager/README.md +4 -10
  33. fastmcp/contrib/component_manager/__init__.py +1 -2
  34. fastmcp/contrib/component_manager/component_manager.py +95 -160
  35. fastmcp/contrib/component_manager/example.py +1 -1
  36. fastmcp/contrib/mcp_mixin/example.py +4 -4
  37. fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
  38. fastmcp/decorators.py +41 -0
  39. fastmcp/dependencies.py +12 -1
  40. fastmcp/exceptions.py +4 -0
  41. fastmcp/experimental/server/openapi/__init__.py +18 -15
  42. fastmcp/mcp_config.py +13 -4
  43. fastmcp/prompts/__init__.py +6 -3
  44. fastmcp/prompts/function_prompt.py +465 -0
  45. fastmcp/prompts/prompt.py +321 -271
  46. fastmcp/resources/__init__.py +5 -3
  47. fastmcp/resources/function_resource.py +335 -0
  48. fastmcp/resources/resource.py +325 -115
  49. fastmcp/resources/template.py +215 -43
  50. fastmcp/resources/types.py +27 -12
  51. fastmcp/server/__init__.py +2 -2
  52. fastmcp/server/auth/__init__.py +14 -0
  53. fastmcp/server/auth/auth.py +30 -10
  54. fastmcp/server/auth/authorization.py +190 -0
  55. fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
  56. fastmcp/server/auth/oauth_proxy/consent.py +361 -0
  57. fastmcp/server/auth/oauth_proxy/models.py +178 -0
  58. fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
  59. fastmcp/server/auth/oauth_proxy/ui.py +277 -0
  60. fastmcp/server/auth/oidc_proxy.py +2 -2
  61. fastmcp/server/auth/providers/auth0.py +24 -94
  62. fastmcp/server/auth/providers/aws.py +26 -95
  63. fastmcp/server/auth/providers/azure.py +41 -129
  64. fastmcp/server/auth/providers/descope.py +18 -49
  65. fastmcp/server/auth/providers/discord.py +25 -86
  66. fastmcp/server/auth/providers/github.py +23 -87
  67. fastmcp/server/auth/providers/google.py +24 -87
  68. fastmcp/server/auth/providers/introspection.py +60 -79
  69. fastmcp/server/auth/providers/jwt.py +30 -67
  70. fastmcp/server/auth/providers/oci.py +47 -110
  71. fastmcp/server/auth/providers/scalekit.py +23 -61
  72. fastmcp/server/auth/providers/supabase.py +18 -47
  73. fastmcp/server/auth/providers/workos.py +34 -127
  74. fastmcp/server/context.py +372 -419
  75. fastmcp/server/dependencies.py +541 -251
  76. fastmcp/server/elicitation.py +20 -18
  77. fastmcp/server/event_store.py +3 -3
  78. fastmcp/server/http.py +16 -6
  79. fastmcp/server/lifespan.py +198 -0
  80. fastmcp/server/low_level.py +92 -2
  81. fastmcp/server/middleware/__init__.py +5 -1
  82. fastmcp/server/middleware/authorization.py +312 -0
  83. fastmcp/server/middleware/caching.py +101 -54
  84. fastmcp/server/middleware/middleware.py +6 -9
  85. fastmcp/server/middleware/ping.py +70 -0
  86. fastmcp/server/middleware/tool_injection.py +2 -2
  87. fastmcp/server/mixins/__init__.py +7 -0
  88. fastmcp/server/mixins/lifespan.py +217 -0
  89. fastmcp/server/mixins/mcp_operations.py +392 -0
  90. fastmcp/server/mixins/transport.py +342 -0
  91. fastmcp/server/openapi/__init__.py +41 -21
  92. fastmcp/server/openapi/components.py +16 -339
  93. fastmcp/server/openapi/routing.py +34 -118
  94. fastmcp/server/openapi/server.py +67 -392
  95. fastmcp/server/providers/__init__.py +71 -0
  96. fastmcp/server/providers/aggregate.py +261 -0
  97. fastmcp/server/providers/base.py +578 -0
  98. fastmcp/server/providers/fastmcp_provider.py +674 -0
  99. fastmcp/server/providers/filesystem.py +226 -0
  100. fastmcp/server/providers/filesystem_discovery.py +327 -0
  101. fastmcp/server/providers/local_provider/__init__.py +11 -0
  102. fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
  103. fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
  104. fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
  105. fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
  106. fastmcp/server/providers/local_provider/local_provider.py +465 -0
  107. fastmcp/server/providers/openapi/__init__.py +39 -0
  108. fastmcp/server/providers/openapi/components.py +332 -0
  109. fastmcp/server/providers/openapi/provider.py +405 -0
  110. fastmcp/server/providers/openapi/routing.py +109 -0
  111. fastmcp/server/providers/proxy.py +867 -0
  112. fastmcp/server/providers/skills/__init__.py +59 -0
  113. fastmcp/server/providers/skills/_common.py +101 -0
  114. fastmcp/server/providers/skills/claude_provider.py +44 -0
  115. fastmcp/server/providers/skills/directory_provider.py +153 -0
  116. fastmcp/server/providers/skills/skill_provider.py +432 -0
  117. fastmcp/server/providers/skills/vendor_providers.py +142 -0
  118. fastmcp/server/providers/wrapped_provider.py +140 -0
  119. fastmcp/server/proxy.py +34 -700
  120. fastmcp/server/sampling/run.py +341 -2
  121. fastmcp/server/sampling/sampling_tool.py +4 -3
  122. fastmcp/server/server.py +1214 -2171
  123. fastmcp/server/tasks/__init__.py +2 -1
  124. fastmcp/server/tasks/capabilities.py +13 -1
  125. fastmcp/server/tasks/config.py +66 -3
  126. fastmcp/server/tasks/handlers.py +65 -273
  127. fastmcp/server/tasks/keys.py +4 -6
  128. fastmcp/server/tasks/requests.py +474 -0
  129. fastmcp/server/tasks/routing.py +76 -0
  130. fastmcp/server/tasks/subscriptions.py +20 -11
  131. fastmcp/server/telemetry.py +131 -0
  132. fastmcp/server/transforms/__init__.py +244 -0
  133. fastmcp/server/transforms/namespace.py +193 -0
  134. fastmcp/server/transforms/prompts_as_tools.py +175 -0
  135. fastmcp/server/transforms/resources_as_tools.py +190 -0
  136. fastmcp/server/transforms/tool_transform.py +96 -0
  137. fastmcp/server/transforms/version_filter.py +124 -0
  138. fastmcp/server/transforms/visibility.py +526 -0
  139. fastmcp/settings.py +34 -96
  140. fastmcp/telemetry.py +122 -0
  141. fastmcp/tools/__init__.py +10 -3
  142. fastmcp/tools/function_parsing.py +201 -0
  143. fastmcp/tools/function_tool.py +467 -0
  144. fastmcp/tools/tool.py +215 -362
  145. fastmcp/tools/tool_transform.py +38 -21
  146. fastmcp/utilities/async_utils.py +69 -0
  147. fastmcp/utilities/components.py +152 -91
  148. fastmcp/utilities/inspect.py +8 -20
  149. fastmcp/utilities/json_schema.py +12 -5
  150. fastmcp/utilities/json_schema_type.py +17 -15
  151. fastmcp/utilities/lifespan.py +56 -0
  152. fastmcp/utilities/logging.py +12 -4
  153. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
  154. fastmcp/utilities/openapi/parser.py +3 -3
  155. fastmcp/utilities/pagination.py +80 -0
  156. fastmcp/utilities/skills.py +253 -0
  157. fastmcp/utilities/tests.py +0 -16
  158. fastmcp/utilities/timeout.py +47 -0
  159. fastmcp/utilities/types.py +1 -1
  160. fastmcp/utilities/versions.py +285 -0
  161. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
  162. fastmcp-3.0.0b1.dist-info/RECORD +228 -0
  163. fastmcp/client/transports.py +0 -1170
  164. fastmcp/contrib/component_manager/component_service.py +0 -209
  165. fastmcp/prompts/prompt_manager.py +0 -117
  166. fastmcp/resources/resource_manager.py +0 -338
  167. fastmcp/server/tasks/converters.py +0 -206
  168. fastmcp/server/tasks/protocol.py +0 -359
  169. fastmcp/tools/tool_manager.py +0 -170
  170. fastmcp/utilities/mcp_config.py +0 -56
  171. fastmcp-2.14.4.dist-info/RECORD +0 -161
  172. /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
  173. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
  174. {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
  175. {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