use rmcp::{ handler::server::wrapper::Parameters, model::*, tool, tool_handler, tool_router, ServerHandler, }; use crate::database::{Database, DatabasePool}; use crate::tools::{dast, findings, oscal, pentest, sbom}; pub struct ComplianceMcpServer { pool: DatabasePool, /// Tenant this session serves. Bound once at session creation (the HTTP /// factory reads the bearer-set tenant while still in the request scope; /// stdio passes a synthetic id) — NOT a per-request `task_local`, which is /// lost across the `tokio::spawn` that runs the Streamable-HTTP session. tenant_id: String, #[allow(dead_code)] tool_router: rmcp::handler::server::router::tool::ToolRouter, } impl ComplianceMcpServer { /// The per-tenant `Database` for this session. fn tenant_db(&self) -> Result { Ok(self.pool.for_tenant_id(&self.tenant_id)) } } #[tool_router] impl ComplianceMcpServer { pub fn new(pool: DatabasePool, tenant_id: String) -> Self { Self { pool, tenant_id, tool_router: Self::tool_router(), } } // ── Findings ────────────────────────────────────────── #[tool( description = "List security findings with optional filters for repo, severity, status, and scan type" )] async fn list_findings( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; findings::list_findings(&db, params).await } #[tool(description = "Get a single finding by its ID")] async fn get_finding( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; findings::get_finding(&db, params).await } #[tool(description = "Get a summary of findings counts grouped by severity and status")] async fn findings_summary( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; findings::findings_summary(&db, params).await } #[tool( description = "Emit an OSCAL 1.1 assessment-results document for a repo's findings (mapped findings target their compliance controls; unmapped findings are reported as-is)" )] async fn oscal_assessment( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; oscal::oscal_assessment(&db, params).await } // ── SBOM ────────────────────────────────────────────── #[tool( description = "List SBOM packages with optional filters for repo, vulnerabilities, package manager, and license" )] async fn list_sbom_packages( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; sbom::list_sbom_packages(&db, params).await } #[tool( description = "Generate a vulnerability report for a repository showing all packages with known CVEs" )] async fn sbom_vuln_report( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; sbom::sbom_vuln_report(&db, params).await } // ── DAST ────────────────────────────────────────────── #[tool( description = "List DAST findings with optional filters for target, scan run, severity, exploitability, and vulnerability type" )] async fn list_dast_findings( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; dast::list_dast_findings(&db, params).await } #[tool(description = "Get a summary of recent DAST scan runs and finding counts")] async fn dast_scan_summary( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; dast::dast_scan_summary(&db, params).await } // ── Pentest ───────────────────────────────────────────── #[tool( description = "List AI pentest sessions with optional filters for target, status, and strategy" )] async fn list_pentest_sessions( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; pentest::list_pentest_sessions(&db, params).await } #[tool(description = "Get a single AI pentest session by its ID")] async fn get_pentest_session( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; pentest::get_pentest_session(&db, params).await } #[tool( description = "Get the attack chain DAG for a pentest session showing each tool invocation, its reasoning, and results" )] async fn get_attack_chain( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; pentest::get_attack_chain(&db, params).await } #[tool(description = "Get chat messages from a pentest session")] async fn get_pentest_messages( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; pentest::get_pentest_messages(&db, params).await } #[tool( description = "Get aggregated pentest statistics including running sessions, vulnerability counts, and severity distribution" )] async fn pentest_stats( &self, Parameters(params): Parameters, ) -> Result { let db = self.tenant_db()?; pentest::pentest_stats(&db, params).await } } #[tool_handler] impl ServerHandler for ComplianceMcpServer { fn get_info(&self) -> ServerInfo { ServerInfo { protocol_version: ProtocolVersion::V_2024_11_05, capabilities: ServerCapabilities::builder() .enable_tools() .build(), server_info: Implementation::from_build_env(), instructions: Some( "Compliance Scanner MCP server. Query security findings, SBOM data, DAST results, and AI pentest sessions for your tenant." .to_string(), ), } } }