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
@@ -2,10 +2,19 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import inspect
5
+ import base64
6
6
  from collections.abc import Callable
7
- from typing import TYPE_CHECKING, Annotated, Any
7
+ from typing import TYPE_CHECKING, Annotated, Any, ClassVar, overload
8
8
 
9
+ import mcp.types
10
+
11
+ if TYPE_CHECKING:
12
+ from docket import Docket
13
+ from docket.execution import Execution
14
+
15
+ from fastmcp.resources.function_resource import FunctionResource
16
+
17
+ import pydantic
9
18
  import pydantic_core
10
19
  from mcp.types import Annotations, Icon
11
20
  from mcp.types import Resource as SDKResource
@@ -19,20 +28,189 @@ from pydantic import (
19
28
  )
20
29
  from typing_extensions import Self
21
30
 
22
- from fastmcp.server.dependencies import get_context, without_injected_parameters
23
- from fastmcp.server.tasks.config import TaskConfig
31
+ from fastmcp.server.tasks.config import TaskConfig, TaskMeta
32
+ from fastmcp.tools.tool import AuthCheckCallable
24
33
  from fastmcp.utilities.components import FastMCPComponent
25
- from fastmcp.utilities.types import (
26
- get_fn_name,
27
- )
28
34
 
29
- if TYPE_CHECKING:
30
- pass
35
+
36
+ class ResourceContent(pydantic.BaseModel):
37
+ """Wrapper for resource content with optional MIME type and metadata.
38
+
39
+ Accepts any value for content - strings and bytes pass through directly,
40
+ other types (dict, list, BaseModel, etc.) are automatically JSON-serialized.
41
+
42
+ Example:
43
+ ```python
44
+ from fastmcp.resources import ResourceContent
45
+
46
+ # String content
47
+ ResourceContent("plain text")
48
+
49
+ # Binary content
50
+ ResourceContent(b"binary data", mime_type="application/octet-stream")
51
+
52
+ # Auto-serialized to JSON
53
+ ResourceContent({"key": "value"})
54
+ ResourceContent(["a", "b", "c"])
55
+ ```
56
+ """
57
+
58
+ content: str | bytes
59
+ mime_type: str | None = None
60
+ meta: dict[str, Any] | None = None
61
+
62
+ def __init__(
63
+ self,
64
+ content: Any,
65
+ mime_type: str | None = None,
66
+ meta: dict[str, Any] | None = None,
67
+ ):
68
+ """Create ResourceContent with automatic serialization.
69
+
70
+ Args:
71
+ content: The content value. str and bytes pass through directly.
72
+ Other types (dict, list, BaseModel) are JSON-serialized.
73
+ mime_type: Optional MIME type. Defaults based on content type:
74
+ str → "text/plain", bytes → "application/octet-stream",
75
+ other → "application/json"
76
+ meta: Optional metadata dictionary.
77
+ """
78
+ if isinstance(content, str):
79
+ normalized_content: str | bytes = content
80
+ mime_type = mime_type or "text/plain"
81
+ elif isinstance(content, bytes):
82
+ normalized_content = content
83
+ mime_type = mime_type or "application/octet-stream"
84
+ else:
85
+ # dict, list, BaseModel, etc → JSON
86
+ normalized_content = pydantic_core.to_json(content, fallback=str).decode()
87
+ mime_type = mime_type or "application/json"
88
+
89
+ super().__init__(content=normalized_content, mime_type=mime_type, meta=meta)
90
+
91
+ def to_mcp_resource_contents(
92
+ self, uri: AnyUrl | str
93
+ ) -> mcp.types.TextResourceContents | mcp.types.BlobResourceContents:
94
+ """Convert to MCP resource contents type.
95
+
96
+ Args:
97
+ uri: The URI of the resource (required by MCP types)
98
+
99
+ Returns:
100
+ TextResourceContents for str content, BlobResourceContents for bytes
101
+ """
102
+ if isinstance(self.content, str):
103
+ return mcp.types.TextResourceContents(
104
+ uri=AnyUrl(uri) if isinstance(uri, str) else uri,
105
+ text=self.content,
106
+ mimeType=self.mime_type or "text/plain",
107
+ _meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
108
+ )
109
+ else:
110
+ return mcp.types.BlobResourceContents(
111
+ uri=AnyUrl(uri) if isinstance(uri, str) else uri,
112
+ blob=base64.b64encode(self.content).decode(),
113
+ mimeType=self.mime_type or "application/octet-stream",
114
+ _meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
115
+ )
116
+
117
+
118
+ class ResourceResult(pydantic.BaseModel):
119
+ """Canonical result type for resource reads.
120
+
121
+ Provides explicit control over resource responses: multiple content items,
122
+ per-item MIME types, and metadata at both the item and result level.
123
+
124
+ Accepts:
125
+ - str: Wrapped as single ResourceContent (text/plain)
126
+ - bytes: Wrapped as single ResourceContent (application/octet-stream)
127
+ - list[ResourceContent]: Used directly for multiple items or custom MIME types
128
+
129
+ Example:
130
+ ```python
131
+ from fastmcp import FastMCP
132
+ from fastmcp.resources import ResourceResult, ResourceContent
133
+
134
+ mcp = FastMCP()
135
+
136
+ # Simple string content
137
+ @mcp.resource("data://simple")
138
+ def get_simple() -> ResourceResult:
139
+ return ResourceResult("hello world")
140
+
141
+ # Multiple items with custom MIME types
142
+ @mcp.resource("data://items")
143
+ def get_items() -> ResourceResult:
144
+ return ResourceResult(
145
+ contents=[
146
+ ResourceContent({"key": "value"}), # auto-serialized to JSON
147
+ ResourceContent(b"binary data"),
148
+ ],
149
+ meta={"count": 2}
150
+ )
151
+ ```
152
+ """
153
+
154
+ contents: list[ResourceContent]
155
+ meta: dict[str, Any] | None = None
156
+
157
+ def __init__(
158
+ self,
159
+ contents: str | bytes | list[ResourceContent],
160
+ meta: dict[str, Any] | None = None,
161
+ ):
162
+ """Create ResourceResult.
163
+
164
+ Args:
165
+ contents: String, bytes, or list of ResourceContent objects.
166
+ meta: Optional metadata about the resource result.
167
+ """
168
+ normalized = self._normalize_contents(contents)
169
+ super().__init__(contents=normalized, meta=meta)
170
+
171
+ @staticmethod
172
+ def _normalize_contents(
173
+ contents: str | bytes | list[ResourceContent],
174
+ ) -> list[ResourceContent]:
175
+ """Normalize input to list[ResourceContent]."""
176
+ if isinstance(contents, str):
177
+ return [ResourceContent(contents)]
178
+ if isinstance(contents, bytes):
179
+ return [ResourceContent(contents)]
180
+ if isinstance(contents, list):
181
+ # Validate all items are ResourceContent
182
+ for i, item in enumerate(contents):
183
+ if not isinstance(item, ResourceContent):
184
+ raise TypeError(
185
+ f"contents[{i}] must be ResourceContent, got {type(item).__name__}. "
186
+ f"Use ResourceContent({item!r}) to wrap the value."
187
+ )
188
+ return contents
189
+ raise TypeError(
190
+ f"contents must be str, bytes, or list[ResourceContent], got {type(contents).__name__}"
191
+ )
192
+
193
+ def to_mcp_result(self, uri: AnyUrl | str) -> mcp.types.ReadResourceResult:
194
+ """Convert to MCP ReadResourceResult.
195
+
196
+ Args:
197
+ uri: The URI of the resource (required by MCP types)
198
+
199
+ Returns:
200
+ MCP ReadResourceResult with converted contents
201
+ """
202
+ mcp_contents = [item.to_mcp_resource_contents(uri) for item in self.contents]
203
+ return mcp.types.ReadResourceResult(
204
+ contents=mcp_contents,
205
+ _meta=self.meta, # type: ignore[call-arg] # _meta is Pydantic alias for meta field
206
+ )
31
207
 
32
208
 
33
209
  class Resource(FastMCPComponent):
34
210
  """Base class for all resources."""
35
211
 
212
+ KEY_PREFIX: ClassVar[str] = "resource"
213
+
36
214
  model_config = ConfigDict(validate_default=True)
37
215
 
38
216
  uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(
@@ -47,51 +225,47 @@ class Resource(FastMCPComponent):
47
225
  Annotations | None,
48
226
  Field(description="Optional annotations about the resource's behavior"),
49
227
  ] = None
228
+ auth: Annotated[
229
+ AuthCheckCallable | list[AuthCheckCallable] | None,
230
+ Field(description="Authorization checks for this resource", exclude=True),
231
+ ] = None
50
232
 
51
- def enable(self) -> None:
52
- super().enable()
53
- try:
54
- context = get_context()
55
- context._queue_resource_list_changed() # type: ignore[private-use]
56
- except RuntimeError:
57
- pass # No context available
58
-
59
- def disable(self) -> None:
60
- super().disable()
61
- try:
62
- context = get_context()
63
- context._queue_resource_list_changed() # type: ignore[private-use]
64
- except RuntimeError:
65
- pass # No context available
66
-
67
- @staticmethod
233
+ @classmethod
68
234
  def from_function(
235
+ cls,
69
236
  fn: Callable[..., Any],
70
237
  uri: str | AnyUrl,
238
+ *,
71
239
  name: str | None = None,
240
+ version: str | int | None = None,
72
241
  title: str | None = None,
73
242
  description: str | None = None,
74
243
  icons: list[Icon] | None = None,
75
244
  mime_type: str | None = None,
76
245
  tags: set[str] | None = None,
77
- enabled: bool | None = None,
78
246
  annotations: Annotations | None = None,
79
247
  meta: dict[str, Any] | None = None,
80
248
  task: bool | TaskConfig | None = None,
249
+ auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
81
250
  ) -> FunctionResource:
251
+ from fastmcp.resources.function_resource import (
252
+ FunctionResource,
253
+ )
254
+
82
255
  return FunctionResource.from_function(
83
256
  fn=fn,
84
257
  uri=uri,
85
258
  name=name,
259
+ version=version,
86
260
  title=title,
87
261
  description=description,
88
262
  icons=icons,
89
263
  mime_type=mime_type,
90
264
  tags=tags,
91
- enabled=enabled,
92
265
  annotations=annotations,
93
266
  meta=meta,
94
267
  task=task,
268
+ auth=auth,
95
269
  )
96
270
 
97
271
  @field_validator("mime_type", mode="before")
@@ -113,18 +287,76 @@ class Resource(FastMCPComponent):
113
287
  raise ValueError("Either name or uri must be provided")
114
288
  return self
115
289
 
116
- async def read(self) -> str | bytes:
290
+ async def read(
291
+ self,
292
+ ) -> str | bytes | ResourceResult:
117
293
  """Read the resource content.
118
294
 
119
- This method is not implemented in the base Resource class and must be
120
- implemented by subclasses.
295
+ Subclasses implement this to return resource data. Supported return types:
296
+ - str: Text content
297
+ - bytes: Binary content
298
+ - ResourceResult: Full control over contents and result-level meta
121
299
  """
122
300
  raise NotImplementedError("Subclasses must implement read()")
123
301
 
302
+ def convert_result(self, raw_value: Any) -> ResourceResult:
303
+ """Convert a raw result to ResourceResult.
304
+
305
+ This is used in two contexts:
306
+ 1. In _read() to convert user function return values to ResourceResult
307
+ 2. In tasks_result_handler() to convert Docket task results to ResourceResult
308
+
309
+ Handles ResourceResult passthrough and converts raw values using
310
+ ResourceResult's normalization.
311
+ """
312
+ if isinstance(raw_value, ResourceResult):
313
+ return raw_value
314
+
315
+ # ResourceResult.__init__ handles all normalization
316
+ return ResourceResult(raw_value)
317
+
318
+ @overload
319
+ async def _read(self, task_meta: None = None) -> ResourceResult: ...
320
+
321
+ @overload
322
+ async def _read(self, task_meta: TaskMeta) -> mcp.types.CreateTaskResult: ...
323
+
324
+ async def _read(
325
+ self, task_meta: TaskMeta | None = None
326
+ ) -> ResourceResult | mcp.types.CreateTaskResult:
327
+ """Server entry point that handles task routing.
328
+
329
+ This allows ANY Resource subclass to support background execution by setting
330
+ task_config.mode to "supported" or "required". The server calls this
331
+ method instead of read() directly.
332
+
333
+ Args:
334
+ task_meta: If provided, execute as a background task and return
335
+ CreateTaskResult. If None (default), execute synchronously and
336
+ return ResourceResult.
337
+
338
+ Returns:
339
+ ResourceResult when task_meta is None.
340
+ CreateTaskResult when task_meta is provided.
341
+
342
+ Subclasses can override this to customize task routing behavior.
343
+ For example, FastMCPProviderResource overrides to delegate to child
344
+ middleware without submitting to Docket.
345
+ """
346
+ from fastmcp.server.tasks.routing import check_background_task
347
+
348
+ task_result = await check_background_task(
349
+ component=self, task_type="resource", arguments=None, task_meta=task_meta
350
+ )
351
+ if task_result:
352
+ return task_result
353
+
354
+ # Synchronous execution - convert result to ResourceResult
355
+ result = await self.read()
356
+ return self.convert_result(result)
357
+
124
358
  def to_mcp_resource(
125
359
  self,
126
- *,
127
- include_fastmcp_meta: bool | None = None,
128
360
  **overrides: Any,
129
361
  ) -> SDKResource:
130
362
  """Convert the resource to an SDKResource."""
@@ -137,8 +369,8 @@ class Resource(FastMCPComponent):
137
369
  title=overrides.get("title", self.title),
138
370
  icons=overrides.get("icons", self.icons),
139
371
  annotations=overrides.get("annotations", self.annotations),
140
- _meta=overrides.get(
141
- "_meta", self.get_meta(include_fastmcp_meta=include_fastmcp_meta)
372
+ _meta=overrides.get( # type: ignore[call-arg] # _meta is Pydantic alias for meta field
373
+ "_meta", self.get_meta()
142
374
  ),
143
375
  )
144
376
 
@@ -147,94 +379,72 @@ class Resource(FastMCPComponent):
147
379
 
148
380
  @property
149
381
  def key(self) -> str:
150
- """
151
- The key of the component. This is used for internal bookkeeping
152
- and may reflect e.g. prefixes or other identifiers. You should not depend on
153
- keys having a certain value, as the same tool loaded from different
154
- hierarchies of servers may have different keys.
155
- """
156
- return self._key or str(self.uri)
382
+ """The globally unique lookup key for this resource."""
383
+ base_key = self.make_key(str(self.uri))
384
+ return f"{base_key}@{self.version or ''}"
157
385
 
386
+ def register_with_docket(self, docket: Docket) -> None:
387
+ """Register this resource with docket for background execution."""
388
+ if not self.task_config.supports_tasks():
389
+ return
390
+ docket.register(self.read, names=[self.key])
158
391
 
159
- class FunctionResource(Resource):
160
- """A resource that defers data loading by wrapping a function.
392
+ async def add_to_docket( # type: ignore[override]
393
+ self,
394
+ docket: Docket,
395
+ *,
396
+ fn_key: str | None = None,
397
+ task_key: str | None = None,
398
+ **kwargs: Any,
399
+ ) -> Execution:
400
+ """Schedule this resource for background execution via docket.
401
+
402
+ Args:
403
+ docket: The Docket instance
404
+ fn_key: Function lookup key in Docket registry (defaults to self.key)
405
+ task_key: Redis storage key for the result
406
+ **kwargs: Additional kwargs passed to docket.add()
407
+ """
408
+ lookup_key = fn_key or self.key
409
+ if task_key:
410
+ kwargs["key"] = task_key
411
+ return await docket.add(lookup_key, **kwargs)()
161
412
 
162
- The function is only called when the resource is read, allowing for lazy loading
163
- of potentially expensive data. This is particularly useful when listing resources,
164
- as the function won't be called until the resource is actually accessed.
413
+ def get_span_attributes(self) -> dict[str, Any]:
414
+ return super().get_span_attributes() | {
415
+ "fastmcp.component.type": "resource",
416
+ "fastmcp.provider.type": "LocalProvider",
417
+ }
165
418
 
166
- The function can return:
167
- - str for text content (default)
168
- - bytes for binary content
169
- - other types will be converted to JSON
170
- """
171
419
 
172
- fn: Callable[..., Any]
173
- task_config: Annotated[
174
- TaskConfig,
175
- Field(description="Background task execution configuration (SEP-1686)."),
176
- ] = Field(default_factory=lambda: TaskConfig(mode="forbidden"))
420
+ __all__ = [
421
+ "Resource",
422
+ "ResourceContent",
423
+ "ResourceResult",
424
+ ]
177
425
 
178
- @classmethod
179
- def from_function(
180
- cls,
181
- fn: Callable[..., Any],
182
- uri: str | AnyUrl,
183
- name: str | None = None,
184
- title: str | None = None,
185
- description: str | None = None,
186
- icons: list[Icon] | None = None,
187
- mime_type: str | None = None,
188
- tags: set[str] | None = None,
189
- enabled: bool | None = None,
190
- annotations: Annotations | None = None,
191
- meta: dict[str, Any] | None = None,
192
- task: bool | TaskConfig | None = None,
193
- ) -> FunctionResource:
194
- """Create a FunctionResource from a function."""
195
- if isinstance(uri, str):
196
- uri = AnyUrl(uri)
197
426
 
198
- func_name = name or get_fn_name(fn)
427
+ def __getattr__(name: str) -> Any:
428
+ """Deprecated re-exports for backwards compatibility."""
429
+ deprecated_exports = {
430
+ "FunctionResource": "FunctionResource",
431
+ "resource": "resource",
432
+ }
199
433
 
200
- # Normalize task to TaskConfig and validate
201
- if task is None:
202
- task_config = TaskConfig(mode="forbidden")
203
- elif isinstance(task, bool):
204
- task_config = TaskConfig.from_bool(task)
205
- else:
206
- task_config = task
207
- task_config.validate_function(fn, func_name)
434
+ if name in deprecated_exports:
435
+ import warnings
208
436
 
209
- # Wrap fn to handle dependency resolution internally
210
- wrapped_fn = without_injected_parameters(fn)
437
+ import fastmcp
211
438
 
212
- return cls(
213
- fn=wrapped_fn,
214
- uri=uri,
215
- name=name or get_fn_name(fn),
216
- title=title,
217
- description=description or inspect.getdoc(fn),
218
- icons=icons,
219
- mime_type=mime_type or "text/plain",
220
- tags=tags or set(),
221
- enabled=enabled if enabled is not None else True,
222
- annotations=annotations,
223
- meta=meta,
224
- task_config=task_config,
225
- )
439
+ if fastmcp.settings.deprecation_warnings:
440
+ warnings.warn(
441
+ f"Importing {name} from fastmcp.resources.resource is deprecated. "
442
+ f"Import from fastmcp.resources.function_resource instead.",
443
+ DeprecationWarning,
444
+ stacklevel=2,
445
+ )
446
+ from fastmcp.resources import function_resource
226
447
 
227
- async def read(self) -> str | bytes:
228
- """Read the resource by calling the wrapped function."""
229
- # self.fn is wrapped by without_injected_parameters which handles
230
- # dependency resolution internally
231
- result = self.fn()
232
- if inspect.isawaitable(result):
233
- result = await result
234
-
235
- if isinstance(result, Resource):
236
- return await result.read()
237
- elif isinstance(result, bytes | str):
238
- return result
239
- else:
240
- return pydantic_core.to_json(result, fallback=str).decode()
448
+ return getattr(function_resource, name)
449
+
450
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")