Jeremiah Lowin commited on
Commit
e1a5c52
·
unverified ·
2 Parent(s): d588742bf1d7b7

Merge pull request #483 from jlowin/lifecycle

Browse files
docs/deployment/asgi.mdx CHANGED
@@ -124,7 +124,7 @@ app = Starlette(
124
  Mount("/mcp-server", app=mcp_app),
125
  # Add other routes as needed
126
  ],
127
- lifespan=mcp_app.router.lifespan_context,
128
  )
129
  ```
130
 
@@ -154,7 +154,7 @@ mcp_app = mcp.http_app(path='/mcp')
154
  inner_app = Starlette(routes=[Mount("/inner", app=mcp_app)])
155
  app = Starlette(
156
  routes=[Mount("/outer", app=inner_app)],
157
- lifespan=mcp_app.router.lifespan_context,
158
  )
159
  ```
160
 
@@ -181,7 +181,7 @@ mcp = FastMCP("MyServer")
181
  mcp_app = mcp.http_app(path='/mcp')
182
 
183
  # Create a FastAPI app and mount the MCP server
184
- app = FastAPI(lifespan=mcp_app.router.lifespan_context)
185
  app.mount("/mcp-server", mcp_app)
186
  ```
187
 
 
124
  Mount("/mcp-server", app=mcp_app),
125
  # Add other routes as needed
126
  ],
127
+ lifespan=mcp_app.lifespan,
128
  )
129
  ```
130
 
 
154
  inner_app = Starlette(routes=[Mount("/inner", app=mcp_app)])
155
  app = Starlette(
156
  routes=[Mount("/outer", app=inner_app)],
157
+ lifespan=mcp_app.lifespan,
158
  )
159
  ```
160
 
 
181
  mcp_app = mcp.http_app(path='/mcp')
182
 
183
  # Create a FastAPI app and mount the MCP server
184
+ app = FastAPI(lifespan=mcp_app.lifespan)
185
  app.mount("/mcp-server", mcp_app)
186
  ```
187
 
src/fastmcp/server/http.py CHANGED
@@ -27,7 +27,7 @@ from starlette.middleware.authentication import AuthenticationMiddleware
27
  from starlette.requests import Request
28
  from starlette.responses import Response
29
  from starlette.routing import BaseRoute, Mount, Route
30
- from starlette.types import Receive, Scope, Send
31
 
32
  from fastmcp.utilities.logging import get_logger
33
 
@@ -43,6 +43,12 @@ _current_http_request: ContextVar[Request | None] = ContextVar(
43
  )
44
 
45
 
 
 
 
 
 
 
46
  @contextmanager
47
  def set_http_request(request: Request) -> Generator[Request, None, None]:
48
  token = _current_http_request.set(request)
@@ -122,7 +128,7 @@ def create_base_app(
122
  middleware: list[Middleware],
123
  debug: bool = False,
124
  lifespan: Callable | None = None,
125
- ) -> Starlette:
126
  """Create a base Starlette app with common middleware and routes.
127
 
128
  Args:
@@ -137,7 +143,7 @@ def create_base_app(
137
  # Always add RequestContextMiddleware as the outermost middleware
138
  middleware.append(Middleware(RequestContextMiddleware))
139
 
140
- return Starlette(
141
  routes=routes,
142
  middleware=middleware,
143
  debug=debug,
@@ -157,7 +163,7 @@ def create_sse_app(
157
  debug: bool = False,
158
  routes: list[BaseRoute] | None = None,
159
  middleware: list[Middleware] | None = None,
160
- ) -> Starlette:
161
  """Return an instance of the SSE server app.
162
 
163
  Args:
@@ -262,7 +268,7 @@ def create_streamable_http_app(
262
  debug: bool = False,
263
  routes: list[BaseRoute] | None = None,
264
  middleware: list[Middleware] | None = None,
265
- ) -> Starlette:
266
  """Return an instance of the StreamableHTTP server app.
267
 
268
  Args:
 
27
  from starlette.requests import Request
28
  from starlette.responses import Response
29
  from starlette.routing import BaseRoute, Mount, Route
30
+ from starlette.types import Lifespan, Receive, Scope, Send
31
 
32
  from fastmcp.utilities.logging import get_logger
33
 
 
43
  )
44
 
45
 
46
+ class StarletteWithLifespan(Starlette):
47
+ @property
48
+ def lifespan(self) -> Lifespan:
49
+ return self.router.lifespan_context
50
+
51
+
52
  @contextmanager
53
  def set_http_request(request: Request) -> Generator[Request, None, None]:
54
  token = _current_http_request.set(request)
 
128
  middleware: list[Middleware],
129
  debug: bool = False,
130
  lifespan: Callable | None = None,
131
+ ) -> StarletteWithLifespan:
132
  """Create a base Starlette app with common middleware and routes.
133
 
134
  Args:
 
143
  # Always add RequestContextMiddleware as the outermost middleware
144
  middleware.append(Middleware(RequestContextMiddleware))
145
 
146
+ return StarletteWithLifespan(
147
  routes=routes,
148
  middleware=middleware,
149
  debug=debug,
 
163
  debug: bool = False,
164
  routes: list[BaseRoute] | None = None,
165
  middleware: list[Middleware] | None = None,
166
+ ) -> StarletteWithLifespan:
167
  """Return an instance of the SSE server app.
168
 
169
  Args:
 
268
  debug: bool = False,
269
  routes: list[BaseRoute] | None = None,
270
  middleware: list[Middleware] | None = None,
271
+ ) -> StarletteWithLifespan:
272
  """Return an instance of the StreamableHTTP server app.
273
 
274
  Args:
src/fastmcp/server/server.py CHANGED
@@ -35,7 +35,6 @@ from mcp.types import Resource as MCPResource
35
  from mcp.types import ResourceTemplate as MCPResourceTemplate
36
  from mcp.types import Tool as MCPTool
37
  from pydantic import AnyUrl
38
- from starlette.applications import Starlette
39
  from starlette.middleware import Middleware
40
  from starlette.requests import Request
41
  from starlette.responses import Response
@@ -48,7 +47,11 @@ from fastmcp.prompts import Prompt, PromptManager
48
  from fastmcp.prompts.prompt import PromptResult
49
  from fastmcp.resources import Resource, ResourceManager
50
  from fastmcp.resources.template import ResourceTemplate
51
- from fastmcp.server.http import create_sse_app
 
 
 
 
52
  from fastmcp.tools import ToolManager
53
  from fastmcp.tools.tool import Tool
54
  from fastmcp.utilities.cache import TimedCache
@@ -59,7 +62,6 @@ if TYPE_CHECKING:
59
  from fastmcp.client import Client
60
  from fastmcp.server.openapi import FastMCPOpenAPI
61
  from fastmcp.server.proxy import FastMCPProxy
62
-
63
  logger = get_logger(__name__)
64
 
65
  DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
@@ -806,7 +808,7 @@ class FastMCP(Generic[LifespanResultT]):
806
  path: str | None = None,
807
  message_path: str | None = None,
808
  middleware: list[Middleware] | None = None,
809
- ) -> Starlette:
810
  """
811
  Create a Starlette app for the SSE server.
812
 
@@ -837,7 +839,7 @@ class FastMCP(Generic[LifespanResultT]):
837
  self,
838
  path: str | None = None,
839
  middleware: list[Middleware] | None = None,
840
- ) -> Starlette:
841
  """
842
  Create a Starlette app for the StreamableHTTP server.
843
 
@@ -858,7 +860,7 @@ class FastMCP(Generic[LifespanResultT]):
858
  path: str | None = None,
859
  middleware: list[Middleware] | None = None,
860
  transport: Literal["streamable-http", "sse"] = "streamable-http",
861
- ) -> Starlette:
862
  """Create a Starlette app using the specified HTTP transport.
863
 
864
  Args:
@@ -869,7 +871,6 @@ class FastMCP(Generic[LifespanResultT]):
869
  Returns:
870
  A Starlette application configured with the specified transport
871
  """
872
- from fastmcp.server.http import create_streamable_http_app
873
 
874
  if transport == "streamable-http":
875
  return create_streamable_http_app(
 
35
  from mcp.types import ResourceTemplate as MCPResourceTemplate
36
  from mcp.types import Tool as MCPTool
37
  from pydantic import AnyUrl
 
38
  from starlette.middleware import Middleware
39
  from starlette.requests import Request
40
  from starlette.responses import Response
 
47
  from fastmcp.prompts.prompt import PromptResult
48
  from fastmcp.resources import Resource, ResourceManager
49
  from fastmcp.resources.template import ResourceTemplate
50
+ from fastmcp.server.http import (
51
+ StarletteWithLifespan,
52
+ create_sse_app,
53
+ create_streamable_http_app,
54
+ )
55
  from fastmcp.tools import ToolManager
56
  from fastmcp.tools.tool import Tool
57
  from fastmcp.utilities.cache import TimedCache
 
62
  from fastmcp.client import Client
63
  from fastmcp.server.openapi import FastMCPOpenAPI
64
  from fastmcp.server.proxy import FastMCPProxy
 
65
  logger = get_logger(__name__)
66
 
67
  DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
 
808
  path: str | None = None,
809
  message_path: str | None = None,
810
  middleware: list[Middleware] | None = None,
811
+ ) -> StarletteWithLifespan:
812
  """
813
  Create a Starlette app for the SSE server.
814
 
 
839
  self,
840
  path: str | None = None,
841
  middleware: list[Middleware] | None = None,
842
+ ) -> StarletteWithLifespan:
843
  """
844
  Create a Starlette app for the StreamableHTTP server.
845
 
 
860
  path: str | None = None,
861
  middleware: list[Middleware] | None = None,
862
  transport: Literal["streamable-http", "sse"] = "streamable-http",
863
+ ) -> StarletteWithLifespan:
864
  """Create a Starlette app using the specified HTTP transport.
865
 
866
  Args:
 
871
  Returns:
872
  A Starlette application configured with the specified transport
873
  """
 
874
 
875
  if transport == "streamable-http":
876
  return create_streamable_http_app(
tests/client/test_streamable_http.py CHANGED
@@ -119,7 +119,7 @@ def run_nested_server(host: str, port: int) -> None:
119
  mount = Starlette(routes=[Mount("/nest-inner", app=mcp_app)])
120
  mount2 = Starlette(
121
  routes=[Mount("/nest-outer", app=mount)],
122
- lifespan=mcp_app.router.lifespan_context,
123
  )
124
  server = uvicorn.Server(
125
  config=uvicorn.Config(
 
119
  mount = Starlette(routes=[Mount("/nest-inner", app=mcp_app)])
120
  mount2 = Starlette(
121
  routes=[Mount("/nest-outer", app=mount)],
122
+ lifespan=mcp_app.lifespan,
123
  )
124
  server = uvicorn.Server(
125
  config=uvicorn.Config(