Hmbown/CodeWhale · error

stored MCP OAuth token for '{server_name}' is not valid cred

Error message

stored MCP OAuth token for '{server_name}' is not valid credential JSON; contents were omitted

What it means

Stored MCP OAuth tokens live in the OS secrets store under a key derived from server name and URL, serialized as JSON. On load, the raw value is deserialized into StoredMcpOAuthTokens; if it is not that JSON shape the load fails with this message, which intentionally omits the stored contents because they are credentials.

Source

Thrown at crates/tui/src/mcp/oauth.rs:650

}

fn load_oauth_tokens(server_name: &str, url: &str) -> Result<Option<StoredMcpOAuthTokens>> {
    let secrets = codewhale_secrets::Secrets::auto_detect();
    let key = store_key(server_name, url);
    let Some(serialized) = secrets
        .get(&key)
        .with_context(|| format!("reading MCP OAuth token for '{server_name}'"))?
    else {
        return Ok(None);
    };
    let mut tokens = parse_stored_oauth_tokens(&serialized, server_name)?;
    refresh_expires_in_from_timestamp(&mut tokens);
    Ok(Some(tokens))
}

fn parse_stored_oauth_tokens(serialized: &str, server_name: &str) -> Result<StoredMcpOAuthTokens> {
    serde_json::from_str(serialized).map_err(|_| {
        anyhow!(
            "stored MCP OAuth token for '{server_name}' is not valid credential JSON; contents were omitted"
        )
    })
}

fn save_oauth_tokens(tokens: &StoredMcpOAuthTokens) -> Result<()> {
    let secrets = codewhale_secrets::Secrets::auto_detect();
    let key = store_key(&tokens.server_name, &tokens.url);
    let serialized = serde_json::to_string(tokens).context("serializing MCP OAuth token")?;
    secrets
        .set(&key, &serialized)
        .with_context(|| format!("saving MCP OAuth token for '{}'", tokens.server_name))
}

fn delete_oauth_tokens(server_name: &str, url: &str) -> Result<bool> {
    let secrets = codewhale_secrets::Secrets::auto_detect();
    let key = store_key(server_name, url);
    let existed = secrets

View on GitHub (pinned to 8880682c63)

Solutions

  1. Delete the stored entry for that server (via the secrets/keychain manager) so the next connect re-runs OAuth and stores fresh tokens
  2. Re-authenticate the server (remove and re-add, or the login flow) to overwrite the entry
  3. Ensure all codewhale binaries on the machine are the same version so they agree on the token schema
Defensive patterns

Strategy: fallback

Validate before calling

// Validate the stored token shape before relying on it:
if let Some(serialized) = secrets.get(&store_key(server_name, url))? {
    if serde_json::from_str::<StoredMcpOAuthTokens>(&serialized).is_err() {
        // clear the unusable entry now so the next connect re-runs OAuth instead of failing later
        secrets.delete(&store_key(server_name, url))?;
    }
}

Type guard

fn is_valid_stored_token(raw: &str) -> bool {
    serde_json::from_str::<StoredMcpOAuthTokens>(raw).is_ok()
}

Try / catch

match load_oauth_tokens(server_name, url).await {
    Err(e) if e.to_string().contains("not valid credential JSON") => {
        // delete the corrupted secrets-store entry and fall back to the interactive OAuth flow
    }
    other => other,
}

Prevention

When it happens

Trigger: load_oauth_tokens reads a store entry that is not StoredMcpOAuthTokens JSON - a corrupted entry, a manual edit of the key, or a different codewhale version that wrote another schema to the same key.

Common situations: Upgrading across versions that changed the token schema; a keychain entry damaged or synced between machines; a raw access token pasted into the store; the server URL changed so the derived key now collides with a foreign entry.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/357292ad7dd5d271. Report an issue: GitHub.