PrefectHQ/fastmcp · error · ValueError

No server URL found in OpenAPI spec. Either add a 'servers'

Error message

No server URL found in OpenAPI spec. Either add a 'servers' entry to the spec or provide an httpx2.AsyncClient explicitly.

What it means

When no explicit httpx.AsyncClient is supplied, the provider tries to derive a base URL from the spec's first servers entry; if servers is missing/empty or the first entry has no url, it raises this ValueError telling you to add a servers entry or pass a client explicitly.

Source

Thrown at fastmcp_slim/fastmcp/server/providers/openapi/provider.py:193

            route_tags = set(route.tags) | route_map.mcp_tags | (tags or set())

            if route_type == MCPType.TOOL:
                self._create_openapi_tool(route, component_name, tags=route_tags)
            elif route_type == MCPType.RESOURCE:
                self._create_openapi_resource(route, component_name, tags=route_tags)
            elif route_type == MCPType.RESOURCE_TEMPLATE:
                self._create_openapi_template(route, component_name, tags=route_tags)
            elif route_type == MCPType.EXCLUDE:
                logger.debug(f"Excluding route: {route.method} {route.path}")

        logger.debug(f"Created OpenAPIProvider with {len(http_routes)} routes")

    @classmethod
    def _create_default_client(cls, openapi_spec: dict[str, Any]) -> httpx2.AsyncClient:
        """Create a default httpx client from the OpenAPI spec's server URL."""
        servers = openapi_spec.get("servers", [])
        if not servers or not servers[0].get("url"):
            raise ValueError(
                "No server URL found in OpenAPI spec. Either add a 'servers' "
                "entry to the spec or provide an httpx2.AsyncClient explicitly."
            )
        base_url = servers[0]["url"]
        variables = servers[0].get("variables", {})
        for name, var in variables.items():
            base_url = base_url.replace(f"{{{name}}}", var.get("default", ""))
        return httpx2.AsyncClient(base_url=base_url, timeout=DEFAULT_TIMEOUT)

    @asynccontextmanager
    async def lifespan(self) -> AsyncIterator[None]:
        """Manage the lifecycle of the auto-created httpx client."""
        if self._owns_client:
            async with self._client:
                yield
        else:
            yield

View on GitHub (pinned to 1f02114297)

Solutions

  1. Add a servers entry to the spec: spec['servers'] = [{'url': 'https://api.example.com'}]
  2. Pass an explicit client: FastMCPProvider(spec, client=httpx.AsyncClient(base_url='https://api.example.com'))
  3. Convert Swagger 2.0 host/basePath to an OpenAPI 3 servers entry

Example fix

// before
spec = {'openapi': '3.1.0', 'paths': {...}}
provider = FastMCPProvider(spec)
// after
spec = {'openapi': '3.1.0', 'servers': [{'url': 'https://api.example.com'}], 'paths': {...}}
provider = FastMCPProvider(spec)
Defensive patterns

Strategy: validation

Validate before calling

def ensure_server_url(spec: dict) -> dict:
    servers = spec.get('servers')
    if not servers or not servers[0].get('url'):
        raise ValueError('spec needs a servers[0].url before creating a provider')
    return spec

Type guard

def has_server_url(spec: object) -> bool:
    return (
        isinstance(spec, dict)
        and bool(spec.get('servers'))
        and bool(spec['servers'][0].get('url'))
    )

Try / catch

try:
    provider = FastMCPProvider(spec)
except ValueError as e:
    if 'No server URL found' in str(e):
        provider = FastMCPProvider(spec, client=httpx.AsyncClient(base_url=DEFAULT_BASE_URL))
    else:
        raise

Prevention

When it happens

Trigger: Constructing OpenAPIProvider/FastMCPProvider from a spec dict without a 'servers' key (or with servers: [{}]) and without providing a client.

Common situations: Specs written for Swagger 2.0 use 'host'/'basePath' instead of 'servers'; specs trimmed down for testing with servers removed; relative server URLs used with an assumed host.

Related errors


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