Skip to main content

agentshield/adapter/
mod.rs

1pub mod mcp;
2pub mod openclaw;
3
4use std::path::Path;
5
6use crate::error::{Result, ShieldError};
7use crate::ir::{Framework, ScanTarget};
8
9/// An adapter detects a specific agent framework and loads its artifacts
10/// into the unified IR.
11pub trait Adapter: Send + Sync {
12    /// The framework this adapter handles.
13    fn framework(&self) -> Framework;
14
15    /// Check if this adapter can handle the given directory.
16    fn detect(&self, root: &Path) -> bool;
17
18    /// Load artifacts from the directory into scan targets.
19    fn load(&self, root: &Path) -> Result<Vec<ScanTarget>>;
20}
21
22/// All registered adapters.
23pub fn all_adapters() -> Vec<Box<dyn Adapter>> {
24    vec![
25        Box::new(mcp::McpAdapter),
26        Box::new(openclaw::OpenClawAdapter),
27    ]
28}
29
30/// Auto-detect all matching frameworks and load scan targets from each.
31///
32/// Repos may contain both MCP and OpenClaw artifacts — all matching
33/// adapters contribute targets rather than stopping at the first match.
34pub fn auto_detect_and_load(root: &Path) -> Result<Vec<ScanTarget>> {
35    let adapters = all_adapters();
36    let mut all_targets = Vec::new();
37
38    for adapter in &adapters {
39        if adapter.detect(root) {
40            match adapter.load(root) {
41                Ok(targets) => all_targets.extend(targets),
42                Err(e) => {
43                    tracing::warn!(
44                        framework = %adapter.framework(),
45                        error = %e,
46                        "adapter failed to load, skipping"
47                    );
48                }
49            }
50        }
51    }
52
53    if all_targets.is_empty() {
54        return Err(ShieldError::NoAdapter(root.display().to_string()));
55    }
56
57    Ok(all_targets)
58}