Jeremiah Lowin commited on
Commit
08794a2
·
1 Parent(s): a74ac5b

dry out http app creation

Browse files
Files changed (1) hide show
  1. src/fastmcp/server/http.py +107 -90
src/fastmcp/server/http.py CHANGED
@@ -1,9 +1,9 @@
1
  from __future__ import annotations
2
 
3
- from collections.abc import AsyncGenerator, Generator
4
  from contextlib import asynccontextmanager, contextmanager
5
  from contextvars import ContextVar
6
- from typing import TYPE_CHECKING
7
 
8
  from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
9
  from mcp.server.auth.middleware.bearer_auth import (
@@ -33,7 +33,6 @@ try:
33
  except ImportError:
34
  STREAMABLE_HTTP_AVAILABLE = False
35
 
36
-
37
  if TYPE_CHECKING:
38
  from fastmcp.server.server import FastMCP
39
 
@@ -70,6 +69,85 @@ class RequestContextMiddleware:
70
  await self.app(scope, receive, send)
71
 
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  def create_sse_app(
74
  server: FastMCP,
75
  message_path: str,
@@ -106,42 +184,17 @@ def create_sse_app(
106
  )
107
  return Response()
108
 
109
- # Configure routes and middleware
110
- routes: list[Route | Mount] = []
111
- middleware: list[Middleware] = []
112
-
113
- # Handle authentication configuration
114
- if auth_server_provider:
115
- # Ensure auth settings are provided when auth provider is present
116
- if not auth_settings:
117
- raise ValueError(
118
- "auth_settings must be provided when auth_server_provider is specified"
119
- )
120
-
121
- # Configure auth middleware
122
- middleware = [
123
- Middleware(
124
- AuthenticationMiddleware,
125
- backend=BearerAuthBackend(provider=auth_server_provider),
126
- ),
127
- Middleware(AuthContextMiddleware),
128
- ]
129
 
130
- # Get required scopes for authentication
131
- required_scopes = auth_settings.required_scopes or []
132
 
133
- # Add auth routes
134
- routes.extend(
135
- create_auth_routes(
136
- provider=auth_server_provider,
137
- issuer_url=auth_settings.issuer_url,
138
- service_documentation_url=auth_settings.service_documentation_url,
139
- client_registration_options=auth_settings.client_registration_options,
140
- revocation_options=auth_settings.revocation_options,
141
- )
142
- )
143
-
144
- # Add authenticated routes
145
  routes.append(
146
  Route(
147
  sse_path,
@@ -156,7 +209,7 @@ def create_sse_app(
156
  )
157
  )
158
  else:
159
- # No authentication required
160
  async def sse_endpoint(request: Request) -> Response:
161
  return await handle_sse(request.scope, request.receive, request._send) # type: ignore[reportPrivateUsage]
162
 
@@ -176,13 +229,10 @@ def create_sse_app(
176
 
177
  # Add custom routes with lowest precedence
178
  if additional_routes:
179
- routes.extend(additional_routes)
180
 
181
- # Add RequestContextMiddleware as the outermost middleware
182
- middleware.append(Middleware(RequestContextMiddleware))
183
-
184
- # Create and return the Starlette app with middleware
185
- return Starlette(debug=debug, routes=routes, middleware=middleware)
186
 
187
 
188
  def create_streamable_http_app(
@@ -231,42 +281,17 @@ def create_streamable_http_app(
231
  ) -> None:
232
  await session_manager.handle_request(scope, receive, send)
233
 
234
- # Configure routes and middleware
235
- routes: list[Route | Mount] = []
236
- middleware: list[Middleware] = []
237
-
238
- # Handle authentication configuration
239
- if auth_server_provider:
240
- # Ensure auth settings are provided when auth provider is present
241
- if not auth_settings:
242
- raise ValueError(
243
- "auth_settings must be provided when auth_server_provider is specified"
244
- )
245
-
246
- # Configure auth middleware
247
- middleware = [
248
- Middleware(
249
- AuthenticationMiddleware,
250
- backend=BearerAuthBackend(provider=auth_server_provider),
251
- ),
252
- Middleware(AuthContextMiddleware),
253
- ]
254
-
255
- # Get required scopes for authentication
256
- required_scopes = auth_settings.required_scopes or []
257
 
258
- # Add auth routes
259
- routes.extend(
260
- create_auth_routes(
261
- provider=auth_server_provider,
262
- issuer_url=auth_settings.issuer_url,
263
- service_documentation_url=auth_settings.service_documentation_url,
264
- client_registration_options=auth_settings.client_registration_options,
265
- revocation_options=auth_settings.revocation_options,
266
- )
267
- )
268
 
269
- # Add authenticated route
 
 
270
  routes.append(
271
  Mount(
272
  streamable_http_path,
@@ -274,7 +299,7 @@ def create_streamable_http_app(
274
  )
275
  )
276
  else:
277
- # No authentication required
278
  routes.append(
279
  Mount(
280
  streamable_http_path,
@@ -284,10 +309,7 @@ def create_streamable_http_app(
284
 
285
  # Add custom routes with lowest precedence
286
  if additional_routes:
287
- routes.extend(additional_routes)
288
-
289
- # Add RequestContextMiddleware as the outermost middleware
290
- middleware.append(Middleware(RequestContextMiddleware))
291
 
292
  # Create a lifespan manager to start and stop the session manager
293
  @asynccontextmanager
@@ -295,10 +317,5 @@ def create_streamable_http_app(
295
  async with session_manager.run():
296
  yield
297
 
298
- # Create and return the Starlette app with middleware
299
- return Starlette(
300
- debug=debug,
301
- routes=routes,
302
- middleware=middleware,
303
- lifespan=lifespan,
304
- )
 
1
  from __future__ import annotations
2
 
3
+ from collections.abc import AsyncGenerator, Callable, Generator
4
  from contextlib import asynccontextmanager, contextmanager
5
  from contextvars import ContextVar
6
+ from typing import TYPE_CHECKING, cast
7
 
8
  from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
9
  from mcp.server.auth.middleware.bearer_auth import (
 
33
  except ImportError:
34
  STREAMABLE_HTTP_AVAILABLE = False
35
 
 
36
  if TYPE_CHECKING:
37
  from fastmcp.server.server import FastMCP
38
 
 
69
  await self.app(scope, receive, send)
70
 
71
 
72
+ def setup_auth_middleware_and_routes(
73
+ auth_server_provider: OAuthAuthorizationServerProvider | None,
74
+ auth_settings: AuthSettings | None,
75
+ ) -> tuple[list[Middleware], list[Route | Mount], list[str]]:
76
+ """Set up authentication middleware and routes if auth is enabled.
77
+
78
+ Args:
79
+ auth_server_provider: The OAuth authorization server provider
80
+ auth_settings: The auth settings
81
+
82
+ Returns:
83
+ Tuple of (middleware, auth_routes, required_scopes)
84
+ """
85
+ middleware: list[Middleware] = []
86
+ auth_routes: list[Route | Mount] = []
87
+ required_scopes: list[str] = []
88
+
89
+ if auth_server_provider:
90
+ if not auth_settings:
91
+ raise ValueError(
92
+ "auth_settings must be provided when auth_server_provider is specified"
93
+ )
94
+
95
+ middleware = [
96
+ Middleware(
97
+ AuthenticationMiddleware,
98
+ backend=BearerAuthBackend(provider=auth_server_provider),
99
+ ),
100
+ Middleware(AuthContextMiddleware),
101
+ ]
102
+
103
+ required_scopes = auth_settings.required_scopes or []
104
+
105
+ auth_routes.extend(
106
+ create_auth_routes(
107
+ provider=auth_server_provider,
108
+ issuer_url=auth_settings.issuer_url,
109
+ service_documentation_url=auth_settings.service_documentation_url,
110
+ client_registration_options=auth_settings.client_registration_options,
111
+ revocation_options=auth_settings.revocation_options,
112
+ )
113
+ )
114
+
115
+ return middleware, auth_routes, required_scopes
116
+
117
+
118
+ def create_base_app(
119
+ routes: list[Route | Mount],
120
+ middleware: list[Middleware],
121
+ debug: bool,
122
+ lifespan: Callable | None = None,
123
+ ) -> Starlette:
124
+ """Create a base Starlette app with common middleware and routes.
125
+
126
+ Args:
127
+ routes: List of routes to include in the app
128
+ middleware: List of middleware to include in the app
129
+ debug: Whether to enable debug mode
130
+ lifespan: Optional lifespan manager for the app
131
+
132
+ Returns:
133
+ A Starlette application
134
+ """
135
+ # Always add RequestContextMiddleware as the outermost middleware
136
+ middleware.append(Middleware(RequestContextMiddleware))
137
+
138
+ # Create the app
139
+ app_kwargs = {
140
+ "debug": debug,
141
+ "routes": routes,
142
+ "middleware": middleware,
143
+ }
144
+
145
+ if lifespan:
146
+ app_kwargs["lifespan"] = lifespan
147
+
148
+ return Starlette(**app_kwargs)
149
+
150
+
151
  def create_sse_app(
152
  server: FastMCP,
153
  message_path: str,
 
184
  )
185
  return Response()
186
 
187
+ # Get auth middleware and routes
188
+ middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes(
189
+ auth_server_provider, auth_settings
190
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
 
192
+ # Initialize routes with auth routes
193
+ routes: list[Route | Mount] = auth_routes.copy()
194
 
195
+ # Add SSE routes with or without auth
196
+ if auth_server_provider:
197
+ # Auth is enabled, wrap endpoints with RequireAuthMiddleware
 
 
 
 
 
 
 
 
 
198
  routes.append(
199
  Route(
200
  sse_path,
 
209
  )
210
  )
211
  else:
212
+ # No auth required
213
  async def sse_endpoint(request: Request) -> Response:
214
  return await handle_sse(request.scope, request.receive, request._send) # type: ignore[reportPrivateUsage]
215
 
 
229
 
230
  # Add custom routes with lowest precedence
231
  if additional_routes:
232
+ routes.extend(cast(list[Route | Mount], additional_routes))
233
 
234
+ # Create and return the app
235
+ return create_base_app(routes, middleware, debug)
 
 
 
236
 
237
 
238
  def create_streamable_http_app(
 
281
  ) -> None:
282
  await session_manager.handle_request(scope, receive, send)
283
 
284
+ # Get auth middleware and routes
285
+ middleware, auth_routes, required_scopes = setup_auth_middleware_and_routes(
286
+ auth_server_provider, auth_settings
287
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
+ # Initialize routes with auth routes
290
+ routes: list[Route | Mount] = auth_routes.copy()
 
 
 
 
 
 
 
 
291
 
292
+ # Add StreamableHTTP routes with or without auth
293
+ if auth_server_provider:
294
+ # Auth is enabled, wrap endpoint with RequireAuthMiddleware
295
  routes.append(
296
  Mount(
297
  streamable_http_path,
 
299
  )
300
  )
301
  else:
302
+ # No auth required
303
  routes.append(
304
  Mount(
305
  streamable_http_path,
 
309
 
310
  # Add custom routes with lowest precedence
311
  if additional_routes:
312
+ routes.extend(cast(list[Route | Mount], additional_routes))
 
 
 
313
 
314
  # Create a lifespan manager to start and stop the session manager
315
  @asynccontextmanager
 
317
  async with session_manager.run():
318
  yield
319
 
320
+ # Create and return the app with lifespan
321
+ return create_base_app(routes, middleware, debug, lifespan)