Skip to main content
Glama
config.rs11.3 kB
use anyhow::{Context, Result}; use config::{Config as ConfigBuilder, File}; use serde::{Deserialize, Serialize}; use std::env; use std::path::Path; use std::time::Duration; use tracing::warn; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { pub server: ServerConfig, pub api: ApiConfig, pub cache: CacheConfig, pub logging: LoggingConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerConfig { pub name: String, pub version: String, pub description: String, pub max_connections: Option<u32>, pub timeout_seconds: u64, } #[derive(Clone, Serialize, Deserialize)] pub struct ApiConfig { pub base_url: String, pub api_key: Option<String>, pub timeout_seconds: u64, pub max_retries: u32, pub retry_delay_ms: u64, pub rate_limit_per_second: Option<u32>, } impl std::fmt::Debug for ApiConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ApiConfig") .field("base_url", &self.base_url) .field("api_key", &self.api_key.as_ref().map(|_| "[REDACTED]")) .field("rate_limit_per_second", &self.rate_limit_per_second) .finish() } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CacheConfig { pub enabled: bool, pub ttl_seconds: u64, pub max_entries: usize, pub resource_cache_ttl_seconds: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LoggingConfig { pub level: String, pub format: String, pub enable_colors: bool, pub log_to_file: bool, pub log_file_path: Option<String>, } impl Default for Config { fn default() -> Self { Self { server: ServerConfig { name: "Polymarket MCP Server".to_string(), version: env!("CARGO_PKG_VERSION").to_string(), description: "MCP server for Polymarket prediction market data".to_string(), max_connections: Some(100), timeout_seconds: 30, }, api: ApiConfig { base_url: "https://gamma-api.polymarket.com".to_string(), api_key: None, timeout_seconds: 30, max_retries: 3, retry_delay_ms: 100, rate_limit_per_second: Some(10), }, cache: CacheConfig { enabled: true, ttl_seconds: 60, max_entries: 1000, resource_cache_ttl_seconds: 300, }, logging: LoggingConfig { level: "info".to_string(), format: "pretty".to_string(), enable_colors: true, log_to_file: false, log_file_path: None, }, } } } impl Config { /// Loads configuration from default sources (environment variables and config files). /// /// # Errors /// /// Returns an error if: /// - Configuration validation fails /// - Environment variable parsing fails /// - Config file exists but cannot be parsed pub fn load() -> Result<Self> { let mut config = Self::default(); if let Ok(file_config) = Self::try_load_default_config_files() { config = Self::merge_configs(config, file_config); } config = Self::load_from_env(config)?; config.validate()?; Ok(config) } /// Loads configuration from a specific file path. /// /// # Errors /// /// Returns an error if: /// - The config file doesn't exist /// - The config file cannot be parsed /// - The config contains invalid values pub fn load_from_file(config_path: &str) -> Result<Self> { if !Path::new(config_path).exists() { return Err(anyhow::anyhow!("Config file not found: {}", config_path)); } let builder = ConfigBuilder::builder() .add_source(File::with_name(config_path).required(true)) .build() .context("Failed to build config from file")?; let config: Config = builder .try_deserialize() .context(format!("Failed to deserialize config from {config_path}"))?; Ok(config) } fn try_load_default_config_files() -> Result<Self> { let config_paths = [ "config.toml", "polymarket-mcp.toml", "/etc/polymarket-mcp/config.toml", "~/.config/polymarket-mcp/config.toml", ]; for path in &config_paths { if Path::new(path).exists() { let builder = ConfigBuilder::builder() .add_source(File::with_name(path).required(false)) .build() .context("Failed to build config from file")?; let config: Config = builder .try_deserialize() .context(format!("Failed to deserialize config from {path}"))?; return Ok(config); } } Err(anyhow::anyhow!("No config file found")) } fn load_from_env(mut config: Self) -> Result<Self> { // Server configuration if let Ok(val) = env::var("POLYMARKET_SERVER_NAME") { config.server.name = val; } if let Ok(val) = env::var("POLYMARKET_SERVER_MAX_CONNECTIONS") { config.server.max_connections = Some(val.parse().context("Invalid max_connections")?); } if let Ok(val) = env::var("POLYMARKET_SERVER_TIMEOUT") { config.server.timeout_seconds = val.parse().context("Invalid server timeout")?; } // API configuration if let Ok(val) = env::var("POLYMARKET_API_BASE_URL") { config.api.base_url = val; } if let Ok(val) = env::var("POLYMARKET_API_KEY") { config.api.api_key = Some(val); } if let Ok(val) = env::var("POLYMARKET_API_TIMEOUT") { config.api.timeout_seconds = val.parse().context("Invalid API timeout")?; } if let Ok(val) = env::var("POLYMARKET_API_MAX_RETRIES") { config.api.max_retries = val.parse().context("Invalid max_retries")?; } if let Ok(val) = env::var("POLYMARKET_API_RETRY_DELAY") { config.api.retry_delay_ms = val.parse().context("Invalid retry_delay")?; } if let Ok(val) = env::var("POLYMARKET_API_RATE_LIMIT") { config.api.rate_limit_per_second = Some(val.parse().context("Invalid rate_limit")?); } // Cache configuration if let Ok(val) = env::var("POLYMARKET_CACHE_ENABLED") { config.cache.enabled = val.parse().context("Invalid cache_enabled")?; } if let Ok(val) = env::var("POLYMARKET_CACHE_TTL") { config.cache.ttl_seconds = val.parse().context("Invalid cache_ttl")?; } if let Ok(val) = env::var("POLYMARKET_CACHE_MAX_ENTRIES") { config.cache.max_entries = val.parse().context("Invalid cache_max_entries")?; } if let Ok(val) = env::var("POLYMARKET_RESOURCE_CACHE_TTL") { config.cache.resource_cache_ttl_seconds = val.parse().context("Invalid resource_cache_ttl")?; } // Logging configuration if let Ok(val) = env::var("POLYMARKET_LOG_LEVEL") { config.logging.level = val; } if let Ok(val) = env::var("POLYMARKET_LOG_FORMAT") { config.logging.format = val; } if let Ok(val) = env::var("POLYMARKET_LOG_COLORS") { config.logging.enable_colors = val.parse().context("Invalid log_colors")?; } if let Ok(val) = env::var("POLYMARKET_LOG_TO_FILE") { config.logging.log_to_file = val.parse().context("Invalid log_to_file")?; } if let Ok(val) = env::var("POLYMARKET_LOG_FILE_PATH") { config.logging.log_file_path = Some(val); } if let Ok(val) = env::var("RUST_LOG") { config.logging.level = val; } Ok(config) } fn merge_configs(_base: Self, override_config: Self) -> Self { override_config } fn validate(&self) -> Result<()> { // Validate server configuration if self.server.name.is_empty() { return Err(anyhow::anyhow!("Server name cannot be empty")); } if self.server.timeout_seconds == 0 { return Err(anyhow::anyhow!("Server timeout must be greater than 0")); } // Validate API configuration if self.api.base_url.is_empty() { return Err(anyhow::anyhow!("API base URL cannot be empty")); } if !self.api.base_url.starts_with("http://") && !self.api.base_url.starts_with("https://") { return Err(anyhow::anyhow!( "API base URL must start with http:// or https://" )); } if self.api.timeout_seconds == 0 { return Err(anyhow::anyhow!("API timeout must be greater than 0")); } if self.api.max_retries > 10 { warn!( "API max_retries is very high ({}), consider reducing it", self.api.max_retries ); } // Validate cache configuration if self.cache.ttl_seconds == 0 && self.cache.enabled { return Err(anyhow::anyhow!( "Cache TTL must be greater than 0 when cache is enabled" )); } if self.cache.max_entries == 0 && self.cache.enabled { return Err(anyhow::anyhow!( "Cache max_entries must be greater than 0 when cache is enabled" )); } // Validate logging configuration let valid_levels = ["trace", "debug", "info", "warn", "error"]; if !valid_levels.contains(&self.logging.level.as_str()) { return Err(anyhow::anyhow!( "Invalid log level '{}'. Valid levels: {}", self.logging.level, valid_levels.join(", ") )); } let valid_formats = ["pretty", "json", "compact"]; if !valid_formats.contains(&self.logging.format.as_str()) { return Err(anyhow::anyhow!( "Invalid log format '{}'. Valid formats: {}", self.logging.format, valid_formats.join(", ") )); } if self.logging.log_to_file && self.logging.log_file_path.is_none() { return Err(anyhow::anyhow!( "Log file path must be specified when log_to_file is true" )); } Ok(()) } #[must_use] pub fn api_timeout(&self) -> Duration { Duration::from_secs(self.api.timeout_seconds) } #[must_use] pub fn server_timeout(&self) -> Duration { Duration::from_secs(self.server.timeout_seconds) } #[must_use] pub fn cache_ttl(&self) -> Duration { Duration::from_secs(self.cache.ttl_seconds) } #[must_use] pub fn resource_cache_ttl(&self) -> Duration { Duration::from_secs(self.cache.resource_cache_ttl_seconds) } #[must_use] pub fn retry_delay(&self) -> Duration { Duration::from_millis(self.api.retry_delay_ms) } }

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/ozgureyilmaz/polymarket-mcp'

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