remote-mcp-kernel 0.1.0-alpha.2

A microkernel-based MCP (Model Context Protocol) server with OAuth authentication and multiple transport protocols
Documentation
//! Configuration management for MCP OAuth server
//!
//! This module provides centralized configuration management following
//! the principle of single responsibility and making configuration
//! explicit and testable.

use std::env;

/// Application configuration
#[derive(Debug, Clone)]
pub struct Config {
    pub server: ServerConfig,
    pub oauth: OAuthConfig,
    pub github: GitHubConfig,
    pub cognito: CognitoConfig,
    pub logging: LoggingConfig,
}

/// Server configuration
#[derive(Debug, Clone)]
pub struct ServerConfig {
    pub bind_address: String,
    pub host: String,
    pub port: u16,
    pub name: String,
    pub version: String,
    pub description: String,
}

/// OAuth provider configuration
#[derive(Debug, Clone)]
pub struct OAuthConfig {
    pub authorization_endpoint: String,
    pub token_endpoint: String,
    pub client_registration_endpoint: Option<String>,
    pub scopes_supported: Vec<String>,
    pub allow_implicit_flow: bool,
    pub allow_public_client_registration: bool,
    pub cors_max_age: Option<u64>,
}

/// GitHub OAuth configuration
#[derive(Debug, Clone)]
pub struct GitHubConfig {
    pub client_id: String,
    pub client_secret: String,
    pub redirect_uri: String,
    pub scope: String,
}

/// AWS Cognito OAuth configuration
#[derive(Debug, Clone)]
pub struct CognitoConfig {
    pub client_id: String,
    pub client_secret: Option<String>,
    pub redirect_uri: String,
    pub scope: String,
    pub cognito_domain: String,
    pub region: String,
    pub user_pool_id: String,
}

/// Logging configuration
#[derive(Debug, Clone)]
pub struct LoggingConfig {
    pub level: String,
    pub format: String,
}

impl Config {
    /// Create configuration with default values (for testing/development)
    pub fn default() -> Self {
        Config {
            server: ServerConfig {
                bind_address: "0.0.0.0:8080".to_string(),
                host: "localhost".to_string(),
                port: 8080,
                name: "MCP OAuth Server".to_string(),
                version: env!("CARGO_PKG_VERSION").to_string(),
                description: "MCP server with OAuth authentication capabilities".to_string(),
            },
            oauth: OAuthConfig {
                authorization_endpoint: "/oauth/authorize".to_string(),
                token_endpoint: "/oauth/token".to_string(),
                client_registration_endpoint: Some("/oauth/register".to_string()),
                scopes_supported: vec!["read".to_string(), "write".to_string()],
                allow_implicit_flow: false,
                allow_public_client_registration: true,
                cors_max_age: Some(86400),
            },
            github: GitHubConfig {
                client_id: "".to_string(),
                client_secret: "".to_string(),
                redirect_uri: "http://localhost:8080/oauth/callback".to_string(),
                scope: "read:user".to_string(),
            },
            cognito: CognitoConfig {
                client_id: "".to_string(),
                client_secret: None,
                redirect_uri: "http://localhost:8080/oauth/callback".to_string(),
                scope: "openid email profile phone".to_string(),
                cognito_domain: "mydomain.auth.us-east-1.amazoncognito.com".to_string(),
                region: "us-east-1".to_string(),
                user_pool_id: "us-east-1_XXXXXXXXX".to_string(),
            },
            logging: LoggingConfig {
                level: "info".to_string(),
                format: "json".to_string(),
            },
        }
    }

    /// Load configuration from environment variables with sensible defaults
    pub fn from_env() -> Result<Self, ConfigError> {
        let host = env::var("MCP_HOST").unwrap_or_else(|_| "localhost".to_string());
        let port = env::var("MCP_PORT")
            .unwrap_or_else(|_| "8080".to_string())
            .parse::<u16>()
            .map_err(|_| ConfigError::InvalidPort)?;

        let bind_address =
            env::var("MCP_BIND_ADDRESS").unwrap_or_else(|_| format!("0.0.0.0:{}", port));

        let github_client_id =
            env::var("GITHUB_CLIENT_ID").map_err(|_| ConfigError::MissingGitHubClientId)?;
        let github_client_secret =
            env::var("GITHUB_CLIENT_SECRET").map_err(|_| ConfigError::MissingGitHubClientSecret)?;

        let cognito_client_id =
            env::var("COGNITO_CLIENT_ID").map_err(|_| ConfigError::MissingCognitoClientId)?;
        let cognito_client_secret = env::var("COGNITO_CLIENT_SECRET").ok();
        let cognito_domain =
            env::var("COGNITO_DOMAIN").map_err(|_| ConfigError::MissingCognitoDomain)?;
        let cognito_region =
            env::var("COGNITO_REGION").map_err(|_| ConfigError::MissingCognitoRegion)?;
        let cognito_user_pool_id =
            env::var("COGNITO_USER_POOL_ID").map_err(|_| ConfigError::MissingCognitoUserPoolId)?;

        Ok(Config {
            server: ServerConfig {
                bind_address,
                host: host.clone(),
                port,
                name: "MCP OAuth Server".to_string(),
                version: env!("CARGO_PKG_VERSION").to_string(),
                description: "MCP server with OAuth authentication capabilities".to_string(),
            },
            oauth: OAuthConfig {
                authorization_endpoint: "/oauth/authorize".to_string(),
                token_endpoint: "/oauth/token".to_string(),
                client_registration_endpoint: Some("/oauth/register".to_string()),
                scopes_supported: vec!["read".to_string(), "write".to_string()],
                allow_implicit_flow: false,
                allow_public_client_registration: true,
                cors_max_age: Some(86400),
            },
            github: GitHubConfig {
                client_id: github_client_id,
                client_secret: github_client_secret,
                redirect_uri: format!("http://{}:{}/oauth/callback", host, port),
                scope: env::var("GITHUB_SCOPE").unwrap_or_else(|_| "read:user".to_string()),
            },
            cognito: CognitoConfig {
                client_id: cognito_client_id,
                client_secret: cognito_client_secret,
                redirect_uri: format!("http://{}:{}/oauth/callback", host, port),
                scope: env::var("COGNITO_SCOPE")
                    .unwrap_or_else(|_| "openid email profile phone".to_string()),
                cognito_domain,
                region: cognito_region,
                user_pool_id: cognito_user_pool_id,
            },
            logging: LoggingConfig {
                level: env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
                format: env::var("LOG_FORMAT").unwrap_or_else(|_| "json".to_string()),
            },
        })
    }

    /// Get GitHub OAuth configuration
    pub fn github_oauth_config(&self) -> oauth_provider_rs::GitHubOAuthConfig {
        oauth_provider_rs::GitHubOAuthConfig {
            client_id: self.github.client_id.clone(),
            client_secret: self.github.client_secret.clone(),
            redirect_uri: self.github.redirect_uri.clone(),
            scope: self.github.scope.clone(),
            provider_name: "github".to_string(),
        }
    }

    /// Get OAuth provider configuration for GitHub
    pub fn github_oauth_provider_config(
        &self,
    ) -> oauth_provider_rs::provider_trait::OAuthProviderConfig {
        oauth_provider_rs::provider_trait::OAuthProviderConfig {
            client_id: self.github.client_id.clone(),
            client_secret: self.github.client_secret.clone(),
            redirect_uri: self.github.redirect_uri.clone(),
            scope: self.github.scope.clone(),
            provider_name: "github".to_string(),
        }
    }

    /// Get Cognito OAuth configuration
    pub fn cognito_oauth_config(&self) -> oauth_provider_rs::CognitoOAuthConfig {
        oauth_provider_rs::CognitoOAuthConfig {
            client_id: self.cognito.client_id.clone(),
            client_secret: self.cognito.client_secret.clone().unwrap_or_default(),
            redirect_uri: self.cognito.redirect_uri.clone(),
            scope: self.cognito.scope.clone(),
            provider_name: "cognito".to_string(),
        }
    }

    /// Get OAuth provider configuration for Cognito
    pub fn cognito_oauth_provider_config(
        &self,
    ) -> oauth_provider_rs::provider_trait::OAuthProviderConfig {
        oauth_provider_rs::provider_trait::OAuthProviderConfig {
            client_id: self.cognito.client_id.clone(),
            client_secret: self.cognito.client_secret.clone().unwrap_or_default(),
            redirect_uri: self.cognito.redirect_uri.clone(),
            scope: self.cognito.scope.clone(),
            provider_name: "cognito".to_string(),
        }
    }

    /// Get OAuth provider configuration
    pub fn oauth_provider_config(
        &self,
    ) -> oauth_provider_rs::http_integration::OAuthProviderConfig {
        oauth_provider_rs::http_integration::OAuthProviderConfig {
            authorization_endpoint: self.oauth.authorization_endpoint.clone(),
            token_endpoint: self.oauth.token_endpoint.clone(),
            client_registration_endpoint: self.oauth.client_registration_endpoint.clone(),
            scopes_supported: Some(self.oauth.scopes_supported.clone()),
            allow_implicit_flow: self.oauth.allow_implicit_flow,
            api_routes: vec!["/api".to_string()],
            allow_public_client_registration: self.oauth.allow_public_client_registration,
            cors_allowed_origins: vec![],
            cors_allowed_methods: vec![],
            cors_allowed_headers: vec![],
            cors_max_age: self.oauth.cors_max_age,
            error_callback: None,
        }
    }

    /// Get server bind address as SocketAddr
    pub fn bind_socket_addr(&self) -> Result<std::net::SocketAddr, ConfigError> {
        self.server
            .bind_address
            .parse()
            .map_err(|_| ConfigError::InvalidBindAddress)
    }
}

/// Configuration errors
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
    #[error("Missing GITHUB_CLIENT_ID environment variable")]
    MissingGitHubClientId,
    #[error("Missing GITHUB_CLIENT_SECRET environment variable")]
    MissingGitHubClientSecret,
    #[error("Missing COGNITO_CLIENT_ID environment variable")]
    MissingCognitoClientId,
    #[error("Missing COGNITO_DOMAIN environment variable")]
    MissingCognitoDomain,
    #[error("Missing COGNITO_REGION environment variable")]
    MissingCognitoRegion,
    #[error("Missing COGNITO_USER_POOL_ID environment variable")]
    MissingCognitoUserPoolId,
    #[error("Invalid port number")]
    InvalidPort,
    #[error("Invalid bind address")]
    InvalidBindAddress,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_config_defaults() {
        // Test default configuration without environment variables
        let config = Config::default();

        assert_eq!(config.server.name, "MCP OAuth Server");
        assert_eq!(config.oauth.scopes_supported, vec!["read", "write"]);
        assert!(!config.oauth.allow_implicit_flow);
        assert_eq!(config.github.scope, "read:user");
    }

    #[test]
    fn test_missing_github_config() {
        unsafe {
            env::remove_var("GITHUB_CLIENT_ID");
            env::remove_var("GITHUB_CLIENT_SECRET");
        }

        let result = Config::from_env();
        assert!(result.is_err());
    }
}