feat(oscal): emit unmapped findings as-is + MCP oscal_assessment tool (#214)
CI / Check (push) Skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Docs (push) Skipped
CI / Deploy Agent (push) Failing after 3s
CI / Deploy Dashboard (push) Failing after 3s
CI / Deploy MCP (push) Failing after 2s

This commit was merged in pull request #214.
This commit is contained in:
2026-07-21 09:48:37 +00:00
parent c6baf72c6d
commit 18a23403a1
5 changed files with 180 additions and 136 deletions
+12 -1
View File
@@ -4,7 +4,7 @@ use rmcp::{
use crate::auth::current_tenant_id;
use crate::database::{Database, DatabasePool};
use crate::tools::{dast, findings, pentest, sbom};
use crate::tools::{dast, findings, oscal, pentest, sbom};
pub struct ComplianceMcpServer {
pool: DatabasePool,
@@ -68,6 +68,17 @@ impl ComplianceMcpServer {
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<oscal::OscalAssessmentParams>,
) -> Result<CallToolResult, rmcp::ErrorData> {
let db = self.tenant_db()?;
oscal::oscal_assessment(&db, params).await
}
// ── SBOM ──────────────────────────────────────────────
#[tool(
+1
View File
@@ -1,4 +1,5 @@
pub mod dast;
pub mod findings;
pub mod oscal;
pub mod pentest;
pub mod sbom;
+51
View File
@@ -0,0 +1,51 @@
//! OSCAL assessment MCP tool.
//!
//! Emits a standard OSCAL assessment-results document for a repo's findings —
//! what breakpilot's scanner MCP client pulls. Mapped findings target their
//! compliance controls (via the stamped `control_refs`); unmapped findings are
//! reported as-is, so nothing is lost.
use mongodb::bson::doc;
use rmcp::{model::*, ErrorData as McpError};
use schemars::JsonSchema;
use serde::Deserialize;
use compliance_core::models::oscal_assessment::assess;
use compliance_core::models::Finding;
use crate::database::Database;
#[derive(Debug, Deserialize, JsonSchema)]
pub struct OscalAssessmentParams {
/// Repository / target id to assess.
pub repo_id: String,
}
pub async fn oscal_assessment(
db: &Database,
params: OscalAssessmentParams,
) -> Result<CallToolResult, McpError> {
let mut cursor = db
.findings()
.find(doc! { "repo_id": &params.repo_id })
.await
.map_err(|e| McpError::internal_error(format!("DB error: {e}"), None))?;
let mut findings: Vec<Finding> = Vec::new();
while cursor
.advance()
.await
.map_err(|e| McpError::internal_error(format!("cursor error: {e}"), None))?
{
findings.push(
cursor
.deserialize_current()
.map_err(|e| McpError::internal_error(format!("deserialize error: {e}"), None))?,
);
}
let document = assess(&findings, chrono::Utc::now());
let json = serde_json::to_string_pretty(&document)
.map_err(|e| McpError::internal_error(format!("json error: {e}"), None))?;
Ok(CallToolResult::success(vec![Content::text(json)]))
}