PrefectHQ/fastmcp · error · StateFileError

The Horizon API key is invalid

Error message

The Horizon API key is invalid

What it means

CredentialStore.save validates the key by constructing AuthState; any pydantic ValidationError (e.g. empty key) is caught and converted to StateFileError('The Horizon API key is invalid') so callers get a single CLI-friendly error type instead of a pydantic traceback.

Source

Thrown at fastmcp_slim/fastmcp/cli/deploy/credentials.py:75

class CredentialStore:
    """Persist the active personal Horizon API key."""

    def __init__(self, state_directory: Path | None = None) -> None:
        if state_directory is None:
            import fastmcp

            state_directory = fastmcp.settings.home / "cli"
        self.path = state_directory / "auth.json"

    def load(self) -> SecretStr | None:
        state = read_state(self.path, AuthState, secret=True)
        return state.api_key if state is not None else None

    def save(self, api_key: SecretStr | str) -> None:
        try:
            state = AuthState(schemaVersion=1, apiKey=api_key)
        except ValidationError:
            raise StateFileError("The Horizon API key is invalid") from None
        write_state(
            self.path,
            {
                "schemaVersion": state.schema_version,
                "apiKey": state.api_key.get_secret_value(),
            },
        )

    def save_for_origin(
        self,
        api_key: SecretStr | str,
        *,
        expected_api_origin: str,
    ) -> None:
        """Save a key only while its issuing Horizon origin is active."""
        from fastmcp.cli.deploy.configuration import ConfigurationStore

        expected_api_origin = normalize_api_origin(expected_api_origin)

View on GitHub (pinned to 1f02114297)

Solutions

  1. Pass a non-empty Horizon API key string/SecretStr to save()
  2. Strip whitespace from the key before saving
  3. Re-run `fastmcp deploy login` to obtain and store a valid key
  4. Inspect the upstream secret source for empty values

Example fix

// before
store.save(config.get("apiKey"))  # None/empty -> StateFileError
// after
key = (config.get("apiKey") or "").strip()
if key:
    store.save(key)
Defensive patterns

Strategy: validation

Validate before calling

key = os.environ.get("HORIZON_API_KEY", "").strip()
if not key:
    raise SystemExit("HORIZON_API_KEY is not set")

Type guard

def is_valid_api_key(key: str | SecretStr) -> bool:
    raw = key.get_secret_value() if isinstance(key, SecretStr) else key
    return bool(raw and raw.strip())

Try / catch

from fastmcp.cli.deploy.credentials import StateFileError
try:
    store.save(api_key)
except StateFileError as e:
    print(f"Credential not saved: {e}; re-run `fastmcp deploy login`")

Prevention

When it happens

Trigger: Calling save (directly, via save_for_origin, or via resolve_credential) with a SecretStr or str that fails AuthState validation — most commonly empty/whitespace values.

Common situations: Login scripts reading an unset env var; pasted keys with stray whitespace; empty secrets in CI secret managers surfaced as ''.

Related errors


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