use crate::config::ScannerConfigManager;
use crate::scanner::MCPScanner;
use crate::types::{config_utils, ScanConfigBuilder, ScanOptions, ScanResult};
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ScanRequest {
pub url: String,
pub timeout: Option<u64>,
pub http_timeout: Option<u64>,
pub detailed: Option<bool>,
pub format: Option<String>,
pub auth_headers: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScanResponse {
pub success: bool,
pub result: Option<ScanResult>,
pub error: Option<String>,
pub timestamp: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchScanRequest {
pub urls: Vec<String>,
pub options: Option<ScanRequest>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchScanResponse {
pub success: bool,
pub results: Vec<ScanResponse>,
pub total: usize,
pub successful: usize,
pub failed: usize,
pub timestamp: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationResponse {
pub success: bool,
pub valid: bool,
pub error: Option<String>,
pub timestamp: String,
}
pub struct MCPScannerCore {
scanner: MCPScanner,
config_manager: ScannerConfigManager,
}
impl MCPScannerCore {
pub fn new() -> Result<Self> {
let config_manager = ScannerConfigManager::new();
let scanner_config = config_manager.load_config().unwrap_or_default();
Ok(Self {
scanner: MCPScanner::new_with_timeout(scanner_config.scanner.http_timeout),
config_manager,
})
}
fn parse_scan_options(&self, request: &ScanRequest) -> Result<ScanOptions> {
let scanner_config = self.config_manager.load_config().unwrap_or_default();
let mut builder = ScanConfigBuilder::new()
.timeout(
request
.timeout
.unwrap_or(scanner_config.scanner.scan_timeout),
)
.http_timeout(
request
.http_timeout
.unwrap_or(scanner_config.scanner.http_timeout),
)
.detailed(request.detailed.unwrap_or(scanner_config.scanner.detailed))
.format(
request
.format
.clone()
.unwrap_or(scanner_config.scanner.format),
);
if let Some(auth_headers) = &request.auth_headers {
builder = builder.auth_headers(Some(auth_headers.clone()));
}
Ok(builder.build())
}
pub async fn scan(&self, request: ScanRequest) -> ScanResponse {
let timestamp = chrono::Utc::now().to_rfc3339();
match self.perform_scan_internal(request).await {
Ok(result) => ScanResponse {
success: true,
result: Some(result),
error: None,
timestamp,
},
Err(e) => ScanResponse {
success: false,
result: None,
error: Some(e.to_string()),
timestamp,
},
}
}
async fn perform_scan_internal(&self, request: ScanRequest) -> Result<ScanResult> {
let scan_options = self.parse_scan_options(&request)?;
config_utils::validate_scan_config(&scan_options)
.map_err(|e| anyhow!("Configuration validation failed: {}", e))?;
let result = self.scanner.scan_single(&request.url, scan_options).await?;
Ok(result)
}
pub fn validate_config(&self, request: &ScanRequest) -> ValidationResponse {
let timestamp = chrono::Utc::now().to_rfc3339();
match self.parse_scan_options(request) {
Ok(options) => match config_utils::validate_scan_config(&options) {
Ok(_) => ValidationResponse {
success: true,
valid: true,
error: None,
timestamp,
},
Err(e) => ValidationResponse {
success: false,
valid: false,
error: Some(e.to_string()),
timestamp,
},
},
Err(e) => ValidationResponse {
success: false,
valid: false,
error: Some(e.to_string()),
timestamp,
},
}
}
pub async fn batch_scan(&self, request: BatchScanRequest) -> BatchScanResponse {
let timestamp = chrono::Utc::now().to_rfc3339();
let mut results = Vec::new();
let default_options = request.options.clone().unwrap_or_default();
for url in &request.urls {
let scan_request = ScanRequest {
url: url.clone(),
..default_options.clone()
};
let response = self.scan(scan_request).await;
results.push(response);
}
let successful = results.iter().filter(|r| r.success).count();
let failed = results.len() - successful;
BatchScanResponse {
success: true,
results,
total: request.urls.len(),
successful,
failed,
timestamp,
}
}
}