StoredCredentials was marked #[non_exhaustive] in #715 / #768, but unlike OAuthClientConfig (which got a new() in the same PR), StoredCredentials doesn't have a constructor or Default impl. External crates that implement CredentialStore and need to construct StoredCredentials in their save() path don't have a compile-safe way to do so.
The workaround we landed on is a serde JSON roundtrip:
let credentials: StoredCredentials = serde_json::from_value(serde_json::json!({
"client_id": client_id,
"token_response": token_response,
"granted_scopes": granted_scopes,
"token_received_at": timestamp,
}))?;
This bypasses #[non_exhaustive] via Deserialize but turns what should be a compile-time error (if a new required field is added) into a silent runtime failure during OAuth.
We ran into this in block/goose after bumping to rmcp 1.3.0 for Unix socket transport support (#749).
Suggested fix: A constructor matching the pattern used for OAuthClientConfig:
impl StoredCredentials {
pub fn new(
client_id: String,
token_response: Option<OAuthTokenResponse>,
granted_scopes: Vec<String>,
token_received_at: Option<u64>,
) -> Self {
Self {
client_id,
token_response,
granted_scopes,
token_received_at,
}
}
}