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,392 @@
1
+ """MCP protocol handler setup and wire-format handlers for FastMCP Server."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Awaitable, Callable, Sequence
6
+ from typing import TYPE_CHECKING, Any, TypeVar, cast
7
+
8
+ import mcp.types
9
+ from mcp.shared.exceptions import McpError
10
+ from mcp.types import ContentBlock
11
+ from pydantic import AnyUrl
12
+
13
+ from fastmcp.exceptions import DisabledError, NotFoundError
14
+ from fastmcp.server.tasks.config import TaskMeta
15
+ from fastmcp.utilities.logging import get_logger
16
+ from fastmcp.utilities.pagination import paginate_sequence
17
+ from fastmcp.utilities.versions import VersionSpec, parse_version_key, version_sort_key
18
+
19
+ if TYPE_CHECKING:
20
+ from fastmcp.server.server import FastMCP
21
+
22
+ logger = get_logger(__name__)
23
+
24
+ C = TypeVar("C", bound=Any)
25
+ PaginateT = TypeVar("PaginateT")
26
+
27
+
28
+ def _dedupe_with_versions(
29
+ components: Sequence[C],
30
+ key_fn: Callable[[C], str],
31
+ ) -> list[C]:
32
+ """Deduplicate components by key, keeping highest version.
33
+
34
+ Groups components by key, selects the highest version from each group,
35
+ and injects available versions into meta if any component is versioned.
36
+
37
+ Args:
38
+ components: Sequence of components to deduplicate.
39
+ key_fn: Function to extract the grouping key from a component.
40
+
41
+ Returns:
42
+ Deduplicated list with versions injected into meta.
43
+ """
44
+ by_key: dict[str, list[C]] = {}
45
+ for c in components:
46
+ by_key.setdefault(key_fn(c), []).append(c)
47
+
48
+ result: list[C] = []
49
+ for versions in by_key.values():
50
+ highest: C = cast(C, max(versions, key=version_sort_key))
51
+ if any(c.version is not None for c in versions):
52
+ all_versions = sorted(
53
+ [c.version for c in versions if c.version is not None],
54
+ key=parse_version_key,
55
+ reverse=True,
56
+ )
57
+ meta = highest.meta or {}
58
+ highest = highest.model_copy(
59
+ update={
60
+ "meta": {
61
+ **meta,
62
+ "fastmcp": {
63
+ **meta.get("fastmcp", {}),
64
+ "versions": all_versions,
65
+ },
66
+ }
67
+ }
68
+ )
69
+ result.append(highest)
70
+ return result
71
+
72
+
73
+ def _apply_pagination(
74
+ items: Sequence[PaginateT],
75
+ cursor: str | None,
76
+ page_size: int | None,
77
+ ) -> tuple[list[PaginateT], str | None]:
78
+ """Apply pagination to items, raising McpError for invalid cursors.
79
+
80
+ If page_size is None, returns all items without pagination.
81
+ """
82
+ if page_size is None:
83
+ return list(items), None
84
+ try:
85
+ return paginate_sequence(items, cursor, page_size)
86
+ except ValueError as e:
87
+ raise McpError(mcp.types.ErrorData(code=-32602, message=str(e))) from e
88
+
89
+
90
+ class MCPOperationsMixin:
91
+ """Mixin providing MCP protocol handler setup and wire-format handlers.
92
+
93
+ Note: Methods registered with SDK decorators (e.g., _list_tools_mcp, _call_tool_mcp)
94
+ cannot use `self: FastMCP` type hints because the SDK's `get_type_hints()` fails
95
+ to resolve FastMCP at runtime (it's only available under TYPE_CHECKING). When
96
+ type hints fail to resolve, the SDK falls back to calling handlers with no arguments.
97
+ These methods use untyped `self` to avoid this issue.
98
+ """
99
+
100
+ def _setup_handlers(self: FastMCP) -> None:
101
+ """Set up core MCP protocol handlers.
102
+
103
+ List handlers use SDK decorators that pass the request object to our handler
104
+ (needed for pagination cursor). The SDK also populates caches like _tool_cache.
105
+
106
+ Exception: list_resource_templates SDK decorator doesn't pass the request,
107
+ so we register that handler directly.
108
+
109
+ The call_tool decorator is from the SDK (supports CreateTaskResult + validate_input).
110
+ The read_resource and get_prompt decorators are from LowLevelServer to add
111
+ CreateTaskResult support until the SDK provides it natively.
112
+ """
113
+ self._mcp_server.list_tools()(self._list_tools_mcp)
114
+ self._mcp_server.list_resources()(self._list_resources_mcp)
115
+ self._mcp_server.list_prompts()(self._list_prompts_mcp)
116
+
117
+ # list_resource_templates SDK decorator doesn't pass the request to handlers,
118
+ # so we register directly to get cursor access for pagination
119
+ self._mcp_server.request_handlers[mcp.types.ListResourceTemplatesRequest] = (
120
+ self._wrap_list_handler(self._list_resource_templates_mcp)
121
+ )
122
+
123
+ self._mcp_server.call_tool(validate_input=self.strict_input_validation)(
124
+ self._call_tool_mcp
125
+ )
126
+ self._mcp_server.read_resource()(self._read_resource_mcp)
127
+ self._mcp_server.get_prompt()(self._get_prompt_mcp)
128
+
129
+ # Register SEP-1686 task protocol handlers
130
+ self._setup_task_protocol_handlers()
131
+
132
+ def _wrap_list_handler(
133
+ self: FastMCP, handler: Callable[..., Awaitable[Any]]
134
+ ) -> Callable[..., Awaitable[mcp.types.ServerResult]]:
135
+ """Wrap a list handler to pass the request and return ServerResult."""
136
+
137
+ async def wrapper(request: Any) -> mcp.types.ServerResult:
138
+ result = await handler(request)
139
+ return mcp.types.ServerResult(result)
140
+
141
+ return wrapper
142
+
143
+ async def _list_tools_mcp(
144
+ self, request: mcp.types.ListToolsRequest
145
+ ) -> mcp.types.ListToolsResult:
146
+ """
147
+ List all available tools, in the format expected by the low-level MCP
148
+ server. Supports pagination when list_page_size is configured.
149
+ """
150
+ # Cast self to FastMCP for type checking (see class docstring for why
151
+ # we can't use `self: FastMCP` annotation on SDK-registered handlers)
152
+ server = cast("FastMCP", self)
153
+ logger.debug(f"[{server.name}] Handler called: list_tools")
154
+
155
+ tools = _dedupe_with_versions(list(await server.list_tools()), lambda t: t.name)
156
+ sdk_tools = [tool.to_mcp_tool(name=tool.name) for tool in tools]
157
+ # SDK may pass None for internal cache refresh despite type hint
158
+ cursor = (
159
+ request.params.cursor if request is not None and request.params else None
160
+ )
161
+ page, next_cursor = _apply_pagination(sdk_tools, cursor, server._list_page_size)
162
+ return mcp.types.ListToolsResult(tools=page, nextCursor=next_cursor)
163
+
164
+ async def _list_resources_mcp(
165
+ self, request: mcp.types.ListResourcesRequest
166
+ ) -> mcp.types.ListResourcesResult:
167
+ """
168
+ List all available resources, in the format expected by the low-level MCP
169
+ server. Supports pagination when list_page_size is configured.
170
+ """
171
+ server = cast("FastMCP", self)
172
+ logger.debug(f"[{server.name}] Handler called: list_resources")
173
+
174
+ resources = _dedupe_with_versions(
175
+ list(await server.list_resources()), lambda r: str(r.uri)
176
+ )
177
+ sdk_resources = [
178
+ resource.to_mcp_resource(uri=str(resource.uri)) for resource in resources
179
+ ]
180
+ cursor = request.params.cursor if request.params else None
181
+ page, next_cursor = _apply_pagination(
182
+ sdk_resources, cursor, server._list_page_size
183
+ )
184
+ return mcp.types.ListResourcesResult(resources=page, nextCursor=next_cursor)
185
+
186
+ async def _list_resource_templates_mcp(
187
+ self, request: mcp.types.ListResourceTemplatesRequest
188
+ ) -> mcp.types.ListResourceTemplatesResult:
189
+ """
190
+ List all available resource templates, in the format expected by the low-level MCP
191
+ server. Supports pagination when list_page_size is configured.
192
+ """
193
+ server = cast("FastMCP", self)
194
+ logger.debug(f"[{server.name}] Handler called: list_resource_templates")
195
+
196
+ templates = _dedupe_with_versions(
197
+ list(await server.list_resource_templates()), lambda t: t.uri_template
198
+ )
199
+ sdk_templates = [
200
+ template.to_mcp_template(uriTemplate=template.uri_template)
201
+ for template in templates
202
+ ]
203
+ cursor = request.params.cursor if request.params else None
204
+ page, next_cursor = _apply_pagination(
205
+ sdk_templates, cursor, server._list_page_size
206
+ )
207
+ return mcp.types.ListResourceTemplatesResult(
208
+ resourceTemplates=page, nextCursor=next_cursor
209
+ )
210
+
211
+ async def _list_prompts_mcp(
212
+ self, request: mcp.types.ListPromptsRequest
213
+ ) -> mcp.types.ListPromptsResult:
214
+ """
215
+ List all available prompts, in the format expected by the low-level MCP
216
+ server. Supports pagination when list_page_size is configured.
217
+ """
218
+ server = cast("FastMCP", self)
219
+ logger.debug(f"[{server.name}] Handler called: list_prompts")
220
+
221
+ prompts = _dedupe_with_versions(
222
+ list(await server.list_prompts()), lambda p: p.name
223
+ )
224
+ sdk_prompts = [prompt.to_mcp_prompt(name=prompt.name) for prompt in prompts]
225
+ cursor = request.params.cursor if request.params else None
226
+ page, next_cursor = _apply_pagination(
227
+ sdk_prompts, cursor, server._list_page_size
228
+ )
229
+ return mcp.types.ListPromptsResult(prompts=page, nextCursor=next_cursor)
230
+
231
+ async def _call_tool_mcp(
232
+ self, key: str, arguments: dict[str, Any]
233
+ ) -> (
234
+ list[ContentBlock]
235
+ | tuple[list[ContentBlock], dict[str, Any]]
236
+ | mcp.types.CallToolResult
237
+ | mcp.types.CreateTaskResult
238
+ ):
239
+ """
240
+ Handle MCP 'callTool' requests.
241
+
242
+ Extracts task metadata from MCP request context and passes it explicitly
243
+ to call_tool(). The tool's _run() method handles the backgrounding decision,
244
+ ensuring middleware runs before Docket.
245
+
246
+ Args:
247
+ key: The name of the tool to call
248
+ arguments: Arguments to pass to the tool
249
+
250
+ Returns:
251
+ Tool result or CreateTaskResult for background execution
252
+ """
253
+ server = cast("FastMCP", self)
254
+ logger.debug(
255
+ f"[{server.name}] Handler called: call_tool %s with %s", key, arguments
256
+ )
257
+
258
+ try:
259
+ # Extract version and task metadata from request context.
260
+ # fn_key is set by call_tool() after finding the tool.
261
+ version_str: str | None = None
262
+ task_meta: TaskMeta | None = None
263
+ try:
264
+ ctx = server._mcp_server.request_context
265
+ # Extract version from request-level _meta.fastmcp.version
266
+ if ctx.meta:
267
+ meta_dict = ctx.meta.model_dump(exclude_none=True)
268
+ version_str = meta_dict.get("fastmcp", {}).get("version")
269
+ # Extract SEP-1686 task metadata
270
+ if ctx.experimental.is_task:
271
+ mcp_task_meta = ctx.experimental.task_metadata
272
+ task_meta_dict = mcp_task_meta.model_dump(exclude_none=True)
273
+ task_meta = TaskMeta(ttl=task_meta_dict.get("ttl"))
274
+ except (AttributeError, LookupError):
275
+ pass
276
+
277
+ version = VersionSpec(eq=version_str) if version_str else None
278
+ result = await server.call_tool(
279
+ key, arguments, version=version, task_meta=task_meta
280
+ )
281
+
282
+ if isinstance(result, mcp.types.CreateTaskResult):
283
+ return result
284
+ return result.to_mcp_result()
285
+
286
+ except DisabledError as e:
287
+ raise NotFoundError(f"Unknown tool: {key!r}") from e
288
+ except NotFoundError as e:
289
+ raise NotFoundError(f"Unknown tool: {key!r}") from e
290
+
291
+ async def _read_resource_mcp(
292
+ self, uri: AnyUrl | str
293
+ ) -> mcp.types.ReadResourceResult | mcp.types.CreateTaskResult:
294
+ """Handle MCP 'readResource' requests.
295
+
296
+ Extracts task metadata from MCP request context and passes it explicitly
297
+ to read_resource(). The resource's _read() method handles the backgrounding
298
+ decision, ensuring middleware runs before Docket.
299
+
300
+ Args:
301
+ uri: The resource URI
302
+
303
+ Returns:
304
+ ReadResourceResult or CreateTaskResult for background execution
305
+ """
306
+ server = cast("FastMCP", self)
307
+ logger.debug(f"[{server.name}] Handler called: read_resource %s", uri)
308
+
309
+ try:
310
+ # Extract version and task metadata from request context.
311
+ version_str: str | None = None
312
+ task_meta: TaskMeta | None = None
313
+ try:
314
+ ctx = server._mcp_server.request_context
315
+ # Extract version from _meta.fastmcp.version if provided
316
+ if ctx.meta:
317
+ meta_dict = ctx.meta.model_dump(exclude_none=True)
318
+ fastmcp_meta = meta_dict.get("fastmcp") or {}
319
+ version_str = fastmcp_meta.get("version")
320
+ # Extract SEP-1686 task metadata
321
+ if ctx.experimental.is_task:
322
+ mcp_task_meta = ctx.experimental.task_metadata
323
+ task_meta_dict = mcp_task_meta.model_dump(exclude_none=True)
324
+ task_meta = TaskMeta(ttl=task_meta_dict.get("ttl"))
325
+ except (AttributeError, LookupError):
326
+ pass
327
+
328
+ version = VersionSpec(eq=version_str) if version_str else None
329
+ result = await server.read_resource(
330
+ str(uri), version=version, task_meta=task_meta
331
+ )
332
+
333
+ if isinstance(result, mcp.types.CreateTaskResult):
334
+ return result
335
+ return result.to_mcp_result(uri)
336
+ except DisabledError as e:
337
+ raise NotFoundError(f"Unknown resource: {str(uri)!r}") from e
338
+ except NotFoundError:
339
+ raise
340
+
341
+ async def _get_prompt_mcp(
342
+ self, name: str, arguments: dict[str, Any] | None
343
+ ) -> mcp.types.GetPromptResult | mcp.types.CreateTaskResult:
344
+ """Handle MCP 'getPrompt' requests.
345
+
346
+ Extracts task metadata from MCP request context and passes it explicitly
347
+ to render_prompt(). The prompt's _render() method handles the backgrounding
348
+ decision, ensuring middleware runs before Docket.
349
+
350
+ Args:
351
+ name: The prompt name
352
+ arguments: Prompt arguments
353
+
354
+ Returns:
355
+ GetPromptResult or CreateTaskResult for background execution
356
+ """
357
+ server = cast("FastMCP", self)
358
+ logger.debug(
359
+ f"[{server.name}] Handler called: get_prompt %s with %s", name, arguments
360
+ )
361
+
362
+ try:
363
+ # Extract version and task metadata from request context.
364
+ # fn_key is set by render_prompt() after finding the prompt.
365
+ version_str: str | None = None
366
+ task_meta: TaskMeta | None = None
367
+ try:
368
+ ctx = server._mcp_server.request_context
369
+ # Extract version from request-level _meta.fastmcp.version
370
+ if ctx.meta:
371
+ meta_dict = ctx.meta.model_dump(exclude_none=True)
372
+ version_str = meta_dict.get("fastmcp", {}).get("version")
373
+ # Extract SEP-1686 task metadata
374
+ if ctx.experimental.is_task:
375
+ mcp_task_meta = ctx.experimental.task_metadata
376
+ task_meta_dict = mcp_task_meta.model_dump(exclude_none=True)
377
+ task_meta = TaskMeta(ttl=task_meta_dict.get("ttl"))
378
+ except (AttributeError, LookupError):
379
+ pass
380
+
381
+ version = VersionSpec(eq=version_str) if version_str else None
382
+ result = await server.render_prompt(
383
+ name, arguments, version=version, task_meta=task_meta
384
+ )
385
+
386
+ if isinstance(result, mcp.types.CreateTaskResult):
387
+ return result
388
+ return result.to_mcp_prompt_result()
389
+ except DisabledError as e:
390
+ raise NotFoundError(f"Unknown prompt: {name!r}") from e
391
+ except NotFoundError:
392
+ raise