{"record":{"id":"7f6e7974fce8ccdd","repo":"PrefectHQ/fastmcp","slug":"server-failed-to-start-after-max-attempts-attemp","errorCode":null,"errorMessage":"Server failed to start after {max_attempts} attempts","messagePattern":"Server failed to start after (.+?) attempts","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/utilities/tests.py","lineNumber":140,"sourceCode":"\n    # Wait for server to be running\n    max_attempts = 30\n    attempt = 0\n    while attempt < max_attempts and proc.is_alive():\n        try:\n            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:\n                s.connect((host, port))\n                break\n        except ConnectionRefusedError:\n            if attempt < 5:\n                time.sleep(0.05)\n            elif attempt < 15:\n                time.sleep(0.1)\n            else:\n                time.sleep(0.2)\n            attempt += 1\n    else:\n        raise RuntimeError(f\"Server failed to start after {max_attempts} attempts\")\n\n    yield f\"http://{host}:{port}\"\n\n    proc.terminate()\n    proc.join(timeout=5)\n    if proc.is_alive():\n        # If it's still alive, then force kill it\n        proc.kill()\n        proc.join(timeout=2)\n        if proc.is_alive():\n            raise RuntimeError(\"Server process failed to terminate even after kill\")\n\n\nasync def _wait_for_port(host: str, port: int, timeout: float = 5.0) -> None:\n    \"\"\"Poll until a TCP connection to `host:port` is accepted, or raise on timeout.\"\"\"\n    deadline = time.monotonic() + timeout\n    while True:\n        try:","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/utilities/tests.py#L122-L158","documentation":"run_server_in_process polls the spawned server subprocess with a bounded retry loop (attempts with escalating sleeps up to max_attempts). If the port never becomes reachable after max_attempts, the for/else clause raises RuntimeError. It means the server process never started listening, not that the test code is wrong per se.","triggerScenarios":"The spawned uvicorn server crashes at import/startup (bad app, missing dependency), the chosen port is already in use, the process is slow to boot past the retry window, or the server binds a different host/port than expected.","commonSituations":"CI machines with slow startup exceeding the retry budget; port conflicts with other services; exceptions in the server module under test printed in the subprocess but ignored by the test; firewall/IPv4-vs-IPv6 binding issues.","solutions":["Check the subprocess output/logs for the real startup exception and fix it","Choose a free port or bind to port 0 and derive the actual port","Increase max_attempts / retry sleeps in the helper if startup is legitimately slow","Verify the server binds the same host the poller connects to (e.g. 127.0.0.1 vs localhost/IPv6)"],"exampleFix":"// before\nwith run_server_in_process(mcp_server, port=8000) as url: ...\n\n// after\nwith run_server_in_process(mcp_server, port=0) as url: ...  # let OS pick a free port","handlingStrategy":"retry","validationCode":"import socket\ns = socket.socket(); s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\ntry:\n    s.bind((\"127.0.0.1\", port))\nfinally:\n    s.close()  # raises if port already in use","typeGuard":null,"tryCatchPattern":"try:\n    with run_server_in_process(mcp_server) as url:\n        yield url\nexcept RuntimeError as e:\n    if \"failed to start\" in str(e):\n        pytest.fail(f\"server subprocess never listened: {e}; check subprocess logs\")\n    raise","preventionTips":["Capture and assert on subprocess stderr to see real startup errors","Bind to port 0 to avoid port conflicts","Raise max_attempts on slow CI runners","Verify host binding matches the polled host"],"tags":["testing","server-startup","timeout","subprocess"],"backgroundTag":"server-startup-timeout","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}