PrefectHQ/fastmcp · error · RuntimeError

Unexpected ASGI message type

Error message

Unexpected ASGI message type: {message['type']}

What it means

During ASGI response streaming, the transport's send_response handler accepts only 'http.response.start' and 'http.response.body' messages. Any other ASGI message type (e.g. lifecycle or websocket messages on an HTTP response path) triggers this RuntimeError.

Solutions

  1. Inspect the ASGI app/middleware for messages emitted outside the HTTP response contract
  2. Ensure the request scope is a plain HTTP request when using this transport
  3. Log the offending message['type'] to identify which component misbehaves
Defensive patterns

Strategy: try-catch

Validate before calling

# Ensure scope type is http before sending requests through the transport
assert scope["type"] == "http"

Try / catch

try:
    response = await client.send(request)
except RuntimeError as e:
    if "Unexpected ASGI message type" in str(e):
        logger.error("ASGI app emitted non-HTTP message: %s", e)
        # inspect middleware / app message emission
    else:
        raise

Prevention

When it happens

Trigger: The ASGI app sends a message type other than http.response.start/body while the transport is reading the HTTP response, such as 'http.disconnect' misuse or websocket-scope messages.

Common situations: ASGI apps with buggy custom middleware sending non-HTTP messages; using the transport against a websocket or lifespan scope; framework upgrades changing message emission.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/3d9f9b1f2ca96070. Report an issue: GitHub.

Appendix: source

Thrown at fastmcp_slim/fastmcp/utilities/asgi_transport.py:181

                request_delivered = True
                return {
                    "type": "http.request",
                    "body": request_body,
                    "more_body": False,
                }
            await client_disconnected.wait()
            return {"type": "http.disconnect"}

        async def send_response(message: Message) -> None:
            nonlocal response_status, response_headers, start_received
            if message["type"] == "http.response.start":
                start_received = True
                response_status = message["status"]
                response_headers = list(message.get("headers", []))
                response_started.set()
                return
            if message["type"] != "http.response.body":
                raise RuntimeError(f"Unexpected ASGI message type: {message['type']}")
            body: bytes = message.get("body", b"")
            if body:
                await chunk_writer.send(body)
            if not message.get("more_body", False):
                await chunk_writer.aclose()

        async def run_application() -> None:
            nonlocal application_error
            try:
                await self._app(scope, receive_request, send_response)
            except Exception as exc:
                # The bridge is the application's outermost boundary: a crash must fail the
                # originating request (or show up in the already-started response), never
                # tear down the task group shared with every other in-flight request.
                application_error = exc
            finally:
                response_started.set()
                await chunk_writer.aclose()

View on GitHub (pinned to 1f02114297)