PrefectHQ/fastmcp · error · ValueError

JWKS document contains no keys

Error message

JWKS document contains no keys

What it means

This error means the JWKS document fetched (or supplied) for a CIMD client contains an empty `keys` array, so no public key can be extracted to verify a client assertion (private_key_jwt). The library raises it early rather than failing later with a confusing 'no matching key' error. It is a ValueError wrapping the key-selection step of assertion validation.

Source

Thrown at fastmcp_slim/fastmcp/server/auth/cimd.py:678

        Returns:
            PEM-encoded public key

        Raises:
            ValueError: If key cannot be found or extracted
        """
        # Extract kid from token header
        try:
            header_b64 = token.split(".")[0]
            header_b64 += "=" * (4 - len(header_b64) % 4)  # Add padding
            header = json.loads(base64.urlsafe_b64decode(header_b64))
            kid = header.get("kid")
        except (IndexError, ValueError, json.JSONDecodeError) as e:
            raise ValueError(f"Failed to extract key ID from token: {e}") from e

        # Find matching key in JWKS
        keys = jwks.get("keys", [])
        if not keys:
            raise ValueError("JWKS document contains no keys")

        matching_key = None
        for key in keys:
            if kid and key.get("kid") == kid:
                matching_key = key
                break

        if not matching_key:
            # If no kid match, try first key as fallback
            if len(keys) == 1:
                matching_key = keys[0]
                self.logger.warning(
                    "No matching kid in JWKS, using single available key"
                )
            else:
                raise ValueError(f"No matching key found for kid={kid} in JWKS")

        # Convert JWK to PEM

View on GitHub (pinned to 1f02114297)

Solutions

  1. Inspect the JWKS document at the CIMD client's jwks_uri and confirm it contains a non-empty `keys` array with the signing key
  2. Fix the issuer's JWKS publishing endpoint so it serves the public keys (e.g. re-export the key set)
  3. If the JWKS is embedded in the CIMD document, update the metadata to include the `keys` array
  4. Re-fetch after the issuer rotates keys — the empty set may be transient during rotation

Example fix

// before (bad JWKS served by issuer)
{"keys": []}
// after
{"keys": [{"kty": "RSA", "kid": "key-1", "n": "...", "e": "AQAB"}]}
Defensive patterns

Strategy: validation

Validate before calling

import json, urllib.request
jwks = json.load(urllib.request.urlopen(client_meta["jwks_uri"]))
assert isinstance(jwks.get("keys"), list) and len(jwks["keys"]) > 0, "JWKS has no keys"

Type guard

def has_keys(jwks: dict) -> bool:
    keys = jwks.get("keys")
    return isinstance(keys, list) and len(keys) > 0

Try / catch

try:
    key = extract_public_key(jwks, kid)
except ValueError as e:
    if "contains no keys" in str(e):
        logger.error("Issuer JWKS is empty; check jwks_uri %s", jwks_uri)
    raise

Prevention

When it happens

Trigger: `_extract_public_key_from_jwks` is called via `validate_assertion` with a JWKS document (from the client's CIMD metadata, e.g. `jwks_uri` or embedded `jwks`) whose parsed JSON has no `keys` member or `keys: []`.

Common situations: Authorization server publishes a malformed/placeholder JWKS at the jwks_uri in the CIMD document; a static/mocked JWKS file used in testing is empty; the JWKS fetch succeeded but the issuer rotated keys and returned an empty set; misconfigured jwks_uri pointing to a non-JWKS JSON doc that happens to parse.

Related errors


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