fix(mcp): fix SSE streaming crash and reduce get_canvas token usage

- Replace BaseHTTPMiddleware with pure ASGI middleware in auth.py to fix
  the "Unexpected message: http.response.start" crash on SSE streams
- Migrate from SseServerTransport to StreamableHTTPSessionManager in main.py
- Add _slim_canvas() in tools.py to strip React Flow layout fields from
  get_canvas responses (60-80% payload reduction)
- Update tests: assert canvas slimming, mock session_manager.handle_request
  in auth tests to avoid uninitialized task group errors
This commit is contained in:
Pouzor
2026-03-13 17:28:38 +01:00
parent e1d16b86e3
commit e41dbe579c
5 changed files with 137 additions and 27 deletions
+13 -14
View File
@@ -1,7 +1,7 @@
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi import FastAPI, Request
from mcp.server import Server
from mcp.server.sse import SseServerTransport
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from .auth import ApiKeyMiddleware
from .backend_client import backend
@@ -13,29 +13,28 @@ mcp_server = Server("homelable")
register_resources(mcp_server)
register_tools(mcp_server)
session_manager = StreamableHTTPSessionManager(
app=mcp_server,
json_response=False,
stateless=True,
)
@asynccontextmanager
async def lifespan(app: FastAPI):
await backend.start()
yield
async with session_manager.run():
yield
await backend.stop()
app = FastAPI(title="Homelable MCP", lifespan=lifespan)
app.add_middleware(ApiKeyMiddleware)
sse = SseServerTransport("/mcp/messages")
@app.get("/mcp")
async def mcp_sse(request):
async with sse.connect_sse(request.scope, request.receive, request._send) as streams:
await mcp_server.run(streams[0], streams[1], mcp_server.create_initialization_options())
@app.post("/mcp/messages")
async def mcp_messages(request):
await sse.handle_post_message(request.scope, request.receive, request._send)
@app.api_route("/mcp", methods=["GET", "POST", "DELETE"])
async def mcp_endpoint(request: Request):
await session_manager.handle_request(request.scope, request.receive, request._send)
@app.get("/health")