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,465 @@
1
+ """Standalone @prompt decorator for FastMCP."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ import json
7
+ import warnings
8
+ from collections.abc import Callable
9
+ from dataclasses import dataclass, field
10
+ from typing import (
11
+ TYPE_CHECKING,
12
+ Any,
13
+ Literal,
14
+ Protocol,
15
+ TypeVar,
16
+ overload,
17
+ runtime_checkable,
18
+ )
19
+
20
+ import pydantic_core
21
+ from mcp.types import Icon
22
+
23
+ import fastmcp
24
+ from fastmcp.decorators import resolve_task_config
25
+ from fastmcp.exceptions import PromptError
26
+ from fastmcp.prompts.prompt import Prompt, PromptArgument, PromptResult
27
+ from fastmcp.server.dependencies import (
28
+ transform_context_annotations,
29
+ without_injected_parameters,
30
+ )
31
+ from fastmcp.server.tasks.config import TaskConfig
32
+ from fastmcp.tools.tool import AuthCheckCallable
33
+ from fastmcp.utilities.async_utils import call_sync_fn_in_threadpool
34
+ from fastmcp.utilities.json_schema import compress_schema
35
+ from fastmcp.utilities.logging import get_logger
36
+ from fastmcp.utilities.types import get_cached_typeadapter
37
+
38
+ if TYPE_CHECKING:
39
+ from docket import Docket
40
+ from docket.execution import Execution
41
+
42
+ F = TypeVar("F", bound=Callable[..., Any])
43
+
44
+ logger = get_logger(__name__)
45
+
46
+
47
+ @runtime_checkable
48
+ class DecoratedPrompt(Protocol):
49
+ """Protocol for functions decorated with @prompt."""
50
+
51
+ __fastmcp__: PromptMeta
52
+
53
+ def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
54
+
55
+
56
+ @dataclass(frozen=True, kw_only=True)
57
+ class PromptMeta:
58
+ """Metadata attached to functions by the @prompt decorator."""
59
+
60
+ type: Literal["prompt"] = field(default="prompt", init=False)
61
+ name: str | None = None
62
+ version: str | int | None = None
63
+ title: str | None = None
64
+ description: str | None = None
65
+ icons: list[Icon] | None = None
66
+ tags: set[str] | None = None
67
+ meta: dict[str, Any] | None = None
68
+ task: bool | TaskConfig | None = None
69
+ auth: AuthCheckCallable | list[AuthCheckCallable] | None = None
70
+ enabled: bool = True
71
+
72
+
73
+ class FunctionPrompt(Prompt):
74
+ """A prompt that is a function."""
75
+
76
+ fn: Callable[..., Any]
77
+
78
+ @classmethod
79
+ def from_function(
80
+ cls,
81
+ fn: Callable[..., Any],
82
+ *,
83
+ metadata: PromptMeta | None = None,
84
+ # Keep individual params for backwards compat
85
+ name: str | None = None,
86
+ version: str | int | None = None,
87
+ title: str | None = None,
88
+ description: str | None = None,
89
+ icons: list[Icon] | None = None,
90
+ tags: set[str] | None = None,
91
+ meta: dict[str, Any] | None = None,
92
+ task: bool | TaskConfig | None = None,
93
+ auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
94
+ ) -> FunctionPrompt:
95
+ """Create a Prompt from a function.
96
+
97
+ Args:
98
+ fn: The function to wrap
99
+ metadata: PromptMeta object with all configuration. If provided,
100
+ individual parameters must not be passed.
101
+ name, title, etc.: Individual parameters for backwards compatibility.
102
+ Cannot be used together with metadata parameter.
103
+
104
+ The function can return:
105
+ - str: wrapped as single user Message
106
+ - list[Message | str]: converted to list[Message]
107
+ - PromptResult: used directly
108
+ """
109
+ # Check mutual exclusion
110
+ individual_params_provided = any(
111
+ x is not None
112
+ for x in [name, version, title, description, icons, tags, meta, task, auth]
113
+ )
114
+
115
+ if metadata is not None and individual_params_provided:
116
+ raise TypeError(
117
+ "Cannot pass both 'metadata' and individual parameters to from_function(). "
118
+ "Use metadata alone or individual parameters alone."
119
+ )
120
+
121
+ # Build metadata from kwargs if not provided
122
+ if metadata is None:
123
+ metadata = PromptMeta(
124
+ name=name,
125
+ version=version,
126
+ title=title,
127
+ description=description,
128
+ icons=icons,
129
+ tags=tags,
130
+ meta=meta,
131
+ task=task,
132
+ auth=auth,
133
+ )
134
+
135
+ func_name = (
136
+ metadata.name or getattr(fn, "__name__", None) or fn.__class__.__name__
137
+ )
138
+
139
+ if func_name == "<lambda>":
140
+ raise ValueError("You must provide a name for lambda functions")
141
+
142
+ # Reject functions with *args or **kwargs
143
+ sig = inspect.signature(fn)
144
+ for param in sig.parameters.values():
145
+ if param.kind == inspect.Parameter.VAR_POSITIONAL:
146
+ raise ValueError("Functions with *args are not supported as prompts")
147
+ if param.kind == inspect.Parameter.VAR_KEYWORD:
148
+ raise ValueError("Functions with **kwargs are not supported as prompts")
149
+
150
+ description = metadata.description or inspect.getdoc(fn)
151
+
152
+ # Normalize task to TaskConfig and validate
153
+ task_value = metadata.task
154
+ if task_value is None:
155
+ task_config = TaskConfig(mode="forbidden")
156
+ elif isinstance(task_value, bool):
157
+ task_config = TaskConfig.from_bool(task_value)
158
+ else:
159
+ task_config = task_value
160
+ task_config.validate_function(fn, func_name)
161
+
162
+ # if the fn is a callable class, we need to get the __call__ method from here out
163
+ if not inspect.isroutine(fn):
164
+ fn = fn.__call__
165
+ # if the fn is a staticmethod, we need to work with the underlying function
166
+ if isinstance(fn, staticmethod):
167
+ fn = fn.__func__
168
+
169
+ # Transform Context type annotations to Depends() for unified DI
170
+ fn = transform_context_annotations(fn)
171
+
172
+ # Wrap fn to handle dependency resolution internally
173
+ wrapped_fn = without_injected_parameters(fn)
174
+ type_adapter = get_cached_typeadapter(wrapped_fn)
175
+ parameters = type_adapter.json_schema()
176
+ parameters = compress_schema(parameters, prune_titles=True)
177
+
178
+ # Convert parameters to PromptArguments
179
+ arguments: list[PromptArgument] = []
180
+ if "properties" in parameters:
181
+ for param_name, param in parameters["properties"].items():
182
+ arg_description = param.get("description")
183
+
184
+ # For non-string parameters, append JSON schema info to help users
185
+ # understand the expected format when passing as strings (MCP requirement)
186
+ if param_name in sig.parameters:
187
+ sig_param = sig.parameters[param_name]
188
+ if (
189
+ sig_param.annotation != inspect.Parameter.empty
190
+ and sig_param.annotation is not str
191
+ ):
192
+ # Get the JSON schema for this specific parameter type
193
+ try:
194
+ param_adapter = get_cached_typeadapter(sig_param.annotation)
195
+ param_schema = param_adapter.json_schema()
196
+
197
+ # Create compact schema representation
198
+ schema_str = json.dumps(param_schema, separators=(",", ":"))
199
+
200
+ # Append schema info to description
201
+ schema_note = f"Provide as a JSON string matching the following schema: {schema_str}"
202
+ if arg_description:
203
+ arg_description = f"{arg_description}\n\n{schema_note}"
204
+ else:
205
+ arg_description = schema_note
206
+ except Exception as e:
207
+ # If schema generation fails, skip enhancement
208
+ logger.debug(
209
+ "Failed to generate schema for prompt argument %s: %s",
210
+ param_name,
211
+ e,
212
+ )
213
+
214
+ arguments.append(
215
+ PromptArgument(
216
+ name=param_name,
217
+ description=arg_description,
218
+ required=param_name in parameters.get("required", []),
219
+ )
220
+ )
221
+
222
+ return cls(
223
+ name=func_name,
224
+ version=str(metadata.version) if metadata.version is not None else None,
225
+ title=metadata.title,
226
+ description=description,
227
+ icons=metadata.icons,
228
+ arguments=arguments,
229
+ tags=metadata.tags or set(),
230
+ fn=wrapped_fn,
231
+ meta=metadata.meta,
232
+ task_config=task_config,
233
+ auth=metadata.auth,
234
+ )
235
+
236
+ def _convert_string_arguments(self, kwargs: dict[str, Any]) -> dict[str, Any]:
237
+ """Convert string arguments to expected types based on function signature."""
238
+ from fastmcp.server.dependencies import without_injected_parameters
239
+
240
+ wrapper_fn = without_injected_parameters(self.fn)
241
+ sig = inspect.signature(wrapper_fn)
242
+ converted_kwargs = {}
243
+
244
+ for param_name, param_value in kwargs.items():
245
+ if param_name in sig.parameters:
246
+ param = sig.parameters[param_name]
247
+
248
+ # If parameter has no annotation or annotation is str, pass as-is
249
+ if (
250
+ param.annotation == inspect.Parameter.empty
251
+ or param.annotation is str
252
+ ) or not isinstance(param_value, str):
253
+ converted_kwargs[param_name] = param_value
254
+ else:
255
+ # Try to convert string argument using type adapter
256
+ try:
257
+ adapter = get_cached_typeadapter(param.annotation)
258
+ # Try JSON parsing first for complex types
259
+ try:
260
+ converted_kwargs[param_name] = adapter.validate_json(
261
+ param_value
262
+ )
263
+ except (ValueError, TypeError, pydantic_core.ValidationError):
264
+ # Fallback to direct validation
265
+ converted_kwargs[param_name] = adapter.validate_python(
266
+ param_value
267
+ )
268
+ except (ValueError, TypeError, pydantic_core.ValidationError) as e:
269
+ # If conversion fails, provide informative error
270
+ raise PromptError(
271
+ f"Could not convert argument '{param_name}' with value '{param_value}' "
272
+ f"to expected type {param.annotation}. Error: {e}"
273
+ ) from e
274
+ else:
275
+ # Parameter not in function signature, pass as-is
276
+ converted_kwargs[param_name] = param_value
277
+
278
+ return converted_kwargs
279
+
280
+ async def render(
281
+ self,
282
+ arguments: dict[str, Any] | None = None,
283
+ ) -> PromptResult:
284
+ """Render the prompt with arguments."""
285
+ # Validate required arguments
286
+ if self.arguments:
287
+ required = {arg.name for arg in self.arguments if arg.required}
288
+ provided = set(arguments or {})
289
+ missing = required - provided
290
+ if missing:
291
+ raise ValueError(f"Missing required arguments: {missing}")
292
+
293
+ try:
294
+ # Prepare arguments
295
+ kwargs = arguments.copy() if arguments else {}
296
+
297
+ # Convert string arguments to expected types BEFORE validation
298
+ kwargs = self._convert_string_arguments(kwargs)
299
+
300
+ # self.fn is wrapped by without_injected_parameters which handles
301
+ # dependency resolution internally
302
+ if inspect.iscoroutinefunction(self.fn):
303
+ result = await self.fn(**kwargs)
304
+ else:
305
+ # Run sync functions in threadpool to avoid blocking the event loop
306
+ result = await call_sync_fn_in_threadpool(self.fn, **kwargs)
307
+ # Handle sync wrappers that return awaitables (e.g., partial(async_fn))
308
+ if inspect.isawaitable(result):
309
+ result = await result
310
+
311
+ return self.convert_result(result)
312
+ except Exception as e:
313
+ logger.exception(f"Error rendering prompt {self.name}")
314
+ raise PromptError(f"Error rendering prompt {self.name}.") from e
315
+
316
+ def register_with_docket(self, docket: Docket) -> None:
317
+ """Register this prompt with docket for background execution.
318
+
319
+ FunctionPrompt registers the underlying function, which has the user's
320
+ Depends parameters for docket to resolve.
321
+ """
322
+ if not self.task_config.supports_tasks():
323
+ return
324
+ docket.register(self.fn, names=[self.key])
325
+
326
+ async def add_to_docket(
327
+ self,
328
+ docket: Docket,
329
+ arguments: dict[str, Any] | None,
330
+ *,
331
+ fn_key: str | None = None,
332
+ task_key: str | None = None,
333
+ **kwargs: Any,
334
+ ) -> Execution:
335
+ """Schedule this prompt for background execution via docket.
336
+
337
+ FunctionPrompt splats the arguments dict since .fn expects **kwargs.
338
+
339
+ Args:
340
+ docket: The Docket instance
341
+ arguments: Prompt arguments
342
+ fn_key: Function lookup key in Docket registry (defaults to self.key)
343
+ task_key: Redis storage key for the result
344
+ **kwargs: Additional kwargs passed to docket.add()
345
+ """
346
+ lookup_key = fn_key or self.key
347
+ if task_key:
348
+ kwargs["key"] = task_key
349
+ return await docket.add(lookup_key, **kwargs)(**(arguments or {}))
350
+
351
+
352
+ @overload
353
+ def prompt(fn: F) -> F: ...
354
+ @overload
355
+ def prompt(
356
+ name_or_fn: str,
357
+ *,
358
+ version: str | int | None = None,
359
+ title: str | None = None,
360
+ description: str | None = None,
361
+ icons: list[Icon] | None = None,
362
+ tags: set[str] | None = None,
363
+ meta: dict[str, Any] | None = None,
364
+ task: bool | TaskConfig | None = None,
365
+ auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
366
+ ) -> Callable[[F], F]: ...
367
+ @overload
368
+ def prompt(
369
+ name_or_fn: None = None,
370
+ *,
371
+ name: str | None = None,
372
+ version: str | int | None = None,
373
+ title: str | None = None,
374
+ description: str | None = None,
375
+ icons: list[Icon] | None = None,
376
+ tags: set[str] | None = None,
377
+ meta: dict[str, Any] | None = None,
378
+ task: bool | TaskConfig | None = None,
379
+ auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
380
+ ) -> Callable[[F], F]: ...
381
+
382
+
383
+ def prompt(
384
+ name_or_fn: str | Callable[..., Any] | None = None,
385
+ *,
386
+ name: str | None = None,
387
+ version: str | int | None = None,
388
+ title: str | None = None,
389
+ description: str | None = None,
390
+ icons: list[Icon] | None = None,
391
+ tags: set[str] | None = None,
392
+ meta: dict[str, Any] | None = None,
393
+ task: bool | TaskConfig | None = None,
394
+ auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
395
+ ) -> Any:
396
+ """Standalone decorator to mark a function as an MCP prompt.
397
+
398
+ Returns the original function with metadata attached. Register with a server
399
+ using mcp.add_prompt().
400
+ """
401
+ if isinstance(name_or_fn, classmethod):
402
+ raise TypeError(
403
+ "To decorate a classmethod, use @classmethod above @prompt. "
404
+ "See https://gofastmcp.com/servers/prompts#using-with-methods"
405
+ )
406
+
407
+ def create_prompt(
408
+ fn: Callable[..., Any], prompt_name: str | None
409
+ ) -> FunctionPrompt:
410
+ # Create metadata first, then pass it
411
+ prompt_meta = PromptMeta(
412
+ name=prompt_name,
413
+ version=version,
414
+ title=title,
415
+ description=description,
416
+ icons=icons,
417
+ tags=tags,
418
+ meta=meta,
419
+ task=resolve_task_config(task),
420
+ auth=auth,
421
+ )
422
+ return FunctionPrompt.from_function(fn, metadata=prompt_meta)
423
+
424
+ def attach_metadata(fn: F, prompt_name: str | None) -> F:
425
+ metadata = PromptMeta(
426
+ name=prompt_name,
427
+ version=version,
428
+ title=title,
429
+ description=description,
430
+ icons=icons,
431
+ tags=tags,
432
+ meta=meta,
433
+ task=task,
434
+ auth=auth,
435
+ )
436
+ target = fn.__func__ if hasattr(fn, "__func__") else fn
437
+ target.__fastmcp__ = metadata
438
+ return fn
439
+
440
+ def decorator(fn: F, prompt_name: str | None) -> F:
441
+ if fastmcp.settings.decorator_mode == "object":
442
+ warnings.warn(
443
+ "decorator_mode='object' is deprecated and will be removed in a future version. "
444
+ "Decorators now return the original function with metadata attached.",
445
+ DeprecationWarning,
446
+ stacklevel=4,
447
+ )
448
+ return create_prompt(fn, prompt_name) # type: ignore[return-value]
449
+ return attach_metadata(fn, prompt_name)
450
+
451
+ if inspect.isroutine(name_or_fn):
452
+ return decorator(name_or_fn, name)
453
+ elif isinstance(name_or_fn, str):
454
+ if name is not None:
455
+ raise TypeError("Cannot specify name both as first argument and keyword")
456
+ prompt_name = name_or_fn
457
+ elif name_or_fn is None:
458
+ prompt_name = name
459
+ else:
460
+ raise TypeError(f"Invalid first argument: {type(name_or_fn)}")
461
+
462
+ def wrapper(fn: F) -> F:
463
+ return decorator(fn, prompt_name)
464
+
465
+ return wrapper