Skip to content

Python API

Use the public Python API when another tool needs typed agent-bom results without parsing terminal output, or when services and notebooks need a small control-plane client for stable REST endpoints.

Install

pip install agent-bom

Use the API extra when the same environment also starts a local control plane:

pip install 'agent-bom[api]'

Local Scan Helpers

from agent_bom import check, diff, scan
from agent_bom.sdk import inventory

report = scan(project=".", offline=True)
for finding in report.to_findings():
    print(finding.id, finding.severity)

package = check("requests@2.31.0", ecosystem="pypi", offline=True)
print(package.status, package.vulnerabilities)

fleet = inventory("fleet-inventory.json")
print(fleet.agent_count, fleet.package_count)

delta = diff("baseline.json", "current.json")
print(delta.summary)

The API delegates to the same scanner, inventory, and history-diff primitives used elsewhere in the product. It is not a parallel scan engine.

Control-Plane Client

from agent_bom import AgentBomClient

with AgentBomClient(
    base_url="https://agent-bom.internal",
    api_key="agent-bom-api-key",
    tenant_id="tenant-a",
) as client:
    print(client.health()["status"])
    manifest = client.agent_manifest()
    runtime = client.runtime_production_index()
    intel = client.intel_sources()
    decision = client.should_i_deploy("flask@2.0.0", block_risk=80)

print(manifest["schema_version"], runtime["schema_version"], len(intel.get("sources", [])), decision["decision"])

Run the packaged smoke example against a live API:

AGENT_BOM_BASE_URL=http://127.0.0.1:8422 \
AGENT_BOM_API_KEY=dev-key \
python examples/python_sdk/control_plane_smoke.py

The client accepts either api_key or bearer_token. tenant_id is sent as X-Agent-Bom-Tenant-ID and used as the default tenant scope for tenant-aware methods.

Payload-first methods accept the obvious positional form:

client.ingest_findings(
    [{"id": "finding-1", "severity": "high", "title": "External scanner finding"}],
    source="external-scanner",
)
client.register_dataset_version("training-set", version_id="2026-05-24")
client.should_i_deploy("flask@2.0.0", block_risk=80)

agent_bom.sdk

Public Python API for embedding agent-bom in other tools.

This module is intentionally thin: it exposes stable Python functions while delegating to the same scanner, inventory, and history primitives used by the CLI and MCP surfaces. It is not a second scan implementation.

Asset dataclass

What is affected by this finding.

Source code in src/agent_bom/finding.py
@dataclass
class Asset:
    """What is affected by this finding."""

    name: str  # human-readable name (server name, package name, cloud resource ID)
    asset_type: str  # "mcp_server" | "package" | "container" | "cloud_resource" | "agent"
    identifier: Optional[str] = None  # purl, ARN, image digest, etc.
    location: Optional[str] = None  # file path, URL, cloud region

    @property
    def stable_id(self) -> str:
        """Deterministic UUID derived from asset content.

        Same asset type + identifier always produces the same ID across scans.
        This enables tracking: first seen, last seen, resolved, re-emerged.
        """
        identifier = self.identifier or f"{self.name}:{self.location or ''}"
        return _stable_id(self.asset_type, identifier)

    @property
    def canonical_id(self) -> str:
        """Canonical alias for stable_id used by reports and graph joins."""
        return self.stable_id

    @property
    def source_ids(self) -> dict[str, str]:
        """Original source identifiers retained as provenance."""
        return source_ids(identifier=self.identifier, location=self.location)

stable_id property

stable_id: str

Deterministic UUID derived from asset content.

Same asset type + identifier always produces the same ID across scans. This enables tracking: first seen, last seen, resolved, re-emerged.

canonical_id property

canonical_id: str

Canonical alias for stable_id used by reports and graph joins.

source_ids property

source_ids: dict[str, str]

Original source identifiers retained as provenance.

Finding dataclass

Unified finding — one model for all issue types across all sources.

Phase 1 covers CVE findings (migrated from BlastRadius). Phase 2 will add cloud CIS, proxy, SAST, skill findings.

Source code in src/agent_bom/finding.py
@dataclass
class Finding:
    """Unified finding — one model for all issue types across all sources.

    Phase 1 covers CVE findings (migrated from BlastRadius).
    Phase 2 will add cloud CIS, proxy, SAST, skill findings.
    """

    # Core identity
    finding_type: FindingType
    source: FindingSource
    asset: Asset
    severity: str  # mirrors Severity enum value; str for forward-compat

    # Vendor severity (from source scanner) vs normalised CVSS severity
    vendor_severity: Optional[str] = None  # severity as reported by vendor/scanner
    cvss_severity: Optional[str] = None  # normalised from CVSS base score

    # Finding content
    title: str = ""
    description: str = ""
    cve_id: Optional[str] = None  # e.g. "CVE-2024-1234"
    cwe_ids: list[str] = field(default_factory=list)  # e.g. ["CWE-79"]
    cvss_score: Optional[float] = None
    epss_score: Optional[float] = None
    is_kev: bool = False  # CISA Known Exploited Vulnerability

    # Remediation
    fixed_version: Optional[str] = None
    remediation_guidance: Optional[str] = None

    # Compliance mappings (same tags as BlastRadius for parity)
    compliance_tags: list[str] = field(default_factory=list)  # all framework tags combined
    # Framework slugs that govern this finding (set by compliance_hub.apply_hub_classification).
    # Distinct from the per-framework `*_tags` fields below, which hold control codes.
    applicable_frameworks: list[str] = field(default_factory=list)
    controls: list[ControlTag] = field(default_factory=list)
    owasp_tags: list[str] = field(default_factory=list)
    atlas_tags: list[str] = field(default_factory=list)
    attack_tags: list[str] = field(default_factory=list)
    nist_ai_rmf_tags: list[str] = field(default_factory=list)
    owasp_mcp_tags: list[str] = field(default_factory=list)
    owasp_agentic_tags: list[str] = field(default_factory=list)
    eu_ai_act_tags: list[str] = field(default_factory=list)
    nist_csf_tags: list[str] = field(default_factory=list)
    iso_27001_tags: list[str] = field(default_factory=list)
    soc2_tags: list[str] = field(default_factory=list)
    cis_tags: list[str] = field(default_factory=list)
    cmmc_tags: list[str] = field(default_factory=list)
    nist_800_53_tags: list[str] = field(default_factory=list)
    fedramp_tags: list[str] = field(default_factory=list)
    pci_dss_tags: list[str] = field(default_factory=list)

    # Graph / correlation
    related_findings: list[str] = field(default_factory=list)  # IDs of related findings
    evidence: dict = field(default_factory=dict)  # raw evidence payload

    # Risk
    risk_score: float = 0.0  # 0-10 unified risk score

    # Unique ID — deterministic UUID v5 based on content (computed in __post_init__)
    # Pass an explicit id= to override (e.g. when ingesting from external scanner)
    id: str = field(default="")

    def __post_init__(self) -> None:
        """Compute stable ID from finding content if not explicitly set."""
        self.controls = _dedupe_control_tags(
            [
                *(tag if isinstance(tag, ControlTag) else ControlTag.from_dict(tag) for tag in self.controls),
                *self._legacy_control_tags(),
            ]
        )
        if not self.id:
            # Deterministic ID: same CVE on same asset always same ID
            cve_part = self.cve_id or self.title
            pkg_name = ""
            pkg_version = ""
            if self.asset.asset_type == "package" and self.asset.identifier:
                # purl like "pkg:pypi/torch@2.3.0" — extract name/version
                purl = self.asset.identifier
                pkg_part = purl.split("/")[-1] if "/" in purl else purl
                if "@" in pkg_part:
                    pkg_name, pkg_version = pkg_part.rsplit("@", 1)
            self.id = canonical_finding_id(
                self.asset.stable_id,
                cve_part,
                pkg_name,
                pkg_version,
            )

    @property
    def canonical_id(self) -> str:
        """Canonical alias for id used by report and graph consumers."""
        return self.id

    def _legacy_control_tags(self) -> list[ControlTag]:
        """Return normalized controls derived from legacy tag arrays."""
        tags: list[ControlTag] = []
        for field_name, framework in LEGACY_CONTROL_FIELDS:
            values = getattr(self, field_name)
            for value in values:
                if value:
                    tags.append(
                        ControlTag(
                            framework=framework,
                            control=str(value),
                            version=_LEGACY_CONTROL_VERSION_BY_FRAMEWORK.get(framework, "legacy"),
                            confidence=0.75,
                            source=f"legacy:{field_name}",
                            via=field_name,
                        )
                    )
        return tags

    def normalized_controls(self) -> list[ControlTag]:
        """Return deduplicated structured controls for this finding."""
        return _dedupe_control_tags([*self.controls, *self._legacy_control_tags()])

    def all_compliance_tags(self) -> list[str]:
        """Return deduplicated union of all compliance tag lists."""
        seen: set[str] = set()
        result: list[str] = []
        for tag in (
            self.compliance_tags
            + self.owasp_tags
            + self.atlas_tags
            + self.attack_tags
            + self.nist_ai_rmf_tags
            + self.owasp_mcp_tags
            + self.owasp_agentic_tags
            + self.eu_ai_act_tags
            + self.nist_csf_tags
            + self.iso_27001_tags
            + self.soc2_tags
            + self.cis_tags
            + self.cmmc_tags
            + self.nist_800_53_tags
            + self.fedramp_tags
            + self.pci_dss_tags
            + [tag.control for tag in self.normalized_controls()]
        ):
            if tag not in seen:
                seen.add(tag)
                result.append(tag)
        return result

    def effective_severity(self) -> str:
        """Return the best severity value: vendor > cvss > base severity."""
        return self.vendor_severity or self.cvss_severity or self.severity

    def to_dict(self) -> dict:
        """Return a JSON-serializable finding payload."""
        return {
            "schema_version": FINDING_SCHEMA_VERSION,
            "id": self.id,
            "canonical_id": self.canonical_id,
            "finding_type": self.finding_type.value,
            "source": self.source.value,
            "asset": {
                "name": self.asset.name,
                "asset_type": self.asset.asset_type,
                "identifier": self.asset.identifier,
                "location": self.asset.location,
                "stable_id": self.asset.stable_id,
                "canonical_id": self.asset.canonical_id,
                "source_ids": self.asset.source_ids,
            },
            "severity": self.severity,
            "effective_severity": self.effective_severity(),
            "vendor_severity": self.vendor_severity,
            "cvss_severity": self.cvss_severity,
            "title": self.title,
            "description": self.description,
            "cve_id": self.cve_id,
            "cwe_ids": self.cwe_ids,
            "cvss_score": self.cvss_score,
            "epss_score": self.epss_score,
            "is_kev": self.is_kev,
            "fixed_version": self.fixed_version,
            "remediation_guidance": self.remediation_guidance,
            "compliance_tags": self.all_compliance_tags(),
            "applicable_frameworks": list(self.applicable_frameworks),
            "controls": [tag.to_dict() for tag in self.normalized_controls()],
            "owasp_tags": self.owasp_tags,
            "atlas_tags": self.atlas_tags,
            "attack_tags": self.attack_tags,
            "nist_ai_rmf_tags": self.nist_ai_rmf_tags,
            "owasp_mcp_tags": self.owasp_mcp_tags,
            "owasp_agentic_tags": self.owasp_agentic_tags,
            "eu_ai_act_tags": self.eu_ai_act_tags,
            "nist_csf_tags": self.nist_csf_tags,
            "iso_27001_tags": self.iso_27001_tags,
            "soc2_tags": self.soc2_tags,
            "cis_tags": self.cis_tags,
            "cmmc_tags": self.cmmc_tags,
            "nist_800_53_tags": self.nist_800_53_tags,
            "fedramp_tags": self.fedramp_tags,
            "pci_dss_tags": self.pci_dss_tags,
            "related_findings": self.related_findings,
            "evidence": self.evidence,
            "risk_score": self.risk_score,
        }

canonical_id property

canonical_id: str

Canonical alias for id used by report and graph consumers.

__post_init__

__post_init__() -> None

Compute stable ID from finding content if not explicitly set.

Source code in src/agent_bom/finding.py
def __post_init__(self) -> None:
    """Compute stable ID from finding content if not explicitly set."""
    self.controls = _dedupe_control_tags(
        [
            *(tag if isinstance(tag, ControlTag) else ControlTag.from_dict(tag) for tag in self.controls),
            *self._legacy_control_tags(),
        ]
    )
    if not self.id:
        # Deterministic ID: same CVE on same asset always same ID
        cve_part = self.cve_id or self.title
        pkg_name = ""
        pkg_version = ""
        if self.asset.asset_type == "package" and self.asset.identifier:
            # purl like "pkg:pypi/torch@2.3.0" — extract name/version
            purl = self.asset.identifier
            pkg_part = purl.split("/")[-1] if "/" in purl else purl
            if "@" in pkg_part:
                pkg_name, pkg_version = pkg_part.rsplit("@", 1)
        self.id = canonical_finding_id(
            self.asset.stable_id,
            cve_part,
            pkg_name,
            pkg_version,
        )

normalized_controls

normalized_controls() -> list[ControlTag]

Return deduplicated structured controls for this finding.

Source code in src/agent_bom/finding.py
def normalized_controls(self) -> list[ControlTag]:
    """Return deduplicated structured controls for this finding."""
    return _dedupe_control_tags([*self.controls, *self._legacy_control_tags()])

all_compliance_tags

all_compliance_tags() -> list[str]

Return deduplicated union of all compliance tag lists.

Source code in src/agent_bom/finding.py
def all_compliance_tags(self) -> list[str]:
    """Return deduplicated union of all compliance tag lists."""
    seen: set[str] = set()
    result: list[str] = []
    for tag in (
        self.compliance_tags
        + self.owasp_tags
        + self.atlas_tags
        + self.attack_tags
        + self.nist_ai_rmf_tags
        + self.owasp_mcp_tags
        + self.owasp_agentic_tags
        + self.eu_ai_act_tags
        + self.nist_csf_tags
        + self.iso_27001_tags
        + self.soc2_tags
        + self.cis_tags
        + self.cmmc_tags
        + self.nist_800_53_tags
        + self.fedramp_tags
        + self.pci_dss_tags
        + [tag.control for tag in self.normalized_controls()]
    ):
        if tag not in seen:
            seen.add(tag)
            result.append(tag)
    return result

effective_severity

effective_severity() -> str

Return the best severity value: vendor > cvss > base severity.

Source code in src/agent_bom/finding.py
def effective_severity(self) -> str:
    """Return the best severity value: vendor > cvss > base severity."""
    return self.vendor_severity or self.cvss_severity or self.severity

to_dict

to_dict() -> dict

Return a JSON-serializable finding payload.

Source code in src/agent_bom/finding.py
def to_dict(self) -> dict:
    """Return a JSON-serializable finding payload."""
    return {
        "schema_version": FINDING_SCHEMA_VERSION,
        "id": self.id,
        "canonical_id": self.canonical_id,
        "finding_type": self.finding_type.value,
        "source": self.source.value,
        "asset": {
            "name": self.asset.name,
            "asset_type": self.asset.asset_type,
            "identifier": self.asset.identifier,
            "location": self.asset.location,
            "stable_id": self.asset.stable_id,
            "canonical_id": self.asset.canonical_id,
            "source_ids": self.asset.source_ids,
        },
        "severity": self.severity,
        "effective_severity": self.effective_severity(),
        "vendor_severity": self.vendor_severity,
        "cvss_severity": self.cvss_severity,
        "title": self.title,
        "description": self.description,
        "cve_id": self.cve_id,
        "cwe_ids": self.cwe_ids,
        "cvss_score": self.cvss_score,
        "epss_score": self.epss_score,
        "is_kev": self.is_kev,
        "fixed_version": self.fixed_version,
        "remediation_guidance": self.remediation_guidance,
        "compliance_tags": self.all_compliance_tags(),
        "applicable_frameworks": list(self.applicable_frameworks),
        "controls": [tag.to_dict() for tag in self.normalized_controls()],
        "owasp_tags": self.owasp_tags,
        "atlas_tags": self.atlas_tags,
        "attack_tags": self.attack_tags,
        "nist_ai_rmf_tags": self.nist_ai_rmf_tags,
        "owasp_mcp_tags": self.owasp_mcp_tags,
        "owasp_agentic_tags": self.owasp_agentic_tags,
        "eu_ai_act_tags": self.eu_ai_act_tags,
        "nist_csf_tags": self.nist_csf_tags,
        "iso_27001_tags": self.iso_27001_tags,
        "soc2_tags": self.soc2_tags,
        "cis_tags": self.cis_tags,
        "cmmc_tags": self.cmmc_tags,
        "nist_800_53_tags": self.nist_800_53_tags,
        "fedramp_tags": self.fedramp_tags,
        "pci_dss_tags": self.pci_dss_tags,
        "related_findings": self.related_findings,
        "evidence": self.evidence,
        "risk_score": self.risk_score,
    }

AIBOMReport dataclass

Complete AI-BOM report.

Source code in src/agent_bom/models.py
@dataclass
class AIBOMReport:
    """Complete AI-BOM report."""

    agents: list[Agent] = field(default_factory=list)
    blast_radii: list[BlastRadius] = field(default_factory=list)
    generated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    scan_id: str = ""  # Deterministic UUID v5 from scan inputs (set by CLI after discovery)
    tool_version: str = ""
    executive_summary: Optional[str] = None  # LLM-generated executive summary
    ai_threat_chains: list[str] = field(default_factory=list)  # LLM-generated threat chain analyses
    mcp_config_analysis: Optional[dict] = None  # LLM-powered MCP config security analysis
    skill_audit_data: Optional[dict] = None  # Serialized SkillAuditResult (set by CLI)
    trust_assessment_data: Optional[dict] = None  # Serialized TrustAssessmentResult (set by CLI)
    prompt_scan_data: Optional[dict] = None  # Serialized PromptScanResult (set by CLI)
    model_files: list[dict] = field(default_factory=list)
    model_manifests: list[dict] = field(default_factory=list)
    model_provenance: list[dict] = field(default_factory=list)  # HuggingFace provenance results
    model_hash_verification_data: Optional[dict] = None  # Serialized model hash verification report
    model_supply_chain_data: Optional[dict] = None  # Consolidated model file/provenance/hash summary
    enforcement_data: Optional[dict] = None  # Serialized EnforcementReport (set by CLI)
    context_graph_data: Optional[dict] = None  # Serialized context graph (set by CLI)
    license_report: Optional[dict] = None  # Serialized license compliance report
    vex_data: Optional[dict] = None  # Serialized VEX document
    toxic_combinations: Optional[list] = None  # Serialized ToxicCombination list
    prioritized_findings: Optional[list] = None  # Priority-ordered findings
    sast_data: Optional[dict] = None  # Serialized SAST scan results (Semgrep)
    cis_benchmark_data: Optional[dict] = None  # Serialized CIS AWS Benchmark results
    snowflake_cis_benchmark_data: Optional[dict] = None  # Serialized CIS Snowflake Benchmark results
    azure_cis_benchmark_data: Optional[dict] = None  # Serialized CIS Azure Benchmark results
    gcp_cis_benchmark_data: Optional[dict] = None  # Serialized CIS GCP Benchmark results
    databricks_cis_benchmark_data: Optional[dict] = None  # Serialized Databricks Security Best Practices results
    aisvs_benchmark_data: Optional[dict] = None  # Serialized AISVS compliance results
    vector_db_scan_data: Optional[list] = None  # Serialized vector DB security assessments
    gpu_infra_data: Optional[dict] = None  # Serialized GPU/AI compute infra scan results
    iac_findings_data: Optional[dict] = None  # Serialized IaC misconfiguration findings (set by CLI)
    runtime_correlation: Optional[dict] = None  # Runtime ↔ scan correlation (proxy audit vs CVE findings)
    delta_data: Optional[dict] = None  # Baseline/delta comparison metadata for CI gate outputs
    scan_performance_data: Optional[dict] = None  # Cache coverage / scan latency metadata
    training_pipelines: Optional[dict] = None  # Serialized TrainingPipelineScanResult
    dataset_cards: Optional[dict] = None  # Serialized DatasetScanResult
    serving_configs: Optional[list] = None  # Serialized ServingConfig list
    browser_extensions: Optional[dict] = None  # Serialized browser extension scan results
    ai_inventory_data: Optional[dict] = None  # AI component source scan results (SDK imports, models, keys)
    project_inventory_data: Optional[dict] = None  # Project manifest / lockfile inventory summary
    introspection_data: Optional[dict] = None  # Runtime MCP introspection results (tools, resources, drift)
    health_check_data: Optional[dict] = None  # MCP server reachability/health results
    runtime_session_graph: Optional[dict] = None  # Structured runtime session graph/timeline evidence

    # Unified Finding stream (issue #566 — Phase 1).
    # Populated alongside blast_radii for backward compatibility.
    # Future phases will migrate cloud_reports and proxy alerts here too.
    findings: list["Finding"] = field(default_factory=list)

    # Scan context metadata — what input sources were actually processed.
    # Populated by the CLI/API after scan completes. Consumers use this to
    # determine which UI panels, compliance frameworks, and graphs apply.
    scan_sources: list[str] = field(default_factory=list)  # e.g. ["agent_discovery", "image", "sbom"]

    @property
    def has_mcp_context(self) -> bool:
        """True if scan discovered real MCP servers (not synthetic SBOM/image wrappers).

        Uses explicit server surface classification rather than command heuristics.
        """
        return any(s.is_mcp_surface for a in self.agents for s in a.mcp_servers)

    @property
    def has_agent_context(self) -> bool:
        """True if scan discovered real AI agents (not synthetic SBOM/image wrappers).

        Synthetic agents (SBOM ingest, image scan) use ``AgentType.CUSTOM`` with
        names prefixed by ``sbom:`` or ``image:``.  Real discovered agents have
        specific agent types (CLAUDE_DESKTOP, CURSOR, etc.).
        """
        return any(a.agent_type != AgentType.CUSTOM for a in self.agents)

    def __post_init__(self) -> None:
        if not self.tool_version:
            from agent_bom import __version__

            self.tool_version = __version__

    @property
    def total_agents(self) -> int:
        return len(self.agents)

    @property
    def total_servers(self) -> int:
        return sum(len(a.mcp_servers) for a in self.agents)

    @property
    def total_packages(self) -> int:
        return sum(a.total_packages for a in self.agents)

    @property
    def total_vulnerabilities(self) -> int:
        return sum(a.total_vulnerabilities for a in self.agents)

    @property
    def critical_vulns(self) -> list[BlastRadius]:
        from agent_bom.vex import active_blast_radii

        return [br for br in active_blast_radii(self.blast_radii) if br.vulnerability.severity == Severity.CRITICAL]

    def to_findings(self) -> "list[Finding]":
        """Return the unified findings list, auto-populating from blast_radii if empty.

        Phase 1 shim: if ``self.findings`` is already populated (dual-write path),
        return it directly.  Otherwise convert ``blast_radii`` on the fly so callers
        can always work with the unified model.
        """
        if self.findings:
            return self.findings
        from agent_bom.finding import blast_radius_to_finding

        return [blast_radius_to_finding(br) for br in self.blast_radii]

    def cve_findings(self) -> "list[Finding]":
        """Return only CVE-type findings from the unified stream."""
        from agent_bom.finding import FindingType

        return [f for f in self.to_findings() if f.finding_type == FindingType.CVE]

has_mcp_context property

has_mcp_context: bool

True if scan discovered real MCP servers (not synthetic SBOM/image wrappers).

Uses explicit server surface classification rather than command heuristics.

has_agent_context property

has_agent_context: bool

True if scan discovered real AI agents (not synthetic SBOM/image wrappers).

Synthetic agents (SBOM ingest, image scan) use AgentType.CUSTOM with names prefixed by sbom: or image:. Real discovered agents have specific agent types (CLAUDE_DESKTOP, CURSOR, etc.).

to_findings

to_findings() -> 'list[Finding]'

Return the unified findings list, auto-populating from blast_radii if empty.

Phase 1 shim: if self.findings is already populated (dual-write path), return it directly. Otherwise convert blast_radii on the fly so callers can always work with the unified model.

Source code in src/agent_bom/models.py
def to_findings(self) -> "list[Finding]":
    """Return the unified findings list, auto-populating from blast_radii if empty.

    Phase 1 shim: if ``self.findings`` is already populated (dual-write path),
    return it directly.  Otherwise convert ``blast_radii`` on the fly so callers
    can always work with the unified model.
    """
    if self.findings:
        return self.findings
    from agent_bom.finding import blast_radius_to_finding

    return [blast_radius_to_finding(br) for br in self.blast_radii]

cve_findings

cve_findings() -> 'list[Finding]'

Return only CVE-type findings from the unified stream.

Source code in src/agent_bom/models.py
def cve_findings(self) -> "list[Finding]":
    """Return only CVE-type findings from the unified stream."""
    from agent_bom.finding import FindingType

    return [f for f in self.to_findings() if f.finding_type == FindingType.CVE]

AgentBomSDKError

Bases: RuntimeError

Raised when the public Python API cannot complete a requested operation.

Source code in src/agent_bom/sdk.py
class AgentBomSDKError(RuntimeError):
    """Raised when the public Python API cannot complete a requested operation."""

PackageCheckResult dataclass

Typed result returned by :func:check and :func:async_check.

Source code in src/agent_bom/sdk.py
@dataclass(frozen=True)
class PackageCheckResult:
    """Typed result returned by :func:`check` and :func:`async_check`."""

    package: str
    version: str
    ecosystem: str
    status: str
    vulnerabilities: int
    details: list[dict[str, Any]] = field(default_factory=list)
    message: str = ""

    @property
    def is_clean(self) -> bool:
        return self.status == "clean"

    def to_dict(self) -> dict[str, Any]:
        return {
            "package": self.package,
            "version": self.version,
            "ecosystem": self.ecosystem,
            "status": self.status,
            "vulnerabilities": self.vulnerabilities,
            "details": self.details,
            "message": self.message,
        }

InventoryResult dataclass

Typed inventory wrapper returned by :func:inventory.

Source code in src/agent_bom/sdk.py
@dataclass(frozen=True)
class InventoryResult:
    """Typed inventory wrapper returned by :func:`inventory`."""

    data: dict[str, Any]
    agent_count: int
    server_count: int
    package_count: int

    def to_dict(self) -> dict[str, Any]:
        return {
            "data": self.data,
            "agent_count": self.agent_count,
            "server_count": self.server_count,
            "package_count": self.package_count,
        }

DiffResult dataclass

Typed report diff wrapper returned by :func:diff.

Source code in src/agent_bom/sdk.py
@dataclass(frozen=True)
class DiffResult:
    """Typed report diff wrapper returned by :func:`diff`."""

    data: dict[str, Any]

    @property
    def summary(self) -> dict[str, Any]:
        summary = self.data.get("summary", {})
        return summary if isinstance(summary, dict) else {}

    @property
    def new_findings(self) -> int:
        return int(self.summary.get("new_findings", 0) or 0)

    def to_dict(self) -> dict[str, Any]:
        return self.data

scan

scan(*, config_path: str | Path | None = None, project: str | Path | None = None, demo: bool = False, offline: bool = False, enrich: bool = False, compliance: bool = False, transitive: bool = False, max_depth: int = 3, blast_radius_depth: int = 2) -> AIBOMReport

Run the standard local scan pipeline and return a typed report.

project and config_path both point at a local project/config scope. The name config_path is kept for MCP/API users who are already familiar with that argument; internally this delegates to the same simple scan runner used by CLI commands.

Source code in src/agent_bom/sdk.py
def scan(
    *,
    config_path: str | Path | None = None,
    project: str | Path | None = None,
    demo: bool = False,
    offline: bool = False,
    enrich: bool = False,
    compliance: bool = False,
    transitive: bool = False,
    max_depth: int = 3,
    blast_radius_depth: int = 2,
) -> AIBOMReport:
    """Run the standard local scan pipeline and return a typed report.

    ``project`` and ``config_path`` both point at a local project/config scope.
    The name ``config_path`` is kept for MCP/API users who are already familiar
    with that argument; internally this delegates to the same simple scan runner
    used by CLI commands.
    """

    if project is not None and config_path is not None and Path(project).expanduser() != Path(config_path).expanduser():
        raise ValueError("project and config_path refer to different scan scopes")

    from rich.console import Console

    from agent_bom.cli._scan_runner import ScanConfig, run_default_scan

    output = io.StringIO()
    console = Console(file=output, force_terminal=False, no_color=True, width=120)
    scan_scope = project if project is not None else config_path
    result = run_default_scan(
        ScanConfig(
            project=str(scan_scope) if scan_scope is not None else None,
            demo=demo,
            offline=offline,
            enrich=enrich,
            compliance=compliance,
            resolve_transitive=transitive,
            max_depth=max_depth,
            blast_radius_depth=blast_radius_depth,
            quiet=True,
        ),
        console,
    )
    if result.report is None:
        raise AgentBomSDKError("scan completed without a report")
    return result.report

async_check async

async_check(package: str, *, ecosystem: str = 'npm', offline: bool = False) -> PackageCheckResult

Check one package spec and return a typed vulnerability result.

Source code in src/agent_bom/sdk.py
async def async_check(package: str, *, ecosystem: str = "npm", offline: bool = False) -> PackageCheckResult:
    """Check one package spec and return a typed vulnerability result."""

    eco = validate_ecosystem(ecosystem, SUPPORTED_PACKAGE_ECOSYSTEM_SET)
    name, version = _parse_package_spec(package)
    if eco in {"deb", "apk", "rpm"} and version in {"", "latest"}:
        raise ValueError(f"explicit version required for {eco} packages")

    from agent_bom.scanners import ScanOptions, scan_packages

    pkg = Package(name=name, version=version, ecosystem=eco)
    await scan_packages([pkg], options=ScanOptions(offline=offline))
    details = _vulnerability_details(pkg)
    status = "vulnerable" if details else "clean"
    return PackageCheckResult(
        package=pkg.name,
        version=pkg.version,
        ecosystem=eco,
        status=status,
        vulnerabilities=len(details),
        details=details,
        message=f"No known vulnerabilities in {pkg.name}@{pkg.version}" if status == "clean" else "",
    )

check

check(package: str, *, ecosystem: str = 'npm', offline: bool = False) -> PackageCheckResult

Synchronous wrapper around :func:async_check.

Async applications should call :func:async_check directly to avoid nesting event loops.

Source code in src/agent_bom/sdk.py
def check(package: str, *, ecosystem: str = "npm", offline: bool = False) -> PackageCheckResult:
    """Synchronous wrapper around :func:`async_check`.

    Async applications should call :func:`async_check` directly to avoid nesting
    event loops.
    """

    try:
        asyncio.get_running_loop()
    except RuntimeError:
        return asyncio.run(async_check(package, ecosystem=ecosystem, offline=offline))
    raise AgentBomSDKError("check() cannot run inside an active event loop; use async_check() instead")

inventory

inventory(source: str | Path) -> InventoryResult

Load a JSON/CSV/NDJSON inventory artifact and return typed counts.

Source code in src/agent_bom/sdk.py
def inventory(source: str | Path) -> InventoryResult:
    """Load a JSON/CSV/NDJSON inventory artifact and return typed counts."""

    from agent_bom.inventory import load_inventory

    data = load_inventory(str(source))
    agents = data.get("agents", [])
    agent_count = len(agents) if isinstance(agents, list) else 0
    server_count = 0
    package_count = 0
    if isinstance(agents, list):
        for agent in agents:
            servers = agent.get("mcp_servers", []) if isinstance(agent, dict) else []
            if not isinstance(servers, list):
                continue
            server_count += len(servers)
            for server in servers:
                packages = server.get("packages", []) if isinstance(server, dict) else []
                if isinstance(packages, list):
                    package_count += len(packages)
    return InventoryResult(
        data=data,
        agent_count=agent_count,
        server_count=server_count,
        package_count=package_count,
    )

diff

diff(baseline: str | Path | Mapping[str, Any], current: str | Path | Mapping[str, Any]) -> DiffResult

Diff two agent-bom reports or SBOM documents.

Source code in src/agent_bom/sdk.py
def diff(baseline: str | Path | Mapping[str, Any], current: str | Path | Mapping[str, Any]) -> DiffResult:
    """Diff two agent-bom reports or SBOM documents."""

    from agent_bom.history import diff_reports

    return DiffResult(diff_reports(_coerce_report_input(baseline), _coerce_report_input(current)))