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.
Files changed (175) hide show
  1. fastmcp/_vendor/__init__.py +1 -0
  2. fastmcp/_vendor/docket_di/README.md +7 -0
  3. fastmcp/_vendor/docket_di/__init__.py +163 -0
  4. fastmcp/cli/cli.py +112 -28
  5. fastmcp/cli/install/claude_code.py +1 -5
  6. fastmcp/cli/install/claude_desktop.py +1 -5
  7. fastmcp/cli/install/cursor.py +1 -5
  8. fastmcp/cli/install/gemini_cli.py +1 -5
  9. fastmcp/cli/install/mcp_json.py +1 -6
  10. fastmcp/cli/run.py +146 -5
  11. fastmcp/client/__init__.py +7 -9
  12. fastmcp/client/auth/oauth.py +18 -17
  13. fastmcp/client/client.py +100 -870
  14. fastmcp/client/elicitation.py +1 -1
  15. fastmcp/client/mixins/__init__.py +13 -0
  16. fastmcp/client/mixins/prompts.py +295 -0
  17. fastmcp/client/mixins/resources.py +325 -0
  18. fastmcp/client/mixins/task_management.py +157 -0
  19. fastmcp/client/mixins/tools.py +397 -0
  20. fastmcp/client/sampling/handlers/anthropic.py +2 -2
  21. fastmcp/client/sampling/handlers/openai.py +1 -1
  22. fastmcp/client/tasks.py +3 -3
  23. fastmcp/client/telemetry.py +47 -0
  24. fastmcp/client/transports/__init__.py +38 -0
  25. fastmcp/client/transports/base.py +82 -0
  26. fastmcp/client/transports/config.py +170 -0
  27. fastmcp/client/transports/http.py +145 -0
  28. fastmcp/client/transports/inference.py +154 -0
  29. fastmcp/client/transports/memory.py +90 -0
  30. fastmcp/client/transports/sse.py +89 -0
  31. fastmcp/client/transports/stdio.py +543 -0
  32. fastmcp/contrib/component_manager/README.md +4 -10
  33. fastmcp/contrib/component_manager/__init__.py +1 -2
  34. fastmcp/contrib/component_manager/component_manager.py +95 -160
  35. fastmcp/contrib/component_manager/example.py +1 -1
  36. fastmcp/contrib/mcp_mixin/example.py +4 -4
  37. fastmcp/contrib/mcp_mixin/mcp_mixin.py +11 -4
  38. fastmcp/decorators.py +41 -0
  39. fastmcp/dependencies.py +12 -1
  40. fastmcp/exceptions.py +4 -0
  41. fastmcp/experimental/server/openapi/__init__.py +18 -15
  42. fastmcp/mcp_config.py +13 -4
  43. fastmcp/prompts/__init__.py +6 -3
  44. fastmcp/prompts/function_prompt.py +465 -0
  45. fastmcp/prompts/prompt.py +321 -271
  46. fastmcp/resources/__init__.py +5 -3
  47. fastmcp/resources/function_resource.py +335 -0
  48. fastmcp/resources/resource.py +325 -115
  49. fastmcp/resources/template.py +215 -43
  50. fastmcp/resources/types.py +27 -12
  51. fastmcp/server/__init__.py +2 -2
  52. fastmcp/server/auth/__init__.py +14 -0
  53. fastmcp/server/auth/auth.py +30 -10
  54. fastmcp/server/auth/authorization.py +190 -0
  55. fastmcp/server/auth/oauth_proxy/__init__.py +14 -0
  56. fastmcp/server/auth/oauth_proxy/consent.py +361 -0
  57. fastmcp/server/auth/oauth_proxy/models.py +178 -0
  58. fastmcp/server/auth/{oauth_proxy.py → oauth_proxy/proxy.py} +24 -778
  59. fastmcp/server/auth/oauth_proxy/ui.py +277 -0
  60. fastmcp/server/auth/oidc_proxy.py +2 -2
  61. fastmcp/server/auth/providers/auth0.py +24 -94
  62. fastmcp/server/auth/providers/aws.py +26 -95
  63. fastmcp/server/auth/providers/azure.py +41 -129
  64. fastmcp/server/auth/providers/descope.py +18 -49
  65. fastmcp/server/auth/providers/discord.py +25 -86
  66. fastmcp/server/auth/providers/github.py +23 -87
  67. fastmcp/server/auth/providers/google.py +24 -87
  68. fastmcp/server/auth/providers/introspection.py +60 -79
  69. fastmcp/server/auth/providers/jwt.py +30 -67
  70. fastmcp/server/auth/providers/oci.py +47 -110
  71. fastmcp/server/auth/providers/scalekit.py +23 -61
  72. fastmcp/server/auth/providers/supabase.py +18 -47
  73. fastmcp/server/auth/providers/workos.py +34 -127
  74. fastmcp/server/context.py +372 -419
  75. fastmcp/server/dependencies.py +541 -251
  76. fastmcp/server/elicitation.py +20 -18
  77. fastmcp/server/event_store.py +3 -3
  78. fastmcp/server/http.py +16 -6
  79. fastmcp/server/lifespan.py +198 -0
  80. fastmcp/server/low_level.py +92 -2
  81. fastmcp/server/middleware/__init__.py +5 -1
  82. fastmcp/server/middleware/authorization.py +312 -0
  83. fastmcp/server/middleware/caching.py +101 -54
  84. fastmcp/server/middleware/middleware.py +6 -9
  85. fastmcp/server/middleware/ping.py +70 -0
  86. fastmcp/server/middleware/tool_injection.py +2 -2
  87. fastmcp/server/mixins/__init__.py +7 -0
  88. fastmcp/server/mixins/lifespan.py +217 -0
  89. fastmcp/server/mixins/mcp_operations.py +392 -0
  90. fastmcp/server/mixins/transport.py +342 -0
  91. fastmcp/server/openapi/__init__.py +41 -21
  92. fastmcp/server/openapi/components.py +16 -339
  93. fastmcp/server/openapi/routing.py +34 -118
  94. fastmcp/server/openapi/server.py +67 -392
  95. fastmcp/server/providers/__init__.py +71 -0
  96. fastmcp/server/providers/aggregate.py +261 -0
  97. fastmcp/server/providers/base.py +578 -0
  98. fastmcp/server/providers/fastmcp_provider.py +674 -0
  99. fastmcp/server/providers/filesystem.py +226 -0
  100. fastmcp/server/providers/filesystem_discovery.py +327 -0
  101. fastmcp/server/providers/local_provider/__init__.py +11 -0
  102. fastmcp/server/providers/local_provider/decorators/__init__.py +15 -0
  103. fastmcp/server/providers/local_provider/decorators/prompts.py +256 -0
  104. fastmcp/server/providers/local_provider/decorators/resources.py +240 -0
  105. fastmcp/server/providers/local_provider/decorators/tools.py +315 -0
  106. fastmcp/server/providers/local_provider/local_provider.py +465 -0
  107. fastmcp/server/providers/openapi/__init__.py +39 -0
  108. fastmcp/server/providers/openapi/components.py +332 -0
  109. fastmcp/server/providers/openapi/provider.py +405 -0
  110. fastmcp/server/providers/openapi/routing.py +109 -0
  111. fastmcp/server/providers/proxy.py +867 -0
  112. fastmcp/server/providers/skills/__init__.py +59 -0
  113. fastmcp/server/providers/skills/_common.py +101 -0
  114. fastmcp/server/providers/skills/claude_provider.py +44 -0
  115. fastmcp/server/providers/skills/directory_provider.py +153 -0
  116. fastmcp/server/providers/skills/skill_provider.py +432 -0
  117. fastmcp/server/providers/skills/vendor_providers.py +142 -0
  118. fastmcp/server/providers/wrapped_provider.py +140 -0
  119. fastmcp/server/proxy.py +34 -700
  120. fastmcp/server/sampling/run.py +341 -2
  121. fastmcp/server/sampling/sampling_tool.py +4 -3
  122. fastmcp/server/server.py +1214 -2171
  123. fastmcp/server/tasks/__init__.py +2 -1
  124. fastmcp/server/tasks/capabilities.py +13 -1
  125. fastmcp/server/tasks/config.py +66 -3
  126. fastmcp/server/tasks/handlers.py +65 -273
  127. fastmcp/server/tasks/keys.py +4 -6
  128. fastmcp/server/tasks/requests.py +474 -0
  129. fastmcp/server/tasks/routing.py +76 -0
  130. fastmcp/server/tasks/subscriptions.py +20 -11
  131. fastmcp/server/telemetry.py +131 -0
  132. fastmcp/server/transforms/__init__.py +244 -0
  133. fastmcp/server/transforms/namespace.py +193 -0
  134. fastmcp/server/transforms/prompts_as_tools.py +175 -0
  135. fastmcp/server/transforms/resources_as_tools.py +190 -0
  136. fastmcp/server/transforms/tool_transform.py +96 -0
  137. fastmcp/server/transforms/version_filter.py +124 -0
  138. fastmcp/server/transforms/visibility.py +526 -0
  139. fastmcp/settings.py +34 -96
  140. fastmcp/telemetry.py +122 -0
  141. fastmcp/tools/__init__.py +10 -3
  142. fastmcp/tools/function_parsing.py +201 -0
  143. fastmcp/tools/function_tool.py +467 -0
  144. fastmcp/tools/tool.py +215 -362
  145. fastmcp/tools/tool_transform.py +38 -21
  146. fastmcp/utilities/async_utils.py +69 -0
  147. fastmcp/utilities/components.py +152 -91
  148. fastmcp/utilities/inspect.py +8 -20
  149. fastmcp/utilities/json_schema.py +12 -5
  150. fastmcp/utilities/json_schema_type.py +17 -15
  151. fastmcp/utilities/lifespan.py +56 -0
  152. fastmcp/utilities/logging.py +12 -4
  153. fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +3 -3
  154. fastmcp/utilities/openapi/parser.py +3 -3
  155. fastmcp/utilities/pagination.py +80 -0
  156. fastmcp/utilities/skills.py +253 -0
  157. fastmcp/utilities/tests.py +0 -16
  158. fastmcp/utilities/timeout.py +47 -0
  159. fastmcp/utilities/types.py +1 -1
  160. fastmcp/utilities/versions.py +285 -0
  161. {fastmcp-2.14.5.dist-info → fastmcp-3.0.0b1.dist-info}/METADATA +8 -5
  162. fastmcp-3.0.0b1.dist-info/RECORD +228 -0
  163. fastmcp/client/transports.py +0 -1170
  164. fastmcp/contrib/component_manager/component_service.py +0 -209
  165. fastmcp/prompts/prompt_manager.py +0 -117
  166. fastmcp/resources/resource_manager.py +0 -338
  167. fastmcp/server/tasks/converters.py +0 -206
  168. fastmcp/server/tasks/protocol.py +0 -359
  169. fastmcp/tools/tool_manager.py +0 -170
  170. fastmcp/utilities/mcp_config.py +0 -56
  171. fastmcp-2.14.5.dist-info/RECORD +0 -161
  172. /fastmcp/server/{openapi → providers/openapi}/README.md +0 -0
  173. {fastmcp-2.14.5.dist-info → fastmcp-3.0.0b1.dist-info}/WHEEL +0 -0
  174. {fastmcp-2.14.5.dist-info → fastmcp-3.0.0b1.dist-info}/entry_points.txt +0 -0
  175. {fastmcp-2.14.5.dist-info → fastmcp-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,405 @@
1
+ """OpenAPIProvider for creating MCP components from OpenAPI specifications."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import Counter
6
+ from collections.abc import Sequence
7
+ from typing import Any, Literal
8
+
9
+ import httpx
10
+ from jsonschema_path import SchemaPath
11
+
12
+ from fastmcp.prompts import Prompt
13
+ from fastmcp.resources import Resource, ResourceTemplate
14
+ from fastmcp.server.providers.base import Provider
15
+ from fastmcp.server.providers.openapi.components import (
16
+ OpenAPIResource,
17
+ OpenAPIResourceTemplate,
18
+ OpenAPITool,
19
+ _slugify,
20
+ )
21
+ from fastmcp.server.providers.openapi.routing import (
22
+ DEFAULT_ROUTE_MAPPINGS,
23
+ ComponentFn,
24
+ MCPType,
25
+ RouteMap,
26
+ RouteMapFn,
27
+ _determine_route_type,
28
+ )
29
+ from fastmcp.tools.tool import Tool
30
+ from fastmcp.utilities.components import FastMCPComponent
31
+ from fastmcp.utilities.logging import get_logger
32
+ from fastmcp.utilities.openapi import (
33
+ HTTPRoute,
34
+ extract_output_schema_from_responses,
35
+ format_simple_description,
36
+ parse_openapi_to_http_routes,
37
+ )
38
+ from fastmcp.utilities.openapi.director import RequestDirector
39
+ from fastmcp.utilities.versions import VersionSpec, version_sort_key
40
+
41
+ __all__ = [
42
+ "OpenAPIProvider",
43
+ ]
44
+
45
+ logger = get_logger(__name__)
46
+
47
+
48
+ class OpenAPIProvider(Provider):
49
+ """Provider that creates MCP components from an OpenAPI specification.
50
+
51
+ Components are created eagerly during initialization by parsing the OpenAPI
52
+ spec. Each component makes HTTP calls to the described API endpoints.
53
+
54
+ Example:
55
+ ```python
56
+ from fastmcp import FastMCP
57
+ from fastmcp.server.providers.openapi import OpenAPIProvider
58
+ import httpx
59
+
60
+ client = httpx.AsyncClient(base_url="https://api.example.com")
61
+ provider = OpenAPIProvider(openapi_spec=spec, client=client)
62
+
63
+ mcp = FastMCP("API Server")
64
+ mcp.add_provider(provider)
65
+ ```
66
+ """
67
+
68
+ def __init__(
69
+ self,
70
+ openapi_spec: dict[str, Any],
71
+ client: httpx.AsyncClient,
72
+ *,
73
+ route_maps: list[RouteMap] | None = None,
74
+ route_map_fn: RouteMapFn | None = None,
75
+ mcp_component_fn: ComponentFn | None = None,
76
+ mcp_names: dict[str, str] | None = None,
77
+ tags: set[str] | None = None,
78
+ timeout: float | None = None,
79
+ ):
80
+ """Initialize provider by parsing OpenAPI spec and creating components.
81
+
82
+ Args:
83
+ openapi_spec: OpenAPI schema as a dictionary
84
+ client: httpx AsyncClient for making HTTP requests
85
+ route_maps: Optional list of RouteMap objects defining route mappings
86
+ route_map_fn: Optional callable for advanced route type mapping
87
+ mcp_component_fn: Optional callable for component customization
88
+ mcp_names: Optional dictionary mapping operationId to component names
89
+ tags: Optional set of tags to add to all components
90
+ timeout: Optional timeout (in seconds) for all requests
91
+ """
92
+ super().__init__()
93
+
94
+ self._client = client
95
+ self._timeout = timeout
96
+ self._mcp_component_fn = mcp_component_fn
97
+
98
+ # Keep track of names to detect collisions
99
+ self._used_names: dict[str, Counter[str]] = {
100
+ "tool": Counter(),
101
+ "resource": Counter(),
102
+ "resource_template": Counter(),
103
+ "prompt": Counter(),
104
+ }
105
+
106
+ # Pre-created component storage
107
+ self._tools: dict[str, OpenAPITool] = {}
108
+ self._resources: dict[str, OpenAPIResource] = {}
109
+ self._templates: dict[str, OpenAPIResourceTemplate] = {}
110
+
111
+ # Create openapi-core Spec and RequestDirector
112
+ try:
113
+ self._spec = SchemaPath.from_dict(openapi_spec) # type: ignore[arg-type]
114
+ self._director = RequestDirector(self._spec)
115
+ except Exception as e:
116
+ logger.exception("Failed to initialize RequestDirector")
117
+ raise ValueError(f"Invalid OpenAPI specification: {e}") from e
118
+
119
+ http_routes = parse_openapi_to_http_routes(openapi_spec)
120
+
121
+ # Process routes
122
+ route_maps = (route_maps or []) + DEFAULT_ROUTE_MAPPINGS
123
+ for route in http_routes:
124
+ route_map = _determine_route_type(route, route_maps)
125
+ route_type = route_map.mcp_type
126
+
127
+ if route_map_fn is not None:
128
+ try:
129
+ result = route_map_fn(route, route_type)
130
+ if result is not None:
131
+ route_type = result
132
+ logger.debug(
133
+ f"Route {route.method} {route.path} mapping customized: "
134
+ f"type={route_type.name}"
135
+ )
136
+ except Exception as e:
137
+ logger.warning(
138
+ f"Error in route_map_fn for {route.method} {route.path}: {e}. "
139
+ f"Using default values."
140
+ )
141
+
142
+ component_name = self._generate_default_name(route, mcp_names)
143
+ route_tags = set(route.tags) | route_map.mcp_tags | (tags or set())
144
+
145
+ if route_type == MCPType.TOOL:
146
+ self._create_openapi_tool(route, component_name, tags=route_tags)
147
+ elif route_type == MCPType.RESOURCE:
148
+ self._create_openapi_resource(route, component_name, tags=route_tags)
149
+ elif route_type == MCPType.RESOURCE_TEMPLATE:
150
+ self._create_openapi_template(route, component_name, tags=route_tags)
151
+ elif route_type == MCPType.EXCLUDE:
152
+ logger.debug(f"Excluding route: {route.method} {route.path}")
153
+
154
+ logger.debug(f"Created OpenAPIProvider with {len(http_routes)} routes")
155
+
156
+ def _generate_default_name(
157
+ self, route: HTTPRoute, mcp_names_map: dict[str, str] | None = None
158
+ ) -> str:
159
+ """Generate a default name from the route."""
160
+ mcp_names_map = mcp_names_map or {}
161
+
162
+ if route.operation_id:
163
+ if route.operation_id in mcp_names_map:
164
+ name = mcp_names_map[route.operation_id]
165
+ else:
166
+ name = route.operation_id.split("__")[0]
167
+ else:
168
+ name = route.summary or f"{route.method}_{route.path}"
169
+
170
+ name = _slugify(name)
171
+
172
+ if len(name) > 56:
173
+ name = name[:56]
174
+
175
+ return name
176
+
177
+ def _get_unique_name(
178
+ self,
179
+ name: str,
180
+ component_type: Literal["tool", "resource", "resource_template", "prompt"],
181
+ ) -> str:
182
+ """Ensure the name is unique by appending numbers if needed."""
183
+ self._used_names[component_type][name] += 1
184
+ if self._used_names[component_type][name] == 1:
185
+ return name
186
+
187
+ new_name = f"{name}_{self._used_names[component_type][name]}"
188
+ logger.debug(
189
+ f"Name collision: '{name}' exists as {component_type}. Using '{new_name}'."
190
+ )
191
+ return new_name
192
+
193
+ def _create_openapi_tool(
194
+ self,
195
+ route: HTTPRoute,
196
+ name: str,
197
+ tags: set[str],
198
+ ) -> None:
199
+ """Create and register an OpenAPITool."""
200
+ combined_schema = route.flat_param_schema
201
+ output_schema = extract_output_schema_from_responses(
202
+ route.responses,
203
+ route.response_schemas,
204
+ route.openapi_version,
205
+ )
206
+
207
+ tool_name = self._get_unique_name(name, "tool")
208
+ base_description = (
209
+ route.description
210
+ or route.summary
211
+ or f"Executes {route.method} {route.path}"
212
+ )
213
+ enhanced_description = format_simple_description(
214
+ base_description=base_description,
215
+ parameters=route.parameters,
216
+ request_body=route.request_body,
217
+ )
218
+
219
+ tool = OpenAPITool(
220
+ client=self._client,
221
+ route=route,
222
+ director=self._director,
223
+ name=tool_name,
224
+ description=enhanced_description,
225
+ parameters=combined_schema,
226
+ output_schema=output_schema,
227
+ tags=set(route.tags or []) | tags,
228
+ timeout=self._timeout,
229
+ )
230
+
231
+ if self._mcp_component_fn is not None:
232
+ try:
233
+ self._mcp_component_fn(route, tool)
234
+ logger.debug(f"Tool {tool_name} customized by component_fn")
235
+ except Exception as e:
236
+ logger.warning(f"Error in component_fn for tool {tool_name}: {e}")
237
+
238
+ self._tools[tool.name] = tool
239
+
240
+ def _create_openapi_resource(
241
+ self,
242
+ route: HTTPRoute,
243
+ name: str,
244
+ tags: set[str],
245
+ ) -> None:
246
+ """Create and register an OpenAPIResource."""
247
+ resource_name = self._get_unique_name(name, "resource")
248
+ resource_uri = f"resource://{resource_name}"
249
+ base_description = (
250
+ route.description or route.summary or f"Represents {route.path}"
251
+ )
252
+ enhanced_description = format_simple_description(
253
+ base_description=base_description,
254
+ parameters=route.parameters,
255
+ request_body=route.request_body,
256
+ )
257
+
258
+ resource = OpenAPIResource(
259
+ client=self._client,
260
+ route=route,
261
+ director=self._director,
262
+ uri=resource_uri,
263
+ name=resource_name,
264
+ description=enhanced_description,
265
+ tags=set(route.tags or []) | tags,
266
+ timeout=self._timeout,
267
+ )
268
+
269
+ if self._mcp_component_fn is not None:
270
+ try:
271
+ self._mcp_component_fn(route, resource)
272
+ logger.debug(f"Resource {resource_uri} customized by component_fn")
273
+ except Exception as e:
274
+ logger.warning(
275
+ f"Error in component_fn for resource {resource_uri}: {e}"
276
+ )
277
+
278
+ self._resources[str(resource.uri)] = resource
279
+
280
+ def _create_openapi_template(
281
+ self,
282
+ route: HTTPRoute,
283
+ name: str,
284
+ tags: set[str],
285
+ ) -> None:
286
+ """Create and register an OpenAPIResourceTemplate."""
287
+ template_name = self._get_unique_name(name, "resource_template")
288
+
289
+ path_params = sorted(p.name for p in route.parameters if p.location == "path")
290
+ uri_template_str = f"resource://{template_name}"
291
+ if path_params:
292
+ uri_template_str += "/" + "/".join(f"{{{p}}}" for p in path_params)
293
+
294
+ base_description = (
295
+ route.description or route.summary or f"Template for {route.path}"
296
+ )
297
+ enhanced_description = format_simple_description(
298
+ base_description=base_description,
299
+ parameters=route.parameters,
300
+ request_body=route.request_body,
301
+ )
302
+
303
+ template_params_schema = {
304
+ "type": "object",
305
+ "properties": {
306
+ p.name: {
307
+ **(p.schema_.copy() if isinstance(p.schema_, dict) else {}),
308
+ **(
309
+ {"description": p.description}
310
+ if p.description
311
+ and not (
312
+ isinstance(p.schema_, dict) and "description" in p.schema_
313
+ )
314
+ else {}
315
+ ),
316
+ }
317
+ for p in route.parameters
318
+ if p.location == "path"
319
+ },
320
+ "required": [
321
+ p.name for p in route.parameters if p.location == "path" and p.required
322
+ ],
323
+ }
324
+
325
+ template = OpenAPIResourceTemplate(
326
+ client=self._client,
327
+ route=route,
328
+ director=self._director,
329
+ uri_template=uri_template_str,
330
+ name=template_name,
331
+ description=enhanced_description,
332
+ parameters=template_params_schema,
333
+ tags=set(route.tags or []) | tags,
334
+ timeout=self._timeout,
335
+ )
336
+
337
+ if self._mcp_component_fn is not None:
338
+ try:
339
+ self._mcp_component_fn(route, template)
340
+ logger.debug(f"Template {uri_template_str} customized by component_fn")
341
+ except Exception as e:
342
+ logger.warning(
343
+ f"Error in component_fn for template {uri_template_str}: {e}"
344
+ )
345
+
346
+ self._templates[template.uri_template] = template
347
+
348
+ # -------------------------------------------------------------------------
349
+ # Provider interface
350
+ # -------------------------------------------------------------------------
351
+
352
+ async def _list_tools(self) -> Sequence[Tool]:
353
+ """Return all tools created from the OpenAPI spec."""
354
+ return list(self._tools.values())
355
+
356
+ async def _get_tool(
357
+ self, name: str, version: VersionSpec | None = None
358
+ ) -> Tool | None:
359
+ """Get a tool by name."""
360
+ tool = self._tools.get(name)
361
+ if tool is None:
362
+ return None
363
+ if version is not None and not version.matches(tool.version):
364
+ return None
365
+ return tool
366
+
367
+ async def _list_resources(self) -> Sequence[Resource]:
368
+ """Return all resources created from the OpenAPI spec."""
369
+ return list(self._resources.values())
370
+
371
+ async def _get_resource(
372
+ self, uri: str, version: VersionSpec | None = None
373
+ ) -> Resource | None:
374
+ """Get a resource by URI."""
375
+ resource = self._resources.get(uri)
376
+ if resource is None:
377
+ return None
378
+ if version is not None and not version.matches(resource.version):
379
+ return None
380
+ return resource
381
+
382
+ async def _list_resource_templates(self) -> Sequence[ResourceTemplate]:
383
+ """Return all resource templates created from the OpenAPI spec."""
384
+ return list(self._templates.values())
385
+
386
+ async def _get_resource_template(
387
+ self, uri: str, version: VersionSpec | None = None
388
+ ) -> ResourceTemplate | None:
389
+ """Get a resource template that matches the given URI."""
390
+ matching = [t for t in self._templates.values() if t.matches(uri) is not None]
391
+ if not matching:
392
+ return None
393
+ if version is not None:
394
+ matching = [t for t in matching if version.matches(t.version)]
395
+ if not matching:
396
+ return None
397
+ return max(matching, key=version_sort_key) # type: ignore[type-var]
398
+
399
+ async def _list_prompts(self) -> Sequence[Prompt]:
400
+ """Return empty list - OpenAPI doesn't create prompts."""
401
+ return []
402
+
403
+ async def get_tasks(self) -> Sequence[FastMCPComponent]:
404
+ """Return empty list - OpenAPI components don't support tasks."""
405
+ return []
@@ -0,0 +1,109 @@
1
+ """Route mapping logic for OpenAPI operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import enum
6
+ import re
7
+ from collections.abc import Callable
8
+ from dataclasses import dataclass, field
9
+ from re import Pattern
10
+ from typing import TYPE_CHECKING, Literal
11
+
12
+ if TYPE_CHECKING:
13
+ from fastmcp.server.providers.openapi.components import (
14
+ OpenAPIResource,
15
+ OpenAPIResourceTemplate,
16
+ OpenAPITool,
17
+ )
18
+
19
+ from fastmcp.utilities.logging import get_logger
20
+ from fastmcp.utilities.openapi import HttpMethod, HTTPRoute
21
+
22
+ __all__ = [
23
+ "ComponentFn",
24
+ "MCPType",
25
+ "RouteMap",
26
+ "RouteMapFn",
27
+ ]
28
+
29
+ logger = get_logger(__name__)
30
+
31
+ # Type definitions for the mapping functions
32
+ RouteMapFn = Callable[[HTTPRoute, "MCPType"], "MCPType | None"]
33
+ ComponentFn = Callable[
34
+ [
35
+ HTTPRoute,
36
+ "OpenAPITool | OpenAPIResource | OpenAPIResourceTemplate",
37
+ ],
38
+ None,
39
+ ]
40
+
41
+
42
+ class MCPType(enum.Enum):
43
+ """Type of FastMCP component to create from a route.
44
+
45
+ Enum values:
46
+ TOOL: Convert the route to a callable Tool
47
+ RESOURCE: Convert the route to a Resource (typically GET endpoints)
48
+ RESOURCE_TEMPLATE: Convert the route to a ResourceTemplate (typically GET with path params)
49
+ EXCLUDE: Exclude the route from being converted to any MCP component
50
+ """
51
+
52
+ TOOL = "TOOL"
53
+ RESOURCE = "RESOURCE"
54
+ RESOURCE_TEMPLATE = "RESOURCE_TEMPLATE"
55
+ EXCLUDE = "EXCLUDE"
56
+
57
+
58
+ @dataclass(kw_only=True)
59
+ class RouteMap:
60
+ """Mapping configuration for HTTP routes to FastMCP component types."""
61
+
62
+ methods: list[HttpMethod] | Literal["*"] = field(default="*")
63
+ pattern: Pattern[str] | str = field(default=r".*")
64
+
65
+ tags: set[str] = field(
66
+ default_factory=set,
67
+ metadata={"description": "A set of tags to match. All tags must match."},
68
+ )
69
+ mcp_type: MCPType = field(
70
+ metadata={"description": "The type of FastMCP component to create."},
71
+ )
72
+ mcp_tags: set[str] = field(
73
+ default_factory=set,
74
+ metadata={
75
+ "description": "A set of tags to apply to the generated FastMCP component."
76
+ },
77
+ )
78
+
79
+
80
+ # Default route mapping: all routes become tools.
81
+ DEFAULT_ROUTE_MAPPINGS = [
82
+ RouteMap(mcp_type=MCPType.TOOL),
83
+ ]
84
+
85
+
86
+ def _determine_route_type(
87
+ route: HTTPRoute,
88
+ mappings: list[RouteMap],
89
+ ) -> RouteMap:
90
+ """Determine the FastMCP component type based on the route and mappings."""
91
+ for route_map in mappings:
92
+ if route_map.methods == "*" or route.method in route_map.methods:
93
+ if isinstance(route_map.pattern, Pattern):
94
+ pattern_matches = route_map.pattern.search(route.path)
95
+ else:
96
+ pattern_matches = re.search(route_map.pattern, route.path)
97
+
98
+ if pattern_matches:
99
+ if route_map.tags:
100
+ route_tags_set = set(route.tags or [])
101
+ if not route_map.tags.issubset(route_tags_set):
102
+ continue
103
+
104
+ logger.debug(
105
+ f"Route {route.method} {route.path} mapped to {route_map.mcp_type.name}"
106
+ )
107
+ return route_map
108
+
109
+ return RouteMap(mcp_type=MCPType.TOOL)