feat: seed default MCP servers (Findings, SBOM, DAST) on dashboard startup
Some checks failed
CI / Format (push) Failing after 3s
CI / Clippy (push) Failing after 2m35s
CI / Security Audit (push) Has been skipped
CI / Tests (push) Has been skipped
CI / Format (pull_request) Failing after 3s
CI / Deploy MCP (push) Has been cancelled
CI / Deploy Docs (push) Has been cancelled
CI / Deploy Agent (push) Has been cancelled
CI / Deploy Dashboard (push) Has been cancelled
CI / Detect Changes (push) Has been cancelled
CI / Clippy (pull_request) Failing after 2m40s
CI / Security Audit (pull_request) Has been skipped
CI / Tests (pull_request) Has been skipped
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped

- 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 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-03-09 12:02:31 +01:00
parent 492a93a83e
commit b973887754
4 changed files with 84 additions and 0 deletions

View File

@@ -38,6 +38,9 @@ GIT_CLONE_BASE_PATH=/tmp/compliance-scanner/repos
DASHBOARD_PORT=8080 DASHBOARD_PORT=8080
AGENT_API_URL=http://localhost:3001 AGENT_API_URL=http://localhost:3001
# MCP Server
MCP_ENDPOINT_URL=http://localhost:8090
# Keycloak (required for authentication) # Keycloak (required for authentication)
KEYCLOAK_URL=http://localhost:8080 KEYCLOAK_URL=http://localhost:8080
KEYCLOAK_REALM=compliance KEYCLOAK_REALM=compliance

View File

@@ -35,4 +35,5 @@ pub struct DashboardConfig {
pub mongodb_database: String, pub mongodb_database: String,
pub agent_api_url: String, pub agent_api_url: String,
pub dashboard_port: u16, pub dashboard_port: u16,
pub mcp_endpoint_url: Option<String>,
} }

View File

@@ -14,5 +14,6 @@ pub fn load_config() -> Result<DashboardConfig, DashboardError> {
.ok() .ok()
.and_then(|p| p.parse().ok()) .and_then(|p| p.parse().ok())
.unwrap_or(8080), .unwrap_or(8080),
mcp_endpoint_url: std::env::var("MCP_ENDPOINT_URL").ok().filter(|v| !v.is_empty()),
}) })
} }

View File

@@ -4,6 +4,9 @@ use dioxus::prelude::*;
use time::Duration; use time::Duration;
use tower_sessions::{cookie::Key, MemoryStore, SessionManagerLayer}; use tower_sessions::{cookie::Key, MemoryStore, SessionManagerLayer};
use compliance_core::models::{McpServerConfig, McpServerStatus, McpTransport};
use mongodb::bson::doc;
use super::config; use super::config;
use super::database::Database; use super::database::Database;
use super::error::DashboardError; 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))); KeycloakConfig::from_env().map(|kc| &*Box::leak(Box::new(kc)));
let db = Database::connect(&config.mongodb_uri, &config.mongodb_database).await?; 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 { if let Some(kc) = keycloak {
tracing::info!("Keycloak configured for realm '{}'", kc.realm); tracing::info!("Keycloak configured for realm '{}'", kc.realm);
} else { } else {
@@ -70,3 +76,76 @@ pub fn server_start(app: fn() -> Element) -> Result<(), DashboardError> {
Ok(()) 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}"),
}
}
}