fastmcp 2.12.1__py3-none-any.whl → 2.13.2__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/__init__.py +2 -2
- fastmcp/cli/cli.py +56 -36
- fastmcp/cli/install/__init__.py +2 -0
- fastmcp/cli/install/claude_code.py +7 -16
- fastmcp/cli/install/claude_desktop.py +4 -12
- fastmcp/cli/install/cursor.py +20 -30
- fastmcp/cli/install/gemini_cli.py +241 -0
- fastmcp/cli/install/mcp_json.py +4 -12
- fastmcp/cli/run.py +15 -94
- fastmcp/client/__init__.py +9 -9
- fastmcp/client/auth/oauth.py +117 -206
- fastmcp/client/client.py +123 -47
- fastmcp/client/elicitation.py +6 -1
- fastmcp/client/logging.py +18 -14
- fastmcp/client/oauth_callback.py +85 -171
- fastmcp/client/sampling.py +1 -1
- fastmcp/client/transports.py +81 -26
- fastmcp/contrib/component_manager/__init__.py +1 -1
- fastmcp/contrib/component_manager/component_manager.py +2 -2
- fastmcp/contrib/component_manager/component_service.py +7 -7
- fastmcp/contrib/mcp_mixin/README.md +35 -4
- fastmcp/contrib/mcp_mixin/__init__.py +2 -2
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +54 -7
- fastmcp/experimental/sampling/handlers/openai.py +2 -2
- fastmcp/experimental/server/openapi/__init__.py +5 -8
- fastmcp/experimental/server/openapi/components.py +11 -7
- fastmcp/experimental/server/openapi/routing.py +2 -2
- fastmcp/experimental/utilities/openapi/__init__.py +10 -15
- fastmcp/experimental/utilities/openapi/director.py +16 -10
- fastmcp/experimental/utilities/openapi/json_schema_converter.py +6 -2
- fastmcp/experimental/utilities/openapi/models.py +3 -3
- fastmcp/experimental/utilities/openapi/parser.py +37 -16
- fastmcp/experimental/utilities/openapi/schemas.py +33 -7
- fastmcp/mcp_config.py +3 -4
- fastmcp/prompts/__init__.py +1 -1
- fastmcp/prompts/prompt.py +32 -27
- fastmcp/prompts/prompt_manager.py +16 -101
- fastmcp/resources/__init__.py +5 -5
- fastmcp/resources/resource.py +28 -20
- fastmcp/resources/resource_manager.py +9 -168
- fastmcp/resources/template.py +119 -27
- fastmcp/resources/types.py +30 -24
- fastmcp/server/__init__.py +1 -1
- fastmcp/server/auth/__init__.py +9 -5
- fastmcp/server/auth/auth.py +80 -47
- fastmcp/server/auth/handlers/authorize.py +326 -0
- fastmcp/server/auth/jwt_issuer.py +236 -0
- fastmcp/server/auth/middleware.py +96 -0
- fastmcp/server/auth/oauth_proxy.py +1556 -265
- fastmcp/server/auth/oidc_proxy.py +412 -0
- fastmcp/server/auth/providers/auth0.py +193 -0
- fastmcp/server/auth/providers/aws.py +263 -0
- fastmcp/server/auth/providers/azure.py +314 -129
- fastmcp/server/auth/providers/bearer.py +1 -1
- fastmcp/server/auth/providers/debug.py +114 -0
- fastmcp/server/auth/providers/descope.py +229 -0
- fastmcp/server/auth/providers/discord.py +308 -0
- fastmcp/server/auth/providers/github.py +31 -6
- fastmcp/server/auth/providers/google.py +50 -7
- fastmcp/server/auth/providers/in_memory.py +27 -3
- fastmcp/server/auth/providers/introspection.py +281 -0
- fastmcp/server/auth/providers/jwt.py +48 -31
- fastmcp/server/auth/providers/oci.py +233 -0
- fastmcp/server/auth/providers/scalekit.py +238 -0
- fastmcp/server/auth/providers/supabase.py +188 -0
- fastmcp/server/auth/providers/workos.py +37 -15
- fastmcp/server/context.py +194 -67
- fastmcp/server/dependencies.py +56 -16
- fastmcp/server/elicitation.py +1 -1
- fastmcp/server/http.py +57 -18
- fastmcp/server/low_level.py +121 -2
- fastmcp/server/middleware/__init__.py +1 -1
- fastmcp/server/middleware/caching.py +476 -0
- fastmcp/server/middleware/error_handling.py +14 -10
- fastmcp/server/middleware/logging.py +158 -116
- fastmcp/server/middleware/middleware.py +30 -16
- fastmcp/server/middleware/rate_limiting.py +3 -3
- fastmcp/server/middleware/tool_injection.py +116 -0
- fastmcp/server/openapi.py +15 -7
- fastmcp/server/proxy.py +22 -11
- fastmcp/server/server.py +744 -254
- fastmcp/settings.py +65 -15
- fastmcp/tools/__init__.py +1 -1
- fastmcp/tools/tool.py +173 -108
- fastmcp/tools/tool_manager.py +30 -112
- fastmcp/tools/tool_transform.py +13 -11
- fastmcp/utilities/cli.py +67 -28
- fastmcp/utilities/components.py +7 -2
- fastmcp/utilities/inspect.py +79 -23
- fastmcp/utilities/json_schema.py +21 -4
- fastmcp/utilities/json_schema_type.py +4 -4
- fastmcp/utilities/logging.py +182 -10
- fastmcp/utilities/mcp_server_config/__init__.py +3 -3
- fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
- fastmcp/utilities/mcp_server_config/v1/environments/uv.py +10 -45
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +8 -7
- fastmcp/utilities/mcp_server_config/v1/schema.json +5 -1
- fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
- fastmcp/utilities/openapi.py +11 -11
- fastmcp/utilities/tests.py +93 -10
- fastmcp/utilities/types.py +87 -21
- fastmcp/utilities/ui.py +626 -0
- {fastmcp-2.12.1.dist-info → fastmcp-2.13.2.dist-info}/METADATA +141 -60
- fastmcp-2.13.2.dist-info/RECORD +144 -0
- {fastmcp-2.12.1.dist-info → fastmcp-2.13.2.dist-info}/WHEEL +1 -1
- fastmcp/cli/claude.py +0 -144
- fastmcp-2.12.1.dist-info/RECORD +0 -128
- {fastmcp-2.12.1.dist-info → fastmcp-2.13.2.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.12.1.dist-info → fastmcp-2.13.2.dist-info}/licenses/LICENSE +0 -0
fastmcp/server/http.py
CHANGED
|
@@ -5,10 +5,12 @@ from contextlib import asynccontextmanager, contextmanager
|
|
|
5
5
|
from contextvars import ContextVar
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from mcp.server.auth.
|
|
8
|
+
from mcp.server.auth.routes import build_resource_metadata_url
|
|
9
9
|
from mcp.server.lowlevel.server import LifespanResultT
|
|
10
10
|
from mcp.server.sse import SseServerTransport
|
|
11
|
-
from mcp.server.streamable_http import
|
|
11
|
+
from mcp.server.streamable_http import (
|
|
12
|
+
EventStore,
|
|
13
|
+
)
|
|
12
14
|
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
|
13
15
|
from starlette.applications import Starlette
|
|
14
16
|
from starlette.middleware import Middleware
|
|
@@ -18,6 +20,7 @@ from starlette.routing import BaseRoute, Mount, Route
|
|
|
18
20
|
from starlette.types import Lifespan, Receive, Scope, Send
|
|
19
21
|
|
|
20
22
|
from fastmcp.server.auth import AuthProvider
|
|
23
|
+
from fastmcp.server.auth.middleware import RequireAuthMiddleware
|
|
21
24
|
from fastmcp.utilities.logging import get_logger
|
|
22
25
|
|
|
23
26
|
if TYPE_CHECKING:
|
|
@@ -113,7 +116,7 @@ def create_base_app(
|
|
|
113
116
|
A Starlette application
|
|
114
117
|
"""
|
|
115
118
|
# Always add RequestContextMiddleware as the outermost middleware
|
|
116
|
-
middleware.
|
|
119
|
+
middleware.insert(0, Middleware(RequestContextMiddleware))
|
|
117
120
|
|
|
118
121
|
return StarletteWithLifespan(
|
|
119
122
|
routes=routes,
|
|
@@ -167,23 +170,38 @@ def create_sse_app(
|
|
|
167
170
|
# Get auth middleware from the provider
|
|
168
171
|
auth_middleware = auth.get_middleware()
|
|
169
172
|
|
|
170
|
-
# Get auth routes
|
|
171
|
-
auth_routes = auth.get_routes(
|
|
172
|
-
mcp_path=sse_path,
|
|
173
|
-
mcp_endpoint=handle_sse,
|
|
174
|
-
)
|
|
175
|
-
|
|
173
|
+
# Get auth provider's own routes (OAuth endpoints, metadata, etc)
|
|
174
|
+
auth_routes = auth.get_routes(mcp_path=sse_path)
|
|
176
175
|
server_routes.extend(auth_routes)
|
|
177
176
|
server_middleware.extend(auth_middleware)
|
|
178
177
|
|
|
179
|
-
#
|
|
178
|
+
# Build RFC 9728-compliant metadata URL
|
|
179
|
+
resource_url = auth._get_resource_url(sse_path)
|
|
180
|
+
resource_metadata_url = (
|
|
181
|
+
build_resource_metadata_url(resource_url) if resource_url else None
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Create protected SSE endpoint route
|
|
185
|
+
server_routes.append(
|
|
186
|
+
Route(
|
|
187
|
+
sse_path,
|
|
188
|
+
endpoint=RequireAuthMiddleware(
|
|
189
|
+
handle_sse,
|
|
190
|
+
auth.required_scopes,
|
|
191
|
+
resource_metadata_url,
|
|
192
|
+
),
|
|
193
|
+
methods=["GET"],
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Wrap the SSE message endpoint with RequireAuthMiddleware
|
|
180
198
|
server_routes.append(
|
|
181
199
|
Mount(
|
|
182
200
|
message_path,
|
|
183
201
|
app=RequireAuthMiddleware(
|
|
184
202
|
sse.handle_post_message,
|
|
185
203
|
auth.required_scopes,
|
|
186
|
-
|
|
204
|
+
resource_metadata_url,
|
|
187
205
|
),
|
|
188
206
|
)
|
|
189
207
|
)
|
|
@@ -215,11 +233,17 @@ def create_sse_app(
|
|
|
215
233
|
if middleware:
|
|
216
234
|
server_middleware.extend(middleware)
|
|
217
235
|
|
|
236
|
+
@asynccontextmanager
|
|
237
|
+
async def lifespan(app: Starlette) -> AsyncGenerator[None, None]:
|
|
238
|
+
async with server._lifespan_manager():
|
|
239
|
+
yield
|
|
240
|
+
|
|
218
241
|
# Create and return the app
|
|
219
242
|
app = create_base_app(
|
|
220
243
|
routes=server_routes,
|
|
221
244
|
middleware=server_middleware,
|
|
222
245
|
debug=debug,
|
|
246
|
+
lifespan=lifespan,
|
|
223
247
|
)
|
|
224
248
|
# Store the FastMCP server instance on the Starlette app state
|
|
225
249
|
app.state.fastmcp_server = server
|
|
@@ -274,14 +298,29 @@ def create_streamable_http_app(
|
|
|
274
298
|
# Get auth middleware from the provider
|
|
275
299
|
auth_middleware = auth.get_middleware()
|
|
276
300
|
|
|
277
|
-
# Get auth routes
|
|
278
|
-
auth_routes = auth.get_routes(
|
|
279
|
-
mcp_path=streamable_http_path,
|
|
280
|
-
mcp_endpoint=streamable_http_app,
|
|
281
|
-
)
|
|
282
|
-
|
|
301
|
+
# Get auth provider's own routes (OAuth endpoints, metadata, etc)
|
|
302
|
+
auth_routes = auth.get_routes(mcp_path=streamable_http_path)
|
|
283
303
|
server_routes.extend(auth_routes)
|
|
284
304
|
server_middleware.extend(auth_middleware)
|
|
305
|
+
|
|
306
|
+
# Build RFC 9728-compliant metadata URL
|
|
307
|
+
resource_url = auth._get_resource_url(streamable_http_path)
|
|
308
|
+
resource_metadata_url = (
|
|
309
|
+
build_resource_metadata_url(resource_url) if resource_url else None
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Create protected HTTP endpoint route
|
|
313
|
+
server_routes.append(
|
|
314
|
+
Route(
|
|
315
|
+
streamable_http_path,
|
|
316
|
+
endpoint=RequireAuthMiddleware(
|
|
317
|
+
streamable_http_app,
|
|
318
|
+
auth.required_scopes,
|
|
319
|
+
resource_metadata_url,
|
|
320
|
+
),
|
|
321
|
+
methods=["GET", "POST", "DELETE"],
|
|
322
|
+
)
|
|
323
|
+
)
|
|
285
324
|
else:
|
|
286
325
|
# No auth required
|
|
287
326
|
server_routes.append(
|
|
@@ -303,7 +342,7 @@ def create_streamable_http_app(
|
|
|
303
342
|
# Create a lifespan manager to start and stop the session manager
|
|
304
343
|
@asynccontextmanager
|
|
305
344
|
async def lifespan(app: Starlette) -> AsyncGenerator[None, None]:
|
|
306
|
-
async with session_manager.run():
|
|
345
|
+
async with server._lifespan_manager(), session_manager.run():
|
|
307
346
|
yield
|
|
308
347
|
|
|
309
348
|
# Create and return the app with lifespan
|
fastmcp/server/low_level.py
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import weakref
|
|
4
|
+
from contextlib import AsyncExitStack
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
import anyio
|
|
8
|
+
import mcp.types
|
|
9
|
+
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
3
10
|
from mcp.server.lowlevel.server import (
|
|
4
11
|
LifespanResultT,
|
|
5
12
|
NotificationOptions,
|
|
@@ -9,11 +16,82 @@ from mcp.server.lowlevel.server import (
|
|
|
9
16
|
Server as _Server,
|
|
10
17
|
)
|
|
11
18
|
from mcp.server.models import InitializationOptions
|
|
19
|
+
from mcp.server.session import ServerSession
|
|
20
|
+
from mcp.server.stdio import stdio_server as stdio_server
|
|
21
|
+
from mcp.shared.message import SessionMessage
|
|
22
|
+
from mcp.shared.session import RequestResponder
|
|
23
|
+
|
|
24
|
+
from fastmcp.utilities.logging import get_logger
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from fastmcp.server.server import FastMCP
|
|
28
|
+
|
|
29
|
+
logger = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MiddlewareServerSession(ServerSession):
|
|
33
|
+
"""ServerSession that routes initialization requests through FastMCP middleware."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, fastmcp: FastMCP, *args, **kwargs):
|
|
36
|
+
super().__init__(*args, **kwargs)
|
|
37
|
+
self._fastmcp_ref: weakref.ref[FastMCP] = weakref.ref(fastmcp)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def fastmcp(self) -> FastMCP:
|
|
41
|
+
"""Get the FastMCP instance."""
|
|
42
|
+
fastmcp = self._fastmcp_ref()
|
|
43
|
+
if fastmcp is None:
|
|
44
|
+
raise RuntimeError("FastMCP instance is no longer available")
|
|
45
|
+
return fastmcp
|
|
46
|
+
|
|
47
|
+
async def _received_request(
|
|
48
|
+
self,
|
|
49
|
+
responder: RequestResponder[mcp.types.ClientRequest, mcp.types.ServerResult],
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
Override the _received_request method to route initialization requests
|
|
53
|
+
through FastMCP middleware.
|
|
54
|
+
|
|
55
|
+
These are not handled by routes that FastMCP typically overrides and
|
|
56
|
+
require special handling.
|
|
57
|
+
"""
|
|
58
|
+
import fastmcp.server.context
|
|
59
|
+
from fastmcp.server.middleware.middleware import MiddlewareContext
|
|
60
|
+
|
|
61
|
+
if isinstance(responder.request.root, mcp.types.InitializeRequest):
|
|
62
|
+
|
|
63
|
+
async def call_original_handler(
|
|
64
|
+
ctx: MiddlewareContext,
|
|
65
|
+
) -> None:
|
|
66
|
+
return await super(MiddlewareServerSession, self)._received_request(
|
|
67
|
+
responder
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
async with fastmcp.server.context.Context(
|
|
71
|
+
fastmcp=self.fastmcp
|
|
72
|
+
) as fastmcp_ctx:
|
|
73
|
+
# Create the middleware context.
|
|
74
|
+
mw_context = MiddlewareContext(
|
|
75
|
+
message=responder.request.root,
|
|
76
|
+
source="client",
|
|
77
|
+
type="request",
|
|
78
|
+
method="initialize",
|
|
79
|
+
fastmcp_context=fastmcp_ctx,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return await self.fastmcp._apply_middleware(
|
|
83
|
+
mw_context, call_original_handler
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
return await super()._received_request(responder)
|
|
12
87
|
|
|
13
88
|
|
|
14
89
|
class LowLevelServer(_Server[LifespanResultT, RequestT]):
|
|
15
|
-
def __init__(self, *args, **kwargs):
|
|
90
|
+
def __init__(self, fastmcp: FastMCP, *args: Any, **kwargs: Any):
|
|
16
91
|
super().__init__(*args, **kwargs)
|
|
92
|
+
# Store a weak reference to FastMCP to avoid circular references
|
|
93
|
+
self._fastmcp_ref: weakref.ref[FastMCP] = weakref.ref(fastmcp)
|
|
94
|
+
|
|
17
95
|
# FastMCP servers support notifications for all components
|
|
18
96
|
self.notification_options = NotificationOptions(
|
|
19
97
|
prompts_changed=True,
|
|
@@ -21,6 +99,14 @@ class LowLevelServer(_Server[LifespanResultT, RequestT]):
|
|
|
21
99
|
tools_changed=True,
|
|
22
100
|
)
|
|
23
101
|
|
|
102
|
+
@property
|
|
103
|
+
def fastmcp(self) -> FastMCP:
|
|
104
|
+
"""Get the FastMCP instance."""
|
|
105
|
+
fastmcp = self._fastmcp_ref()
|
|
106
|
+
if fastmcp is None:
|
|
107
|
+
raise RuntimeError("FastMCP instance is no longer available")
|
|
108
|
+
return fastmcp
|
|
109
|
+
|
|
24
110
|
def create_initialization_options(
|
|
25
111
|
self,
|
|
26
112
|
notification_options: NotificationOptions | None = None,
|
|
@@ -35,3 +121,36 @@ class LowLevelServer(_Server[LifespanResultT, RequestT]):
|
|
|
35
121
|
experimental_capabilities=experimental_capabilities,
|
|
36
122
|
**kwargs,
|
|
37
123
|
)
|
|
124
|
+
|
|
125
|
+
async def run(
|
|
126
|
+
self,
|
|
127
|
+
read_stream: MemoryObjectReceiveStream[SessionMessage | Exception],
|
|
128
|
+
write_stream: MemoryObjectSendStream[SessionMessage],
|
|
129
|
+
initialization_options: InitializationOptions,
|
|
130
|
+
raise_exceptions: bool = False,
|
|
131
|
+
stateless: bool = False,
|
|
132
|
+
):
|
|
133
|
+
"""
|
|
134
|
+
Overrides the run method to use the MiddlewareServerSession.
|
|
135
|
+
"""
|
|
136
|
+
async with AsyncExitStack() as stack:
|
|
137
|
+
lifespan_context = await stack.enter_async_context(self.lifespan(self))
|
|
138
|
+
session = await stack.enter_async_context(
|
|
139
|
+
MiddlewareServerSession(
|
|
140
|
+
self.fastmcp,
|
|
141
|
+
read_stream,
|
|
142
|
+
write_stream,
|
|
143
|
+
initialization_options,
|
|
144
|
+
stateless=stateless,
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
async with anyio.create_task_group() as tg:
|
|
149
|
+
async for message in session.incoming_messages:
|
|
150
|
+
tg.start_soon(
|
|
151
|
+
self._handle_message,
|
|
152
|
+
message,
|
|
153
|
+
session,
|
|
154
|
+
lifespan_context,
|
|
155
|
+
raise_exceptions,
|
|
156
|
+
)
|