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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions