|
2 | 2 |
|
3 | 3 | from __future__ import annotations as _annotations
|
4 | 4 |
|
| 5 | +import contextlib |
5 | 6 | import inspect
|
6 | 7 | import re
|
7 | 8 | from collections.abc import AsyncIterator, Awaitable, Callable, Collection, Iterable, Sequence
|
@@ -845,14 +846,21 @@ def decorator(
|
845 | 846 |
|
846 | 847 | return decorator
|
847 | 848 |
|
| 849 | + @contextlib.asynccontextmanager |
| 850 | + async def _stdio_lifespan(self) -> AsyncIterator[None]: |
| 851 | + """Lifespan that manages stdio operations.""" |
| 852 | + async with self._async_operations.run(): |
| 853 | + yield |
| 854 | + |
848 | 855 | async def run_stdio_async(self) -> None:
|
849 | 856 | """Run the server using stdio transport."""
|
850 | 857 | async with stdio_server() as (read_stream, write_stream):
|
851 |
| - await self._mcp_server.run( |
852 |
| - read_stream, |
853 |
| - write_stream, |
854 |
| - self._mcp_server.create_initialization_options(), |
855 |
| - ) |
| 858 | + async with self._stdio_lifespan(): |
| 859 | + await self._mcp_server.run( |
| 860 | + read_stream, |
| 861 | + write_stream, |
| 862 | + self._mcp_server.create_initialization_options(), |
| 863 | + ) |
856 | 864 |
|
857 | 865 | async def run_sse_async(self, mount_path: str | None = None) -> None:
|
858 | 866 | """Run the server using SSE transport."""
|
@@ -910,6 +918,12 @@ def _normalize_path(self, mount_path: str, endpoint: str) -> str:
|
910 | 918 | # Combine paths
|
911 | 919 | return mount_path + endpoint
|
912 | 920 |
|
| 921 | + @contextlib.asynccontextmanager |
| 922 | + async def _sse_lifespan(self) -> AsyncIterator[None]: |
| 923 | + """Lifespan that manages SSE operations.""" |
| 924 | + async with self._async_operations.run(): |
| 925 | + yield |
| 926 | + |
913 | 927 | def sse_app(self, mount_path: str | None = None) -> Starlette:
|
914 | 928 | """Return an instance of the SSE server app."""
|
915 | 929 | from starlette.middleware import Middleware
|
@@ -1040,7 +1054,16 @@ async def sse_endpoint(request: Request) -> Response:
|
1040 | 1054 | routes.extend(self._custom_starlette_routes)
|
1041 | 1055 |
|
1042 | 1056 | # Create Starlette app with routes and middleware
|
1043 |
| - return Starlette(debug=self.settings.debug, routes=routes, middleware=middleware) |
| 1057 | + return Starlette( |
| 1058 | + debug=self.settings.debug, routes=routes, middleware=middleware, lifespan=lambda app: self._sse_lifespan() |
| 1059 | + ) |
| 1060 | + |
| 1061 | + @contextlib.asynccontextmanager |
| 1062 | + async def _streamable_http_lifespan(self) -> AsyncIterator[None]: |
| 1063 | + """Lifespan that manages Streamable HTTP operations.""" |
| 1064 | + async with self.session_manager.run(): |
| 1065 | + async with self._async_operations.run(): |
| 1066 | + yield |
1044 | 1067 |
|
1045 | 1068 | def streamable_http_app(self) -> Starlette:
|
1046 | 1069 | """Return an instance of the StreamableHTTP server app."""
|
@@ -1135,7 +1158,7 @@ def streamable_http_app(self) -> Starlette:
|
1135 | 1158 | debug=self.settings.debug,
|
1136 | 1159 | routes=routes,
|
1137 | 1160 | middleware=middleware,
|
1138 |
| - lifespan=lambda app: self.session_manager.run(), |
| 1161 | + lifespan=lambda app: self._streamable_http_lifespan(), |
1139 | 1162 | )
|
1140 | 1163 |
|
1141 | 1164 | async def list_prompts(self) -> list[MCPPrompt]:
|
@@ -1337,12 +1360,17 @@ async def log(
|
1337 | 1360 | logger_name: Optional logger name
|
1338 | 1361 | **extra: Additional structured data to include
|
1339 | 1362 | """
|
1340 |
| - await self.request_context.session.send_log_message( |
1341 |
| - level=level, |
1342 |
| - data=message, |
1343 |
| - logger=logger_name, |
1344 |
| - related_request_id=self.request_id, |
1345 |
| - ) |
| 1363 | + try: |
| 1364 | + await self.request_context.session.send_log_message( |
| 1365 | + level=level, |
| 1366 | + data=message, |
| 1367 | + logger=logger_name, |
| 1368 | + related_request_id=self.request_id, |
| 1369 | + ) |
| 1370 | + except Exception: |
| 1371 | + # Session might be closed (e.g., client disconnected) |
| 1372 | + logger.warning(f"Failed to send log message to client (session closed?): {message}") |
| 1373 | + pass |
1346 | 1374 |
|
1347 | 1375 | @property
|
1348 | 1376 | def client_id(self) -> str | None:
|
|
0 commit comments