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,256 @@
|
|
|
1
|
+
"""Prompt decorator mixin for LocalProvider.
|
|
2
|
+
|
|
3
|
+
This module provides the PromptDecoratorMixin class that adds prompt
|
|
4
|
+
registration functionality to LocalProvider.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import inspect
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from functools import partial
|
|
12
|
+
from typing import TYPE_CHECKING, Any, overload
|
|
13
|
+
|
|
14
|
+
import mcp.types
|
|
15
|
+
from mcp.types import AnyFunction
|
|
16
|
+
|
|
17
|
+
import fastmcp
|
|
18
|
+
from fastmcp.prompts.function_prompt import FunctionPrompt
|
|
19
|
+
from fastmcp.prompts.prompt import Prompt
|
|
20
|
+
from fastmcp.server.tasks.config import TaskConfig
|
|
21
|
+
from fastmcp.tools.tool import AuthCheckCallable
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from fastmcp.server.providers.local_provider import LocalProvider
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PromptDecoratorMixin:
|
|
28
|
+
"""Mixin class providing prompt decorator functionality for LocalProvider.
|
|
29
|
+
|
|
30
|
+
This mixin contains all methods related to:
|
|
31
|
+
- Prompt registration via add_prompt()
|
|
32
|
+
- Prompt decorator (@provider.prompt)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def add_prompt(self: LocalProvider, prompt: Prompt | Callable[..., Any]) -> Prompt:
|
|
36
|
+
"""Add a prompt to this provider's storage.
|
|
37
|
+
|
|
38
|
+
Accepts either a Prompt object or a decorated function with __fastmcp__ metadata.
|
|
39
|
+
"""
|
|
40
|
+
enabled = True
|
|
41
|
+
if not isinstance(prompt, Prompt):
|
|
42
|
+
from fastmcp.decorators import get_fastmcp_meta
|
|
43
|
+
from fastmcp.prompts.function_prompt import PromptMeta
|
|
44
|
+
|
|
45
|
+
meta = get_fastmcp_meta(prompt)
|
|
46
|
+
if meta is not None and isinstance(meta, PromptMeta):
|
|
47
|
+
resolved_task = meta.task if meta.task is not None else False
|
|
48
|
+
enabled = meta.enabled
|
|
49
|
+
prompt = Prompt.from_function(
|
|
50
|
+
prompt,
|
|
51
|
+
name=meta.name,
|
|
52
|
+
version=meta.version,
|
|
53
|
+
title=meta.title,
|
|
54
|
+
description=meta.description,
|
|
55
|
+
icons=meta.icons,
|
|
56
|
+
tags=meta.tags,
|
|
57
|
+
meta=meta.meta,
|
|
58
|
+
task=resolved_task,
|
|
59
|
+
auth=meta.auth,
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
raise TypeError(
|
|
63
|
+
f"Expected Prompt or @prompt-decorated function, got {type(prompt).__name__}. "
|
|
64
|
+
"Use @prompt decorator or pass a Prompt instance."
|
|
65
|
+
)
|
|
66
|
+
self._add_component(prompt)
|
|
67
|
+
if not enabled:
|
|
68
|
+
self.disable(keys={prompt.key})
|
|
69
|
+
return prompt
|
|
70
|
+
|
|
71
|
+
@overload
|
|
72
|
+
def prompt(
|
|
73
|
+
self: LocalProvider,
|
|
74
|
+
name_or_fn: AnyFunction,
|
|
75
|
+
*,
|
|
76
|
+
name: str | None = None,
|
|
77
|
+
version: str | int | None = None,
|
|
78
|
+
title: str | None = None,
|
|
79
|
+
description: str | None = None,
|
|
80
|
+
icons: list[mcp.types.Icon] | None = None,
|
|
81
|
+
tags: set[str] | None = None,
|
|
82
|
+
enabled: bool = True,
|
|
83
|
+
meta: dict[str, Any] | None = None,
|
|
84
|
+
task: bool | TaskConfig | None = None,
|
|
85
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
|
|
86
|
+
) -> FunctionPrompt: ...
|
|
87
|
+
|
|
88
|
+
@overload
|
|
89
|
+
def prompt(
|
|
90
|
+
self: LocalProvider,
|
|
91
|
+
name_or_fn: str | None = None,
|
|
92
|
+
*,
|
|
93
|
+
name: str | None = None,
|
|
94
|
+
version: str | int | None = None,
|
|
95
|
+
title: str | None = None,
|
|
96
|
+
description: str | None = None,
|
|
97
|
+
icons: list[mcp.types.Icon] | None = None,
|
|
98
|
+
tags: set[str] | None = None,
|
|
99
|
+
enabled: bool = True,
|
|
100
|
+
meta: dict[str, Any] | None = None,
|
|
101
|
+
task: bool | TaskConfig | None = None,
|
|
102
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
|
|
103
|
+
) -> Callable[[AnyFunction], FunctionPrompt]: ...
|
|
104
|
+
|
|
105
|
+
def prompt(
|
|
106
|
+
self: LocalProvider,
|
|
107
|
+
name_or_fn: str | AnyFunction | None = None,
|
|
108
|
+
*,
|
|
109
|
+
name: str | None = None,
|
|
110
|
+
version: str | int | None = None,
|
|
111
|
+
title: str | None = None,
|
|
112
|
+
description: str | None = None,
|
|
113
|
+
icons: list[mcp.types.Icon] | None = None,
|
|
114
|
+
tags: set[str] | None = None,
|
|
115
|
+
enabled: bool = True,
|
|
116
|
+
meta: dict[str, Any] | None = None,
|
|
117
|
+
task: bool | TaskConfig | None = None,
|
|
118
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
|
|
119
|
+
) -> (
|
|
120
|
+
Callable[[AnyFunction], FunctionPrompt]
|
|
121
|
+
| FunctionPrompt
|
|
122
|
+
| partial[Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt]
|
|
123
|
+
):
|
|
124
|
+
"""Decorator to register a prompt.
|
|
125
|
+
|
|
126
|
+
This decorator supports multiple calling patterns:
|
|
127
|
+
- @provider.prompt (without parentheses)
|
|
128
|
+
- @provider.prompt() (with empty parentheses)
|
|
129
|
+
- @provider.prompt("custom_name") (with name as first argument)
|
|
130
|
+
- @provider.prompt(name="custom_name") (with name as keyword argument)
|
|
131
|
+
- provider.prompt(function, name="custom_name") (direct function call)
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
name_or_fn: Either a function (when used as @prompt), a string name, or None
|
|
135
|
+
name: Optional name for the prompt (keyword-only, alternative to name_or_fn)
|
|
136
|
+
title: Optional title for the prompt
|
|
137
|
+
description: Optional description of what the prompt does
|
|
138
|
+
icons: Optional icons for the prompt
|
|
139
|
+
tags: Optional set of tags for categorizing the prompt
|
|
140
|
+
enabled: Whether the prompt is enabled (default True). If False, adds to blocklist.
|
|
141
|
+
meta: Optional meta information about the prompt
|
|
142
|
+
task: Optional task configuration for background execution
|
|
143
|
+
auth: Optional authorization checks for the prompt
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
The registered FunctionPrompt or a decorator function.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
```python
|
|
150
|
+
provider = LocalProvider()
|
|
151
|
+
|
|
152
|
+
@provider.prompt
|
|
153
|
+
def analyze(topic: str) -> list:
|
|
154
|
+
return [{"role": "user", "content": f"Analyze: {topic}"}]
|
|
155
|
+
|
|
156
|
+
@provider.prompt("custom_name")
|
|
157
|
+
def my_prompt(data: str) -> list:
|
|
158
|
+
return [{"role": "user", "content": data}]
|
|
159
|
+
```
|
|
160
|
+
"""
|
|
161
|
+
if isinstance(name_or_fn, classmethod):
|
|
162
|
+
raise TypeError(
|
|
163
|
+
"To decorate a classmethod, use @classmethod above @prompt. "
|
|
164
|
+
"See https://gofastmcp.com/servers/prompts#using-with-methods"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def decorate_and_register(
|
|
168
|
+
fn: AnyFunction, prompt_name: str | None
|
|
169
|
+
) -> FunctionPrompt | AnyFunction:
|
|
170
|
+
# Check for unbound method
|
|
171
|
+
try:
|
|
172
|
+
params = list(inspect.signature(fn).parameters.keys())
|
|
173
|
+
except (ValueError, TypeError):
|
|
174
|
+
params = []
|
|
175
|
+
if params and params[0] in ("self", "cls"):
|
|
176
|
+
fn_name = getattr(fn, "__name__", "function")
|
|
177
|
+
raise TypeError(
|
|
178
|
+
f"The function '{fn_name}' has '{params[0]}' as its first parameter. "
|
|
179
|
+
f"Use the standalone @prompt decorator and register the bound method:\n\n"
|
|
180
|
+
f" from fastmcp.prompts import prompt\n\n"
|
|
181
|
+
f" class MyClass:\n"
|
|
182
|
+
f" @prompt\n"
|
|
183
|
+
f" def {fn_name}(...):\n"
|
|
184
|
+
f" ...\n\n"
|
|
185
|
+
f" obj = MyClass()\n"
|
|
186
|
+
f" mcp.add_prompt(obj.{fn_name})\n\n"
|
|
187
|
+
f"See https://gofastmcp.com/servers/prompts#using-with-methods"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
resolved_task: bool | TaskConfig = task if task is not None else False
|
|
191
|
+
|
|
192
|
+
if fastmcp.settings.decorator_mode == "object":
|
|
193
|
+
prompt_obj = Prompt.from_function(
|
|
194
|
+
fn,
|
|
195
|
+
name=prompt_name,
|
|
196
|
+
version=version,
|
|
197
|
+
title=title,
|
|
198
|
+
description=description,
|
|
199
|
+
icons=icons,
|
|
200
|
+
tags=tags,
|
|
201
|
+
meta=meta,
|
|
202
|
+
task=resolved_task,
|
|
203
|
+
auth=auth,
|
|
204
|
+
)
|
|
205
|
+
self._add_component(prompt_obj)
|
|
206
|
+
if not enabled:
|
|
207
|
+
self.disable(keys={prompt_obj.key})
|
|
208
|
+
return prompt_obj
|
|
209
|
+
else:
|
|
210
|
+
from fastmcp.prompts.function_prompt import PromptMeta
|
|
211
|
+
|
|
212
|
+
metadata = PromptMeta(
|
|
213
|
+
name=prompt_name,
|
|
214
|
+
version=version,
|
|
215
|
+
title=title,
|
|
216
|
+
description=description,
|
|
217
|
+
icons=icons,
|
|
218
|
+
tags=tags,
|
|
219
|
+
meta=meta,
|
|
220
|
+
task=task,
|
|
221
|
+
auth=auth,
|
|
222
|
+
enabled=enabled,
|
|
223
|
+
)
|
|
224
|
+
target = fn.__func__ if hasattr(fn, "__func__") else fn
|
|
225
|
+
target.__fastmcp__ = metadata # type: ignore[attr-defined]
|
|
226
|
+
self.add_prompt(fn)
|
|
227
|
+
return fn
|
|
228
|
+
|
|
229
|
+
if inspect.isroutine(name_or_fn):
|
|
230
|
+
return decorate_and_register(name_or_fn, name)
|
|
231
|
+
|
|
232
|
+
elif isinstance(name_or_fn, str):
|
|
233
|
+
if name is not None:
|
|
234
|
+
raise TypeError(
|
|
235
|
+
f"Cannot specify both a name as first argument and as keyword argument. "
|
|
236
|
+
f"Use either @prompt('{name_or_fn}') or @prompt(name='{name}'), not both."
|
|
237
|
+
)
|
|
238
|
+
prompt_name = name_or_fn
|
|
239
|
+
elif name_or_fn is None:
|
|
240
|
+
prompt_name = name
|
|
241
|
+
else:
|
|
242
|
+
raise TypeError(f"Invalid first argument: {type(name_or_fn)}")
|
|
243
|
+
|
|
244
|
+
return partial(
|
|
245
|
+
self.prompt,
|
|
246
|
+
name=prompt_name,
|
|
247
|
+
version=version,
|
|
248
|
+
title=title,
|
|
249
|
+
description=description,
|
|
250
|
+
icons=icons,
|
|
251
|
+
tags=tags,
|
|
252
|
+
meta=meta,
|
|
253
|
+
enabled=enabled,
|
|
254
|
+
task=task,
|
|
255
|
+
auth=auth,
|
|
256
|
+
)
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Resource decorator mixin for LocalProvider.
|
|
2
|
+
|
|
3
|
+
This module provides the ResourceDecoratorMixin class that adds resource
|
|
4
|
+
and template registration functionality to LocalProvider.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import inspect
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
import mcp.types
|
|
14
|
+
from mcp.types import Annotations, AnyFunction
|
|
15
|
+
|
|
16
|
+
import fastmcp
|
|
17
|
+
from fastmcp.resources.function_resource import resource as standalone_resource
|
|
18
|
+
from fastmcp.resources.resource import Resource
|
|
19
|
+
from fastmcp.resources.template import ResourceTemplate
|
|
20
|
+
from fastmcp.server.tasks.config import TaskConfig
|
|
21
|
+
from fastmcp.tools.tool import AuthCheckCallable
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from fastmcp.server.providers.local_provider import LocalProvider
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ResourceDecoratorMixin:
|
|
28
|
+
"""Mixin class providing resource decorator functionality for LocalProvider.
|
|
29
|
+
|
|
30
|
+
This mixin contains all methods related to:
|
|
31
|
+
- Resource registration via add_resource()
|
|
32
|
+
- Resource template registration via add_template()
|
|
33
|
+
- Resource decorator (@provider.resource)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def add_resource(
|
|
37
|
+
self: LocalProvider, resource: Resource | ResourceTemplate | Callable[..., Any]
|
|
38
|
+
) -> Resource | ResourceTemplate:
|
|
39
|
+
"""Add a resource to this provider's storage.
|
|
40
|
+
|
|
41
|
+
Accepts either a Resource/ResourceTemplate object or a decorated function with __fastmcp__ metadata.
|
|
42
|
+
"""
|
|
43
|
+
enabled = True
|
|
44
|
+
if not isinstance(resource, (Resource, ResourceTemplate)):
|
|
45
|
+
from fastmcp.decorators import get_fastmcp_meta
|
|
46
|
+
from fastmcp.resources.function_resource import ResourceMeta
|
|
47
|
+
from fastmcp.server.dependencies import without_injected_parameters
|
|
48
|
+
|
|
49
|
+
meta = get_fastmcp_meta(resource)
|
|
50
|
+
if meta is not None and isinstance(meta, ResourceMeta):
|
|
51
|
+
resolved_task = meta.task if meta.task is not None else False
|
|
52
|
+
enabled = meta.enabled
|
|
53
|
+
has_uri_params = "{" in meta.uri and "}" in meta.uri
|
|
54
|
+
wrapper_fn = without_injected_parameters(resource)
|
|
55
|
+
has_func_params = bool(inspect.signature(wrapper_fn).parameters)
|
|
56
|
+
|
|
57
|
+
if has_uri_params or has_func_params:
|
|
58
|
+
resource = ResourceTemplate.from_function(
|
|
59
|
+
fn=resource,
|
|
60
|
+
uri_template=meta.uri,
|
|
61
|
+
name=meta.name,
|
|
62
|
+
version=meta.version,
|
|
63
|
+
title=meta.title,
|
|
64
|
+
description=meta.description,
|
|
65
|
+
icons=meta.icons,
|
|
66
|
+
mime_type=meta.mime_type,
|
|
67
|
+
tags=meta.tags,
|
|
68
|
+
annotations=meta.annotations,
|
|
69
|
+
meta=meta.meta,
|
|
70
|
+
task=resolved_task,
|
|
71
|
+
auth=meta.auth,
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
resource = Resource.from_function(
|
|
75
|
+
fn=resource,
|
|
76
|
+
uri=meta.uri,
|
|
77
|
+
name=meta.name,
|
|
78
|
+
version=meta.version,
|
|
79
|
+
title=meta.title,
|
|
80
|
+
description=meta.description,
|
|
81
|
+
icons=meta.icons,
|
|
82
|
+
mime_type=meta.mime_type,
|
|
83
|
+
tags=meta.tags,
|
|
84
|
+
annotations=meta.annotations,
|
|
85
|
+
meta=meta.meta,
|
|
86
|
+
task=resolved_task,
|
|
87
|
+
auth=meta.auth,
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
raise TypeError(
|
|
91
|
+
f"Expected Resource, ResourceTemplate, or @resource-decorated function, got {type(resource).__name__}. "
|
|
92
|
+
"Use @resource('uri') decorator or pass a Resource/ResourceTemplate instance."
|
|
93
|
+
)
|
|
94
|
+
self._add_component(resource)
|
|
95
|
+
if not enabled:
|
|
96
|
+
self.disable(keys={resource.key})
|
|
97
|
+
return resource
|
|
98
|
+
|
|
99
|
+
def add_template(
|
|
100
|
+
self: LocalProvider, template: ResourceTemplate
|
|
101
|
+
) -> ResourceTemplate:
|
|
102
|
+
"""Add a resource template to this provider's storage."""
|
|
103
|
+
return self._add_component(template)
|
|
104
|
+
|
|
105
|
+
def resource(
|
|
106
|
+
self: LocalProvider,
|
|
107
|
+
uri: str,
|
|
108
|
+
*,
|
|
109
|
+
name: str | None = None,
|
|
110
|
+
version: str | int | None = None,
|
|
111
|
+
title: str | None = None,
|
|
112
|
+
description: str | None = None,
|
|
113
|
+
icons: list[mcp.types.Icon] | None = None,
|
|
114
|
+
mime_type: str | None = None,
|
|
115
|
+
tags: set[str] | None = None,
|
|
116
|
+
enabled: bool = True,
|
|
117
|
+
annotations: Annotations | dict[str, Any] | None = None,
|
|
118
|
+
meta: dict[str, Any] | None = None,
|
|
119
|
+
task: bool | TaskConfig | None = None,
|
|
120
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
|
|
121
|
+
) -> Callable[[AnyFunction], Resource | ResourceTemplate | AnyFunction]:
|
|
122
|
+
"""Decorator to register a function as a resource.
|
|
123
|
+
|
|
124
|
+
If the URI contains parameters (e.g. "resource://{param}") or the function
|
|
125
|
+
has parameters, it will be registered as a template resource.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
uri: URI for the resource (e.g. "resource://my-resource" or "resource://{param}")
|
|
129
|
+
name: Optional name for the resource
|
|
130
|
+
title: Optional title for the resource
|
|
131
|
+
description: Optional description of the resource
|
|
132
|
+
icons: Optional icons for the resource
|
|
133
|
+
mime_type: Optional MIME type for the resource
|
|
134
|
+
tags: Optional set of tags for categorizing the resource
|
|
135
|
+
enabled: Whether the resource is enabled (default True). If False, adds to blocklist.
|
|
136
|
+
annotations: Optional annotations about the resource's behavior
|
|
137
|
+
meta: Optional meta information about the resource
|
|
138
|
+
task: Optional task configuration for background execution
|
|
139
|
+
auth: Optional authorization checks for the resource
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
A decorator function.
|
|
143
|
+
|
|
144
|
+
Example:
|
|
145
|
+
```python
|
|
146
|
+
provider = LocalProvider()
|
|
147
|
+
|
|
148
|
+
@provider.resource("data://config")
|
|
149
|
+
def get_config() -> str:
|
|
150
|
+
return '{"setting": "value"}'
|
|
151
|
+
|
|
152
|
+
@provider.resource("data://{city}/weather")
|
|
153
|
+
def get_weather(city: str) -> str:
|
|
154
|
+
return f"Weather for {city}"
|
|
155
|
+
```
|
|
156
|
+
"""
|
|
157
|
+
if isinstance(annotations, dict):
|
|
158
|
+
annotations = Annotations(**annotations)
|
|
159
|
+
|
|
160
|
+
if inspect.isroutine(uri):
|
|
161
|
+
raise TypeError(
|
|
162
|
+
"The @resource decorator was used incorrectly. "
|
|
163
|
+
"It requires a URI as the first argument. "
|
|
164
|
+
"Use @resource('uri') instead of @resource"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
resolved_task: bool | TaskConfig = task if task is not None else False
|
|
168
|
+
|
|
169
|
+
def decorator(fn: AnyFunction) -> Resource | ResourceTemplate | AnyFunction:
|
|
170
|
+
# Check for unbound method
|
|
171
|
+
try:
|
|
172
|
+
params = list(inspect.signature(fn).parameters.keys())
|
|
173
|
+
except (ValueError, TypeError):
|
|
174
|
+
params = []
|
|
175
|
+
if params and params[0] in ("self", "cls"):
|
|
176
|
+
fn_name = getattr(fn, "__name__", "function")
|
|
177
|
+
raise TypeError(
|
|
178
|
+
f"The function '{fn_name}' has '{params[0]}' as its first parameter. "
|
|
179
|
+
f"Use the standalone @resource decorator and register the bound method:\n\n"
|
|
180
|
+
f" from fastmcp.resources import resource\n\n"
|
|
181
|
+
f" class MyClass:\n"
|
|
182
|
+
f" @resource('{uri}')\n"
|
|
183
|
+
f" def {fn_name}(...):\n"
|
|
184
|
+
f" ...\n\n"
|
|
185
|
+
f" obj = MyClass()\n"
|
|
186
|
+
f" mcp.add_resource(obj.{fn_name})\n\n"
|
|
187
|
+
f"See https://gofastmcp.com/servers/resources#using-with-methods"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if fastmcp.settings.decorator_mode == "object":
|
|
191
|
+
create_resource = standalone_resource(
|
|
192
|
+
uri,
|
|
193
|
+
name=name,
|
|
194
|
+
version=version,
|
|
195
|
+
title=title,
|
|
196
|
+
description=description,
|
|
197
|
+
icons=icons,
|
|
198
|
+
mime_type=mime_type,
|
|
199
|
+
tags=tags,
|
|
200
|
+
annotations=annotations,
|
|
201
|
+
meta=meta,
|
|
202
|
+
task=resolved_task,
|
|
203
|
+
auth=auth,
|
|
204
|
+
)
|
|
205
|
+
obj = create_resource(fn)
|
|
206
|
+
# In legacy mode, standalone_resource always returns a component
|
|
207
|
+
assert isinstance(obj, (Resource, ResourceTemplate))
|
|
208
|
+
if isinstance(obj, ResourceTemplate):
|
|
209
|
+
self.add_template(obj)
|
|
210
|
+
if not enabled:
|
|
211
|
+
self.disable(keys={obj.key})
|
|
212
|
+
else:
|
|
213
|
+
self.add_resource(obj)
|
|
214
|
+
if not enabled:
|
|
215
|
+
self.disable(keys={obj.key})
|
|
216
|
+
return obj
|
|
217
|
+
else:
|
|
218
|
+
from fastmcp.resources.function_resource import ResourceMeta
|
|
219
|
+
|
|
220
|
+
metadata = ResourceMeta(
|
|
221
|
+
uri=uri,
|
|
222
|
+
name=name,
|
|
223
|
+
version=version,
|
|
224
|
+
title=title,
|
|
225
|
+
description=description,
|
|
226
|
+
icons=icons,
|
|
227
|
+
tags=tags,
|
|
228
|
+
mime_type=mime_type,
|
|
229
|
+
annotations=annotations,
|
|
230
|
+
meta=meta,
|
|
231
|
+
task=task,
|
|
232
|
+
auth=auth,
|
|
233
|
+
enabled=enabled,
|
|
234
|
+
)
|
|
235
|
+
target = fn.__func__ if hasattr(fn, "__func__") else fn
|
|
236
|
+
target.__fastmcp__ = metadata # type: ignore[attr-defined]
|
|
237
|
+
self.add_resource(fn)
|
|
238
|
+
return fn
|
|
239
|
+
|
|
240
|
+
return decorator
|