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.
- fastmcp/_vendor/__init__.py +1 -0
- fastmcp/_vendor/docket_di/README.md +7 -0
- fastmcp/_vendor/docket_di/__init__.py +163 -0
- fastmcp/cli/cli.py +112 -28
- fastmcp/cli/install/claude_code.py +1 -5
- fastmcp/cli/install/claude_desktop.py +1 -5
- fastmcp/cli/install/cursor.py +1 -5
- fastmcp/cli/install/gemini_cli.py +1 -5
- fastmcp/cli/install/mcp_json.py +1 -6
- fastmcp/cli/run.py +146 -5
- fastmcp/client/__init__.py +7 -9
- fastmcp/client/auth/oauth.py +18 -17
- fastmcp/client/client.py +100 -870
- fastmcp/client/elicitation.py +1 -1
- fastmcp/client/mixins/__init__.py +13 -0
- fastmcp/client/mixins/prompts.py +295 -0
- fastmcp/client/mixins/resources.py +325 -0
- fastmcp/client/mixins/task_management.py +157 -0
- fastmcp/client/mixins/tools.py +397 -0
- fastmcp/client/sampling/handlers/anthropic.py +2 -2
- fastmcp/client/sampling/handlers/openai.py +1 -1
- fastmcp/client/tasks.py +3 -3
- fastmcp/client/telemetry.py +47 -0
- fastmcp/client/transports/__init__.py +38 -0
- fastmcp/client/transports/base.py +82 -0
- fastmcp/client/transports/config.py +170 -0
- fastmcp/client/transports/http.py +145 -0
- fastmcp/client/transports/inference.py +154 -0
- fastmcp/client/transports/memory.py +90 -0
- fastmcp/client/transports/sse.py +89 -0
- fastmcp/client/transports/stdio.py +543 -0
- fastmcp/contrib/component_manager/README.md +4 -10
- fastmcp/contrib/component_manager/__init__.py +1 -2
- fastmcp/contrib/component_manager/component_manager.py +95 -160
- fastmcp/contrib/component_manager/example.py +1 -1
- fastmcp/contrib/mcp_mixin/example.py +4 -4
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
- fastmcp/decorators.py +41 -0
- fastmcp/dependencies.py +12 -1
- fastmcp/exceptions.py +4 -0
- fastmcp/experimental/server/openapi/__init__.py +18 -15
- fastmcp/mcp_config.py +13 -4
- fastmcp/prompts/__init__.py +6 -3
- fastmcp/prompts/function_prompt.py +465 -0
- fastmcp/prompts/prompt.py +321 -271
- fastmcp/resources/__init__.py +5 -3
- fastmcp/resources/function_resource.py +335 -0
- fastmcp/resources/resource.py +325 -115
- fastmcp/resources/template.py +215 -43
- fastmcp/resources/types.py +27 -12
- fastmcp/server/__init__.py +2 -2
- fastmcp/server/auth/__init__.py +14 -0
- fastmcp/server/auth/auth.py +30 -10
- fastmcp/server/auth/authorization.py +190 -0
- fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
- fastmcp/server/auth/oauth_proxy/consent.py +361 -0
- fastmcp/server/auth/oauth_proxy/models.py +178 -0
- fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
- fastmcp/server/auth/oauth_proxy/ui.py +277 -0
- fastmcp/server/auth/oidc_proxy.py +2 -2
- fastmcp/server/auth/providers/auth0.py +24 -94
- fastmcp/server/auth/providers/aws.py +26 -95
- fastmcp/server/auth/providers/azure.py +41 -129
- fastmcp/server/auth/providers/descope.py +18 -49
- fastmcp/server/auth/providers/discord.py +25 -86
- fastmcp/server/auth/providers/github.py +23 -87
- fastmcp/server/auth/providers/google.py +24 -87
- fastmcp/server/auth/providers/introspection.py +60 -79
- fastmcp/server/auth/providers/jwt.py +30 -67
- fastmcp/server/auth/providers/oci.py +47 -110
- fastmcp/server/auth/providers/scalekit.py +23 -61
- fastmcp/server/auth/providers/supabase.py +18 -47
- fastmcp/server/auth/providers/workos.py +34 -127
- fastmcp/server/context.py +372 -419
- fastmcp/server/dependencies.py +541 -251
- fastmcp/server/elicitation.py +20 -18
- fastmcp/server/event_store.py +3 -3
- fastmcp/server/http.py +16 -6
- fastmcp/server/lifespan.py +198 -0
- fastmcp/server/low_level.py +92 -2
- fastmcp/server/middleware/__init__.py +5 -1
- fastmcp/server/middleware/authorization.py +312 -0
- fastmcp/server/middleware/caching.py +101 -54
- fastmcp/server/middleware/middleware.py +6 -9
- fastmcp/server/middleware/ping.py +70 -0
- fastmcp/server/middleware/tool_injection.py +2 -2
- fastmcp/server/mixins/__init__.py +7 -0
- fastmcp/server/mixins/lifespan.py +217 -0
- fastmcp/server/mixins/mcp_operations.py +392 -0
- fastmcp/server/mixins/transport.py +342 -0
- fastmcp/server/openapi/__init__.py +41 -21
- fastmcp/server/openapi/components.py +16 -339
- fastmcp/server/openapi/routing.py +34 -118
- fastmcp/server/openapi/server.py +67 -392
- fastmcp/server/providers/__init__.py +71 -0
- fastmcp/server/providers/aggregate.py +261 -0
- fastmcp/server/providers/base.py +578 -0
- fastmcp/server/providers/fastmcp_provider.py +674 -0
- fastmcp/server/providers/filesystem.py +226 -0
- fastmcp/server/providers/filesystem_discovery.py +327 -0
- fastmcp/server/providers/local_provider/__init__.py +11 -0
- fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
- fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
- fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
- fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
- fastmcp/server/providers/local_provider/local_provider.py +465 -0
- fastmcp/server/providers/openapi/__init__.py +39 -0
- fastmcp/server/providers/openapi/components.py +332 -0
- fastmcp/server/providers/openapi/provider.py +405 -0
- fastmcp/server/providers/openapi/routing.py +109 -0
- fastmcp/server/providers/proxy.py +867 -0
- fastmcp/server/providers/skills/__init__.py +59 -0
- fastmcp/server/providers/skills/_common.py +101 -0
- fastmcp/server/providers/skills/claude_provider.py +44 -0
- fastmcp/server/providers/skills/directory_provider.py +153 -0
- fastmcp/server/providers/skills/skill_provider.py +432 -0
- fastmcp/server/providers/skills/vendor_providers.py +142 -0
- fastmcp/server/providers/wrapped_provider.py +140 -0
- fastmcp/server/proxy.py +34 -700
- fastmcp/server/sampling/run.py +341 -2
- fastmcp/server/sampling/sampling_tool.py +4 -3
- fastmcp/server/server.py +1214 -2171
- fastmcp/server/tasks/__init__.py +2 -1
- fastmcp/server/tasks/capabilities.py +13 -1
- fastmcp/server/tasks/config.py +66 -3
- fastmcp/server/tasks/handlers.py +65 -273
- fastmcp/server/tasks/keys.py +4 -6
- fastmcp/server/tasks/requests.py +474 -0
- fastmcp/server/tasks/routing.py +76 -0
- fastmcp/server/tasks/subscriptions.py +20 -11
- fastmcp/server/telemetry.py +131 -0
- fastmcp/server/transforms/__init__.py +244 -0
- fastmcp/server/transforms/namespace.py +193 -0
- fastmcp/server/transforms/prompts_as_tools.py +175 -0
- fastmcp/server/transforms/resources_as_tools.py +190 -0
- fastmcp/server/transforms/tool_transform.py +96 -0
- fastmcp/server/transforms/version_filter.py +124 -0
- fastmcp/server/transforms/visibility.py +526 -0
- fastmcp/settings.py +34 -96
- fastmcp/telemetry.py +122 -0
- fastmcp/tools/__init__.py +10 -3
- fastmcp/tools/function_parsing.py +201 -0
- fastmcp/tools/function_tool.py +467 -0
- fastmcp/tools/tool.py +215 -362
- fastmcp/tools/tool_transform.py +38 -21
- fastmcp/utilities/async_utils.py +69 -0
- fastmcp/utilities/components.py +152 -91
- fastmcp/utilities/inspect.py +8 -20
- fastmcp/utilities/json_schema.py +12 -5
- fastmcp/utilities/json_schema_type.py +17 -15
- fastmcp/utilities/lifespan.py +56 -0
- fastmcp/utilities/logging.py +12 -4
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
- fastmcp/utilities/openapi/parser.py +3 -3
- fastmcp/utilities/pagination.py +80 -0
- fastmcp/utilities/skills.py +253 -0
- fastmcp/utilities/tests.py +0 -16
- fastmcp/utilities/timeout.py +47 -0
- fastmcp/utilities/types.py +1 -1
- fastmcp/utilities/versions.py +285 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
- fastmcp-3.0.0b1.dist-info/RECORD +228 -0
- fastmcp/client/transports.py +0 -1170
- fastmcp/contrib/component_manager/component_service.py +0 -209
- fastmcp/prompts/prompt_manager.py +0 -117
- fastmcp/resources/resource_manager.py +0 -338
- fastmcp/server/tasks/converters.py +0 -206
- fastmcp/server/tasks/protocol.py +0 -359
- fastmcp/tools/tool_manager.py +0 -170
- fastmcp/utilities/mcp_config.py +0 -56
- fastmcp-2.14.4.dist-info/RECORD +0 -161
- /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
- {fastmcp-2.14.4.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
- {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
|