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,186 +1,121 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
3
|
-
Provides endpoints for enabling/disabling components via HTTP, with optional authentication scopes.
|
|
4
|
-
"""
|
|
2
|
+
HTTP routes for enabling/disabling components in FastMCP.
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
Provides REST endpoints for controlling component enabled state with optional
|
|
5
|
+
authentication scopes.
|
|
6
|
+
"""
|
|
7
7
|
|
|
8
8
|
from mcp.server.auth.middleware.bearer_auth import RequireAuthMiddleware
|
|
9
9
|
from starlette.applications import Starlette
|
|
10
|
-
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
11
10
|
from starlette.requests import Request
|
|
12
11
|
from starlette.responses import JSONResponse
|
|
13
12
|
from starlette.routing import Mount, Route
|
|
14
13
|
|
|
15
|
-
from fastmcp.contrib.component_manager.component_service import ComponentService
|
|
16
|
-
from fastmcp.exceptions import NotFoundError
|
|
17
14
|
from fastmcp.server.server import FastMCP
|
|
18
15
|
|
|
19
16
|
|
|
20
17
|
def set_up_component_manager(
|
|
21
18
|
server: FastMCP, path: str = "/", required_scopes: list[str] | None = None
|
|
22
|
-
):
|
|
23
|
-
"""Set up routes for enabling/disabling tools, resources, and prompts.
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Set up HTTP routes for enabling/disabling tools, resources, and prompts.
|
|
21
|
+
|
|
24
22
|
Args:
|
|
25
|
-
server: The FastMCP server instance
|
|
26
|
-
path:
|
|
27
|
-
required_scopes: Optional list of scopes required for these routes.
|
|
23
|
+
server: The FastMCP server instance.
|
|
24
|
+
path: Base path for component management routes.
|
|
25
|
+
required_scopes: Optional list of scopes required for these routes.
|
|
26
|
+
Applies only if authentication is enabled.
|
|
27
|
+
|
|
28
|
+
Routes created:
|
|
29
|
+
POST /tools/{name}/enable[?version=v1]
|
|
30
|
+
POST /tools/{name}/disable[?version=v1]
|
|
31
|
+
POST /resources/{uri}/enable[?version=v1]
|
|
32
|
+
POST /resources/{uri}/disable[?version=v1]
|
|
33
|
+
POST /prompts/{name}/enable[?version=v1]
|
|
34
|
+
POST /prompts/{name}/disable[?version=v1]
|
|
28
35
|
"""
|
|
29
|
-
|
|
30
|
-
service = ComponentService(server)
|
|
31
|
-
routes: list[Route] = []
|
|
32
|
-
mounts: list[Mount] = []
|
|
33
|
-
route_configs = {
|
|
34
|
-
"tool": {
|
|
35
|
-
"param": "tool_name",
|
|
36
|
-
"enable": service._enable_tool,
|
|
37
|
-
"disable": service._disable_tool,
|
|
38
|
-
},
|
|
39
|
-
"resource": {
|
|
40
|
-
"param": "uri:path",
|
|
41
|
-
"enable": service._enable_resource,
|
|
42
|
-
"disable": service._disable_resource,
|
|
43
|
-
},
|
|
44
|
-
"prompt": {
|
|
45
|
-
"param": "prompt_name",
|
|
46
|
-
"enable": service._enable_prompt,
|
|
47
|
-
"disable": service._disable_prompt,
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
|
|
51
36
|
if required_scopes is None:
|
|
52
|
-
|
|
37
|
+
# No auth - include path prefix in routes
|
|
38
|
+
routes = _build_routes(server, path)
|
|
39
|
+
server._additional_http_routes.extend(routes)
|
|
53
40
|
else:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
41
|
+
# With auth - Mount handles path prefix, routes shouldn't have it
|
|
42
|
+
routes = _build_routes(server, "/")
|
|
43
|
+
mount = Mount(
|
|
44
|
+
path if path != "/" else "",
|
|
45
|
+
app=RequireAuthMiddleware(Starlette(routes=routes), required_scopes),
|
|
46
|
+
)
|
|
47
|
+
server._additional_http_routes.append(mount)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _build_routes(server: FastMCP, base_path: str) -> list[Route]:
|
|
51
|
+
"""Build all component management routes."""
|
|
52
|
+
prefix = base_path.rstrip("/") if base_path != "/" else ""
|
|
53
|
+
|
|
54
|
+
return [
|
|
55
|
+
# Tools
|
|
56
|
+
Route(
|
|
57
|
+
f"{prefix}/tools/{{name}}/enable",
|
|
58
|
+
endpoint=_make_endpoint(server, "tool", "enable"),
|
|
59
|
+
methods=["POST"],
|
|
60
|
+
),
|
|
61
|
+
Route(
|
|
62
|
+
f"{prefix}/tools/{{name}}/disable",
|
|
63
|
+
endpoint=_make_endpoint(server, "tool", "disable"),
|
|
64
|
+
methods=["POST"],
|
|
65
|
+
),
|
|
66
|
+
# Resources
|
|
67
|
+
Route(
|
|
68
|
+
f"{prefix}/resources/{{uri:path}}/enable",
|
|
69
|
+
endpoint=_make_endpoint(server, "resource", "enable"),
|
|
70
|
+
methods=["POST"],
|
|
71
|
+
),
|
|
72
|
+
Route(
|
|
73
|
+
f"{prefix}/resources/{{uri:path}}/disable",
|
|
74
|
+
endpoint=_make_endpoint(server, "resource", "disable"),
|
|
75
|
+
methods=["POST"],
|
|
76
|
+
),
|
|
77
|
+
# Prompts
|
|
78
|
+
Route(
|
|
79
|
+
f"{prefix}/prompts/{{name}}/enable",
|
|
80
|
+
endpoint=_make_endpoint(server, "prompt", "enable"),
|
|
81
|
+
methods=["POST"],
|
|
82
|
+
),
|
|
83
|
+
Route(
|
|
84
|
+
f"{prefix}/prompts/{{name}}/disable",
|
|
85
|
+
endpoint=_make_endpoint(server, "prompt", "disable"),
|
|
86
|
+
methods=["POST"],
|
|
87
|
+
),
|
|
88
|
+
]
|
|
94
89
|
|
|
95
|
-
try:
|
|
96
|
-
await config[action](name)
|
|
97
|
-
return JSONResponse(
|
|
98
|
-
{"message": f"{action.capitalize()}d {component}: {name}"}
|
|
99
|
-
)
|
|
100
|
-
except NotFoundError as e:
|
|
101
|
-
raise StarletteHTTPException(
|
|
102
|
-
status_code=404,
|
|
103
|
-
detail=f"Unknown {component}: {name}",
|
|
104
|
-
) from e
|
|
105
90
|
|
|
106
|
-
|
|
91
|
+
def _make_endpoint(server: FastMCP, component_type: str, action: str):
|
|
92
|
+
"""Create an endpoint function for enabling/disabling a component type."""
|
|
107
93
|
|
|
94
|
+
async def endpoint(request: Request) -> JSONResponse:
|
|
95
|
+
# Get name from path params (tools/prompts use 'name', resources use 'uri')
|
|
96
|
+
name = request.path_params.get("name") or request.path_params.get("uri")
|
|
97
|
+
version = request.query_params.get("version")
|
|
108
98
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
required_scopes: Optional list of required auth scopes
|
|
117
|
-
root_path: The base path for the route
|
|
118
|
-
Returns:
|
|
119
|
-
A Starlette Route object.
|
|
120
|
-
"""
|
|
121
|
-
endpoint = make_endpoint(action, component, config)
|
|
122
|
-
|
|
123
|
-
if required_scopes is not None and root_path in [
|
|
124
|
-
"/tools",
|
|
125
|
-
"/resources",
|
|
126
|
-
"/prompts",
|
|
127
|
-
]:
|
|
128
|
-
path = f"/{{{config['param']}}}/{action}"
|
|
129
|
-
else:
|
|
130
|
-
if root_path != "/" and required_scopes is None:
|
|
131
|
-
path = f"{root_path}/{component}s/{{{config['param']}}}/{action}"
|
|
99
|
+
# Map component type to components list
|
|
100
|
+
# Note: "resource" in the route can refer to either a resource or template
|
|
101
|
+
# We need to check if it's a template (contains {}) and use "template" if so
|
|
102
|
+
if component_type == "resource" and name is not None and "{" in name:
|
|
103
|
+
components = ["template"]
|
|
104
|
+
elif component_type == "resource":
|
|
105
|
+
components = ["resource"]
|
|
132
106
|
else:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def build_component_manager_endpoints(
|
|
139
|
-
route_configs, root_path, required_scopes=None
|
|
140
|
-
) -> list[Route]:
|
|
141
|
-
"""
|
|
142
|
-
Build a list of Starlette Route objects for all components/actions.
|
|
143
|
-
Args:
|
|
144
|
-
route_configs: Dict describing component types and their handlers
|
|
145
|
-
root_path: The base path for the routes
|
|
146
|
-
required_scopes: Optional list of required auth scopes
|
|
147
|
-
Returns:
|
|
148
|
-
List of Starlette Route objects for component management.
|
|
149
|
-
"""
|
|
150
|
-
component_management_routes: list[Route] = []
|
|
107
|
+
component_map = {
|
|
108
|
+
"tool": ["tool"],
|
|
109
|
+
"prompt": ["prompt"],
|
|
110
|
+
}
|
|
111
|
+
components = component_map[component_type]
|
|
151
112
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
component_management_routes.append(
|
|
156
|
-
make_route(action, component, config, required_scopes, root_path)
|
|
157
|
-
)
|
|
113
|
+
# Call server.enable() or server.disable()
|
|
114
|
+
method = getattr(server, action)
|
|
115
|
+
method(names={name} if name else None, version=version, components=components)
|
|
158
116
|
|
|
159
|
-
|
|
117
|
+
return JSONResponse(
|
|
118
|
+
{"message": f"{action.capitalize()}d {component_type}: {name}"}
|
|
119
|
+
)
|
|
160
120
|
|
|
161
|
-
|
|
162
|
-
def build_component_manager_mount(route_configs, root_path, required_scopes) -> Mount:
|
|
163
|
-
"""
|
|
164
|
-
Build a Starlette Mount with authentication for component management routes.
|
|
165
|
-
Args:
|
|
166
|
-
route_configs: Dict describing component types and their handlers
|
|
167
|
-
root_path: The base path for the mount
|
|
168
|
-
required_scopes: List of required auth scopes
|
|
169
|
-
Returns:
|
|
170
|
-
A Starlette Mount object with authentication middleware.
|
|
171
|
-
"""
|
|
172
|
-
component_management_routes: list[Route] = []
|
|
173
|
-
|
|
174
|
-
for component in route_configs:
|
|
175
|
-
config: dict[str, Any] = route_configs[component]
|
|
176
|
-
for action in ["enable", "disable"]:
|
|
177
|
-
component_management_routes.append(
|
|
178
|
-
make_route(action, component, config, required_scopes, root_path)
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
return Mount(
|
|
182
|
-
f"{root_path}",
|
|
183
|
-
app=RequireAuthMiddleware(
|
|
184
|
-
Starlette(routes=component_management_routes), required_scopes
|
|
185
|
-
),
|
|
186
|
-
)
|
|
121
|
+
return endpoint
|
|
@@ -40,11 +40,11 @@ first_sample.register_all(mcp_server=mcp, prefix="first")
|
|
|
40
40
|
second_sample.register_all(mcp_server=mcp, prefix="second")
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
async def list_components():
|
|
43
|
+
async def list_components() -> None:
|
|
44
44
|
print("MCP Server running with registered components...")
|
|
45
|
-
print("Tools:", list(await mcp.
|
|
46
|
-
print("Resources:", list(await mcp.
|
|
47
|
-
print("Prompts:", list(await mcp.
|
|
45
|
+
print("Tools:", list(await mcp.list_tools()))
|
|
46
|
+
print("Resources:", list(await mcp.list_resources()))
|
|
47
|
+
print("Prompts:", list(await mcp.list_prompts()))
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
if __name__ == "__main__":
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""Provides a base mixin class and decorators for easy registration of class methods with FastMCP."""
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from collections.abc import Callable
|
|
4
5
|
from typing import TYPE_CHECKING, Any
|
|
5
6
|
|
|
6
7
|
from mcp.types import Annotations, ToolAnnotations
|
|
7
8
|
|
|
9
|
+
import fastmcp
|
|
8
10
|
from fastmcp.prompts.prompt import Prompt
|
|
9
11
|
from fastmcp.resources.resource import Resource
|
|
10
12
|
from fastmcp.tools.tool import Tool
|
|
@@ -28,11 +30,19 @@ def mcp_tool(
|
|
|
28
30
|
tags: set[str] | None = None,
|
|
29
31
|
annotations: ToolAnnotations | dict[str, Any] | None = None,
|
|
30
32
|
exclude_args: list[str] | None = None,
|
|
31
|
-
serializer: Callable[[Any], str] | None = None,
|
|
33
|
+
serializer: Callable[[Any], str] | None = None, # Deprecated
|
|
32
34
|
meta: dict[str, Any] | None = None,
|
|
33
35
|
enabled: bool | None = None,
|
|
34
36
|
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
35
37
|
"""Decorator to mark a method as an MCP tool for later registration."""
|
|
38
|
+
if serializer is not None and fastmcp.settings.deprecation_warnings:
|
|
39
|
+
warnings.warn(
|
|
40
|
+
"The `serializer` parameter is deprecated. "
|
|
41
|
+
"Return ToolResult from your tools for full control over serialization. "
|
|
42
|
+
"See https://gofastmcp.com/servers/tools#custom-serialization for migration examples.",
|
|
43
|
+
DeprecationWarning,
|
|
44
|
+
stacklevel=2,
|
|
45
|
+
)
|
|
36
46
|
|
|
37
47
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
38
48
|
call_args = {
|
|
@@ -170,7 +180,6 @@ class MCPMixin:
|
|
|
170
180
|
serializer=registration_info.get("serializer"),
|
|
171
181
|
output_schema=registration_info.get("output_schema"),
|
|
172
182
|
meta=registration_info.get("meta"),
|
|
173
|
-
enabled=registration_info.get("enabled"),
|
|
174
183
|
)
|
|
175
184
|
|
|
176
185
|
mcp_server.add_tool(tool)
|
|
@@ -210,7 +219,6 @@ class MCPMixin:
|
|
|
210
219
|
description=registration_info.get("description"),
|
|
211
220
|
mime_type=registration_info.get("mime_type"),
|
|
212
221
|
tags=registration_info.get("tags"),
|
|
213
|
-
enabled=registration_info.get("enabled"),
|
|
214
222
|
annotations=registration_info.get("annotations"),
|
|
215
223
|
meta=registration_info.get("meta"),
|
|
216
224
|
)
|
|
@@ -245,7 +253,6 @@ class MCPMixin:
|
|
|
245
253
|
title=registration_info.get("title"),
|
|
246
254
|
description=registration_info.get("description"),
|
|
247
255
|
tags=registration_info.get("tags"),
|
|
248
|
-
enabled=registration_info.get("enabled"),
|
|
249
256
|
meta=registration_info.get("meta"),
|
|
250
257
|
)
|
|
251
258
|
mcp_server.add_prompt(prompt)
|
fastmcp/decorators.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Shared decorator utilities for FastMCP."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from fastmcp.prompts.function_prompt import PromptMeta
|
|
10
|
+
from fastmcp.resources.function_resource import ResourceMeta
|
|
11
|
+
from fastmcp.server.tasks.config import TaskConfig
|
|
12
|
+
from fastmcp.tools.function_tool import ToolMeta
|
|
13
|
+
|
|
14
|
+
FastMCPMeta = ToolMeta | ResourceMeta | PromptMeta
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def resolve_task_config(task: bool | TaskConfig | None) -> bool | TaskConfig:
|
|
18
|
+
"""Resolve task config, defaulting None to False."""
|
|
19
|
+
return task if task is not None else False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@runtime_checkable
|
|
23
|
+
class HasFastMCPMeta(Protocol):
|
|
24
|
+
"""Protocol for callables decorated with FastMCP metadata."""
|
|
25
|
+
|
|
26
|
+
__fastmcp__: Any
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_fastmcp_meta(fn: Any) -> Any | None:
|
|
30
|
+
"""Extract FastMCP metadata from a function, handling bound methods and wrappers."""
|
|
31
|
+
if hasattr(fn, "__fastmcp__"):
|
|
32
|
+
return fn.__fastmcp__
|
|
33
|
+
if hasattr(fn, "__func__") and hasattr(fn.__func__, "__fastmcp__"):
|
|
34
|
+
return fn.__func__.__fastmcp__
|
|
35
|
+
try:
|
|
36
|
+
unwrapped = inspect.unwrap(fn)
|
|
37
|
+
if unwrapped is not fn and hasattr(unwrapped, "__fastmcp__"):
|
|
38
|
+
return unwrapped.__fastmcp__
|
|
39
|
+
except ValueError:
|
|
40
|
+
pass
|
|
41
|
+
return None
|
fastmcp/dependencies.py
CHANGED
|
@@ -3,9 +3,18 @@
|
|
|
3
3
|
This module re-exports dependency injection symbols from Docket and FastMCP
|
|
4
4
|
to provide a clean, centralized import location for all dependency-related
|
|
5
5
|
functionality.
|
|
6
|
+
|
|
7
|
+
DI features (Depends, CurrentContext, CurrentFastMCP) work without pydocket
|
|
8
|
+
using a vendored DI engine. Only task-related dependencies (CurrentDocket,
|
|
9
|
+
CurrentWorker) and background task execution require fastmcp[tasks].
|
|
6
10
|
"""
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
# Try docket first for isinstance compatibility, fall back to vendored
|
|
13
|
+
try:
|
|
14
|
+
from docket import Depends
|
|
15
|
+
except ImportError:
|
|
16
|
+
from fastmcp._vendor.docket_di import Depends
|
|
17
|
+
|
|
9
18
|
|
|
10
19
|
from fastmcp.server.dependencies import (
|
|
11
20
|
CurrentContext,
|
|
@@ -13,6 +22,7 @@ from fastmcp.server.dependencies import (
|
|
|
13
22
|
CurrentFastMCP,
|
|
14
23
|
CurrentWorker,
|
|
15
24
|
Progress,
|
|
25
|
+
ProgressLike,
|
|
16
26
|
)
|
|
17
27
|
|
|
18
28
|
__all__ = [
|
|
@@ -22,4 +32,5 @@ __all__ = [
|
|
|
22
32
|
"CurrentWorker",
|
|
23
33
|
"Depends",
|
|
24
34
|
"Progress",
|
|
35
|
+
"ProgressLike",
|
|
25
36
|
]
|
fastmcp/exceptions.py
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
"""Deprecated: Import from fastmcp.server.openapi instead."""
|
|
1
|
+
"""Deprecated: Import from fastmcp.server.providers.openapi instead."""
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
4
|
|
|
5
|
-
from fastmcp.server.openapi import (
|
|
6
|
-
ComponentFn,
|
|
7
|
-
DEFAULT_ROUTE_MAPPINGS,
|
|
8
|
-
FastMCPOpenAPI,
|
|
9
|
-
MCPType,
|
|
10
|
-
OpenAPIResource,
|
|
11
|
-
OpenAPIResourceTemplate,
|
|
12
|
-
OpenAPITool,
|
|
13
|
-
RouteMap,
|
|
14
|
-
RouteMapFn,
|
|
15
|
-
_determine_route_type,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
5
|
# Deprecated in 2.14 when OpenAPI support was promoted out of experimental
|
|
19
6
|
warnings.warn(
|
|
20
7
|
"Importing from fastmcp.experimental.server.openapi is deprecated. "
|
|
21
|
-
"Import from fastmcp.server.openapi instead.",
|
|
8
|
+
"Import from fastmcp.server.providers.openapi instead.",
|
|
22
9
|
DeprecationWarning,
|
|
23
10
|
stacklevel=2,
|
|
24
11
|
)
|
|
25
12
|
|
|
13
|
+
# Import from canonical location
|
|
14
|
+
from fastmcp.server.openapi.server import FastMCPOpenAPI as FastMCPOpenAPI # noqa: E402
|
|
15
|
+
from fastmcp.server.providers.openapi import ( # noqa: E402
|
|
16
|
+
ComponentFn as ComponentFn,
|
|
17
|
+
MCPType as MCPType,
|
|
18
|
+
OpenAPIResource as OpenAPIResource,
|
|
19
|
+
OpenAPIResourceTemplate as OpenAPIResourceTemplate,
|
|
20
|
+
OpenAPITool as OpenAPITool,
|
|
21
|
+
RouteMap as RouteMap,
|
|
22
|
+
RouteMapFn as RouteMapFn,
|
|
23
|
+
)
|
|
24
|
+
from fastmcp.server.providers.openapi.routing import ( # noqa: E402
|
|
25
|
+
DEFAULT_ROUTE_MAPPINGS as DEFAULT_ROUTE_MAPPINGS,
|
|
26
|
+
_determine_route_type as _determine_route_type,
|
|
27
|
+
)
|
|
28
|
+
|
|
26
29
|
__all__ = [
|
|
27
30
|
"DEFAULT_ROUTE_MAPPINGS",
|
|
28
31
|
"ComponentFn",
|
fastmcp/mcp_config.py
CHANGED
|
@@ -95,25 +95,30 @@ class _TransformingMCPServerMixin(FastMCPBaseModel):
|
|
|
95
95
|
client_name: str | None = None,
|
|
96
96
|
) -> tuple[FastMCP[Any], ClientTransport]:
|
|
97
97
|
"""Turn the Transforming MCPServer into a FastMCP Server and also return the underlying transport."""
|
|
98
|
-
from fastmcp import FastMCP
|
|
99
98
|
from fastmcp.client import Client
|
|
100
99
|
from fastmcp.client.transports import (
|
|
101
100
|
ClientTransport, # pyright: ignore[reportUnusedImport]
|
|
102
101
|
)
|
|
102
|
+
from fastmcp.server import create_proxy
|
|
103
103
|
|
|
104
104
|
transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType] # ty: ignore[unresolved-attribute]
|
|
105
105
|
transport = cast(ClientTransport, transport)
|
|
106
106
|
|
|
107
107
|
client: Client[ClientTransport] = Client(transport=transport, name=client_name)
|
|
108
108
|
|
|
109
|
-
wrapped_mcp_server =
|
|
109
|
+
wrapped_mcp_server = create_proxy(
|
|
110
|
+
client,
|
|
110
111
|
name=server_name,
|
|
111
|
-
backend=client,
|
|
112
|
-
tool_transformations=self.tools,
|
|
113
112
|
include_tags=self.include_tags,
|
|
114
113
|
exclude_tags=self.exclude_tags,
|
|
115
114
|
)
|
|
116
115
|
|
|
116
|
+
# Apply tool transforms if configured
|
|
117
|
+
if self.tools:
|
|
118
|
+
from fastmcp.server.transforms import ToolTransform
|
|
119
|
+
|
|
120
|
+
wrapped_mcp_server.add_transform(ToolTransform(self.tools))
|
|
121
|
+
|
|
117
122
|
return wrapped_mcp_server, transport
|
|
118
123
|
|
|
119
124
|
def to_transport(self) -> ClientTransport:
|
|
@@ -143,6 +148,9 @@ class StdioMCPServer(BaseModel):
|
|
|
143
148
|
# Execution context
|
|
144
149
|
cwd: str | None = None # Working directory for command execution
|
|
145
150
|
timeout: int | None = None # Maximum response time in milliseconds
|
|
151
|
+
keep_alive: bool | None = (
|
|
152
|
+
None # Whether to keep the subprocess alive between connections
|
|
153
|
+
)
|
|
146
154
|
|
|
147
155
|
# Metadata
|
|
148
156
|
description: str | None = None # Human-readable server description
|
|
@@ -161,6 +169,7 @@ class StdioMCPServer(BaseModel):
|
|
|
161
169
|
args=self.args,
|
|
162
170
|
env=self.env,
|
|
163
171
|
cwd=self.cwd,
|
|
172
|
+
keep_alive=self.keep_alive,
|
|
164
173
|
)
|
|
165
174
|
|
|
166
175
|
|
fastmcp/prompts/__init__.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .
|
|
1
|
+
from .function_prompt import FunctionPrompt, prompt
|
|
2
|
+
from .prompt import Message, Prompt, PromptArgument, PromptMessage, PromptResult
|
|
3
3
|
|
|
4
4
|
__all__ = [
|
|
5
|
+
"FunctionPrompt",
|
|
5
6
|
"Message",
|
|
6
7
|
"Prompt",
|
|
7
|
-
"
|
|
8
|
+
"PromptArgument",
|
|
8
9
|
"PromptMessage",
|
|
10
|
+
"PromptResult",
|
|
11
|
+
"prompt",
|
|
9
12
|
]
|