PrefectHQ/fastmcp · error · RuntimeError

User server did not start on port {mcp_port}

Error message

User server did not start on port {mcp_port}

What it means

`fastmcp dev` for apps starts the dev UI only after the user's MCP server answers on the configured port. _wait_for_server polls for up to 15 seconds; if the server never becomes reachable, run_dev_apps raises this RuntimeError naming the port. It is a startup-failure guard, not a runtime crash of your server code.

Source

Thrown at fastmcp_slim/fastmcp/cli/apps_dev.py:1812

        logger.info("Fetching app-bridge.js from npm…")

        # Start the server first so user_proc is assigned before anything
        # that might fail (e.g. npm fetch).  This ensures the finally
        # cleanup can kill the subprocess even if the bundle fetch raises.
        user_proc = await _start_user_server(
            server_spec, mcp_port, reload=reload, host=host
        )
        app_bridge_js, import_map_json = await _fetch_app_bridge_bundle(
            _EXT_APPS_VERSION, _MCP_SDK_VERSION
        )

        import_map_tag = (
            f'  <script type="importmap">\n  {import_map_json}\n  </script>'
        )

        ready = await _wait_for_server(mcp_url, timeout=15.0)
        if not ready:
            raise RuntimeError(f"User server did not start on port {mcp_port}")

        logger.info(f"FastMCP dev UI at {dev_url}")

        dev_app = _make_dev_app(
            mcp_url, app_bridge_js, import_map_tag, _MessageLog(), log_panel
        )
        config = uvicorn.Config(
            dev_app,
            host=host,
            port=dev_port,
            log_level="warning",
            ws="websockets-sansio",
        )
        server = uvicorn.Server(config)
        # Suppress uvicorn's own signal handlers — they use signal.signal() which
        # conflicts with asyncio and causes hangs.  We cancel the task instead.
        server.install_signal_handlers = lambda: None  # type: ignore[method-assign]  # ty:ignore[unresolved-attribute]

View on GitHub (pinned to 1f02114297)

Solutions

  1. Run your server directly (e.g. fastmcp run or python your_server.py) to see the actual boot error
  2. Check the port isn't already in use (lsof -i :PORT) and kill the stale process
  3. Confirm host/port match between your server binding and the dev command's mcp_port
  4. If the server just starts slowly, increase the timeout or pre-warm imports

Example fix

# before: server binds elsewhere
mcp.run(host='0.0.0.0', port=9999)  # dev expects 8000
# after
mcp.run(host='127.0.0.1', port=8000)
Defensive patterns

Strategy: retry

Validate before calling

import socket
s = socket.socket()
s.settimeout(2)
try:
    s.connect(('127.0.0.1', mcp_port)); print('port in use')
except OSError:
    print('port free; server not running yet')
finally:
    s.close()

Try / catch

try:
    run_dev_apps(...)
except RuntimeError as exc:
    if str(exc).startswith('User server did not start'):
        logger.error('MCP server failed to boot; run it directly to see the underlying error')
    else:
        raise

Prevention

When it happens

Trigger: The MCP server process failed to boot (import error, bad config, port already bound by another process), or it listens on a different host/port than mcp_url expects, and the 15s poll times out.

Common situations: Syntax/import error in the user's server module; another process occupying the port; server bound to 0.0.0.0:9999 while the dev UI probes a different port; slow imports (heavy ML libs) exceeding the 15s timeout.

Related errors


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