fastmcp 2.14.5__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.5.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.5.dist-info/RECORD +0 -161
- /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
- {fastmcp-2.14.5.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
- {fastmcp-2.14.5.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.14.5.dist-info → fastmcp-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
fastmcp/resources/__init__.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .function_resource import FunctionResource, resource
|
|
2
|
+
from .resource import Resource, ResourceContent, ResourceResult
|
|
2
3
|
from .template import ResourceTemplate
|
|
3
4
|
from .types import (
|
|
4
5
|
BinaryResource,
|
|
@@ -7,7 +8,6 @@ from .types import (
|
|
|
7
8
|
HttpResource,
|
|
8
9
|
TextResource,
|
|
9
10
|
)
|
|
10
|
-
from .resource_manager import ResourceManager
|
|
11
11
|
|
|
12
12
|
__all__ = [
|
|
13
13
|
"BinaryResource",
|
|
@@ -16,7 +16,9 @@ __all__ = [
|
|
|
16
16
|
"FunctionResource",
|
|
17
17
|
"HttpResource",
|
|
18
18
|
"Resource",
|
|
19
|
-
"
|
|
19
|
+
"ResourceContent",
|
|
20
|
+
"ResourceResult",
|
|
20
21
|
"ResourceTemplate",
|
|
21
22
|
"TextResource",
|
|
23
|
+
"resource",
|
|
22
24
|
]
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""Standalone @resource decorator for FastMCP."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import warnings
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeVar, runtime_checkable
|
|
10
|
+
|
|
11
|
+
from mcp.types import Annotations, Icon
|
|
12
|
+
from pydantic import AnyUrl
|
|
13
|
+
|
|
14
|
+
import fastmcp
|
|
15
|
+
from fastmcp.decorators import resolve_task_config
|
|
16
|
+
from fastmcp.resources.resource import Resource, ResourceResult
|
|
17
|
+
from fastmcp.server.dependencies import (
|
|
18
|
+
transform_context_annotations,
|
|
19
|
+
without_injected_parameters,
|
|
20
|
+
)
|
|
21
|
+
from fastmcp.server.tasks.config import TaskConfig
|
|
22
|
+
from fastmcp.tools.tool import AuthCheckCallable
|
|
23
|
+
from fastmcp.utilities.async_utils import call_sync_fn_in_threadpool
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from docket import Docket
|
|
27
|
+
|
|
28
|
+
from fastmcp.resources.template import ResourceTemplate
|
|
29
|
+
|
|
30
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@runtime_checkable
|
|
34
|
+
class DecoratedResource(Protocol):
|
|
35
|
+
"""Protocol for functions decorated with @resource."""
|
|
36
|
+
|
|
37
|
+
__fastmcp__: ResourceMeta
|
|
38
|
+
|
|
39
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True, kw_only=True)
|
|
43
|
+
class ResourceMeta:
|
|
44
|
+
"""Metadata attached to functions by the @resource decorator."""
|
|
45
|
+
|
|
46
|
+
type: Literal["resource"] = field(default="resource", init=False)
|
|
47
|
+
uri: str
|
|
48
|
+
name: str | None = None
|
|
49
|
+
version: str | int | None = None
|
|
50
|
+
title: str | None = None
|
|
51
|
+
description: str | None = None
|
|
52
|
+
icons: list[Icon] | None = None
|
|
53
|
+
tags: set[str] | None = None
|
|
54
|
+
mime_type: str | None = None
|
|
55
|
+
annotations: Annotations | None = None
|
|
56
|
+
meta: dict[str, Any] | None = None
|
|
57
|
+
task: bool | TaskConfig | None = None
|
|
58
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None
|
|
59
|
+
enabled: bool = True
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class FunctionResource(Resource):
|
|
63
|
+
"""A resource that defers data loading by wrapping a function.
|
|
64
|
+
|
|
65
|
+
The function is only called when the resource is read, allowing for lazy loading
|
|
66
|
+
of potentially expensive data. This is particularly useful when listing resources,
|
|
67
|
+
as the function won't be called until the resource is actually accessed.
|
|
68
|
+
|
|
69
|
+
The function can return:
|
|
70
|
+
- str for text content (default)
|
|
71
|
+
- bytes for binary content
|
|
72
|
+
- other types will be converted to JSON
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
fn: Callable[..., Any]
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def from_function(
|
|
79
|
+
cls,
|
|
80
|
+
fn: Callable[..., Any],
|
|
81
|
+
uri: str | AnyUrl | None = None,
|
|
82
|
+
*,
|
|
83
|
+
metadata: ResourceMeta | None = None,
|
|
84
|
+
# Keep individual params for backwards compat
|
|
85
|
+
name: str | None = None,
|
|
86
|
+
version: str | int | None = None,
|
|
87
|
+
title: str | None = None,
|
|
88
|
+
description: str | None = None,
|
|
89
|
+
icons: list[Icon] | None = None,
|
|
90
|
+
mime_type: str | None = None,
|
|
91
|
+
tags: set[str] | None = None,
|
|
92
|
+
annotations: Annotations | None = None,
|
|
93
|
+
meta: dict[str, Any] | None = None,
|
|
94
|
+
task: bool | TaskConfig | None = None,
|
|
95
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
|
|
96
|
+
) -> FunctionResource:
|
|
97
|
+
"""Create a FunctionResource from a function.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
fn: The function to wrap
|
|
101
|
+
uri: The URI for the resource (required if metadata not provided)
|
|
102
|
+
metadata: ResourceMeta object with all configuration. If provided,
|
|
103
|
+
individual parameters must not be passed.
|
|
104
|
+
name, title, etc.: Individual parameters for backwards compatibility.
|
|
105
|
+
Cannot be used together with metadata parameter.
|
|
106
|
+
"""
|
|
107
|
+
# Check mutual exclusion
|
|
108
|
+
individual_params_provided = (
|
|
109
|
+
any(
|
|
110
|
+
x is not None
|
|
111
|
+
for x in [
|
|
112
|
+
name,
|
|
113
|
+
version,
|
|
114
|
+
title,
|
|
115
|
+
description,
|
|
116
|
+
icons,
|
|
117
|
+
mime_type,
|
|
118
|
+
tags,
|
|
119
|
+
annotations,
|
|
120
|
+
meta,
|
|
121
|
+
task,
|
|
122
|
+
auth,
|
|
123
|
+
]
|
|
124
|
+
)
|
|
125
|
+
or uri is not None
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if metadata is not None and individual_params_provided:
|
|
129
|
+
raise TypeError(
|
|
130
|
+
"Cannot pass both 'metadata' and individual parameters to from_function(). "
|
|
131
|
+
"Use metadata alone or individual parameters alone."
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Build metadata from kwargs if not provided
|
|
135
|
+
if metadata is None:
|
|
136
|
+
if uri is None:
|
|
137
|
+
raise TypeError("uri is required when metadata is not provided")
|
|
138
|
+
metadata = ResourceMeta(
|
|
139
|
+
uri=str(uri),
|
|
140
|
+
name=name,
|
|
141
|
+
version=version,
|
|
142
|
+
title=title,
|
|
143
|
+
description=description,
|
|
144
|
+
icons=icons,
|
|
145
|
+
tags=tags,
|
|
146
|
+
mime_type=mime_type,
|
|
147
|
+
annotations=annotations,
|
|
148
|
+
meta=meta,
|
|
149
|
+
task=task,
|
|
150
|
+
auth=auth,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
uri_obj = AnyUrl(metadata.uri)
|
|
154
|
+
|
|
155
|
+
# Get function name - use class name for callable objects
|
|
156
|
+
func_name = (
|
|
157
|
+
metadata.name or getattr(fn, "__name__", None) or fn.__class__.__name__
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Normalize task to TaskConfig and validate
|
|
161
|
+
task_value = metadata.task
|
|
162
|
+
if task_value is None:
|
|
163
|
+
task_config = TaskConfig(mode="forbidden")
|
|
164
|
+
elif isinstance(task_value, bool):
|
|
165
|
+
task_config = TaskConfig.from_bool(task_value)
|
|
166
|
+
else:
|
|
167
|
+
task_config = task_value
|
|
168
|
+
task_config.validate_function(fn, func_name)
|
|
169
|
+
|
|
170
|
+
# if the fn is a callable class, we need to get the __call__ method from here out
|
|
171
|
+
if not inspect.isroutine(fn):
|
|
172
|
+
fn = fn.__call__
|
|
173
|
+
# if the fn is a staticmethod, we need to work with the underlying function
|
|
174
|
+
if isinstance(fn, staticmethod):
|
|
175
|
+
fn = fn.__func__
|
|
176
|
+
|
|
177
|
+
# Transform Context type annotations to Depends() for unified DI
|
|
178
|
+
fn = transform_context_annotations(fn)
|
|
179
|
+
|
|
180
|
+
# Wrap fn to handle dependency resolution internally
|
|
181
|
+
wrapped_fn = without_injected_parameters(fn)
|
|
182
|
+
|
|
183
|
+
return cls(
|
|
184
|
+
fn=wrapped_fn,
|
|
185
|
+
uri=uri_obj,
|
|
186
|
+
name=func_name,
|
|
187
|
+
version=str(metadata.version) if metadata.version is not None else None,
|
|
188
|
+
title=metadata.title,
|
|
189
|
+
description=metadata.description or inspect.getdoc(fn),
|
|
190
|
+
icons=metadata.icons,
|
|
191
|
+
mime_type=metadata.mime_type or "text/plain",
|
|
192
|
+
tags=metadata.tags or set(),
|
|
193
|
+
annotations=metadata.annotations,
|
|
194
|
+
meta=metadata.meta,
|
|
195
|
+
task_config=task_config,
|
|
196
|
+
auth=metadata.auth,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
async def read(
|
|
200
|
+
self,
|
|
201
|
+
) -> str | bytes | ResourceResult:
|
|
202
|
+
"""Read the resource by calling the wrapped function."""
|
|
203
|
+
# self.fn is wrapped by without_injected_parameters which handles
|
|
204
|
+
# dependency resolution internally
|
|
205
|
+
if inspect.iscoroutinefunction(self.fn):
|
|
206
|
+
result = await self.fn()
|
|
207
|
+
else:
|
|
208
|
+
# Run sync functions in threadpool to avoid blocking the event loop
|
|
209
|
+
result = await call_sync_fn_in_threadpool(self.fn)
|
|
210
|
+
# Handle sync wrappers that return awaitables (e.g., partial(async_fn))
|
|
211
|
+
if inspect.isawaitable(result):
|
|
212
|
+
result = await result
|
|
213
|
+
|
|
214
|
+
# If user returned another Resource, read it recursively
|
|
215
|
+
if isinstance(result, Resource):
|
|
216
|
+
return await result.read()
|
|
217
|
+
|
|
218
|
+
return result
|
|
219
|
+
|
|
220
|
+
def register_with_docket(self, docket: Docket) -> None:
|
|
221
|
+
"""Register this resource with docket for background execution.
|
|
222
|
+
|
|
223
|
+
FunctionResource registers the underlying function, which has the user's
|
|
224
|
+
Depends parameters for docket to resolve.
|
|
225
|
+
"""
|
|
226
|
+
if not self.task_config.supports_tasks():
|
|
227
|
+
return
|
|
228
|
+
docket.register(self.fn, names=[self.key])
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def resource(
|
|
232
|
+
uri: str,
|
|
233
|
+
*,
|
|
234
|
+
name: str | None = None,
|
|
235
|
+
version: str | int | None = None,
|
|
236
|
+
title: str | None = None,
|
|
237
|
+
description: str | None = None,
|
|
238
|
+
icons: list[Icon] | None = None,
|
|
239
|
+
mime_type: str | None = None,
|
|
240
|
+
tags: set[str] | None = None,
|
|
241
|
+
annotations: Annotations | dict[str, Any] | None = None,
|
|
242
|
+
meta: dict[str, Any] | None = None,
|
|
243
|
+
task: bool | TaskConfig | None = None,
|
|
244
|
+
auth: AuthCheckCallable | list[AuthCheckCallable] | None = None,
|
|
245
|
+
) -> Callable[[F], F]:
|
|
246
|
+
"""Standalone decorator to mark a function as an MCP resource.
|
|
247
|
+
|
|
248
|
+
Returns the original function with metadata attached. Register with a server
|
|
249
|
+
using mcp.add_resource().
|
|
250
|
+
"""
|
|
251
|
+
if isinstance(annotations, dict):
|
|
252
|
+
annotations = Annotations(**annotations)
|
|
253
|
+
|
|
254
|
+
if inspect.isroutine(uri):
|
|
255
|
+
raise TypeError(
|
|
256
|
+
"The @resource decorator requires a URI. "
|
|
257
|
+
"Use @resource('uri') instead of @resource"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def create_resource(fn: Callable[..., Any]) -> FunctionResource | ResourceTemplate:
|
|
261
|
+
from fastmcp.resources.template import ResourceTemplate
|
|
262
|
+
from fastmcp.server.dependencies import without_injected_parameters
|
|
263
|
+
|
|
264
|
+
resolved = resolve_task_config(task)
|
|
265
|
+
has_uri_params = "{" in uri and "}" in uri
|
|
266
|
+
wrapper_fn = without_injected_parameters(fn)
|
|
267
|
+
has_func_params = bool(inspect.signature(wrapper_fn).parameters)
|
|
268
|
+
|
|
269
|
+
# Create metadata first
|
|
270
|
+
resource_meta = ResourceMeta(
|
|
271
|
+
uri=uri,
|
|
272
|
+
name=name,
|
|
273
|
+
version=version,
|
|
274
|
+
title=title,
|
|
275
|
+
description=description,
|
|
276
|
+
icons=icons,
|
|
277
|
+
tags=tags,
|
|
278
|
+
mime_type=mime_type,
|
|
279
|
+
annotations=annotations,
|
|
280
|
+
meta=meta,
|
|
281
|
+
task=resolved,
|
|
282
|
+
auth=auth,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if has_uri_params or has_func_params:
|
|
286
|
+
# ResourceTemplate doesn't have metadata support yet, so pass individual params
|
|
287
|
+
return ResourceTemplate.from_function(
|
|
288
|
+
fn=fn,
|
|
289
|
+
uri_template=uri,
|
|
290
|
+
name=name,
|
|
291
|
+
version=version,
|
|
292
|
+
title=title,
|
|
293
|
+
description=description,
|
|
294
|
+
icons=icons,
|
|
295
|
+
mime_type=mime_type,
|
|
296
|
+
tags=tags,
|
|
297
|
+
annotations=annotations,
|
|
298
|
+
meta=meta,
|
|
299
|
+
task=resolved,
|
|
300
|
+
auth=auth,
|
|
301
|
+
)
|
|
302
|
+
else:
|
|
303
|
+
return FunctionResource.from_function(fn, metadata=resource_meta)
|
|
304
|
+
|
|
305
|
+
def attach_metadata(fn: F) -> F:
|
|
306
|
+
metadata = ResourceMeta(
|
|
307
|
+
uri=uri,
|
|
308
|
+
name=name,
|
|
309
|
+
version=version,
|
|
310
|
+
title=title,
|
|
311
|
+
description=description,
|
|
312
|
+
icons=icons,
|
|
313
|
+
tags=tags,
|
|
314
|
+
mime_type=mime_type,
|
|
315
|
+
annotations=annotations,
|
|
316
|
+
meta=meta,
|
|
317
|
+
task=task,
|
|
318
|
+
auth=auth,
|
|
319
|
+
)
|
|
320
|
+
target = fn.__func__ if hasattr(fn, "__func__") else fn
|
|
321
|
+
target.__fastmcp__ = metadata
|
|
322
|
+
return fn
|
|
323
|
+
|
|
324
|
+
def decorator(fn: F) -> F:
|
|
325
|
+
if fastmcp.settings.decorator_mode == "object":
|
|
326
|
+
warnings.warn(
|
|
327
|
+
"decorator_mode='object' is deprecated and will be removed in a future version. "
|
|
328
|
+
"Decorators now return the original function with metadata attached.",
|
|
329
|
+
DeprecationWarning,
|
|
330
|
+
stacklevel=3,
|
|
331
|
+
)
|
|
332
|
+
return create_resource(fn) # type: ignore[return-value]
|
|
333
|
+
return attach_metadata(fn)
|
|
334
|
+
|
|
335
|
+
return decorator
|