use async_trait::async_trait;
use everruns_core::McpServerAuthMode;
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct McpCredential {
pub authorization: Option<String>,
pub headers: HashMap<String, String>,
}
impl McpCredential {
pub fn bearer(token: impl Into<String>) -> Self {
Self {
authorization: Some(format!("Bearer {}", token.into())),
headers: HashMap::new(),
}
}
pub fn authorization(value: impl Into<String>) -> Self {
Self {
authorization: Some(value.into()),
headers: HashMap::new(),
}
}
}
pub struct McpAuthRequest<'a> {
pub server_name: &'a str,
pub auth_mode: McpServerAuthMode,
pub oauth_provider_id: Option<&'a str>,
}
#[async_trait]
pub trait McpAuthProvider: Send + Sync {
async fn authorization(
&self,
request: &McpAuthRequest<'_>,
) -> anyhow::Result<Option<McpCredential>>;
}
#[derive(Debug, Default, Clone)]
pub struct NoAuthProvider;
#[async_trait]
impl McpAuthProvider for NoAuthProvider {
async fn authorization(
&self,
_request: &McpAuthRequest<'_>,
) -> anyhow::Result<Option<McpCredential>> {
Ok(None)
}
}
#[derive(Debug, Default, Clone)]
pub struct StaticAuthProvider {
tokens: HashMap<String, McpCredential>,
}
impl StaticAuthProvider {
pub fn new() -> Self {
Self::default()
}
pub fn with_bearer(mut self, server_name: impl Into<String>, token: impl Into<String>) -> Self {
self.tokens
.insert(server_name.into(), McpCredential::bearer(token));
self
}
pub fn with_credential(
mut self,
server_name: impl Into<String>,
credential: McpCredential,
) -> Self {
self.tokens.insert(server_name.into(), credential);
self
}
}
#[async_trait]
impl McpAuthProvider for StaticAuthProvider {
async fn authorization(
&self,
request: &McpAuthRequest<'_>,
) -> anyhow::Result<Option<McpCredential>> {
Ok(self.tokens.get(request.server_name).cloned())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn static_provider_returns_bearer_by_name() {
let provider = StaticAuthProvider::new().with_bearer("docs", "secret");
let cred = provider
.authorization(&McpAuthRequest {
server_name: "docs",
auth_mode: McpServerAuthMode::ApiKey,
oauth_provider_id: None,
})
.await
.unwrap()
.unwrap();
assert_eq!(cred.authorization.as_deref(), Some("Bearer secret"));
}
#[tokio::test]
async fn static_provider_misses_unknown_server() {
let provider = StaticAuthProvider::new().with_bearer("docs", "secret");
let cred = provider
.authorization(&McpAuthRequest {
server_name: "other",
auth_mode: McpServerAuthMode::ApiKey,
oauth_provider_id: None,
})
.await
.unwrap();
assert!(cred.is_none());
}
}