Skip to main content
Glama
nizovtsevnv

Outline knowledge base MCP Server

by nizovtsevnv
config.rs6.07 kB
//! Configuration management //! //! Environment variable parsing and validation. use crate::error::{Error, Result}; use std::net::IpAddr; use url::Url; /// Application configuration #[derive(Debug, Clone)] pub struct Config { /// API key for Outline access pub outline_api_key: ApiKey, /// Outline API URL pub outline_api_url: Url, /// HTTP server IP address pub http_host: IpAddr, /// HTTP server port pub http_port: Port, /// Log level (unused but may be useful in future) #[allow(dead_code)] pub log_level: LogLevel, } impl Config { /// Load configuration from environment variables /// /// # Errors /// /// Returns error if required environment variables are missing or invalid. pub fn from_env() -> Result<Self> { let outline_api_key = std::env::var("OUTLINE_API_KEY").map_err(|_| Error::Config { message: "OUTLINE_API_KEY environment variable required".to_string(), source: None, })?; let outline_api_url = std::env::var("OUTLINE_API_URL") .unwrap_or_else(|_| "https://app.getoutline.com/api".to_string()); let http_host = std::env::var("HTTP_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); let http_port = std::env::var("HTTP_PORT").unwrap_or_else(|_| "3000".to_string()); let log_level = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()); Ok(Self { outline_api_key: ApiKey::new(outline_api_key)?, outline_api_url: outline_api_url .parse() .map_err(|e| Error::config_with_source("Invalid OUTLINE_API_URL", e))?, http_host: http_host .parse() .map_err(|e| Error::config_with_source("Invalid HTTP_HOST", e))?, http_port: Port::new( http_port .parse() .map_err(|e| Error::config_with_source("Invalid HTTP_PORT", e))?, )?, log_level: LogLevel::new(&log_level)?, }) } /// Simple configuration validation /// /// # Errors /// /// Returns error if configuration is invalid (e.g., unsupported URL scheme). #[allow(dead_code)] pub fn validate(&self) -> Result<()> { if self.outline_api_url.scheme() != "https" && self.outline_api_url.scheme() != "http" { return Err(Error::Config { message: "API URL must use HTTP or HTTPS".to_string(), source: None, }); } Ok(()) } /// Create configuration for tests /// /// # Panics /// /// Panics if test configuration values are invalid (should not happen in tests). #[cfg(test)] #[must_use] #[allow(dead_code)] pub fn for_testing() -> Self { Self { outline_api_key: ApiKey::new("test-api-key-12345".to_string()).unwrap(), outline_api_url: "https://test.example.com/api".parse().unwrap(), http_host: "127.0.0.1".parse().unwrap(), http_port: Port::new(3000).unwrap(), log_level: LogLevel::new("info").unwrap(), } } } /// Secure API key wrapper #[derive(Debug, Clone)] pub struct ApiKey { key: String, } impl ApiKey { /// Create new API key with validation /// /// # Errors /// /// Returns error if key is empty or too short. pub fn new(key: String) -> Result<Self> { if key.trim().is_empty() { return Err(Error::Config { message: "API key cannot be empty".to_string(), source: None, }); } if key.len() < 10 { return Err(Error::Config { message: "API key too short (minimum 10 characters)".to_string(), source: None, }); } Ok(Self { key }) } /// Get API key as string reference #[must_use] pub fn as_str(&self) -> &str { &self.key } } /// Safe port number wrapper #[derive(Debug, Clone, Copy)] pub struct Port { port: u16, } impl Port { /// Create new port with validation /// /// # Errors /// /// Returns error if port is below 1024. pub fn new(port: u16) -> Result<Self> { if port < 1024 { return Err(Error::Config { message: "Port must be >= 1024".to_string(), source: None, }); } Ok(Self { port }) } /// Get port as u16 value #[must_use] pub const fn as_u16(self) -> u16 { self.port } } /// Log level configuration #[derive(Debug, Clone)] pub struct LogLevel { level: String, } impl LogLevel { /// Create new log level with validation /// /// # Errors /// /// Returns error if log level is not one of: error, warn, info, debug, trace. pub fn new(level: &str) -> Result<Self> { let normalized = level.to_lowercase(); if !["error", "warn", "info", "debug", "trace"].contains(&normalized.as_str()) { return Err(Error::Config { message: format!("Invalid log level: {level}"), source: None, }); } Ok(Self { level: normalized }) } /// Get log level as string reference #[allow(dead_code)] #[must_use] pub fn as_str(&self) -> &str { &self.level } } #[cfg(test)] mod tests { use super::*; #[test] fn test_api_key_validation() { assert!(ApiKey::new(String::new()).is_err()); assert!(ApiKey::new("short".to_string()).is_err()); assert!(ApiKey::new("valid-api-key-123".to_string()).is_ok()); } #[test] fn test_port_validation() { assert!(Port::new(80).is_err()); assert!(Port::new(1024).is_ok()); assert!(Port::new(8080).is_ok()); } #[test] fn test_log_level_validation() { assert!(LogLevel::new("invalid").is_err()); assert!(LogLevel::new("info").is_ok()); assert!(LogLevel::new("DEBUG").is_ok()); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nizovtsevnv/outline-mcp-rs'

If you have feedback or need assistance with the MCP directory API, please join our Discord server