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
|
@@ -1,347 +1,24 @@
|
|
|
1
|
-
"""OpenAPI component implementations
|
|
1
|
+
"""OpenAPI component implementations - backwards compatibility stub.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
from collections.abc import Callable
|
|
6
|
-
from typing import TYPE_CHECKING, Any
|
|
3
|
+
This module is deprecated. Import from fastmcp.server.providers.openapi instead.
|
|
4
|
+
"""
|
|
7
5
|
|
|
8
|
-
import
|
|
9
|
-
from mcp.types import ToolAnnotations
|
|
10
|
-
from pydantic.networks import AnyUrl
|
|
6
|
+
from __future__ import annotations
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
from fastmcp.server.dependencies import get_http_headers
|
|
14
|
-
from fastmcp.tools.tool import Tool, ToolResult
|
|
15
|
-
from fastmcp.utilities.logging import get_logger
|
|
8
|
+
import warnings
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
from fastmcp.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
logger = get_logger(__name__)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class OpenAPITool(Tool):
|
|
28
|
-
"""Tool implementation for OpenAPI endpoints."""
|
|
29
|
-
|
|
30
|
-
def __init__(
|
|
31
|
-
self,
|
|
32
|
-
client: httpx.AsyncClient,
|
|
33
|
-
route: HTTPRoute,
|
|
34
|
-
director: RequestDirector,
|
|
35
|
-
name: str,
|
|
36
|
-
description: str,
|
|
37
|
-
parameters: dict[str, Any],
|
|
38
|
-
output_schema: dict[str, Any] | None = None,
|
|
39
|
-
tags: set[str] | None = None,
|
|
40
|
-
timeout: float | None = None,
|
|
41
|
-
annotations: ToolAnnotations | None = None,
|
|
42
|
-
serializer: Callable[[Any], str] | None = None,
|
|
43
|
-
):
|
|
44
|
-
super().__init__(
|
|
45
|
-
name=name,
|
|
46
|
-
description=description,
|
|
47
|
-
parameters=parameters,
|
|
48
|
-
output_schema=output_schema,
|
|
49
|
-
tags=tags or set(),
|
|
50
|
-
annotations=annotations,
|
|
51
|
-
serializer=serializer,
|
|
52
|
-
)
|
|
53
|
-
self._client = client
|
|
54
|
-
self._route = route
|
|
55
|
-
self._director = director
|
|
56
|
-
self._timeout = timeout
|
|
57
|
-
|
|
58
|
-
def __repr__(self) -> str:
|
|
59
|
-
"""Custom representation to prevent recursion errors when printing."""
|
|
60
|
-
return f"OpenAPITool(name={self.name!r}, method={self._route.method}, path={self._route.path})"
|
|
61
|
-
|
|
62
|
-
async def run(self, arguments: dict[str, Any]) -> ToolResult:
|
|
63
|
-
"""Execute the HTTP request using RequestDirector for simplified parameter handling."""
|
|
64
|
-
try:
|
|
65
|
-
# Get base URL from client
|
|
66
|
-
base_url = (
|
|
67
|
-
str(self._client.base_url) if hasattr(self._client, "base_url") else ""
|
|
68
|
-
) or "http://localhost"
|
|
69
|
-
|
|
70
|
-
# Get Headers from client
|
|
71
|
-
cli_headers = (
|
|
72
|
-
self._client.headers
|
|
73
|
-
if hasattr(self._client, "headers") and self._client.headers
|
|
74
|
-
else {}
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
# Build the request using RequestDirector
|
|
78
|
-
request = self._director.build(self._route, arguments, base_url)
|
|
79
|
-
|
|
80
|
-
# First add server headers (lowest precedence)
|
|
81
|
-
if cli_headers:
|
|
82
|
-
# Merge with existing headers, _client headers as base
|
|
83
|
-
if request.headers:
|
|
84
|
-
# Start with request headers, then add client headers
|
|
85
|
-
for key, value in cli_headers.items():
|
|
86
|
-
if key not in request.headers:
|
|
87
|
-
request.headers[key] = value
|
|
88
|
-
else:
|
|
89
|
-
# Create new headers from cli_headers
|
|
90
|
-
for key, value in cli_headers.items():
|
|
91
|
-
request.headers[key] = value
|
|
92
|
-
|
|
93
|
-
# Then add MCP client transport headers (highest precedence)
|
|
94
|
-
mcp_headers = get_http_headers()
|
|
95
|
-
if mcp_headers:
|
|
96
|
-
# Merge with existing headers, MCP headers take precedence over all
|
|
97
|
-
if request.headers:
|
|
98
|
-
request.headers.update(mcp_headers)
|
|
99
|
-
else:
|
|
100
|
-
# Create new headers from mcp_headers
|
|
101
|
-
for key, value in mcp_headers.items():
|
|
102
|
-
request.headers[key] = value
|
|
103
|
-
# print logger
|
|
104
|
-
logger.debug(f"run - sending request; headers: {request.headers}")
|
|
105
|
-
|
|
106
|
-
# Execute the request
|
|
107
|
-
# Note: httpx.AsyncClient.send() doesn't accept timeout parameter
|
|
108
|
-
# The timeout should be configured on the client itself
|
|
109
|
-
response = await self._client.send(request)
|
|
110
|
-
|
|
111
|
-
# Raise for 4xx/5xx responses
|
|
112
|
-
response.raise_for_status()
|
|
113
|
-
|
|
114
|
-
# Try to parse as JSON first
|
|
115
|
-
try:
|
|
116
|
-
result = response.json()
|
|
117
|
-
|
|
118
|
-
# Handle structured content based on output schema, if any
|
|
119
|
-
structured_output = None
|
|
120
|
-
if self.output_schema is not None:
|
|
121
|
-
if self.output_schema.get("x-fastmcp-wrap-result"):
|
|
122
|
-
# Schema says wrap - always wrap in result key
|
|
123
|
-
structured_output = {"result": result}
|
|
124
|
-
else:
|
|
125
|
-
structured_output = result
|
|
126
|
-
# If no output schema, use fallback logic for backward compatibility
|
|
127
|
-
elif not isinstance(result, dict):
|
|
128
|
-
structured_output = {"result": result}
|
|
129
|
-
else:
|
|
130
|
-
structured_output = result
|
|
131
|
-
|
|
132
|
-
return ToolResult(structured_content=structured_output)
|
|
133
|
-
except json.JSONDecodeError:
|
|
134
|
-
return ToolResult(content=response.text)
|
|
135
|
-
|
|
136
|
-
except httpx.HTTPStatusError as e:
|
|
137
|
-
# Handle HTTP errors (4xx, 5xx)
|
|
138
|
-
error_message = (
|
|
139
|
-
f"HTTP error {e.response.status_code}: {e.response.reason_phrase}"
|
|
140
|
-
)
|
|
141
|
-
try:
|
|
142
|
-
error_data = e.response.json()
|
|
143
|
-
error_message += f" - {error_data}"
|
|
144
|
-
except (json.JSONDecodeError, ValueError):
|
|
145
|
-
if e.response.text:
|
|
146
|
-
error_message += f" - {e.response.text}"
|
|
147
|
-
|
|
148
|
-
raise ValueError(error_message) from e
|
|
149
|
-
|
|
150
|
-
except httpx.RequestError as e:
|
|
151
|
-
# Handle request errors (connection, timeout, etc.)
|
|
152
|
-
raise ValueError(f"Request error: {e!s}") from e
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
class OpenAPIResource(Resource):
|
|
156
|
-
"""Resource implementation for OpenAPI endpoints."""
|
|
157
|
-
|
|
158
|
-
def __init__(
|
|
159
|
-
self,
|
|
160
|
-
client: httpx.AsyncClient,
|
|
161
|
-
route: HTTPRoute,
|
|
162
|
-
director: RequestDirector,
|
|
163
|
-
uri: str,
|
|
164
|
-
name: str,
|
|
165
|
-
description: str,
|
|
166
|
-
mime_type: str = "application/json",
|
|
167
|
-
tags: set[str] | None = None,
|
|
168
|
-
timeout: float | None = None,
|
|
169
|
-
):
|
|
170
|
-
if tags is None:
|
|
171
|
-
tags = set()
|
|
172
|
-
super().__init__(
|
|
173
|
-
uri=AnyUrl(uri), # Convert string to AnyUrl
|
|
174
|
-
name=name,
|
|
175
|
-
description=description,
|
|
176
|
-
mime_type=mime_type,
|
|
177
|
-
tags=tags,
|
|
178
|
-
)
|
|
179
|
-
self._client = client
|
|
180
|
-
self._route = route
|
|
181
|
-
self._director = director
|
|
182
|
-
self._timeout = timeout
|
|
183
|
-
|
|
184
|
-
def __repr__(self) -> str:
|
|
185
|
-
"""Custom representation to prevent recursion errors when printing."""
|
|
186
|
-
return f"OpenAPIResource(name={self.name!r}, uri={self.uri!r}, path={self._route.path})"
|
|
187
|
-
|
|
188
|
-
async def read(self) -> str | bytes:
|
|
189
|
-
"""Fetch the resource data by making an HTTP request."""
|
|
190
|
-
try:
|
|
191
|
-
# Extract path parameters from the URI if present
|
|
192
|
-
path = self._route.path
|
|
193
|
-
resource_uri = str(self.uri)
|
|
194
|
-
|
|
195
|
-
# If this is a templated resource, extract path parameters from the URI
|
|
196
|
-
if "{" in path and "}" in path:
|
|
197
|
-
# Extract the resource ID from the URI (the last part after the last slash)
|
|
198
|
-
parts = resource_uri.split("/")
|
|
199
|
-
|
|
200
|
-
if len(parts) > 1:
|
|
201
|
-
# Find all path parameters in the route path
|
|
202
|
-
path_params = {}
|
|
203
|
-
|
|
204
|
-
# Find the path parameter names from the route path
|
|
205
|
-
param_matches = re.findall(r"\{([^}]+)\}", path)
|
|
206
|
-
if param_matches:
|
|
207
|
-
# Reverse sorting from creation order (traversal is backwards)
|
|
208
|
-
param_matches.sort(reverse=True)
|
|
209
|
-
# Number of sent parameters is number of parts -1 (assuming first part is resource identifier)
|
|
210
|
-
expected_param_count = len(parts) - 1
|
|
211
|
-
# Map parameters from the end of the URI to the parameters in the path
|
|
212
|
-
# Last parameter in URI (parts[-1]) maps to last parameter in path, and so on
|
|
213
|
-
for i, param_name in enumerate(param_matches):
|
|
214
|
-
# Ensure we don't use resource identifier as parameter
|
|
215
|
-
if i < expected_param_count:
|
|
216
|
-
# Get values from the end of parts
|
|
217
|
-
param_value = parts[-1 - i]
|
|
218
|
-
path_params[param_name] = param_value
|
|
219
|
-
|
|
220
|
-
# Replace path parameters with their values
|
|
221
|
-
for param_name, param_value in path_params.items():
|
|
222
|
-
path = path.replace(f"{{{param_name}}}", str(param_value))
|
|
223
|
-
|
|
224
|
-
# Filter any query parameters - get query parameters and filter out None/empty values
|
|
225
|
-
query_params = {}
|
|
226
|
-
for param in self._route.parameters:
|
|
227
|
-
if param.location == "query" and hasattr(self, f"_{param.name}"):
|
|
228
|
-
value = getattr(self, f"_{param.name}")
|
|
229
|
-
if value is not None and value != "":
|
|
230
|
-
query_params[param.name] = value
|
|
231
|
-
|
|
232
|
-
# Prepare headers with correct precedence: server < client transport
|
|
233
|
-
headers = {}
|
|
234
|
-
# Start with server headers (lowest precedence)
|
|
235
|
-
cli_headers = (
|
|
236
|
-
self._client.headers
|
|
237
|
-
if hasattr(self._client, "headers") and self._client.headers
|
|
238
|
-
else {}
|
|
239
|
-
)
|
|
240
|
-
headers.update(cli_headers)
|
|
241
|
-
|
|
242
|
-
# Add MCP client transport headers (highest precedence)
|
|
243
|
-
mcp_headers = get_http_headers()
|
|
244
|
-
headers.update(mcp_headers)
|
|
245
|
-
|
|
246
|
-
response = await self._client.request(
|
|
247
|
-
method=self._route.method,
|
|
248
|
-
url=path,
|
|
249
|
-
params=query_params,
|
|
250
|
-
headers=headers,
|
|
251
|
-
timeout=self._timeout,
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
# Raise for 4xx/5xx responses
|
|
255
|
-
response.raise_for_status()
|
|
256
|
-
|
|
257
|
-
# Determine content type and return appropriate format
|
|
258
|
-
content_type = response.headers.get("content-type", "").lower()
|
|
259
|
-
|
|
260
|
-
if "application/json" in content_type:
|
|
261
|
-
result = response.json()
|
|
262
|
-
return json.dumps(result)
|
|
263
|
-
elif any(ct in content_type for ct in ["text/", "application/xml"]):
|
|
264
|
-
return response.text
|
|
265
|
-
else:
|
|
266
|
-
return response.content
|
|
267
|
-
|
|
268
|
-
except httpx.HTTPStatusError as e:
|
|
269
|
-
# Handle HTTP errors (4xx, 5xx)
|
|
270
|
-
error_message = (
|
|
271
|
-
f"HTTP error {e.response.status_code}: {e.response.reason_phrase}"
|
|
272
|
-
)
|
|
273
|
-
try:
|
|
274
|
-
error_data = e.response.json()
|
|
275
|
-
error_message += f" - {error_data}"
|
|
276
|
-
except (json.JSONDecodeError, ValueError):
|
|
277
|
-
if e.response.text:
|
|
278
|
-
error_message += f" - {e.response.text}"
|
|
279
|
-
|
|
280
|
-
raise ValueError(error_message) from e
|
|
281
|
-
|
|
282
|
-
except httpx.RequestError as e:
|
|
283
|
-
# Handle request errors (connection, timeout, etc.)
|
|
284
|
-
raise ValueError(f"Request error: {e!s}") from e
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
class OpenAPIResourceTemplate(ResourceTemplate):
|
|
288
|
-
"""Resource template implementation for OpenAPI endpoints."""
|
|
289
|
-
|
|
290
|
-
def __init__(
|
|
291
|
-
self,
|
|
292
|
-
client: httpx.AsyncClient,
|
|
293
|
-
route: HTTPRoute,
|
|
294
|
-
director: RequestDirector,
|
|
295
|
-
uri_template: str,
|
|
296
|
-
name: str,
|
|
297
|
-
description: str,
|
|
298
|
-
parameters: dict[str, Any],
|
|
299
|
-
tags: set[str] | None = None,
|
|
300
|
-
timeout: float | None = None,
|
|
301
|
-
):
|
|
302
|
-
if tags is None:
|
|
303
|
-
tags = set()
|
|
304
|
-
super().__init__(
|
|
305
|
-
uri_template=uri_template,
|
|
306
|
-
name=name,
|
|
307
|
-
description=description,
|
|
308
|
-
parameters=parameters,
|
|
309
|
-
tags=tags,
|
|
310
|
-
)
|
|
311
|
-
self._client = client
|
|
312
|
-
self._route = route
|
|
313
|
-
self._director = director
|
|
314
|
-
self._timeout = timeout
|
|
315
|
-
|
|
316
|
-
def __repr__(self) -> str:
|
|
317
|
-
"""Custom representation to prevent recursion errors when printing."""
|
|
318
|
-
return f"OpenAPIResourceTemplate(name={self.name!r}, uri_template={self.uri_template!r}, path={self._route.path})"
|
|
319
|
-
|
|
320
|
-
async def create_resource(
|
|
321
|
-
self,
|
|
322
|
-
uri: str,
|
|
323
|
-
params: dict[str, Any],
|
|
324
|
-
context: "Context | None" = None,
|
|
325
|
-
) -> Resource:
|
|
326
|
-
"""Create a resource with the given parameters."""
|
|
327
|
-
# Generate a URI for this resource instance
|
|
328
|
-
uri_parts = []
|
|
329
|
-
for key, value in params.items():
|
|
330
|
-
uri_parts.append(f"{key}={value}")
|
|
331
|
-
|
|
332
|
-
# Create and return a resource
|
|
333
|
-
return OpenAPIResource(
|
|
334
|
-
client=self._client,
|
|
335
|
-
route=self._route,
|
|
336
|
-
director=self._director,
|
|
337
|
-
uri=uri,
|
|
338
|
-
name=f"{self.name}-{'-'.join(uri_parts)}",
|
|
339
|
-
description=self.description or f"Resource for {self._route.path}",
|
|
340
|
-
mime_type="application/json",
|
|
341
|
-
tags=set(self._route.tags or []),
|
|
342
|
-
timeout=self._timeout,
|
|
343
|
-
)
|
|
10
|
+
warnings.warn(
|
|
11
|
+
"fastmcp.server.openapi.components is deprecated. "
|
|
12
|
+
"Import from fastmcp.server.providers.openapi instead.",
|
|
13
|
+
DeprecationWarning,
|
|
14
|
+
stacklevel=2,
|
|
15
|
+
)
|
|
344
16
|
|
|
17
|
+
from fastmcp.server.providers.openapi import ( # noqa: E402
|
|
18
|
+
OpenAPIResource,
|
|
19
|
+
OpenAPIResourceTemplate,
|
|
20
|
+
OpenAPITool,
|
|
21
|
+
)
|
|
345
22
|
|
|
346
23
|
# Export public symbols
|
|
347
24
|
__all__ = [
|
|
@@ -1,125 +1,14 @@
|
|
|
1
|
-
"""Route mapping logic for OpenAPI operations.
|
|
1
|
+
"""Route mapping logic for OpenAPI operations.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from dataclasses import dataclass, field
|
|
7
|
-
from re import Pattern
|
|
8
|
-
from typing import TYPE_CHECKING, Literal
|
|
3
|
+
.. deprecated::
|
|
4
|
+
This module is deprecated. Import from fastmcp.server.providers.openapi instead.
|
|
5
|
+
"""
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
from .components import (
|
|
12
|
-
OpenAPIResource,
|
|
13
|
-
OpenAPIResourceTemplate,
|
|
14
|
-
OpenAPITool,
|
|
15
|
-
)
|
|
16
|
-
# Import from our new utilities
|
|
17
|
-
from fastmcp.utilities.logging import get_logger
|
|
18
|
-
from fastmcp.utilities.openapi import HttpMethod, HTTPRoute
|
|
7
|
+
# ruff: noqa: E402
|
|
19
8
|
|
|
20
|
-
|
|
9
|
+
import warnings
|
|
21
10
|
|
|
22
|
-
#
|
|
23
|
-
RouteMapFn = Callable[[HTTPRoute, "MCPType"], "MCPType | None"]
|
|
24
|
-
ComponentFn = Callable[
|
|
25
|
-
[
|
|
26
|
-
HTTPRoute,
|
|
27
|
-
"OpenAPITool | OpenAPIResource | OpenAPIResourceTemplate",
|
|
28
|
-
],
|
|
29
|
-
None,
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class MCPType(enum.Enum):
|
|
34
|
-
"""Type of FastMCP component to create from a route.
|
|
35
|
-
|
|
36
|
-
Enum values:
|
|
37
|
-
TOOL: Convert the route to a callable Tool
|
|
38
|
-
RESOURCE: Convert the route to a Resource (typically GET endpoints)
|
|
39
|
-
RESOURCE_TEMPLATE: Convert the route to a ResourceTemplate (typically GET with path params)
|
|
40
|
-
EXCLUDE: Exclude the route from being converted to any MCP component
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
TOOL = "TOOL"
|
|
44
|
-
RESOURCE = "RESOURCE"
|
|
45
|
-
RESOURCE_TEMPLATE = "RESOURCE_TEMPLATE"
|
|
46
|
-
# PROMPT = "PROMPT"
|
|
47
|
-
EXCLUDE = "EXCLUDE"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@dataclass(kw_only=True)
|
|
51
|
-
class RouteMap:
|
|
52
|
-
"""Mapping configuration for HTTP routes to FastMCP component types."""
|
|
53
|
-
|
|
54
|
-
methods: list[HttpMethod] | Literal["*"] = field(default="*")
|
|
55
|
-
pattern: Pattern[str] | str = field(default=r".*")
|
|
56
|
-
|
|
57
|
-
tags: set[str] = field(
|
|
58
|
-
default_factory=set,
|
|
59
|
-
metadata={"description": "A set of tags to match. All tags must match."},
|
|
60
|
-
)
|
|
61
|
-
mcp_type: MCPType = field(
|
|
62
|
-
metadata={"description": "The type of FastMCP component to create."},
|
|
63
|
-
)
|
|
64
|
-
mcp_tags: set[str] = field(
|
|
65
|
-
default_factory=set,
|
|
66
|
-
metadata={
|
|
67
|
-
"description": "A set of tags to apply to the generated FastMCP component."
|
|
68
|
-
},
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# Default route mapping: all routes become tools.
|
|
73
|
-
# Users can provide custom route_maps to override this behavior.
|
|
74
|
-
DEFAULT_ROUTE_MAPPINGS = [
|
|
75
|
-
RouteMap(mcp_type=MCPType.TOOL),
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def _determine_route_type(
|
|
80
|
-
route: HTTPRoute,
|
|
81
|
-
mappings: list[RouteMap],
|
|
82
|
-
) -> RouteMap:
|
|
83
|
-
"""
|
|
84
|
-
Determines the FastMCP component type based on the route and mappings.
|
|
85
|
-
|
|
86
|
-
Args:
|
|
87
|
-
route: HTTPRoute object
|
|
88
|
-
mappings: List of RouteMap objects in priority order
|
|
89
|
-
|
|
90
|
-
Returns:
|
|
91
|
-
The RouteMap that matches the route, or a catchall "Tool" RouteMap if no match is found.
|
|
92
|
-
"""
|
|
93
|
-
# Check mappings in priority order (first match wins)
|
|
94
|
-
for route_map in mappings:
|
|
95
|
-
# Check if the HTTP method matches
|
|
96
|
-
if route_map.methods == "*" or route.method in route_map.methods:
|
|
97
|
-
# Handle both string patterns and compiled Pattern objects
|
|
98
|
-
if isinstance(route_map.pattern, Pattern):
|
|
99
|
-
pattern_matches = route_map.pattern.search(route.path)
|
|
100
|
-
else:
|
|
101
|
-
pattern_matches = re.search(route_map.pattern, route.path)
|
|
102
|
-
|
|
103
|
-
if pattern_matches:
|
|
104
|
-
# Check if tags match (if specified)
|
|
105
|
-
# If route_map.tags is empty, tags are not matched
|
|
106
|
-
# If route_map.tags is non-empty, all tags must be present in route.tags (AND condition)
|
|
107
|
-
if route_map.tags:
|
|
108
|
-
route_tags_set = set(route.tags or [])
|
|
109
|
-
if not route_map.tags.issubset(route_tags_set):
|
|
110
|
-
# Tags don't match, continue to next mapping
|
|
111
|
-
continue
|
|
112
|
-
|
|
113
|
-
logger.debug(
|
|
114
|
-
f"Route {route.method} {route.path} mapped to {route_map.mcp_type.name}"
|
|
115
|
-
)
|
|
116
|
-
return route_map
|
|
117
|
-
|
|
118
|
-
# Default fallback
|
|
119
|
-
return RouteMap(mcp_type=MCPType.TOOL)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
# Export public symbols
|
|
11
|
+
# Backwards compatibility - export everything that was previously public
|
|
123
12
|
__all__ = [
|
|
124
13
|
"DEFAULT_ROUTE_MAPPINGS",
|
|
125
14
|
"ComponentFn",
|
|
@@ -128,3 +17,30 @@ __all__ = [
|
|
|
128
17
|
"RouteMapFn",
|
|
129
18
|
"_determine_route_type",
|
|
130
19
|
]
|
|
20
|
+
|
|
21
|
+
warnings.warn(
|
|
22
|
+
"fastmcp.server.openapi.routing is deprecated. "
|
|
23
|
+
"Import from fastmcp.server.providers.openapi instead.",
|
|
24
|
+
DeprecationWarning,
|
|
25
|
+
stacklevel=2,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Re-export from new canonical location
|
|
29
|
+
from fastmcp.server.providers.openapi.routing import (
|
|
30
|
+
DEFAULT_ROUTE_MAPPINGS as DEFAULT_ROUTE_MAPPINGS,
|
|
31
|
+
)
|
|
32
|
+
from fastmcp.server.providers.openapi.routing import (
|
|
33
|
+
ComponentFn as ComponentFn,
|
|
34
|
+
)
|
|
35
|
+
from fastmcp.server.providers.openapi.routing import (
|
|
36
|
+
MCPType as MCPType,
|
|
37
|
+
)
|
|
38
|
+
from fastmcp.server.providers.openapi.routing import (
|
|
39
|
+
RouteMap as RouteMap,
|
|
40
|
+
)
|
|
41
|
+
from fastmcp.server.providers.openapi.routing import (
|
|
42
|
+
RouteMapFn as RouteMapFn,
|
|
43
|
+
)
|
|
44
|
+
from fastmcp.server.providers.openapi.routing import (
|
|
45
|
+
_determine_route_type as _determine_route_type,
|
|
46
|
+
)
|