oauth_cognito_dynamodb_mcp_server/
oauth_cognito_dynamodb_mcp_server.rs

1//! OAuth 2.0 MCP Server with AWS Cognito authentication and DynamoDB storage
2//!
3//! This example demonstrates a complete MCP (Model Context Protocol) server implementation
4//! using AWS Cognito for OAuth 2.0 authentication and DynamoDB for persistent storage.
5//! The server follows microkernel architecture principles where independent handlers
6//! are composed at runtime.
7//!
8//! # Features
9//! - AWS Cognito OAuth 2.0 authentication
10//! - DynamoDB persistent storage for OAuth tokens and client data
11//! - MCP over HTTP (streamable)
12//! - MCP over Server-Sent Events (SSE)
13//! - Microkernel architecture with independent, composable handlers
14//!
15//! # Required Environment Variables
16//! ## Cognito Configuration
17//! - `COGNITO_CLIENT_ID`: Your Cognito app client ID
18//! - `COGNITO_CLIENT_SECRET`: Your Cognito app client secret (optional for public clients)
19//! - `COGNITO_DOMAIN`: Your Cognito domain (e.g., mydomain.auth.us-east-1.amazoncognito.com)
20//! - `COGNITO_REGION`: AWS region (e.g., us-east-1)
21//! - `COGNITO_USER_POOL_ID`: Your Cognito user pool ID (e.g., us-east-1_XXXXXXXXX)
22//! - `COGNITO_SCOPE`: OAuth scopes (default: 'openid email profile phone')
23//!
24//! ## AWS Configuration (for DynamoDB)
25//! - `AWS_ACCESS_KEY_ID`: Your AWS access key ID
26//! - `AWS_SECRET_ACCESS_KEY`: Your AWS secret access key
27//! - `AWS_REGION`: AWS region (should match COGNITO_REGION)
28//!
29//! ## Server Configuration
30//! - `MCP_HOST`: Server host (default: localhost)
31//! - `MCP_PORT`: Server port (default: 8080)
32//! - `DYNAMODB_TABLE_NAME`: DynamoDB table name (default: oauth-storage)
33//! - `DYNAMODB_CREATE_TABLE`: Whether to auto-create table (default: true)
34//!
35//! # Usage
36//! ```bash
37//! # Set environment variables
38//! export COGNITO_CLIENT_ID="your_client_id"
39//! export COGNITO_CLIENT_SECRET="your_client_secret"
40//! export COGNITO_DOMAIN="mydomain.auth.us-east-1.amazoncognito.com"
41//! export COGNITO_REGION="us-east-1"
42//! export COGNITO_USER_POOL_ID="us-east-1_XXXXXXXXX"
43//! export AWS_ACCESS_KEY_ID="your_aws_access_key"
44//! export AWS_SECRET_ACCESS_KEY="your_aws_secret_key"
45//! export AWS_REGION="us-east-1"
46//!
47//! # Run the server
48//! cargo run --example oauth_cognito_dynamodb_mcp_server
49//! ```
50
51use oauth_provider_rs::CognitoOAuthConfig;
52use remote_mcp_kernel::{
53    config::Config, error::AppResult, microkernel::create_full_cognito_microkernel_dynamodb,
54};
55use std::env;
56use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
57
58#[tokio::main]
59async fn main() -> AppResult<()> {
60    // Load environment variables
61    dotenv::dotenv().ok();
62
63    // Load configuration
64    let config = Config::from_env()?;
65
66    // Initialize tracing
67    init_tracing(&config)?;
68
69    tracing::info!("Starting MCP OAuth server with Cognito and DynamoDB storage...");
70
71    // Create Cognito OAuth configuration
72    let cognito_config = CognitoOAuthConfig {
73        client_id: config.cognito.client_id.clone(),
74        client_secret: config.cognito.client_secret.clone().unwrap_or_default(),
75        redirect_uri: format!(
76            "http://{}:{}/oauth/callback",
77            config.server.host, config.server.port
78        ),
79        scope: config.cognito.scope.clone(),
80        provider_name: "cognito".to_string(),
81    };
82
83    // Get DynamoDB configuration
84    let table_name =
85        env::var("DYNAMODB_TABLE_NAME").unwrap_or_else(|_| "oauth-storage".to_string());
86    let create_table = env::var("DYNAMODB_CREATE_TABLE")
87        .unwrap_or_else(|_| "true".to_string())
88        .parse::<bool>()
89        .unwrap_or(true);
90
91    // Log configuration
92    log_startup_info(&config, &table_name, create_table);
93
94    // Create microkernel server with Cognito OAuth and DynamoDB storage
95    let microkernel = create_full_cognito_microkernel_dynamodb(
96        cognito_config,
97        config.cognito.cognito_domain.clone(),
98        config.cognito.region.clone(),
99        config.cognito.user_pool_id.clone(),
100        table_name,
101        create_table,
102    )
103    .await
104    .map_err(|e| {
105        remote_mcp_kernel::error::AppError::Internal(format!("Failed to create microkernel: {}", e))
106    })?;
107
108    // Start the microkernel server
109    let bind_address = config.bind_socket_addr()?;
110    microkernel.serve(bind_address).await?;
111
112    Ok(())
113}
114
115fn init_tracing(config: &Config) -> AppResult<()> {
116    tracing_subscriber::registry()
117        .with(
118            tracing_subscriber::EnvFilter::try_from_default_env()
119                .unwrap_or_else(|_| config.logging.level.as_str().into()),
120        )
121        .with(tracing_subscriber::fmt::layer())
122        .init();
123
124    Ok(())
125}
126
127fn log_startup_info(config: &Config, table_name: &str, create_table: bool) {
128    println!("🚀 Starting MCP OAuth server with Cognito and DynamoDB storage...");
129    println!("📋 Configuration:");
130    println!("  - Architecture: Microkernel (independent handlers)");
131    println!("  - OAuth Provider: AWS Cognito");
132    println!("  - Storage Backend: DynamoDB");
133    println!("  - Server: {}:{}", config.server.host, config.server.port);
134    println!("  - Version: {}", config.server.version);
135    println!();
136
137    println!("🔐 AWS Cognito Configuration:");
138    println!(
139        "  - Client ID: {}",
140        if config.cognito.client_id.is_empty() {
141            "Not configured"
142        } else {
143            "Configured"
144        }
145    );
146    println!(
147        "  - Client Secret: {}",
148        match &config.cognito.client_secret {
149            Some(secret) if !secret.is_empty() => "Configured",
150            _ => "Not configured (Public Client)",
151        }
152    );
153    println!(
154        "  - Domain: {}",
155        if config.cognito.cognito_domain.is_empty() {
156            "Not configured"
157        } else {
158            &config.cognito.cognito_domain
159        }
160    );
161    println!(
162        "  - Region: {}",
163        if config.cognito.region.is_empty() {
164            "Not configured"
165        } else {
166            &config.cognito.region
167        }
168    );
169    println!(
170        "  - User Pool ID: {}",
171        if config.cognito.user_pool_id.is_empty() {
172            "Not configured"
173        } else {
174            &config.cognito.user_pool_id
175        }
176    );
177    println!("  - Scopes: {}", config.cognito.scope);
178    println!();
179
180    println!("🗄️  DynamoDB Storage Configuration:");
181    println!("  - Table Name: {}", table_name);
182    println!("  - Auto-create Table: {}", create_table);
183    println!("  - TTL Attribute: expires_at");
184    println!();
185
186    println!("🔧 Handlers:");
187    println!("  - OAuth Provider (Cognito authentication & authorization)");
188    println!("  - Streamable HTTP Handler (MCP over HTTP)");
189    println!("  - SSE Handler (MCP over SSE)");
190    println!();
191
192    println!("🔐 Required Environment Variables:");
193    println!("  ## Cognito Configuration");
194    println!("  - COGNITO_CLIENT_ID: Your Cognito app client ID");
195    println!(
196        "  - COGNITO_CLIENT_SECRET: Your Cognito app client secret (optional for public clients)"
197    );
198    println!(
199        "  - COGNITO_DOMAIN: Your Cognito domain (e.g., mydomain.auth.us-east-1.amazoncognito.com)"
200    );
201    println!("  - COGNITO_REGION: AWS region (e.g., us-east-1)");
202    println!("  - COGNITO_USER_POOL_ID: Your Cognito user pool ID (e.g., us-east-1_XXXXXXXXX)");
203    println!("  - COGNITO_SCOPE: OAuth scopes (default: 'openid email profile phone')");
204    println!();
205    println!("  ## AWS Configuration (for DynamoDB)");
206    println!("  - AWS_ACCESS_KEY_ID: Your AWS access key ID");
207    println!("  - AWS_SECRET_ACCESS_KEY: Your AWS secret access key");
208    println!("  - AWS_REGION: AWS region (should match COGNITO_REGION)");
209    println!();
210    println!("  ## Server Configuration");
211    println!("  - MCP_HOST: Server host (default: localhost)");
212    println!("  - MCP_PORT: Server port (default: 8080)");
213    println!("  - DYNAMODB_TABLE_NAME: DynamoDB table name (default: oauth-storage)");
214    println!("  - DYNAMODB_CREATE_TABLE: Whether to auto-create table (default: true)");
215    println!();
216
217    println!("🌐 OAuth 2.0 Endpoints:");
218    println!(
219        "  - Authorization: https://{}/oauth2/authorize",
220        config.cognito.cognito_domain
221    );
222    println!(
223        "  - Token: https://{}/oauth2/token",
224        config.cognito.cognito_domain
225    );
226    println!(
227        "  - JWKS: https://{}/oauth2/jwks",
228        config.cognito.cognito_domain
229    );
230    println!(
231        "  - UserInfo: https://{}/oauth2/userInfo",
232        config.cognito.cognito_domain
233    );
234    println!();
235
236    println!("💾 DynamoDB Table Configuration:");
237    println!("  - Table Structure: Single table design with 'pk' primary key");
238    println!("  - TTL Support: Automatic cleanup using 'expires_at' attribute");
239    println!("  - Billing Mode: Pay-per-request (on-demand)");
240    println!("  - Encryption: Server-side encryption enabled");
241    println!();
242
243    println!("🏗️  Microkernel Architecture:");
244    println!("  - Independent handlers that can operate standalone");
245    println!("  - Runtime composition of services");
246    println!("  - Single responsibility per handler");
247    println!("  - Easy testing and maintenance");
248    println!();
249}