Skip to content

StoredCredentials needs a constructor — #[non_exhaustive] without new() makes it unconstructable from external crates #777

@wpfleger96

Description

@wpfleger96

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,
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions