use crate::errors::ConfigValidationError;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConnectorConfig {
#[serde(default = "generic_adapter")]
pub adapter: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(default)]
pub scopes: Vec<String>,
#[serde(default)]
pub authorization_origins: Vec<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub authorization_params: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub identity: Option<ConnectorIdentity>,
#[serde(default)]
pub client_id_secret: Option<String>,
#[serde(default)]
pub client_secret: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ConnectorIdentity {
Userinfo,
}
impl ConnectorConfig {
pub const ALLOWED_AUTHORIZATION_PARAMS: [&'static str; 4] =
["access_type", "prompt", "login_hint", "hd"];
pub fn validate(&self, name: &str) -> Result<(), ConfigValidationError> {
for (key, value) in &self.authorization_params {
if !Self::ALLOWED_AUTHORIZATION_PARAMS.contains(&key.as_str()) {
return Err(ConfigValidationError::invalid_field(format!(
"MCP server '{name}': connector authorization_params.{key} is not an \
allowed parameter (allowed: {})",
Self::ALLOWED_AUTHORIZATION_PARAMS.join(", ")
)));
}
if value.is_empty()
|| value
.chars()
.any(|c| c.is_whitespace() || "&=#".contains(c))
{
return Err(ConfigValidationError::invalid_field(format!(
"MCP server '{name}': connector authorization_params.{key} must be a \
single non-empty token"
)));
}
}
if self.identity == Some(ConnectorIdentity::Userinfo)
&& !self.scopes.iter().any(|scope| scope == "openid")
{
return Err(ConfigValidationError::invalid_field(format!(
"MCP server '{name}': connector identity: userinfo requires the openid scope"
)));
}
Ok(())
}
}
fn generic_adapter() -> String {
"generic".to_owned()
}