PrefectHQ/fastmcp · error · RuntimeError

{new_error_message}\nOriginal error: {e}

Error message

{new_error_message}\nOriginal error: {e}

What it means

When a RuntimeError other than the 'task group not initialized' sentinel escapes the ASGI wrapper, FastMCP inspects it and, if it matches its known-cause pattern, re-raises a new RuntimeError that leads with guidance (return the app via fastmcp_instance.http_app(), see the ASGI docs) and appends '\nOriginal error: <e>' for full context.

Source

Thrown at fastmcp_slim/fastmcp/server/http.py:110

            await self.session_manager.handle_request(scope, receive, send)
        except RuntimeError as e:
            if str(e) == "Task group is not initialized. Make sure to use run().":
                logger.error(
                    f"Original RuntimeError from mcp library: {e}", exc_info=True
                )
                new_error_message = (
                    "FastMCP's StreamableHTTPSessionManager task group was not initialized. "
                    "This commonly occurs when the FastMCP application's lifespan is not "
                    "passed to the parent ASGI application (e.g., FastAPI or Starlette). "
                    "Please ensure you are setting `lifespan=mcp_app.lifespan` in your "
                    "parent app's constructor, where `mcp_app` is the application instance "
                    "returned by `fastmcp_instance.http_app()`. \\n"
                    "For more details, see the FastMCP ASGI integration documentation: "
                    "https://gofastmcp.com/deployment/asgi"
                )
                # Raise a new RuntimeError that includes the original error's message
                # for full context, but leads with the more helpful guidance.
                raise RuntimeError(f"{new_error_message}\\nOriginal error: {e}") from e
            else:
                # Re-raise other RuntimeErrors if they don't match the specific message
                raise


def _normalize_host(host: str) -> str:
    host = host.strip().lower()
    if not host:
        return ""

    if host.startswith("["):
        end = host.find("]")
        if end == -1:
            return host
        return host[1:end]

    if host.count(":") == 1:
        return host.rsplit(":", 1)[0]

View on GitHub (pinned to 1f02114297)

Solutions

  1. Serve the app returned by fastmcp_instance.http_app() rather than constructing/mounting internals yourself.
  2. Follow the ASGI integration docs (https://gofastmcp.com/deployment/asgi) for correct mounting and lifespan wiring.
  3. Ensure the session manager's run() executes via the app lifespan before serving requests.
  4. Read the 'Original error' portion of the message to find the underlying cause and fix it.

Example fix

// before
app = FastMCP("x")
starlette_app.mount("/mcp", app._streamable_http_app_raw())
// after
http_app = app.http_app(path="/mcp")
starlette_app.mount("/", http_app)  # lifespan included
Defensive patterns

Strategy: try-catch

Validate before calling

# verify the app came from http_app()
assert hasattr(fastmcp_instance, "http_app")
asgi = fastmcp_instance.http_app(path="/mcp")

Try / catch

try:
    await asgi_app(scope, receive, send)
except RuntimeError as e:
    if "fastmcp_instance.http_app" in str(e):
        logger.error("%s | underlying: %s", e, e.__cause__)
    raise

Prevention

When it happens

Trigger: A RuntimeError raised during request handling in the StreamableHTTP session manager that matches FastMCP's known misconfiguration pattern — typically running the ASGI app incorrectly (e.g. the session manager not running due to missing lifespan).

Common situations: Mounting the streamable-http app without its lifespan; wrapping the ASGI callable manually instead of using http_app(); deployment misconfigurations where the session manager task group is not started.

Related errors


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