feat(oscal): emit unmapped findings as-is + MCP oscal_assessment tool (#214)
This commit was merged in pull request #214.
This commit is contained in:
@@ -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,4 +1,5 @@
|
||||
pub mod dast;
|
||||
pub mod findings;
|
||||
pub mod oscal;
|
||||
pub mod pentest;
|
||||
pub mod sbom;
|
||||
|
||||
@@ -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": ¶ms.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)]))
|
||||
}
|
||||
Reference in New Issue
Block a user