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
fastmcp/server/openapi/server.py
CHANGED
|
@@ -1,100 +1,60 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""FastMCPOpenAPI - backwards compatibility wrapper.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from collections import Counter
|
|
5
|
-
from typing import Any, Literal
|
|
3
|
+
This class is deprecated. Use FastMCP with OpenAPIProvider instead:
|
|
6
4
|
|
|
7
|
-
import
|
|
8
|
-
from
|
|
5
|
+
from fastmcp import FastMCP
|
|
6
|
+
from fastmcp.server.providers.openapi import OpenAPIProvider
|
|
7
|
+
import httpx
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
client = httpx.AsyncClient(base_url="https://api.example.com")
|
|
10
|
+
provider = OpenAPIProvider(openapi_spec=spec, client=client)
|
|
11
|
+
mcp = FastMCP("My API Server", providers=[provider])
|
|
12
|
+
"""
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
from fastmcp.utilities.openapi import (
|
|
15
|
-
HTTPRoute,
|
|
16
|
-
extract_output_schema_from_responses,
|
|
17
|
-
format_simple_description,
|
|
18
|
-
parse_openapi_to_http_routes,
|
|
19
|
-
)
|
|
20
|
-
from fastmcp.utilities.openapi.director import RequestDirector
|
|
14
|
+
from __future__ import annotations
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
from .
|
|
28
|
-
DEFAULT_ROUTE_MAPPINGS,
|
|
16
|
+
import warnings
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import httpx
|
|
20
|
+
|
|
21
|
+
from fastmcp.server.providers.openapi import (
|
|
29
22
|
ComponentFn,
|
|
30
|
-
|
|
23
|
+
OpenAPIProvider,
|
|
31
24
|
RouteMap,
|
|
32
25
|
RouteMapFn,
|
|
33
|
-
_determine_route_type,
|
|
34
26
|
)
|
|
35
|
-
|
|
36
|
-
logger = get_logger(__name__)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def _slugify(text: str) -> str:
|
|
40
|
-
"""
|
|
41
|
-
Convert text to a URL-friendly slug format that only contains lowercase
|
|
42
|
-
letters, uppercase letters, numbers, and underscores.
|
|
43
|
-
"""
|
|
44
|
-
if not text:
|
|
45
|
-
return ""
|
|
46
|
-
|
|
47
|
-
# Replace spaces and common separators with underscores
|
|
48
|
-
slug = re.sub(r"[\s\-\.]+", "_", text)
|
|
49
|
-
|
|
50
|
-
# Remove non-alphanumeric characters except underscores
|
|
51
|
-
slug = re.sub(r"[^a-zA-Z0-9_]", "", slug)
|
|
52
|
-
|
|
53
|
-
# Remove multiple consecutive underscores
|
|
54
|
-
slug = re.sub(r"_+", "_", slug)
|
|
55
|
-
|
|
56
|
-
# Remove leading/trailing underscores
|
|
57
|
-
slug = slug.strip("_")
|
|
58
|
-
|
|
59
|
-
return slug
|
|
27
|
+
from fastmcp.server.server import FastMCP
|
|
60
28
|
|
|
61
29
|
|
|
62
30
|
class FastMCPOpenAPI(FastMCP):
|
|
63
|
-
"""
|
|
64
|
-
FastMCP server implementation that creates components from an OpenAPI schema.
|
|
31
|
+
"""FastMCP server implementation that creates components from an OpenAPI schema.
|
|
65
32
|
|
|
66
|
-
|
|
67
|
-
|
|
33
|
+
.. deprecated::
|
|
34
|
+
Use FastMCP with OpenAPIProvider instead. This class will be
|
|
35
|
+
removed in a future version.
|
|
68
36
|
|
|
69
|
-
Example:
|
|
37
|
+
Example (deprecated):
|
|
70
38
|
```python
|
|
71
|
-
from fastmcp.server.openapi import FastMCPOpenAPI
|
|
39
|
+
from fastmcp.server.openapi import FastMCPOpenAPI
|
|
72
40
|
import httpx
|
|
73
41
|
|
|
74
|
-
# Define custom route mappings
|
|
75
|
-
custom_mappings = [
|
|
76
|
-
# Map all user-related endpoints to ResourceTemplate
|
|
77
|
-
RouteMap(
|
|
78
|
-
methods=["GET", "POST", "PATCH"],
|
|
79
|
-
pattern=r".*/users/.*",
|
|
80
|
-
mcp_type=MCPType.RESOURCE_TEMPLATE
|
|
81
|
-
),
|
|
82
|
-
# Map all analytics endpoints to Tool
|
|
83
|
-
RouteMap(
|
|
84
|
-
methods=["GET"],
|
|
85
|
-
pattern=r".*/analytics/.*",
|
|
86
|
-
mcp_type=MCPType.TOOL
|
|
87
|
-
),
|
|
88
|
-
]
|
|
89
|
-
|
|
90
|
-
# Create server with custom mappings and route mapper
|
|
91
42
|
server = FastMCPOpenAPI(
|
|
92
43
|
openapi_spec=spec,
|
|
93
44
|
client=httpx.AsyncClient(),
|
|
94
|
-
name="API Server",
|
|
95
|
-
route_maps=custom_mappings,
|
|
96
45
|
)
|
|
97
46
|
```
|
|
47
|
+
|
|
48
|
+
New approach:
|
|
49
|
+
```python
|
|
50
|
+
from fastmcp import FastMCP
|
|
51
|
+
from fastmcp.server.providers.openapi import OpenAPIProvider
|
|
52
|
+
import httpx
|
|
53
|
+
|
|
54
|
+
client = httpx.AsyncClient(base_url="https://api.example.com")
|
|
55
|
+
provider = OpenAPIProvider(openapi_spec=spec, client=client)
|
|
56
|
+
mcp = FastMCP("API Server", providers=[provider])
|
|
57
|
+
```
|
|
98
58
|
"""
|
|
99
59
|
|
|
100
60
|
def __init__(
|
|
@@ -110,340 +70,55 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
110
70
|
timeout: float | None = None,
|
|
111
71
|
**settings: Any,
|
|
112
72
|
):
|
|
113
|
-
"""
|
|
114
|
-
|
|
73
|
+
"""Initialize a FastMCP server from an OpenAPI schema.
|
|
74
|
+
|
|
75
|
+
.. deprecated::
|
|
76
|
+
Use FastMCP with OpenAPIProvider instead.
|
|
115
77
|
|
|
116
78
|
Args:
|
|
117
|
-
openapi_spec: OpenAPI schema as a dictionary
|
|
79
|
+
openapi_spec: OpenAPI schema as a dictionary
|
|
118
80
|
client: httpx AsyncClient for making HTTP requests
|
|
119
81
|
name: Optional name for the server
|
|
120
82
|
route_maps: Optional list of RouteMap objects defining route mappings
|
|
121
|
-
route_map_fn: Optional callable for advanced route type mapping
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Receives (route, component) and can modify the component in-place.
|
|
126
|
-
Called on every created component.
|
|
127
|
-
mcp_names: Optional dictionary mapping operationId to desired component names.
|
|
128
|
-
If an operationId is not in the dictionary, falls back to using the
|
|
129
|
-
operationId up to the first double underscore. If no operationId exists,
|
|
130
|
-
falls back to slugified summary or path-based naming.
|
|
131
|
-
All names are truncated to 56 characters maximum.
|
|
132
|
-
tags: Optional set of tags to add to all components. Components always receive any tags
|
|
133
|
-
from the route.
|
|
83
|
+
route_map_fn: Optional callable for advanced route type mapping
|
|
84
|
+
mcp_component_fn: Optional callable for component customization
|
|
85
|
+
mcp_names: Optional dictionary mapping operationId to component names
|
|
86
|
+
tags: Optional set of tags to add to all components
|
|
134
87
|
timeout: Optional timeout (in seconds) for all requests
|
|
135
88
|
**settings: Additional settings for FastMCP
|
|
136
89
|
"""
|
|
90
|
+
warnings.warn(
|
|
91
|
+
"FastMCPOpenAPI is deprecated. Use FastMCP with OpenAPIProvider instead:\n"
|
|
92
|
+
" provider = OpenAPIProvider(openapi_spec=spec, client=client)\n"
|
|
93
|
+
" mcp = FastMCP('name', providers=[provider])",
|
|
94
|
+
DeprecationWarning,
|
|
95
|
+
stacklevel=2,
|
|
96
|
+
)
|
|
97
|
+
|
|
137
98
|
super().__init__(name=name or "OpenAPI FastMCP", **settings)
|
|
138
99
|
|
|
100
|
+
# Store references for backwards compatibility
|
|
139
101
|
self._client = client
|
|
140
102
|
self._timeout = timeout
|
|
141
103
|
self._mcp_component_fn = mcp_component_fn
|
|
142
104
|
|
|
143
|
-
#
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
self._spec = SchemaPath.from_dict(openapi_spec) # type: ignore[arg-type]
|
|
154
|
-
self._director = RequestDirector(self._spec)
|
|
155
|
-
except Exception as e:
|
|
156
|
-
logger.error(f"Failed to initialize RequestDirector: {e}")
|
|
157
|
-
raise ValueError(f"Invalid OpenAPI specification: {e}") from e
|
|
158
|
-
|
|
159
|
-
http_routes = parse_openapi_to_http_routes(openapi_spec)
|
|
160
|
-
|
|
161
|
-
# Process routes
|
|
162
|
-
route_maps = (route_maps or []) + DEFAULT_ROUTE_MAPPINGS
|
|
163
|
-
for route in http_routes:
|
|
164
|
-
# Determine route type based on mappings or default rules
|
|
165
|
-
route_map = _determine_route_type(route, route_maps)
|
|
166
|
-
|
|
167
|
-
route_type = route_map.mcp_type
|
|
168
|
-
|
|
169
|
-
# Call route_map_fn if provided
|
|
170
|
-
if route_map_fn is not None:
|
|
171
|
-
try:
|
|
172
|
-
result = route_map_fn(route, route_type)
|
|
173
|
-
if result is not None:
|
|
174
|
-
route_type = result
|
|
175
|
-
logger.debug(
|
|
176
|
-
f"Route {route.method} {route.path} mapping customized by route_map_fn: "
|
|
177
|
-
f"type={route_type.name}"
|
|
178
|
-
)
|
|
179
|
-
except Exception as e:
|
|
180
|
-
logger.warning(
|
|
181
|
-
f"Error in route_map_fn for {route.method} {route.path}: {e}. "
|
|
182
|
-
f"Using default values."
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
# Generate a default name from the route
|
|
186
|
-
component_name = self._generate_default_name(route, mcp_names)
|
|
187
|
-
|
|
188
|
-
route_tags = set(route.tags) | route_map.mcp_tags | (tags or set())
|
|
189
|
-
|
|
190
|
-
# Create components using simplified approach with RequestDirector
|
|
191
|
-
if route_type == MCPType.TOOL:
|
|
192
|
-
self._create_openapi_tool(route, component_name, tags=route_tags)
|
|
193
|
-
elif route_type == MCPType.RESOURCE:
|
|
194
|
-
self._create_openapi_resource(route, component_name, tags=route_tags)
|
|
195
|
-
elif route_type == MCPType.RESOURCE_TEMPLATE:
|
|
196
|
-
self._create_openapi_template(route, component_name, tags=route_tags)
|
|
197
|
-
elif route_type == MCPType.EXCLUDE:
|
|
198
|
-
logger.debug(f"Excluding route: {route.method} {route.path}")
|
|
199
|
-
|
|
200
|
-
logger.debug(f"Created FastMCP OpenAPI server with {len(http_routes)} routes")
|
|
201
|
-
|
|
202
|
-
def _generate_default_name(
|
|
203
|
-
self, route: HTTPRoute, mcp_names_map: dict[str, str] | None = None
|
|
204
|
-
) -> str:
|
|
205
|
-
"""Generate a default name from the route using the configured strategy."""
|
|
206
|
-
name = ""
|
|
207
|
-
mcp_names_map = mcp_names_map or {}
|
|
208
|
-
|
|
209
|
-
# First check if there's a custom mapping for this operationId
|
|
210
|
-
if route.operation_id:
|
|
211
|
-
if route.operation_id in mcp_names_map:
|
|
212
|
-
name = mcp_names_map[route.operation_id]
|
|
213
|
-
else:
|
|
214
|
-
# If there's a double underscore in the operationId, use the first part
|
|
215
|
-
name = route.operation_id.split("__")[0]
|
|
216
|
-
else:
|
|
217
|
-
name = route.summary or f"{route.method}_{route.path}"
|
|
218
|
-
|
|
219
|
-
name = _slugify(name)
|
|
220
|
-
|
|
221
|
-
# Truncate to 56 characters maximum
|
|
222
|
-
if len(name) > 56:
|
|
223
|
-
name = name[:56]
|
|
224
|
-
|
|
225
|
-
return name
|
|
226
|
-
|
|
227
|
-
def _get_unique_name(
|
|
228
|
-
self,
|
|
229
|
-
name: str,
|
|
230
|
-
component_type: Literal["tool", "resource", "resource_template", "prompt"],
|
|
231
|
-
) -> str:
|
|
232
|
-
"""
|
|
233
|
-
Ensure the name is unique within its component type by appending numbers if needed.
|
|
234
|
-
|
|
235
|
-
Args:
|
|
236
|
-
name: The proposed name
|
|
237
|
-
component_type: The type of component ("tools", "resources", or "templates")
|
|
238
|
-
|
|
239
|
-
Returns:
|
|
240
|
-
str: A unique name for the component
|
|
241
|
-
"""
|
|
242
|
-
# Check if the name is already used
|
|
243
|
-
self._used_names[component_type][name] += 1
|
|
244
|
-
if self._used_names[component_type][name] == 1:
|
|
245
|
-
return name
|
|
246
|
-
|
|
247
|
-
else:
|
|
248
|
-
# Create the new name
|
|
249
|
-
new_name = f"{name}_{self._used_names[component_type][name]}"
|
|
250
|
-
logger.debug(
|
|
251
|
-
f"Name collision detected: '{name}' already exists as a {component_type}. "
|
|
252
|
-
f"Using '{new_name}' instead."
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
return new_name
|
|
256
|
-
|
|
257
|
-
def _create_openapi_tool(
|
|
258
|
-
self,
|
|
259
|
-
route: HTTPRoute,
|
|
260
|
-
name: str,
|
|
261
|
-
tags: set[str],
|
|
262
|
-
):
|
|
263
|
-
"""Creates and registers an OpenAPITool with enhanced description."""
|
|
264
|
-
# Use pre-calculated schema from route
|
|
265
|
-
combined_schema = route.flat_param_schema
|
|
266
|
-
|
|
267
|
-
# Extract output schema from OpenAPI responses
|
|
268
|
-
output_schema = extract_output_schema_from_responses(
|
|
269
|
-
route.responses,
|
|
270
|
-
route.response_schemas,
|
|
271
|
-
route.openapi_version,
|
|
105
|
+
# Create provider with the client
|
|
106
|
+
provider = OpenAPIProvider(
|
|
107
|
+
openapi_spec=openapi_spec,
|
|
108
|
+
client=client,
|
|
109
|
+
route_maps=route_maps,
|
|
110
|
+
route_map_fn=route_map_fn,
|
|
111
|
+
mcp_component_fn=mcp_component_fn,
|
|
112
|
+
mcp_names=mcp_names,
|
|
113
|
+
tags=tags,
|
|
114
|
+
timeout=timeout,
|
|
272
115
|
)
|
|
273
116
|
|
|
274
|
-
|
|
275
|
-
tool_name = self._get_unique_name(name, "tool")
|
|
276
|
-
|
|
277
|
-
base_description = (
|
|
278
|
-
route.description
|
|
279
|
-
or route.summary
|
|
280
|
-
or f"Executes {route.method} {route.path}"
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
# Use simplified description formatter for tools
|
|
284
|
-
enhanced_description = format_simple_description(
|
|
285
|
-
base_description=base_description,
|
|
286
|
-
parameters=route.parameters,
|
|
287
|
-
request_body=route.request_body,
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
tool = OpenAPITool(
|
|
291
|
-
client=self._client,
|
|
292
|
-
route=route,
|
|
293
|
-
director=self._director,
|
|
294
|
-
name=tool_name,
|
|
295
|
-
description=enhanced_description,
|
|
296
|
-
parameters=combined_schema,
|
|
297
|
-
output_schema=output_schema,
|
|
298
|
-
tags=set(route.tags or []) | tags,
|
|
299
|
-
timeout=self._timeout,
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
# Call component_fn if provided
|
|
303
|
-
if self._mcp_component_fn is not None:
|
|
304
|
-
try:
|
|
305
|
-
self._mcp_component_fn(route, tool)
|
|
306
|
-
logger.debug(f"Tool {tool_name} customized by component_fn")
|
|
307
|
-
except Exception as e:
|
|
308
|
-
logger.warning(
|
|
309
|
-
f"Error in component_fn for tool {tool_name}: {e}. "
|
|
310
|
-
f"Using component as-is."
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
# Use the potentially modified tool name as the registration key
|
|
314
|
-
final_tool_name = tool.name
|
|
315
|
-
|
|
316
|
-
# Register the tool by directly assigning to the tools dictionary
|
|
317
|
-
self._tool_manager._tools[final_tool_name] = tool
|
|
318
|
-
|
|
319
|
-
def _create_openapi_resource(
|
|
320
|
-
self,
|
|
321
|
-
route: HTTPRoute,
|
|
322
|
-
name: str,
|
|
323
|
-
tags: set[str],
|
|
324
|
-
):
|
|
325
|
-
"""Creates and registers an OpenAPIResource with enhanced description."""
|
|
326
|
-
# Get a unique resource name
|
|
327
|
-
resource_name = self._get_unique_name(name, "resource")
|
|
328
|
-
|
|
329
|
-
resource_uri = f"resource://{resource_name}"
|
|
330
|
-
base_description = (
|
|
331
|
-
route.description or route.summary or f"Represents {route.path}"
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
# Use simplified description for resources
|
|
335
|
-
enhanced_description = format_simple_description(
|
|
336
|
-
base_description=base_description,
|
|
337
|
-
parameters=route.parameters,
|
|
338
|
-
request_body=route.request_body,
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
resource = OpenAPIResource(
|
|
342
|
-
client=self._client,
|
|
343
|
-
route=route,
|
|
344
|
-
director=self._director,
|
|
345
|
-
uri=resource_uri,
|
|
346
|
-
name=resource_name,
|
|
347
|
-
description=enhanced_description,
|
|
348
|
-
tags=set(route.tags or []) | tags,
|
|
349
|
-
timeout=self._timeout,
|
|
350
|
-
)
|
|
351
|
-
|
|
352
|
-
# Call component_fn if provided
|
|
353
|
-
if self._mcp_component_fn is not None:
|
|
354
|
-
try:
|
|
355
|
-
self._mcp_component_fn(route, resource)
|
|
356
|
-
logger.debug(f"Resource {resource_uri} customized by component_fn")
|
|
357
|
-
except Exception as e:
|
|
358
|
-
logger.warning(
|
|
359
|
-
f"Error in component_fn for resource {resource_uri}: {e}. "
|
|
360
|
-
f"Using component as-is."
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
# Use the potentially modified resource URI as the registration key
|
|
364
|
-
final_resource_uri = str(resource.uri)
|
|
365
|
-
|
|
366
|
-
# Register the resource by directly assigning to the resources dictionary
|
|
367
|
-
self._resource_manager._resources[final_resource_uri] = resource
|
|
368
|
-
|
|
369
|
-
def _create_openapi_template(
|
|
370
|
-
self,
|
|
371
|
-
route: HTTPRoute,
|
|
372
|
-
name: str,
|
|
373
|
-
tags: set[str],
|
|
374
|
-
):
|
|
375
|
-
"""Creates and registers an OpenAPIResourceTemplate with enhanced description."""
|
|
376
|
-
# Get a unique template name
|
|
377
|
-
template_name = self._get_unique_name(name, "resource_template")
|
|
378
|
-
|
|
379
|
-
path_params = [p.name for p in route.parameters if p.location == "path"]
|
|
380
|
-
path_params.sort() # Sort for consistent URIs
|
|
381
|
-
|
|
382
|
-
uri_template_str = f"resource://{template_name}"
|
|
383
|
-
if path_params:
|
|
384
|
-
uri_template_str += "/" + "/".join(f"{{{p}}}" for p in path_params)
|
|
385
|
-
|
|
386
|
-
base_description = (
|
|
387
|
-
route.description or route.summary or f"Template for {route.path}"
|
|
388
|
-
)
|
|
389
|
-
|
|
390
|
-
# Use simplified description for resource templates
|
|
391
|
-
enhanced_description = format_simple_description(
|
|
392
|
-
base_description=base_description,
|
|
393
|
-
parameters=route.parameters,
|
|
394
|
-
request_body=route.request_body,
|
|
395
|
-
)
|
|
396
|
-
|
|
397
|
-
template_params_schema = {
|
|
398
|
-
"type": "object",
|
|
399
|
-
"properties": {
|
|
400
|
-
p.name: {
|
|
401
|
-
**(p.schema_.copy() if isinstance(p.schema_, dict) else {}),
|
|
402
|
-
**(
|
|
403
|
-
{"description": p.description}
|
|
404
|
-
if p.description
|
|
405
|
-
and not (
|
|
406
|
-
isinstance(p.schema_, dict) and "description" in p.schema_
|
|
407
|
-
)
|
|
408
|
-
else {}
|
|
409
|
-
),
|
|
410
|
-
}
|
|
411
|
-
for p in route.parameters
|
|
412
|
-
if p.location == "path"
|
|
413
|
-
},
|
|
414
|
-
"required": [
|
|
415
|
-
p.name for p in route.parameters if p.location == "path" and p.required
|
|
416
|
-
],
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
template = OpenAPIResourceTemplate(
|
|
420
|
-
client=self._client,
|
|
421
|
-
route=route,
|
|
422
|
-
director=self._director,
|
|
423
|
-
uri_template=uri_template_str,
|
|
424
|
-
name=template_name,
|
|
425
|
-
description=enhanced_description,
|
|
426
|
-
parameters=template_params_schema,
|
|
427
|
-
tags=set(route.tags or []) | tags,
|
|
428
|
-
timeout=self._timeout,
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
# Call component_fn if provided
|
|
432
|
-
if self._mcp_component_fn is not None:
|
|
433
|
-
try:
|
|
434
|
-
self._mcp_component_fn(route, template)
|
|
435
|
-
logger.debug(f"Template {uri_template_str} customized by component_fn")
|
|
436
|
-
except Exception as e:
|
|
437
|
-
logger.warning(
|
|
438
|
-
f"Error in component_fn for template {uri_template_str}: {e}. "
|
|
439
|
-
f"Using component as-is."
|
|
440
|
-
)
|
|
441
|
-
|
|
442
|
-
# Use the potentially modified template URI as the registration key
|
|
443
|
-
final_template_uri = template.uri_template
|
|
117
|
+
self.add_provider(provider)
|
|
444
118
|
|
|
445
|
-
#
|
|
446
|
-
self.
|
|
119
|
+
# Expose internal attributes for backwards compatibility
|
|
120
|
+
self._spec = provider._spec
|
|
121
|
+
self._director = provider._director
|
|
447
122
|
|
|
448
123
|
|
|
449
124
|
# Export public symbols
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Providers for dynamic MCP components.
|
|
2
|
+
|
|
3
|
+
This module provides the `Provider` abstraction for providing tools,
|
|
4
|
+
resources, and prompts dynamically at runtime.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
```python
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
from fastmcp.server.providers import Provider
|
|
10
|
+
from fastmcp.tools import Tool
|
|
11
|
+
|
|
12
|
+
class DatabaseProvider(Provider):
|
|
13
|
+
def __init__(self, db_url: str):
|
|
14
|
+
self.db = Database(db_url)
|
|
15
|
+
|
|
16
|
+
async def _list_tools(self) -> list[Tool]:
|
|
17
|
+
rows = await self.db.fetch("SELECT * FROM tools")
|
|
18
|
+
return [self._make_tool(row) for row in rows]
|
|
19
|
+
|
|
20
|
+
async def _get_tool(self, name: str) -> Tool | None:
|
|
21
|
+
row = await self.db.fetchone("SELECT * FROM tools WHERE name = ?", name)
|
|
22
|
+
return self._make_tool(row) if row else None
|
|
23
|
+
|
|
24
|
+
mcp = FastMCP("Server", providers=[DatabaseProvider(db_url)])
|
|
25
|
+
```
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from typing import TYPE_CHECKING
|
|
29
|
+
|
|
30
|
+
from fastmcp.server.providers.aggregate import AggregateProvider
|
|
31
|
+
from fastmcp.server.providers.base import Provider
|
|
32
|
+
from fastmcp.server.providers.fastmcp_provider import FastMCPProvider
|
|
33
|
+
from fastmcp.server.providers.filesystem import FileSystemProvider
|
|
34
|
+
from fastmcp.server.providers.local_provider import LocalProvider
|
|
35
|
+
from fastmcp.server.providers.skills import (
|
|
36
|
+
ClaudeSkillsProvider,
|
|
37
|
+
SkillProvider,
|
|
38
|
+
SkillsDirectoryProvider,
|
|
39
|
+
SkillsProvider,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if TYPE_CHECKING:
|
|
43
|
+
from fastmcp.server.providers.openapi import OpenAPIProvider as OpenAPIProvider
|
|
44
|
+
from fastmcp.server.providers.proxy import ProxyProvider as ProxyProvider
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"AggregateProvider",
|
|
48
|
+
"ClaudeSkillsProvider",
|
|
49
|
+
"FastMCPProvider",
|
|
50
|
+
"FileSystemProvider",
|
|
51
|
+
"LocalProvider",
|
|
52
|
+
"OpenAPIProvider",
|
|
53
|
+
"Provider",
|
|
54
|
+
"ProxyProvider",
|
|
55
|
+
"SkillProvider",
|
|
56
|
+
"SkillsDirectoryProvider",
|
|
57
|
+
"SkillsProvider", # Backwards compatibility alias for SkillsDirectoryProvider
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def __getattr__(name: str):
|
|
62
|
+
"""Lazy import for providers to avoid circular imports."""
|
|
63
|
+
if name == "ProxyProvider":
|
|
64
|
+
from fastmcp.server.providers.proxy import ProxyProvider
|
|
65
|
+
|
|
66
|
+
return ProxyProvider
|
|
67
|
+
if name == "OpenAPIProvider":
|
|
68
|
+
from fastmcp.server.providers.openapi import OpenAPIProvider
|
|
69
|
+
|
|
70
|
+
return OpenAPIProvider
|
|
71
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|