PrefectHQ/fastmcp · error · RuntimeError

Server did not start listening on {host}:{port} within {time

Error message

Server did not start listening on {host}:{port} within {timeout} seconds

What it means

_wait_for_port repeatedly attempts an asyncio TCP connection to host:port; if the deadline passes while connections are still refused, it raises RuntimeError announcing the server never started listening. The `from None` suppresses the chained ConnectionRefusedError/OSError for a clean message.

Source

Thrown at fastmcp_slim/fastmcp/utilities/tests.py:162

    proc.terminate()
    proc.join(timeout=5)
    if proc.is_alive():
        # If it's still alive, then force kill it
        proc.kill()
        proc.join(timeout=2)
        if proc.is_alive():
            raise RuntimeError("Server process failed to terminate even after kill")


async def _wait_for_port(host: str, port: int, timeout: float = 5.0) -> None:
    """Poll until a TCP connection to `host:port` is accepted, or raise on timeout."""
    deadline = time.monotonic() + timeout
    while True:
        try:
            _, writer = await asyncio.open_connection(host, port)
        except (ConnectionRefusedError, OSError):
            if time.monotonic() >= deadline:
                raise RuntimeError(
                    f"Server did not start listening on {host}:{port} "
                    f"within {timeout} seconds"
                ) from None
            await asyncio.sleep(0.001)
        else:
            writer.close()
            with suppress(ConnectionResetError, BrokenPipeError):
                await writer.wait_closed()
            return


@asynccontextmanager
async def run_server_async(
    server: FastMCP,
    port: int | None = None,
    transport: Literal["http", "streamable-http", "sse"] = "http",
    path: str = "/mcp",
    host: str = "127.0.0.1",

View on GitHub (pinned to 1f02114297)

Solutions

  1. Confirm the server actually started and binds the expected host/port before/while polling
  2. Use 127.0.0.1 explicitly instead of localhost to avoid IPv6 mismatch
  3. Increase the timeout parameter if startup is legitimately slow
  4. Check server logs for startup exceptions

Example fix

// before
await _wait_for_port("localhost", 8000)  # may resolve to ::1

// after
await _wait_for_port("127.0.0.1", 8000, timeout=10.0)
Defensive patterns

Strategy: retry

Validate before calling

import socket
try:
    socket.create_connection(("127.0.0.1", port), timeout=1).close()
except OSError:
    ...  # port not open yet — start server before waiting

Try / catch

try:
    await _wait_for_port("127.0.0.1", port, timeout=10.0)
except RuntimeError as e:
    print(f"server not up: {e}")  # inspect server logs before proceeding

Prevention

When it happens

Trigger: Calling run_server_async (or any path using _wait_for_port) against a host:port where no server binds within the timeout (default 5s) — server not started yet, crashed, bound to a different interface, or wrong port passed.

Common situations: Wrong host (localhost resolving to ::1 while server binds 127.0.0.1); server crash during startup; slow CI machines exceeding the 5s window; connecting before the server coroutine actually starts.

Understand the failure class

Related errors


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