fastmcp 2.3.1__py3-none-any.whl → 2.3.3__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/cli/cli.py +7 -8
- fastmcp/server/http.py +48 -30
- fastmcp/server/server.py +151 -48
- fastmcp/utilities/tests.py +5 -3
- {fastmcp-2.3.1.dist-info → fastmcp-2.3.3.dist-info}/METADATA +2 -2
- {fastmcp-2.3.1.dist-info → fastmcp-2.3.3.dist-info}/RECORD +9 -9
- {fastmcp-2.3.1.dist-info → fastmcp-2.3.3.dist-info}/WHEEL +0 -0
- {fastmcp-2.3.1.dist-info → fastmcp-2.3.3.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.3.1.dist-info → fastmcp-2.3.3.dist-info}/licenses/LICENSE +0 -0
fastmcp/cli/cli.py
CHANGED
|
@@ -327,14 +327,14 @@ def run(
|
|
|
327
327
|
typer.Option(
|
|
328
328
|
"--transport",
|
|
329
329
|
"-t",
|
|
330
|
-
help="Transport protocol to use (stdio or sse)",
|
|
330
|
+
help="Transport protocol to use (stdio, streamable-http, or sse)",
|
|
331
331
|
),
|
|
332
332
|
] = None,
|
|
333
333
|
host: Annotated[
|
|
334
334
|
str | None,
|
|
335
335
|
typer.Option(
|
|
336
336
|
"--host",
|
|
337
|
-
help="Host to bind to when using
|
|
337
|
+
help="Host to bind to when using http transport (default: 127.0.0.1)",
|
|
338
338
|
),
|
|
339
339
|
] = None,
|
|
340
340
|
port: Annotated[
|
|
@@ -342,7 +342,7 @@ def run(
|
|
|
342
342
|
typer.Option(
|
|
343
343
|
"--port",
|
|
344
344
|
"-p",
|
|
345
|
-
help="Port to bind to when using
|
|
345
|
+
help="Port to bind to when using http transport (default: 8000)",
|
|
346
346
|
),
|
|
347
347
|
] = None,
|
|
348
348
|
log_level: Annotated[
|
|
@@ -350,20 +350,19 @@ def run(
|
|
|
350
350
|
typer.Option(
|
|
351
351
|
"--log-level",
|
|
352
352
|
"-l",
|
|
353
|
-
help="Log level
|
|
353
|
+
help="Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
|
|
354
354
|
),
|
|
355
355
|
] = None,
|
|
356
356
|
) -> None:
|
|
357
357
|
"""Run a MCP server.
|
|
358
358
|
|
|
359
|
-
The server can be specified in two ways
|
|
359
|
+
The server can be specified in two ways:
|
|
360
360
|
1. Module approach: server.py - runs the module directly, expecting a server.run() call.\n
|
|
361
361
|
2. Import approach: server.py:app - imports and runs the specified server object.\n\n
|
|
362
362
|
|
|
363
363
|
Note: This command runs the server directly. You are responsible for ensuring
|
|
364
|
-
all dependencies are available
|
|
365
|
-
|
|
366
|
-
""" # noqa: E501
|
|
364
|
+
all dependencies are available.
|
|
365
|
+
"""
|
|
367
366
|
file, server_object = _parse_file_path(file_spec)
|
|
368
367
|
|
|
369
368
|
logger.debug(
|
fastmcp/server/http.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import AsyncGenerator, Callable, Generator
|
|
4
4
|
from contextlib import asynccontextmanager, contextmanager
|
|
5
5
|
from contextvars import ContextVar
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
|
|
9
9
|
from mcp.server.auth.middleware.bearer_auth import (
|
|
@@ -19,7 +19,7 @@ from starlette.middleware import Middleware
|
|
|
19
19
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
|
20
20
|
from starlette.requests import Request
|
|
21
21
|
from starlette.responses import Response
|
|
22
|
-
from starlette.routing import Mount, Route
|
|
22
|
+
from starlette.routing import BaseRoute, Mount, Route
|
|
23
23
|
from starlette.types import Receive, Scope, Send
|
|
24
24
|
|
|
25
25
|
from fastmcp.low_level.sse_server_transport import SseServerTransport
|
|
@@ -64,7 +64,7 @@ class RequestContextMiddleware:
|
|
|
64
64
|
def setup_auth_middleware_and_routes(
|
|
65
65
|
auth_server_provider: OAuthAuthorizationServerProvider | None,
|
|
66
66
|
auth_settings: AuthSettings | None,
|
|
67
|
-
) -> tuple[list[Middleware], list[
|
|
67
|
+
) -> tuple[list[Middleware], list[BaseRoute], list[str]]:
|
|
68
68
|
"""Set up authentication middleware and routes if auth is enabled.
|
|
69
69
|
|
|
70
70
|
Args:
|
|
@@ -75,7 +75,7 @@ def setup_auth_middleware_and_routes(
|
|
|
75
75
|
Tuple of (middleware, auth_routes, required_scopes)
|
|
76
76
|
"""
|
|
77
77
|
middleware: list[Middleware] = []
|
|
78
|
-
auth_routes: list[
|
|
78
|
+
auth_routes: list[BaseRoute] = []
|
|
79
79
|
required_scopes: list[str] = []
|
|
80
80
|
|
|
81
81
|
if auth_server_provider:
|
|
@@ -108,7 +108,7 @@ def setup_auth_middleware_and_routes(
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def create_base_app(
|
|
111
|
-
routes: list[
|
|
111
|
+
routes: list[BaseRoute],
|
|
112
112
|
middleware: list[Middleware],
|
|
113
113
|
debug: bool = False,
|
|
114
114
|
lifespan: Callable | None = None,
|
|
@@ -142,7 +142,8 @@ def create_sse_app(
|
|
|
142
142
|
auth_server_provider: OAuthAuthorizationServerProvider | None = None,
|
|
143
143
|
auth_settings: AuthSettings | None = None,
|
|
144
144
|
debug: bool = False,
|
|
145
|
-
|
|
145
|
+
routes: list[BaseRoute] | None = None,
|
|
146
|
+
middleware: list[Middleware] | None = None,
|
|
146
147
|
) -> Starlette:
|
|
147
148
|
"""Return an instance of the SSE server app.
|
|
148
149
|
|
|
@@ -153,11 +154,15 @@ def create_sse_app(
|
|
|
153
154
|
auth_server_provider: Optional auth provider
|
|
154
155
|
auth_settings: Optional auth settings
|
|
155
156
|
debug: Whether to enable debug mode
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
routes: Optional list of custom routes
|
|
158
|
+
middleware: Optional list of middleware
|
|
158
159
|
Returns:
|
|
159
160
|
A Starlette application with RequestContextMiddleware
|
|
160
161
|
"""
|
|
162
|
+
|
|
163
|
+
server_routes: list[BaseRoute] = []
|
|
164
|
+
server_middleware: list[Middleware] = []
|
|
165
|
+
|
|
161
166
|
# Set up SSE transport
|
|
162
167
|
sse = SseServerTransport(message_path)
|
|
163
168
|
|
|
@@ -172,24 +177,24 @@ def create_sse_app(
|
|
|
172
177
|
return Response()
|
|
173
178
|
|
|
174
179
|
# Get auth middleware and routes
|
|
175
|
-
|
|
180
|
+
auth_middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes(
|
|
176
181
|
auth_server_provider, auth_settings
|
|
177
182
|
)
|
|
178
183
|
|
|
179
|
-
|
|
180
|
-
|
|
184
|
+
server_routes.extend(auth_routes)
|
|
185
|
+
server_middleware.extend(auth_middleware)
|
|
181
186
|
|
|
182
187
|
# Add SSE routes with or without auth
|
|
183
188
|
if auth_server_provider:
|
|
184
189
|
# Auth is enabled, wrap endpoints with RequireAuthMiddleware
|
|
185
|
-
|
|
190
|
+
server_routes.append(
|
|
186
191
|
Route(
|
|
187
192
|
sse_path,
|
|
188
193
|
endpoint=RequireAuthMiddleware(handle_sse, required_scopes),
|
|
189
194
|
methods=["GET"],
|
|
190
195
|
)
|
|
191
196
|
)
|
|
192
|
-
|
|
197
|
+
server_routes.append(
|
|
193
198
|
Mount(
|
|
194
199
|
message_path,
|
|
195
200
|
app=RequireAuthMiddleware(sse.handle_post_message, required_scopes),
|
|
@@ -200,14 +205,14 @@ def create_sse_app(
|
|
|
200
205
|
async def sse_endpoint(request: Request) -> Response:
|
|
201
206
|
return await handle_sse(request.scope, request.receive, request._send) # type: ignore[reportPrivateUsage]
|
|
202
207
|
|
|
203
|
-
|
|
208
|
+
server_routes.append(
|
|
204
209
|
Route(
|
|
205
210
|
sse_path,
|
|
206
211
|
endpoint=sse_endpoint,
|
|
207
212
|
methods=["GET"],
|
|
208
213
|
)
|
|
209
214
|
)
|
|
210
|
-
|
|
215
|
+
server_routes.append(
|
|
211
216
|
Mount(
|
|
212
217
|
message_path,
|
|
213
218
|
app=sse.handle_post_message,
|
|
@@ -215,13 +220,17 @@ def create_sse_app(
|
|
|
215
220
|
)
|
|
216
221
|
|
|
217
222
|
# Add custom routes with lowest precedence
|
|
218
|
-
if
|
|
219
|
-
|
|
223
|
+
if routes:
|
|
224
|
+
server_routes.extend(routes)
|
|
225
|
+
|
|
226
|
+
# Add middleware
|
|
227
|
+
if middleware:
|
|
228
|
+
server_middleware.extend(middleware)
|
|
220
229
|
|
|
221
230
|
# Create and return the app
|
|
222
231
|
return create_base_app(
|
|
223
|
-
routes=
|
|
224
|
-
middleware=
|
|
232
|
+
routes=server_routes,
|
|
233
|
+
middleware=server_middleware,
|
|
225
234
|
debug=debug,
|
|
226
235
|
)
|
|
227
236
|
|
|
@@ -235,7 +244,8 @@ def create_streamable_http_app(
|
|
|
235
244
|
json_response: bool = False,
|
|
236
245
|
stateless_http: bool = False,
|
|
237
246
|
debug: bool = False,
|
|
238
|
-
|
|
247
|
+
routes: list[BaseRoute] | None = None,
|
|
248
|
+
middleware: list[Middleware] | None = None,
|
|
239
249
|
) -> Starlette:
|
|
240
250
|
"""Return an instance of the StreamableHTTP server app.
|
|
241
251
|
|
|
@@ -248,11 +258,15 @@ def create_streamable_http_app(
|
|
|
248
258
|
json_response: Whether to use JSON response format
|
|
249
259
|
stateless_http: Whether to use stateless mode (new transport per request)
|
|
250
260
|
debug: Whether to enable debug mode
|
|
251
|
-
|
|
261
|
+
routes: Optional list of custom routes
|
|
262
|
+
middleware: Optional list of middleware
|
|
252
263
|
|
|
253
264
|
Returns:
|
|
254
265
|
A Starlette application with StreamableHTTP support
|
|
255
266
|
"""
|
|
267
|
+
server_routes: list[BaseRoute] = []
|
|
268
|
+
server_middleware: list[Middleware] = []
|
|
269
|
+
|
|
256
270
|
# Create session manager using the provided event store
|
|
257
271
|
session_manager = StreamableHTTPSessionManager(
|
|
258
272
|
app=server._mcp_server,
|
|
@@ -268,17 +282,17 @@ def create_streamable_http_app(
|
|
|
268
282
|
await session_manager.handle_request(scope, receive, send)
|
|
269
283
|
|
|
270
284
|
# Get auth middleware and routes
|
|
271
|
-
|
|
285
|
+
auth_middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes(
|
|
272
286
|
auth_server_provider, auth_settings
|
|
273
287
|
)
|
|
274
288
|
|
|
275
|
-
|
|
276
|
-
|
|
289
|
+
server_routes.extend(auth_routes)
|
|
290
|
+
server_middleware.extend(auth_middleware)
|
|
277
291
|
|
|
278
292
|
# Add StreamableHTTP routes with or without auth
|
|
279
293
|
if auth_server_provider:
|
|
280
294
|
# Auth is enabled, wrap endpoint with RequireAuthMiddleware
|
|
281
|
-
|
|
295
|
+
server_routes.append(
|
|
282
296
|
Mount(
|
|
283
297
|
streamable_http_path,
|
|
284
298
|
app=RequireAuthMiddleware(handle_streamable_http, required_scopes),
|
|
@@ -286,7 +300,7 @@ def create_streamable_http_app(
|
|
|
286
300
|
)
|
|
287
301
|
else:
|
|
288
302
|
# No auth required
|
|
289
|
-
|
|
303
|
+
server_routes.append(
|
|
290
304
|
Mount(
|
|
291
305
|
streamable_http_path,
|
|
292
306
|
app=handle_streamable_http,
|
|
@@ -294,8 +308,12 @@ def create_streamable_http_app(
|
|
|
294
308
|
)
|
|
295
309
|
|
|
296
310
|
# Add custom routes with lowest precedence
|
|
297
|
-
if
|
|
298
|
-
|
|
311
|
+
if routes:
|
|
312
|
+
server_routes.extend(routes)
|
|
313
|
+
|
|
314
|
+
# Add middleware
|
|
315
|
+
if middleware:
|
|
316
|
+
server_middleware.extend(middleware)
|
|
299
317
|
|
|
300
318
|
# Create a lifespan manager to start and stop the session manager
|
|
301
319
|
@asynccontextmanager
|
|
@@ -305,8 +323,8 @@ def create_streamable_http_app(
|
|
|
305
323
|
|
|
306
324
|
# Create and return the app with lifespan
|
|
307
325
|
return create_base_app(
|
|
308
|
-
routes=
|
|
309
|
-
middleware=
|
|
326
|
+
routes=server_routes,
|
|
327
|
+
middleware=server_middleware,
|
|
310
328
|
debug=debug,
|
|
311
329
|
lifespan=lifespan,
|
|
312
330
|
)
|
fastmcp/server/server.py
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import datetime
|
|
6
|
+
import inspect
|
|
7
|
+
import warnings
|
|
6
8
|
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
7
9
|
from contextlib import (
|
|
8
10
|
AbstractAsyncContextManager,
|
|
@@ -35,9 +37,10 @@ from mcp.types import ResourceTemplate as MCPResourceTemplate
|
|
|
35
37
|
from mcp.types import Tool as MCPTool
|
|
36
38
|
from pydantic import AnyUrl
|
|
37
39
|
from starlette.applications import Starlette
|
|
40
|
+
from starlette.middleware import Middleware
|
|
38
41
|
from starlette.requests import Request
|
|
39
42
|
from starlette.responses import Response
|
|
40
|
-
from starlette.routing import Route
|
|
43
|
+
from starlette.routing import BaseRoute, Route
|
|
41
44
|
|
|
42
45
|
import fastmcp.server
|
|
43
46
|
import fastmcp.settings
|
|
@@ -147,7 +150,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
147
150
|
)
|
|
148
151
|
self._auth_server_provider = auth_server_provider
|
|
149
152
|
|
|
150
|
-
self._additional_http_routes: list[
|
|
153
|
+
self._additional_http_routes: list[BaseRoute] = []
|
|
151
154
|
self.dependencies = self.settings.dependencies
|
|
152
155
|
|
|
153
156
|
# Set up MCP protocol handlers
|
|
@@ -169,7 +172,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
169
172
|
|
|
170
173
|
async def run_async(
|
|
171
174
|
self,
|
|
172
|
-
transport: Literal["stdio", "
|
|
175
|
+
transport: Literal["stdio", "streamable-http", "sse"] | None = None,
|
|
173
176
|
**transport_kwargs: Any,
|
|
174
177
|
) -> None:
|
|
175
178
|
"""Run the FastMCP server asynchronously.
|
|
@@ -179,19 +182,21 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
179
182
|
"""
|
|
180
183
|
if transport is None:
|
|
181
184
|
transport = "stdio"
|
|
182
|
-
if transport not in ["stdio", "
|
|
185
|
+
if transport not in ["stdio", "streamable-http", "sse"]:
|
|
183
186
|
raise ValueError(f"Unknown transport: {transport}")
|
|
184
187
|
|
|
185
188
|
if transport == "stdio":
|
|
186
189
|
await self.run_stdio_async(**transport_kwargs)
|
|
190
|
+
elif transport == "streamable-http":
|
|
191
|
+
await self.run_http_async(transport="streamable-http", **transport_kwargs)
|
|
187
192
|
elif transport == "sse":
|
|
188
|
-
await self.
|
|
189
|
-
else:
|
|
190
|
-
|
|
193
|
+
await self.run_http_async(transport="sse", **transport_kwargs)
|
|
194
|
+
else:
|
|
195
|
+
raise ValueError(f"Unknown transport: {transport}")
|
|
191
196
|
|
|
192
197
|
def run(
|
|
193
198
|
self,
|
|
194
|
-
transport: Literal["stdio", "
|
|
199
|
+
transport: Literal["stdio", "streamable-http", "sse"] | None = None,
|
|
195
200
|
**transport_kwargs: Any,
|
|
196
201
|
) -> None:
|
|
197
202
|
"""Run the FastMCP server. Note this is a synchronous function.
|
|
@@ -713,22 +718,31 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
713
718
|
self._mcp_server.create_initialization_options(),
|
|
714
719
|
)
|
|
715
720
|
|
|
716
|
-
async def
|
|
721
|
+
async def run_http_async(
|
|
717
722
|
self,
|
|
723
|
+
transport: Literal["streamable-http", "sse"] = "streamable-http",
|
|
718
724
|
host: str | None = None,
|
|
719
725
|
port: int | None = None,
|
|
720
726
|
log_level: str | None = None,
|
|
721
727
|
path: str | None = None,
|
|
722
|
-
message_path: str | None = None,
|
|
723
728
|
uvicorn_config: dict | None = None,
|
|
724
729
|
) -> None:
|
|
725
|
-
"""Run the server using
|
|
730
|
+
"""Run the server using HTTP transport.
|
|
731
|
+
|
|
732
|
+
Args:
|
|
733
|
+
transport: Transport protocol to use - either "streamable-http" (default) or "sse"
|
|
734
|
+
host: Host address to bind to (defaults to settings.host)
|
|
735
|
+
port: Port to bind to (defaults to settings.port)
|
|
736
|
+
log_level: Log level for the server (defaults to settings.log_level)
|
|
737
|
+
path: Path for the endpoint (defaults to settings.streamable_http_path or settings.sse_path)
|
|
738
|
+
uvicorn_config: Additional configuration for the Uvicorn server
|
|
739
|
+
"""
|
|
726
740
|
uvicorn_config = uvicorn_config or {}
|
|
727
|
-
# the SSE app hangs even when a signal is sent, so we disable the
|
|
728
|
-
# timeout to make it possible to close immediately. see
|
|
729
|
-
# https://github.com/jlowin/fastmcp/issues/296
|
|
730
741
|
uvicorn_config.setdefault("timeout_graceful_shutdown", 0)
|
|
731
|
-
|
|
742
|
+
# lifespan is required for streamable http
|
|
743
|
+
uvicorn_config["lifespan"] = "on"
|
|
744
|
+
|
|
745
|
+
app = self.http_app(path=path, transport=transport)
|
|
732
746
|
|
|
733
747
|
config = uvicorn.Config(
|
|
734
748
|
app,
|
|
@@ -740,12 +754,58 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
740
754
|
server = uvicorn.Server(config)
|
|
741
755
|
await server.serve()
|
|
742
756
|
|
|
757
|
+
async def run_sse_async(
|
|
758
|
+
self,
|
|
759
|
+
host: str | None = None,
|
|
760
|
+
port: int | None = None,
|
|
761
|
+
log_level: str | None = None,
|
|
762
|
+
path: str | None = None,
|
|
763
|
+
message_path: str | None = None,
|
|
764
|
+
uvicorn_config: dict | None = None,
|
|
765
|
+
) -> None:
|
|
766
|
+
"""Run the server using SSE transport."""
|
|
767
|
+
warnings.warn(
|
|
768
|
+
inspect.cleandoc(
|
|
769
|
+
"""
|
|
770
|
+
The run_sse_async method is deprecated. Use run_http_async for a
|
|
771
|
+
modern (non-SSE) alternative, or create an SSE app with
|
|
772
|
+
`fastmcp.server.http.create_sse_app` and run it directly.
|
|
773
|
+
"""
|
|
774
|
+
),
|
|
775
|
+
DeprecationWarning,
|
|
776
|
+
)
|
|
777
|
+
await self.run_http_async(
|
|
778
|
+
transport="sse",
|
|
779
|
+
host=host,
|
|
780
|
+
port=port,
|
|
781
|
+
log_level=log_level,
|
|
782
|
+
path=path,
|
|
783
|
+
uvicorn_config=uvicorn_config,
|
|
784
|
+
)
|
|
785
|
+
|
|
743
786
|
def sse_app(
|
|
744
787
|
self,
|
|
745
788
|
path: str | None = None,
|
|
746
789
|
message_path: str | None = None,
|
|
790
|
+
middleware: list[Middleware] | None = None,
|
|
747
791
|
) -> Starlette:
|
|
748
|
-
"""
|
|
792
|
+
"""
|
|
793
|
+
Create a Starlette app for the SSE server.
|
|
794
|
+
|
|
795
|
+
Args:
|
|
796
|
+
path: The path to the SSE endpoint
|
|
797
|
+
message_path: The path to the message endpoint
|
|
798
|
+
middleware: A list of middleware to apply to the app
|
|
799
|
+
"""
|
|
800
|
+
warnings.warn(
|
|
801
|
+
inspect.cleandoc(
|
|
802
|
+
"""
|
|
803
|
+
The sse_app method is deprecated. Use http_app as a modern (non-SSE)
|
|
804
|
+
alternative, or call `fastmcp.server.http.create_sse_app` directly.
|
|
805
|
+
"""
|
|
806
|
+
),
|
|
807
|
+
DeprecationWarning,
|
|
808
|
+
)
|
|
749
809
|
return create_sse_app(
|
|
750
810
|
server=self,
|
|
751
811
|
message_path=message_path or self.settings.message_path,
|
|
@@ -753,24 +813,70 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
753
813
|
auth_server_provider=self._auth_server_provider,
|
|
754
814
|
auth_settings=self.settings.auth,
|
|
755
815
|
debug=self.settings.debug,
|
|
756
|
-
|
|
816
|
+
routes=self._additional_http_routes,
|
|
817
|
+
middleware=middleware,
|
|
757
818
|
)
|
|
758
819
|
|
|
759
|
-
def streamable_http_app(
|
|
760
|
-
|
|
761
|
-
|
|
820
|
+
def streamable_http_app(
|
|
821
|
+
self,
|
|
822
|
+
path: str | None = None,
|
|
823
|
+
middleware: list[Middleware] | None = None,
|
|
824
|
+
) -> Starlette:
|
|
825
|
+
"""
|
|
826
|
+
Create a Starlette app for the StreamableHTTP server.
|
|
762
827
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
stateless_http=self.settings.stateless_http,
|
|
771
|
-
debug=self.settings.debug,
|
|
772
|
-
additional_routes=self._additional_http_routes,
|
|
828
|
+
Args:
|
|
829
|
+
path: The path to the StreamableHTTP endpoint
|
|
830
|
+
middleware: A list of middleware to apply to the app
|
|
831
|
+
"""
|
|
832
|
+
warnings.warn(
|
|
833
|
+
"The streamable_http_app method is deprecated. Use http_app() instead.",
|
|
834
|
+
DeprecationWarning,
|
|
773
835
|
)
|
|
836
|
+
return self.http_app(path=path, middleware=middleware)
|
|
837
|
+
|
|
838
|
+
def http_app(
|
|
839
|
+
self,
|
|
840
|
+
path: str | None = None,
|
|
841
|
+
middleware: list[Middleware] | None = None,
|
|
842
|
+
transport: Literal["streamable-http", "sse"] = "streamable-http",
|
|
843
|
+
) -> Starlette:
|
|
844
|
+
"""Create a Starlette app using the specified HTTP transport.
|
|
845
|
+
|
|
846
|
+
Args:
|
|
847
|
+
path: The path for the HTTP endpoint
|
|
848
|
+
middleware: A list of middleware to apply to the app
|
|
849
|
+
transport: Transport protocol to use - either "streamable-http" (default) or "sse"
|
|
850
|
+
|
|
851
|
+
Returns:
|
|
852
|
+
A Starlette application configured with the specified transport
|
|
853
|
+
"""
|
|
854
|
+
from fastmcp.server.http import create_streamable_http_app
|
|
855
|
+
|
|
856
|
+
if transport == "streamable-http":
|
|
857
|
+
return create_streamable_http_app(
|
|
858
|
+
server=self,
|
|
859
|
+
streamable_http_path=path or self.settings.streamable_http_path,
|
|
860
|
+
event_store=None,
|
|
861
|
+
auth_server_provider=self._auth_server_provider,
|
|
862
|
+
auth_settings=self.settings.auth,
|
|
863
|
+
json_response=self.settings.json_response,
|
|
864
|
+
stateless_http=self.settings.stateless_http,
|
|
865
|
+
debug=self.settings.debug,
|
|
866
|
+
routes=self._additional_http_routes,
|
|
867
|
+
middleware=middleware,
|
|
868
|
+
)
|
|
869
|
+
elif transport == "sse":
|
|
870
|
+
return create_sse_app(
|
|
871
|
+
server=self,
|
|
872
|
+
message_path=self.settings.message_path,
|
|
873
|
+
sse_path=path or self.settings.sse_path,
|
|
874
|
+
auth_server_provider=self._auth_server_provider,
|
|
875
|
+
auth_settings=self.settings.auth,
|
|
876
|
+
debug=self.settings.debug,
|
|
877
|
+
routes=self._additional_http_routes,
|
|
878
|
+
middleware=middleware,
|
|
879
|
+
)
|
|
774
880
|
|
|
775
881
|
async def run_streamable_http_async(
|
|
776
882
|
self,
|
|
@@ -780,23 +886,18 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
780
886
|
path: str | None = None,
|
|
781
887
|
uvicorn_config: dict | None = None,
|
|
782
888
|
) -> None:
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
# lifespan is required for streamable http
|
|
795
|
-
lifespan="on",
|
|
796
|
-
**uvicorn_config,
|
|
889
|
+
warnings.warn(
|
|
890
|
+
"The run_streamable_http_async method is deprecated. Use run_http_async instead.",
|
|
891
|
+
DeprecationWarning,
|
|
892
|
+
)
|
|
893
|
+
await self.run_http_async(
|
|
894
|
+
transport="streamable-http",
|
|
895
|
+
host=host,
|
|
896
|
+
port=port,
|
|
897
|
+
log_level=log_level,
|
|
898
|
+
path=path,
|
|
899
|
+
uvicorn_config=uvicorn_config,
|
|
797
900
|
)
|
|
798
|
-
server = uvicorn.Server(config)
|
|
799
|
-
await server.serve()
|
|
800
901
|
|
|
801
902
|
def mount(
|
|
802
903
|
self,
|
|
@@ -993,11 +1094,13 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
993
1094
|
|
|
994
1095
|
def _validate_resource_prefix(prefix: str) -> None:
|
|
995
1096
|
valid_resource = "resource://path/to/resource"
|
|
1097
|
+
test_case = f"{prefix}{valid_resource}"
|
|
996
1098
|
try:
|
|
997
|
-
AnyUrl(
|
|
1099
|
+
AnyUrl(test_case)
|
|
998
1100
|
except pydantic.ValidationError as e:
|
|
999
1101
|
raise ValueError(
|
|
1000
|
-
|
|
1102
|
+
"Resource prefix or separator would result in an "
|
|
1103
|
+
f"invalid resource URI (test case was {test_case!r}): {e}"
|
|
1001
1104
|
)
|
|
1002
1105
|
|
|
1003
1106
|
|
fastmcp/utilities/tests.py
CHANGED
|
@@ -55,7 +55,7 @@ def temporary_settings(**kwargs: Any):
|
|
|
55
55
|
def _run_server(mcp_server: FastMCP, transport: Literal["sse"], port: int) -> None:
|
|
56
56
|
# Some Starlette apps are not pickleable, so we need to create them here based on the indicated transport
|
|
57
57
|
if transport == "sse":
|
|
58
|
-
app = mcp_server.
|
|
58
|
+
app = mcp_server.http_app(transport="sse")
|
|
59
59
|
else:
|
|
60
60
|
raise ValueError(f"Invalid transport: {transport}")
|
|
61
61
|
uvicorn_server = uvicorn.Server(
|
|
@@ -71,7 +71,7 @@ def _run_server(mcp_server: FastMCP, transport: Literal["sse"], port: int) -> No
|
|
|
71
71
|
|
|
72
72
|
@contextmanager
|
|
73
73
|
def run_server_in_process(
|
|
74
|
-
server_fn: Callable[[str, int], None],
|
|
74
|
+
server_fn: Callable[[str, int], None], *args
|
|
75
75
|
) -> Generator[str, None, None]:
|
|
76
76
|
"""
|
|
77
77
|
Context manager that runs a Starlette app in a separate process and returns the
|
|
@@ -88,7 +88,9 @@ def run_server_in_process(
|
|
|
88
88
|
s.bind((host, 0))
|
|
89
89
|
port = s.getsockname()[1]
|
|
90
90
|
|
|
91
|
-
proc = multiprocessing.Process(
|
|
91
|
+
proc = multiprocessing.Process(
|
|
92
|
+
target=server_fn, args=(host, port, *args), daemon=True
|
|
93
|
+
)
|
|
92
94
|
proc.start()
|
|
93
95
|
|
|
94
96
|
# Wait for server to be running
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.3
|
|
4
4
|
Summary: The fast, Pythonic way to build MCP servers.
|
|
5
5
|
Project-URL: Homepage, https://gofastmcp.com
|
|
6
6
|
Project-URL: Repository, https://github.com/jlowin/fastmcp
|
|
@@ -44,7 +44,7 @@ Description-Content-Type: text/markdown
|
|
|
44
44
|
> [!NOTE]
|
|
45
45
|
> #### FastMCP 2.0 & The Official MCP SDK
|
|
46
46
|
>
|
|
47
|
-
> Recognize the `FastMCP` name? You might have
|
|
47
|
+
> Recognize the `FastMCP` name? You might have seen the version that was contributed to the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk), which was based on **FastMCP 1.0**.
|
|
48
48
|
>
|
|
49
49
|
> **Welcome to FastMCP 2.0!** This is the actively developed successor, and it significantly expands on 1.0 by introducing powerful client capabilities, server proxying & composition, OpenAPI/FastAPI integration, and more advanced features.
|
|
50
50
|
>
|
|
@@ -4,7 +4,7 @@ fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
4
4
|
fastmcp/settings.py,sha256=rDClnYEpYjEl8VsvvVrKp9oaE4YLfNQcMoZ41H_bDL0,2968
|
|
5
5
|
fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
|
|
6
6
|
fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
|
|
7
|
-
fastmcp/cli/cli.py,sha256=
|
|
7
|
+
fastmcp/cli/cli.py,sha256=Tb-WiIXFZiq4nqlZ6LMXN2iYY30clC4Om_gP89HbJcE,15641
|
|
8
8
|
fastmcp/client/__init__.py,sha256=BXO9NUhntZ5GnUACfaRCzDJ5IzxqFJs8qKG-CRMSco4,490
|
|
9
9
|
fastmcp/client/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
fastmcp/client/client.py,sha256=zfSLWSGqiBoADveKehsAL66CdyGGqmzVHR1q46zfdQY,15479
|
|
@@ -35,10 +35,10 @@ fastmcp/resources/types.py,sha256=QPDeka_cM1hmvwW4FeFhqy6BEEi4MlwtpvhWUVWh5Fc,64
|
|
|
35
35
|
fastmcp/server/__init__.py,sha256=bMD4aQD4yJqLz7-mudoNsyeV8UgQfRAg3PRwPvwTEds,119
|
|
36
36
|
fastmcp/server/context.py,sha256=ykitQygA7zT5prbFTLCuYlnAzuljf_9ErUT0FYBPv3E,8135
|
|
37
37
|
fastmcp/server/dependencies.py,sha256=1utkxFsV37HZcWBwI69JyngVN2ppGO_PEgxUlUHHy_Q,742
|
|
38
|
-
fastmcp/server/http.py,sha256=
|
|
38
|
+
fastmcp/server/http.py,sha256=utl7vJkMvKUnKIflCptVWk1oqOi7_sJJHqUl22g4JC8,10473
|
|
39
39
|
fastmcp/server/openapi.py,sha256=0nANnwHJ5VZInNyo2f9ErmO0K3igMv6bwyxf3G-BSls,23473
|
|
40
40
|
fastmcp/server/proxy.py,sha256=LDTjzc_iQj8AldsfMU37flGRAfJic1w6qsherfyHPAA,9622
|
|
41
|
-
fastmcp/server/server.py,sha256=
|
|
41
|
+
fastmcp/server/server.py,sha256=FLcZe-ccCUe5288sdEEPl7xlrKgaVunGhJnq7nv70vc,44483
|
|
42
42
|
fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
|
|
43
43
|
fastmcp/tools/tool.py,sha256=HGcHjMecqAeN6eI-IfE_2UBcd1KpTV-VOTFLx9tlbpU,7809
|
|
44
44
|
fastmcp/tools/tool_manager.py,sha256=p2nHyLFgz28tbsLpWOurkbWRU2Z34_HcDohjrvwjI0E,3369
|
|
@@ -48,10 +48,10 @@ fastmcp/utilities/decorators.py,sha256=AjhjsetQZF4YOPV5MTZmIxO21iFp_4fDIS3O2_KNC
|
|
|
48
48
|
fastmcp/utilities/json_schema.py,sha256=mSakhP8bENxhLFMwHJSxJAFllNeByIBDjVohwlpac6w,2026
|
|
49
49
|
fastmcp/utilities/logging.py,sha256=zav8pnFxG_fvGJHUV2XpobmT9WVrmv1mlQBSCz-CPx4,1159
|
|
50
50
|
fastmcp/utilities/openapi.py,sha256=Er3G1MyFwiWVxZXicXtD2j-BvttHEDTi1dgkq1KiBQc,51073
|
|
51
|
-
fastmcp/utilities/tests.py,sha256=
|
|
51
|
+
fastmcp/utilities/tests.py,sha256=mAV2EjDeCbm9V9NsVIUjcmzf93MgDjfj8kMvHpf4vgo,3224
|
|
52
52
|
fastmcp/utilities/types.py,sha256=6CcqAQ1QqCO2HGSFlPS6FO5JRWnacjCcO2-EhyEnZV0,4400
|
|
53
|
-
fastmcp-2.3.
|
|
54
|
-
fastmcp-2.3.
|
|
55
|
-
fastmcp-2.3.
|
|
56
|
-
fastmcp-2.3.
|
|
57
|
-
fastmcp-2.3.
|
|
53
|
+
fastmcp-2.3.3.dist-info/METADATA,sha256=4z9X0B1oEDwqQBFJ7d2_YJtEoj2cKLn6TcJafKxATIE,15754
|
|
54
|
+
fastmcp-2.3.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
55
|
+
fastmcp-2.3.3.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
56
|
+
fastmcp-2.3.3.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
57
|
+
fastmcp-2.3.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|