1use std::env;
8
9#[derive(Debug, Clone)]
11pub struct Config {
12 pub server: ServerConfig,
13 pub oauth: OAuthConfig,
14 pub github: GitHubConfig,
15 pub cognito: CognitoConfig,
16 pub logging: LoggingConfig,
17}
18
19#[derive(Debug, Clone)]
21pub struct ServerConfig {
22 pub bind_address: String,
23 pub host: String,
24 pub port: u16,
25 pub name: String,
26 pub version: String,
27 pub description: String,
28}
29
30#[derive(Debug, Clone)]
32pub struct OAuthConfig {
33 pub authorization_endpoint: String,
34 pub token_endpoint: String,
35 pub client_registration_endpoint: Option<String>,
36 pub scopes_supported: Vec<String>,
37 pub allow_implicit_flow: bool,
38 pub allow_public_client_registration: bool,
39 pub cors_max_age: Option<u64>,
40}
41
42#[derive(Debug, Clone)]
44pub struct GitHubConfig {
45 pub client_id: String,
46 pub client_secret: String,
47 pub redirect_uri: String,
48 pub scope: String,
49}
50
51#[derive(Debug, Clone)]
53pub struct CognitoConfig {
54 pub client_id: String,
55 pub client_secret: Option<String>,
56 pub redirect_uri: String,
57 pub scope: String,
58 pub cognito_domain: String,
59 pub region: String,
60 pub user_pool_id: String,
61}
62
63#[derive(Debug, Clone)]
65pub struct LoggingConfig {
66 pub level: String,
67 pub format: String,
68}
69
70impl Config {
71 pub fn default() -> Self {
73 Config {
74 server: ServerConfig {
75 bind_address: "0.0.0.0:8080".to_string(),
76 host: "localhost".to_string(),
77 port: 8080,
78 name: "MCP OAuth Server".to_string(),
79 version: env!("CARGO_PKG_VERSION").to_string(),
80 description: "MCP server with OAuth authentication capabilities".to_string(),
81 },
82 oauth: OAuthConfig {
83 authorization_endpoint: "/oauth/authorize".to_string(),
84 token_endpoint: "/oauth/token".to_string(),
85 client_registration_endpoint: Some("/oauth/register".to_string()),
86 scopes_supported: vec!["read".to_string(), "write".to_string()],
87 allow_implicit_flow: false,
88 allow_public_client_registration: true,
89 cors_max_age: Some(86400),
90 },
91 github: GitHubConfig {
92 client_id: "".to_string(),
93 client_secret: "".to_string(),
94 redirect_uri: "http://localhost:8080/oauth/callback".to_string(),
95 scope: "read:user".to_string(),
96 },
97 cognito: CognitoConfig {
98 client_id: "".to_string(),
99 client_secret: None,
100 redirect_uri: "http://localhost:8080/oauth/callback".to_string(),
101 scope: "openid email profile phone".to_string(),
102 cognito_domain: "mydomain.auth.us-east-1.amazoncognito.com".to_string(),
103 region: "us-east-1".to_string(),
104 user_pool_id: "us-east-1_XXXXXXXXX".to_string(),
105 },
106 logging: LoggingConfig {
107 level: "info".to_string(),
108 format: "json".to_string(),
109 },
110 }
111 }
112
113 pub fn from_env() -> Result<Self, ConfigError> {
115 let host = env::var("MCP_HOST").unwrap_or_else(|_| "localhost".to_string());
116 let port = env::var("MCP_PORT")
117 .unwrap_or_else(|_| "8080".to_string())
118 .parse::<u16>()
119 .map_err(|_| ConfigError::InvalidPort)?;
120
121 let bind_address =
122 env::var("MCP_BIND_ADDRESS").unwrap_or_else(|_| format!("0.0.0.0:{}", port));
123
124 let github_client_id =
125 env::var("GITHUB_CLIENT_ID").map_err(|_| ConfigError::MissingGitHubClientId)?;
126 let github_client_secret =
127 env::var("GITHUB_CLIENT_SECRET").map_err(|_| ConfigError::MissingGitHubClientSecret)?;
128
129 let cognito_client_id =
130 env::var("COGNITO_CLIENT_ID").map_err(|_| ConfigError::MissingCognitoClientId)?;
131 let cognito_client_secret = env::var("COGNITO_CLIENT_SECRET").ok();
132 let cognito_domain =
133 env::var("COGNITO_DOMAIN").map_err(|_| ConfigError::MissingCognitoDomain)?;
134 let cognito_region =
135 env::var("COGNITO_REGION").map_err(|_| ConfigError::MissingCognitoRegion)?;
136 let cognito_user_pool_id =
137 env::var("COGNITO_USER_POOL_ID").map_err(|_| ConfigError::MissingCognitoUserPoolId)?;
138
139 Ok(Config {
140 server: ServerConfig {
141 bind_address,
142 host: host.clone(),
143 port,
144 name: "MCP OAuth Server".to_string(),
145 version: env!("CARGO_PKG_VERSION").to_string(),
146 description: "MCP server with OAuth authentication capabilities".to_string(),
147 },
148 oauth: OAuthConfig {
149 authorization_endpoint: "/oauth/authorize".to_string(),
150 token_endpoint: "/oauth/token".to_string(),
151 client_registration_endpoint: Some("/oauth/register".to_string()),
152 scopes_supported: vec!["read".to_string(), "write".to_string()],
153 allow_implicit_flow: false,
154 allow_public_client_registration: true,
155 cors_max_age: Some(86400),
156 },
157 github: GitHubConfig {
158 client_id: github_client_id,
159 client_secret: github_client_secret,
160 redirect_uri: format!("http://{}:{}/oauth/callback", host, port),
161 scope: env::var("GITHUB_SCOPE").unwrap_or_else(|_| "read:user".to_string()),
162 },
163 cognito: CognitoConfig {
164 client_id: cognito_client_id,
165 client_secret: cognito_client_secret,
166 redirect_uri: format!("http://{}:{}/oauth/callback", host, port),
167 scope: env::var("COGNITO_SCOPE")
168 .unwrap_or_else(|_| "openid email profile phone".to_string()),
169 cognito_domain,
170 region: cognito_region,
171 user_pool_id: cognito_user_pool_id,
172 },
173 logging: LoggingConfig {
174 level: env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
175 format: env::var("LOG_FORMAT").unwrap_or_else(|_| "json".to_string()),
176 },
177 })
178 }
179
180 pub fn github_oauth_config(&self) -> oauth_provider_rs::GitHubOAuthConfig {
182 oauth_provider_rs::GitHubOAuthConfig {
183 client_id: self.github.client_id.clone(),
184 client_secret: self.github.client_secret.clone(),
185 redirect_uri: self.github.redirect_uri.clone(),
186 scope: self.github.scope.clone(),
187 provider_name: "github".to_string(),
188 }
189 }
190
191 pub fn github_oauth_provider_config(
193 &self,
194 ) -> oauth_provider_rs::provider_trait::OAuthProviderConfig {
195 oauth_provider_rs::provider_trait::OAuthProviderConfig {
196 client_id: self.github.client_id.clone(),
197 client_secret: self.github.client_secret.clone(),
198 redirect_uri: self.github.redirect_uri.clone(),
199 scope: self.github.scope.clone(),
200 provider_name: "github".to_string(),
201 }
202 }
203
204 pub fn cognito_oauth_config(&self) -> oauth_provider_rs::CognitoOAuthConfig {
206 oauth_provider_rs::CognitoOAuthConfig {
207 client_id: self.cognito.client_id.clone(),
208 client_secret: self.cognito.client_secret.clone().unwrap_or_default(),
209 redirect_uri: self.cognito.redirect_uri.clone(),
210 scope: self.cognito.scope.clone(),
211 provider_name: "cognito".to_string(),
212 }
213 }
214
215 pub fn cognito_oauth_provider_config(
217 &self,
218 ) -> oauth_provider_rs::provider_trait::OAuthProviderConfig {
219 oauth_provider_rs::provider_trait::OAuthProviderConfig {
220 client_id: self.cognito.client_id.clone(),
221 client_secret: self.cognito.client_secret.clone().unwrap_or_default(),
222 redirect_uri: self.cognito.redirect_uri.clone(),
223 scope: self.cognito.scope.clone(),
224 provider_name: "cognito".to_string(),
225 }
226 }
227
228 pub fn oauth_provider_config(&self) -> oauth_provider_rs::http_integration::OAuthProviderConfig {
230 oauth_provider_rs::http_integration::OAuthProviderConfig {
231 authorization_endpoint: self.oauth.authorization_endpoint.clone(),
232 token_endpoint: self.oauth.token_endpoint.clone(),
233 client_registration_endpoint: self.oauth.client_registration_endpoint.clone(),
234 scopes_supported: Some(self.oauth.scopes_supported.clone()),
235 allow_implicit_flow: self.oauth.allow_implicit_flow,
236 api_routes: vec!["/api".to_string()],
237 allow_public_client_registration: self.oauth.allow_public_client_registration,
238 cors_allowed_origins: vec![],
239 cors_allowed_methods: vec![],
240 cors_allowed_headers: vec![],
241 cors_max_age: self.oauth.cors_max_age,
242 error_callback: None,
243 }
244 }
245
246 pub fn bind_socket_addr(&self) -> Result<std::net::SocketAddr, ConfigError> {
248 self.server
249 .bind_address
250 .parse()
251 .map_err(|_| ConfigError::InvalidBindAddress)
252 }
253}
254
255#[derive(Debug, thiserror::Error)]
257pub enum ConfigError {
258 #[error("Missing GITHUB_CLIENT_ID environment variable")]
259 MissingGitHubClientId,
260 #[error("Missing GITHUB_CLIENT_SECRET environment variable")]
261 MissingGitHubClientSecret,
262 #[error("Missing COGNITO_CLIENT_ID environment variable")]
263 MissingCognitoClientId,
264 #[error("Missing COGNITO_DOMAIN environment variable")]
265 MissingCognitoDomain,
266 #[error("Missing COGNITO_REGION environment variable")]
267 MissingCognitoRegion,
268 #[error("Missing COGNITO_USER_POOL_ID environment variable")]
269 MissingCognitoUserPoolId,
270 #[error("Invalid port number")]
271 InvalidPort,
272 #[error("Invalid bind address")]
273 InvalidBindAddress,
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn test_config_defaults() {
282 let config = Config::default();
284
285 assert_eq!(config.server.name, "MCP OAuth Server");
286 assert_eq!(config.oauth.scopes_supported, vec!["read", "write"]);
287 assert!(!config.oauth.allow_implicit_flow);
288 assert_eq!(config.github.scope, "read:user");
289 }
290
291 #[test]
292 fn test_missing_github_config() {
293 unsafe {
294 env::remove_var("GITHUB_CLIENT_ID");
295 env::remove_var("GITHUB_CLIENT_SECRET");
296 }
297
298 let result = Config::from_env();
299 assert!(result.is_err());
300 }
301}