Skip to main content

mcp_kit/auth/
composite.rs

1use crate::{
2    auth::{
3        credentials::Credentials,
4        provider::{AuthFuture, AuthProvider, DynAuthProvider},
5    },
6    error::McpError,
7};
8
9/// Tries a list of [`AuthProvider`]s in order, returning the first successful result.
10///
11/// Providers are tested with [`AuthProvider::accepts`] first; if no provider
12/// accepts the credential type, `Unauthorized` is returned immediately.
13/// If a matching provider returns `Unauthorized`, the next matching provider is tried.
14///
15/// # Examples
16///
17/// ```rust,no_run
18/// use mcp_kit::auth::{BearerTokenProvider, ApiKeyProvider, CompositeAuthProvider, IntoDynProvider};
19///
20/// let provider = CompositeAuthProvider::new(vec![
21///     BearerTokenProvider::new(["token-abc"]).into_dyn(),
22///     ApiKeyProvider::new(["key-xyz"]).into_dyn(),
23/// ]);
24/// ```
25pub struct CompositeAuthProvider {
26    providers: Vec<DynAuthProvider>,
27}
28
29impl CompositeAuthProvider {
30    /// Create a composite from an ordered list of type-erased providers.
31    pub fn new(providers: Vec<DynAuthProvider>) -> Self {
32        Self { providers }
33    }
34}
35
36impl AuthProvider for CompositeAuthProvider {
37    fn authenticate<'a>(&'a self, credentials: &'a Credentials) -> AuthFuture<'a> {
38        Box::pin(async move {
39            let mut last_err: Option<McpError> = None;
40
41            for provider in &self.providers {
42                if !provider.accepts(credentials) {
43                    continue;
44                }
45                match provider.authenticate(credentials).await {
46                    Ok(identity) => return Ok(identity),
47                    Err(e) => last_err = Some(e),
48                }
49            }
50
51            Err(last_err.unwrap_or_else(|| {
52                McpError::Unauthorized(format!(
53                    "no provider accepts '{}' credentials",
54                    credentials.kind()
55                ))
56            }))
57        })
58    }
59
60    fn accepts(&self, credentials: &Credentials) -> bool {
61        self.providers.iter().any(|p| p.accepts(credentials))
62    }
63}
64
65/// Extension trait that makes it ergonomic to convert a concrete provider into a
66/// `DynAuthProvider` for use with [`CompositeAuthProvider`].
67pub trait IntoDynProvider: AuthProvider + Sized {
68    fn into_dyn(self) -> DynAuthProvider {
69        std::sync::Arc::new(self)
70    }
71}
72
73impl<T: AuthProvider> IntoDynProvider for T {}