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,674 @@
1
+ """FastMCPProvider for wrapping FastMCP servers as providers.
2
+
3
+ This module provides the `FastMCPProvider` class that wraps a FastMCP server
4
+ and exposes its components through the Provider interface.
5
+
6
+ It also provides FastMCPProvider* component classes that delegate execution to
7
+ the wrapped server's middleware, ensuring middleware runs when components are
8
+ executed.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import re
14
+ from collections.abc import AsyncIterator, Sequence
15
+ from contextlib import asynccontextmanager
16
+ from typing import TYPE_CHECKING, Any, overload
17
+
18
+ import mcp.types
19
+ from mcp.types import AnyUrl
20
+
21
+ from fastmcp.prompts.prompt import Prompt, PromptResult
22
+ from fastmcp.resources.resource import Resource, ResourceResult
23
+ from fastmcp.resources.template import ResourceTemplate
24
+ from fastmcp.server.providers.base import Provider
25
+ from fastmcp.server.tasks.config import TaskMeta
26
+ from fastmcp.server.telemetry import delegate_span
27
+ from fastmcp.tools.tool import Tool, ToolResult
28
+ from fastmcp.utilities.components import FastMCPComponent
29
+ from fastmcp.utilities.versions import VersionSpec
30
+
31
+ if TYPE_CHECKING:
32
+ from docket import Docket
33
+ from docket.execution import Execution
34
+
35
+ from fastmcp.server.server import FastMCP
36
+
37
+
38
+ def _expand_uri_template(template: str, params: dict[str, Any]) -> str:
39
+ """Expand a URI template with parameters.
40
+
41
+ Simple implementation that handles {name} style placeholders.
42
+ """
43
+ result = template
44
+ for key, value in params.items():
45
+ result = re.sub(rf"\{{{key}\}}", str(value), result)
46
+ return result
47
+
48
+
49
+ # -----------------------------------------------------------------------------
50
+ # FastMCPProvider component classes
51
+ # -----------------------------------------------------------------------------
52
+
53
+
54
+ class FastMCPProviderTool(Tool):
55
+ """Tool that delegates execution to a wrapped server's middleware.
56
+
57
+ When `run()` is called, this tool invokes the wrapped server's
58
+ `_call_tool_middleware()` method, ensuring the server's middleware
59
+ chain is executed.
60
+ """
61
+
62
+ _server: Any = None # FastMCP, but Any to avoid circular import
63
+ _original_name: str | None = None
64
+
65
+ def __init__(
66
+ self,
67
+ server: Any,
68
+ original_name: str,
69
+ **kwargs: Any,
70
+ ):
71
+ super().__init__(**kwargs)
72
+ self._server = server
73
+ self._original_name = original_name
74
+
75
+ @classmethod
76
+ def wrap(cls, server: Any, tool: Tool) -> FastMCPProviderTool:
77
+ """Wrap a Tool to delegate execution to the server's middleware."""
78
+ return cls(
79
+ server=server,
80
+ original_name=tool.name,
81
+ name=tool.name,
82
+ version=tool.version,
83
+ description=tool.description,
84
+ parameters=tool.parameters,
85
+ output_schema=tool.output_schema,
86
+ tags=tool.tags,
87
+ annotations=tool.annotations,
88
+ task_config=tool.task_config,
89
+ )
90
+
91
+ @overload
92
+ async def _run(
93
+ self,
94
+ arguments: dict[str, Any],
95
+ task_meta: None = None,
96
+ ) -> ToolResult: ...
97
+
98
+ @overload
99
+ async def _run(
100
+ self,
101
+ arguments: dict[str, Any],
102
+ task_meta: TaskMeta,
103
+ ) -> mcp.types.CreateTaskResult: ...
104
+
105
+ async def _run(
106
+ self,
107
+ arguments: dict[str, Any],
108
+ task_meta: TaskMeta | None = None,
109
+ ) -> ToolResult | mcp.types.CreateTaskResult:
110
+ """Delegate to child server's call_tool() with task_meta.
111
+
112
+ Passes task_meta through to the child server so it can handle
113
+ backgrounding appropriately. fn_key is already set by the parent
114
+ server before calling this method.
115
+ """
116
+ # Pass exact version so child executes the correct version
117
+ version = VersionSpec(eq=self.version) if self.version else None
118
+
119
+ with delegate_span(
120
+ self._original_name or "", "FastMCPProvider", self._original_name or ""
121
+ ):
122
+ return await self._server.call_tool(
123
+ self._original_name, arguments, version=version, task_meta=task_meta
124
+ )
125
+
126
+ async def run(self, arguments: dict[str, Any]) -> ToolResult:
127
+ """Delegate to child server's call_tool() without task_meta.
128
+
129
+ This is called when the tool is used within a TransformedTool
130
+ forwarding function or other contexts where task_meta is not available.
131
+ """
132
+ # Pass exact version so child executes the correct version
133
+ version = VersionSpec(eq=self.version) if self.version else None
134
+
135
+ result = await self._server.call_tool(
136
+ self._original_name, arguments, version=version
137
+ )
138
+ # Result from call_tool should always be ToolResult when no task_meta
139
+ if isinstance(result, mcp.types.CreateTaskResult):
140
+ raise RuntimeError(
141
+ "Unexpected CreateTaskResult from call_tool without task_meta"
142
+ )
143
+ return result
144
+
145
+ def get_span_attributes(self) -> dict[str, Any]:
146
+ return super().get_span_attributes() | {
147
+ "fastmcp.provider.type": "FastMCPProvider",
148
+ "fastmcp.delegate.original_name": self._original_name,
149
+ }
150
+
151
+
152
+ class FastMCPProviderResource(Resource):
153
+ """Resource that delegates reading to a wrapped server's read_resource().
154
+
155
+ When `read()` is called, this resource invokes the wrapped server's
156
+ `read_resource()` method, ensuring the server's middleware chain is executed.
157
+ """
158
+
159
+ _server: Any = None # FastMCP, but Any to avoid circular import
160
+ _original_uri: str | None = None
161
+
162
+ def __init__(
163
+ self,
164
+ server: Any,
165
+ original_uri: str,
166
+ **kwargs: Any,
167
+ ):
168
+ super().__init__(**kwargs)
169
+ self._server = server
170
+ self._original_uri = original_uri
171
+
172
+ @classmethod
173
+ def wrap(cls, server: Any, resource: Resource) -> FastMCPProviderResource:
174
+ """Wrap a Resource to delegate reading to the server's middleware."""
175
+ return cls(
176
+ server=server,
177
+ original_uri=str(resource.uri),
178
+ uri=resource.uri,
179
+ version=resource.version,
180
+ name=resource.name,
181
+ description=resource.description,
182
+ mime_type=resource.mime_type,
183
+ tags=resource.tags,
184
+ annotations=resource.annotations,
185
+ task_config=resource.task_config,
186
+ )
187
+
188
+ @overload
189
+ async def _read(self, task_meta: None = None) -> ResourceResult: ...
190
+
191
+ @overload
192
+ async def _read(self, task_meta: TaskMeta) -> mcp.types.CreateTaskResult: ...
193
+
194
+ async def _read(
195
+ self, task_meta: TaskMeta | None = None
196
+ ) -> ResourceResult | mcp.types.CreateTaskResult:
197
+ """Delegate to child server's read_resource() with task_meta.
198
+
199
+ Passes task_meta through to the child server so it can handle
200
+ backgrounding appropriately. fn_key is already set by the parent
201
+ server before calling this method.
202
+ """
203
+ # Pass exact version so child reads the correct version
204
+ version = VersionSpec(eq=self.version) if self.version else None
205
+
206
+ with delegate_span(
207
+ self._original_uri or "", "FastMCPProvider", self._original_uri or ""
208
+ ):
209
+ return await self._server.read_resource(
210
+ self._original_uri, version=version, task_meta=task_meta
211
+ )
212
+
213
+ def get_span_attributes(self) -> dict[str, Any]:
214
+ return super().get_span_attributes() | {
215
+ "fastmcp.provider.type": "FastMCPProvider",
216
+ "fastmcp.delegate.original_uri": self._original_uri,
217
+ }
218
+
219
+
220
+ class FastMCPProviderPrompt(Prompt):
221
+ """Prompt that delegates rendering to a wrapped server's render_prompt().
222
+
223
+ When `render()` is called, this prompt invokes the wrapped server's
224
+ `render_prompt()` method, ensuring the server's middleware chain is executed.
225
+ """
226
+
227
+ _server: Any = None # FastMCP, but Any to avoid circular import
228
+ _original_name: str | None = None
229
+
230
+ def __init__(
231
+ self,
232
+ server: Any,
233
+ original_name: str,
234
+ **kwargs: Any,
235
+ ):
236
+ super().__init__(**kwargs)
237
+ self._server = server
238
+ self._original_name = original_name
239
+
240
+ @classmethod
241
+ def wrap(cls, server: Any, prompt: Prompt) -> FastMCPProviderPrompt:
242
+ """Wrap a Prompt to delegate rendering to the server's middleware."""
243
+ return cls(
244
+ server=server,
245
+ original_name=prompt.name,
246
+ name=prompt.name,
247
+ version=prompt.version,
248
+ description=prompt.description,
249
+ arguments=prompt.arguments,
250
+ tags=prompt.tags,
251
+ task_config=prompt.task_config,
252
+ )
253
+
254
+ @overload
255
+ async def _render(
256
+ self,
257
+ arguments: dict[str, Any] | None = None,
258
+ task_meta: None = None,
259
+ ) -> PromptResult: ...
260
+
261
+ @overload
262
+ async def _render(
263
+ self,
264
+ arguments: dict[str, Any] | None,
265
+ task_meta: TaskMeta,
266
+ ) -> mcp.types.CreateTaskResult: ...
267
+
268
+ async def _render(
269
+ self,
270
+ arguments: dict[str, Any] | None = None,
271
+ task_meta: TaskMeta | None = None,
272
+ ) -> PromptResult | mcp.types.CreateTaskResult:
273
+ """Delegate to child server's render_prompt() with task_meta.
274
+
275
+ Passes task_meta through to the child server so it can handle
276
+ backgrounding appropriately. fn_key is already set by the parent
277
+ server before calling this method.
278
+ """
279
+ # Pass exact version so child renders the correct version
280
+ version = VersionSpec(eq=self.version) if self.version else None
281
+
282
+ with delegate_span(
283
+ self._original_name or "", "FastMCPProvider", self._original_name or ""
284
+ ):
285
+ return await self._server.render_prompt(
286
+ self._original_name, arguments, version=version, task_meta=task_meta
287
+ )
288
+
289
+ async def render(self, arguments: dict[str, Any] | None = None) -> PromptResult:
290
+ """Delegate to child server's render_prompt() without task_meta.
291
+
292
+ This is called when the prompt is used within a transformed context
293
+ or other contexts where task_meta is not available.
294
+ """
295
+ # Pass exact version so child renders the correct version
296
+ version = VersionSpec(eq=self.version) if self.version else None
297
+
298
+ result = await self._server.render_prompt(
299
+ self._original_name, arguments, version=version
300
+ )
301
+ # Result from render_prompt should always be PromptResult when no task_meta
302
+ if isinstance(result, mcp.types.CreateTaskResult):
303
+ raise RuntimeError(
304
+ "Unexpected CreateTaskResult from render_prompt without task_meta"
305
+ )
306
+ return result
307
+
308
+ def get_span_attributes(self) -> dict[str, Any]:
309
+ return super().get_span_attributes() | {
310
+ "fastmcp.provider.type": "FastMCPProvider",
311
+ "fastmcp.delegate.original_name": self._original_name,
312
+ }
313
+
314
+
315
+ class FastMCPProviderResourceTemplate(ResourceTemplate):
316
+ """Resource template that creates FastMCPProviderResources.
317
+
318
+ When `create_resource()` is called, this template creates a
319
+ FastMCPProviderResource that will invoke the wrapped server's middleware
320
+ when read.
321
+ """
322
+
323
+ _server: Any = None # FastMCP, but Any to avoid circular import
324
+ _original_uri_template: str | None = None
325
+
326
+ def __init__(
327
+ self,
328
+ server: Any,
329
+ original_uri_template: str,
330
+ **kwargs: Any,
331
+ ):
332
+ super().__init__(**kwargs)
333
+ self._server = server
334
+ self._original_uri_template = original_uri_template
335
+
336
+ @classmethod
337
+ def wrap(
338
+ cls, server: Any, template: ResourceTemplate
339
+ ) -> FastMCPProviderResourceTemplate:
340
+ """Wrap a ResourceTemplate to create FastMCPProviderResources."""
341
+ return cls(
342
+ server=server,
343
+ original_uri_template=template.uri_template,
344
+ uri_template=template.uri_template,
345
+ version=template.version,
346
+ name=template.name,
347
+ description=template.description,
348
+ mime_type=template.mime_type,
349
+ parameters=template.parameters,
350
+ tags=template.tags,
351
+ annotations=template.annotations,
352
+ task_config=template.task_config,
353
+ )
354
+
355
+ async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
356
+ """Create a FastMCPProviderResource for the given URI.
357
+
358
+ The `uri` is the external/transformed URI (e.g., with namespace prefix).
359
+ We use `_original_uri_template` with `params` to construct the internal
360
+ URI that the nested server understands.
361
+ """
362
+ # Expand the original template with params to get internal URI
363
+ original_uri = _expand_uri_template(self._original_uri_template or "", params)
364
+ return FastMCPProviderResource(
365
+ server=self._server,
366
+ original_uri=original_uri,
367
+ uri=AnyUrl(uri),
368
+ name=self.name,
369
+ description=self.description,
370
+ mime_type=self.mime_type,
371
+ )
372
+
373
+ @overload
374
+ async def _read(
375
+ self, uri: str, params: dict[str, Any], task_meta: None = None
376
+ ) -> ResourceResult: ...
377
+
378
+ @overload
379
+ async def _read(
380
+ self, uri: str, params: dict[str, Any], task_meta: TaskMeta
381
+ ) -> mcp.types.CreateTaskResult: ...
382
+
383
+ async def _read(
384
+ self, uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None
385
+ ) -> ResourceResult | mcp.types.CreateTaskResult:
386
+ """Delegate to child server's read_resource() with task_meta.
387
+
388
+ Passes task_meta through to the child server so it can handle
389
+ backgrounding appropriately. fn_key is already set by the parent
390
+ server before calling this method.
391
+ """
392
+ # Expand the original template with params to get internal URI
393
+ original_uri = _expand_uri_template(self._original_uri_template or "", params)
394
+
395
+ # Pass exact version so child reads the correct version
396
+ version = VersionSpec(eq=self.version) if self.version else None
397
+
398
+ with delegate_span(
399
+ original_uri, "FastMCPProvider", self._original_uri_template or ""
400
+ ):
401
+ return await self._server.read_resource(
402
+ original_uri, version=version, task_meta=task_meta
403
+ )
404
+
405
+ async def read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult:
406
+ """Read the resource content for background task execution.
407
+
408
+ Reads the resource via the wrapped server and returns the ResourceResult.
409
+ This method is called by Docket during background task execution.
410
+ """
411
+ # Expand the original template with arguments to get internal URI
412
+ original_uri = _expand_uri_template(
413
+ self._original_uri_template or "", arguments
414
+ )
415
+
416
+ # Pass exact version so child reads the correct version
417
+ version = VersionSpec(eq=self.version) if self.version else None
418
+
419
+ # Read from the wrapped server
420
+ result = await self._server.read_resource(original_uri, version=version)
421
+ if isinstance(result, mcp.types.CreateTaskResult):
422
+ raise RuntimeError("Unexpected CreateTaskResult during Docket execution")
423
+
424
+ return result
425
+
426
+ def register_with_docket(self, docket: Docket) -> None:
427
+ """No-op: the child's actual template is registered via get_tasks()."""
428
+
429
+ async def add_to_docket(
430
+ self,
431
+ docket: Docket,
432
+ params: dict[str, Any],
433
+ *,
434
+ fn_key: str | None = None,
435
+ task_key: str | None = None,
436
+ **kwargs: Any,
437
+ ) -> Execution:
438
+ """Schedule this template for background execution via docket.
439
+
440
+ The child's FunctionResourceTemplate.fn is registered (via get_tasks),
441
+ and it expects splatted **kwargs, so we splat params here.
442
+ """
443
+ lookup_key = fn_key or self.key
444
+ if task_key:
445
+ kwargs["key"] = task_key
446
+ return await docket.add(lookup_key, **kwargs)(**params)
447
+
448
+ def get_span_attributes(self) -> dict[str, Any]:
449
+ return super().get_span_attributes() | {
450
+ "fastmcp.provider.type": "FastMCPProvider",
451
+ "fastmcp.delegate.original_uri_template": self._original_uri_template,
452
+ }
453
+
454
+
455
+ # -----------------------------------------------------------------------------
456
+ # FastMCPProvider
457
+ # -----------------------------------------------------------------------------
458
+
459
+
460
+ class FastMCPProvider(Provider):
461
+ """Provider that wraps a FastMCP server.
462
+
463
+ This provider enables mounting one FastMCP server onto another, exposing
464
+ the mounted server's tools, resources, and prompts through the parent
465
+ server.
466
+
467
+ Components returned by this provider are wrapped in FastMCPProvider*
468
+ classes that delegate execution to the wrapped server's middleware chain.
469
+ This ensures middleware runs when components are executed.
470
+
471
+ Example:
472
+ ```python
473
+ from fastmcp import FastMCP
474
+ from fastmcp.server.providers import FastMCPProvider
475
+
476
+ main = FastMCP("Main")
477
+ sub = FastMCP("Sub")
478
+
479
+ @sub.tool
480
+ def greet(name: str) -> str:
481
+ return f"Hello, {name}!"
482
+
483
+ # Mount directly - tools accessible by original names
484
+ main.add_provider(FastMCPProvider(sub))
485
+
486
+ # Or with namespace
487
+ from fastmcp.server.transforms import Namespace
488
+ provider = FastMCPProvider(sub)
489
+ provider.add_transform(Namespace("sub"))
490
+ main.add_provider(provider)
491
+ ```
492
+
493
+ Note:
494
+ Normally you would use `FastMCP.mount()` which handles proxy conversion
495
+ and creates the provider with namespace automatically.
496
+ """
497
+
498
+ def __init__(self, server: FastMCP[Any]):
499
+ """Initialize a FastMCPProvider.
500
+
501
+ Args:
502
+ server: The FastMCP server to wrap.
503
+ """
504
+ super().__init__()
505
+ self.server = server
506
+
507
+ # -------------------------------------------------------------------------
508
+ # Tool methods
509
+ # -------------------------------------------------------------------------
510
+
511
+ async def _list_tools(self) -> Sequence[Tool]:
512
+ """List all tools from the mounted server as FastMCPProviderTools.
513
+
514
+ Runs the mounted server's middleware so filtering/transformation applies.
515
+ Wraps each tool as a FastMCPProviderTool that delegates execution to
516
+ the nested server's middleware.
517
+ """
518
+ raw_tools = await self.server.list_tools()
519
+ return [FastMCPProviderTool.wrap(self.server, t) for t in raw_tools]
520
+
521
+ async def _get_tool(
522
+ self, name: str, version: VersionSpec | None = None
523
+ ) -> Tool | None:
524
+ """Get a tool by name as a FastMCPProviderTool.
525
+
526
+ Passes the full VersionSpec to the nested server, which handles both
527
+ exact version matching and range filtering. Uses get_tool to ensure
528
+ the nested server's transforms are applied.
529
+ """
530
+ raw_tool = await self.server.get_tool(name, version)
531
+ if raw_tool is None:
532
+ return None
533
+ return FastMCPProviderTool.wrap(self.server, raw_tool)
534
+
535
+ # -------------------------------------------------------------------------
536
+ # Resource methods
537
+ # -------------------------------------------------------------------------
538
+
539
+ async def _list_resources(self) -> Sequence[Resource]:
540
+ """List all resources from the mounted server as FastMCPProviderResources.
541
+
542
+ Runs the mounted server's middleware so filtering/transformation applies.
543
+ Wraps each resource as a FastMCPProviderResource that delegates reading
544
+ to the nested server's middleware.
545
+ """
546
+ raw_resources = await self.server.list_resources()
547
+ return [FastMCPProviderResource.wrap(self.server, r) for r in raw_resources]
548
+
549
+ async def _get_resource(
550
+ self, uri: str, version: VersionSpec | None = None
551
+ ) -> Resource | None:
552
+ """Get a concrete resource by URI as a FastMCPProviderResource.
553
+
554
+ Passes the full VersionSpec to the nested server, which handles both
555
+ exact version matching and range filtering. Uses get_resource to ensure
556
+ the nested server's transforms are applied.
557
+ """
558
+ raw_resource = await self.server.get_resource(uri, version)
559
+ if raw_resource is None:
560
+ return None
561
+ return FastMCPProviderResource.wrap(self.server, raw_resource)
562
+
563
+ # -------------------------------------------------------------------------
564
+ # Resource template methods
565
+ # -------------------------------------------------------------------------
566
+
567
+ async def _list_resource_templates(self) -> Sequence[ResourceTemplate]:
568
+ """List all resource templates from the mounted server.
569
+
570
+ Runs the mounted server's middleware so filtering/transformation applies.
571
+ Returns FastMCPProviderResourceTemplate instances that create
572
+ FastMCPProviderResources when materialized.
573
+ """
574
+ raw_templates = await self.server.list_resource_templates()
575
+ return [
576
+ FastMCPProviderResourceTemplate.wrap(self.server, t) for t in raw_templates
577
+ ]
578
+
579
+ async def _get_resource_template(
580
+ self, uri: str, version: VersionSpec | None = None
581
+ ) -> ResourceTemplate | None:
582
+ """Get a resource template that matches the given URI.
583
+
584
+ Passes the full VersionSpec to the nested server, which handles both
585
+ exact version matching and range filtering. Uses get_resource_template
586
+ to ensure the nested server's transforms are applied.
587
+ """
588
+ raw_template = await self.server.get_resource_template(uri, version)
589
+ if raw_template is None:
590
+ return None
591
+ return FastMCPProviderResourceTemplate.wrap(self.server, raw_template)
592
+
593
+ # -------------------------------------------------------------------------
594
+ # Prompt methods
595
+ # -------------------------------------------------------------------------
596
+
597
+ async def _list_prompts(self) -> Sequence[Prompt]:
598
+ """List all prompts from the mounted server as FastMCPProviderPrompts.
599
+
600
+ Runs the mounted server's middleware so filtering/transformation applies.
601
+ Returns FastMCPProviderPrompt instances that delegate rendering to the
602
+ wrapped server's middleware.
603
+ """
604
+ raw_prompts = await self.server.list_prompts()
605
+ return [FastMCPProviderPrompt.wrap(self.server, p) for p in raw_prompts]
606
+
607
+ async def _get_prompt(
608
+ self, name: str, version: VersionSpec | None = None
609
+ ) -> Prompt | None:
610
+ """Get a prompt by name as a FastMCPProviderPrompt.
611
+
612
+ Passes the full VersionSpec to the nested server, which handles both
613
+ exact version matching and range filtering. Uses get_prompt to ensure
614
+ the nested server's transforms are applied.
615
+ """
616
+ raw_prompt = await self.server.get_prompt(name, version)
617
+ if raw_prompt is None:
618
+ return None
619
+ return FastMCPProviderPrompt.wrap(self.server, raw_prompt)
620
+
621
+ # -------------------------------------------------------------------------
622
+ # Task registration
623
+ # -------------------------------------------------------------------------
624
+
625
+ async def get_tasks(self) -> Sequence[FastMCPComponent]:
626
+ """Return task-eligible components from the mounted server.
627
+
628
+ Returns the child's ACTUAL components (not wrapped) so their actual
629
+ functions get registered with Docket. Gets components with child
630
+ server's transforms applied, then applies this provider's transforms
631
+ for correct registration keys.
632
+ """
633
+ # Get tasks with child server's transforms already applied
634
+ components = list(await self.server.get_tasks())
635
+
636
+ # Separate by type for this provider's transform application
637
+ tools = [c for c in components if isinstance(c, Tool)]
638
+ resources = [c for c in components if isinstance(c, Resource)]
639
+ templates = [c for c in components if isinstance(c, ResourceTemplate)]
640
+ prompts = [c for c in components if isinstance(c, Prompt)]
641
+
642
+ # Apply this provider's transforms sequentially
643
+ for transform in self.transforms:
644
+ tools = await transform.list_tools(tools)
645
+ resources = await transform.list_resources(resources)
646
+ templates = await transform.list_resource_templates(templates)
647
+ prompts = await transform.list_prompts(prompts)
648
+
649
+ # Filter to only task-eligible components (same as base Provider)
650
+ return [
651
+ c
652
+ for c in [
653
+ *tools,
654
+ *resources,
655
+ *templates,
656
+ *prompts,
657
+ ]
658
+ if c.task_config.supports_tasks()
659
+ ]
660
+
661
+ # -------------------------------------------------------------------------
662
+ # Lifecycle methods
663
+ # -------------------------------------------------------------------------
664
+
665
+ @asynccontextmanager
666
+ async def lifespan(self) -> AsyncIterator[None]:
667
+ """Start the mounted server's user lifespan.
668
+
669
+ This starts only the wrapped server's user-defined lifespan, NOT its
670
+ full _lifespan_manager() (which includes Docket). The parent server's
671
+ Docket handles all background tasks.
672
+ """
673
+ async with self.server._lifespan(self.server):
674
+ yield