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