From b973887754eb21222283895dbfecc582ace9feb2 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Mon, 9 Mar 2026 12:02:31 +0100 Subject: [PATCH] feat: seed default MCP servers (Findings, SBOM, DAST) on dashboard startup - Add MCP_ENDPOINT_URL env var to configure MCP server base URL - Seed three default MCP server configs on dashboard startup if not present - Each server has its own tool subset: findings (3 tools), SBOM (2 tools), DAST (2 tools) - Uses upsert-by-name to avoid duplicates on restart Co-Authored-By: Claude Opus 4.6 --- .env.example | 3 + compliance-core/src/config.rs | 1 + .../src/infrastructure/config.rs | 1 + .../src/infrastructure/server.rs | 79 +++++++++++++++++++ 4 files changed, 84 insertions(+) diff --git a/.env.example b/.env.example index e8d8ec7..f5e9f4f 100644 --- a/.env.example +++ b/.env.example @@ -38,6 +38,9 @@ GIT_CLONE_BASE_PATH=/tmp/compliance-scanner/repos DASHBOARD_PORT=8080 AGENT_API_URL=http://localhost:3001 +# MCP Server +MCP_ENDPOINT_URL=http://localhost:8090 + # Keycloak (required for authentication) KEYCLOAK_URL=http://localhost:8080 KEYCLOAK_REALM=compliance diff --git a/compliance-core/src/config.rs b/compliance-core/src/config.rs index de60b26..401f9a8 100644 --- a/compliance-core/src/config.rs +++ b/compliance-core/src/config.rs @@ -35,4 +35,5 @@ pub struct DashboardConfig { pub mongodb_database: String, pub agent_api_url: String, pub dashboard_port: u16, + pub mcp_endpoint_url: Option, } diff --git a/compliance-dashboard/src/infrastructure/config.rs b/compliance-dashboard/src/infrastructure/config.rs index 953d931..2781328 100644 --- a/compliance-dashboard/src/infrastructure/config.rs +++ b/compliance-dashboard/src/infrastructure/config.rs @@ -14,5 +14,6 @@ pub fn load_config() -> Result { .ok() .and_then(|p| p.parse().ok()) .unwrap_or(8080), + mcp_endpoint_url: std::env::var("MCP_ENDPOINT_URL").ok().filter(|v| !v.is_empty()), }) } diff --git a/compliance-dashboard/src/infrastructure/server.rs b/compliance-dashboard/src/infrastructure/server.rs index f28fc05..e526596 100644 --- a/compliance-dashboard/src/infrastructure/server.rs +++ b/compliance-dashboard/src/infrastructure/server.rs @@ -4,6 +4,9 @@ use dioxus::prelude::*; use time::Duration; use tower_sessions::{cookie::Key, MemoryStore, SessionManagerLayer}; +use compliance_core::models::{McpServerConfig, McpServerStatus, McpTransport}; +use mongodb::bson::doc; + use super::config; use super::database::Database; use super::error::DashboardError; @@ -22,6 +25,9 @@ pub fn server_start(app: fn() -> Element) -> Result<(), DashboardError> { KeycloakConfig::from_env().map(|kc| &*Box::leak(Box::new(kc))); let db = Database::connect(&config.mongodb_uri, &config.mongodb_database).await?; + // Seed default MCP server configs + seed_default_mcp_servers(&db, config.mcp_endpoint_url.as_deref()).await; + if let Some(kc) = keycloak { tracing::info!("Keycloak configured for realm '{}'", kc.realm); } else { @@ -70,3 +76,76 @@ pub fn server_start(app: fn() -> Element) -> Result<(), DashboardError> { Ok(()) }) } + +/// Seed three default MCP server configs (Findings, SBOM, DAST) if they don't already exist. +async fn seed_default_mcp_servers(db: &Database, mcp_endpoint_url: Option<&str>) { + let endpoint = mcp_endpoint_url.unwrap_or("http://localhost:8090"); + + let defaults = [ + ( + "Findings MCP", + "Exposes security findings, triage data, and finding summaries to LLM agents", + vec![ + "list_findings", + "get_finding", + "findings_summary", + ], + ), + ( + "SBOM MCP", + "Exposes software bill of materials and vulnerability reports to LLM agents", + vec![ + "list_sbom_packages", + "sbom_vuln_report", + ], + ), + ( + "DAST MCP", + "Exposes DAST scan findings and scan summaries to LLM agents", + vec![ + "list_dast_findings", + "dast_scan_summary", + ], + ), + ]; + + let collection = db.mcp_servers(); + + for (name, description, tools) in defaults { + // Skip if already exists + let exists = collection + .find_one(doc! { "name": name }) + .await + .ok() + .flatten() + .is_some(); + + if exists { + continue; + } + + let now = chrono::Utc::now(); + let token = format!("mcp_{}", uuid::Uuid::new_v4().to_string().replace('-', "")); + + let server = McpServerConfig { + id: None, + name: name.to_string(), + endpoint_url: format!("{endpoint}/mcp"), + transport: McpTransport::Http, + port: Some(8090), + status: McpServerStatus::Stopped, + access_token: token, + tools_enabled: tools.into_iter().map(|s| s.to_string()).collect(), + description: Some(description.to_string()), + mongodb_uri: None, + mongodb_database: None, + created_at: now, + updated_at: now, + }; + + match collection.insert_one(server).await { + Ok(_) => tracing::info!("Seeded default MCP server: {name}"), + Err(e) => tracing::warn!("Failed to seed MCP server '{name}': {e}"), + } + } +}