Skip to main content

agentshield/parser/
json_schema.rs

1use crate::ir::tool_surface::{DeclaredPermission, PermissionType, ToolSurface};
2
3/// Extract tool definitions from an MCP-style JSON tool list.
4pub fn parse_tools_from_json(value: &serde_json::Value) -> Vec<ToolSurface> {
5    let mut tools = Vec::new();
6
7    let items = if let Some(arr) = value.as_array() {
8        arr.clone()
9    } else if let Some(arr) = value.get("tools").and_then(|v| v.as_array()) {
10        arr.clone()
11    } else {
12        return tools;
13    };
14
15    for item in &items {
16        let name = item
17            .get("name")
18            .and_then(|v| v.as_str())
19            .unwrap_or("unknown")
20            .to_string();
21        let description = item
22            .get("description")
23            .and_then(|v| v.as_str())
24            .map(|s| s.to_string());
25        let input_schema = item
26            .get("inputSchema")
27            .or(item.get("input_schema"))
28            .cloned();
29
30        // Infer permissions from description text
31        let desc_text = description.as_deref().unwrap_or("");
32        let permissions = infer_permissions_from_description(desc_text);
33
34        tools.push(ToolSurface {
35            name,
36            description,
37            input_schema,
38            output_schema: None,
39            declared_permissions: permissions,
40            defined_at: None,
41        });
42    }
43
44    tools
45}
46
47fn infer_permissions_from_description(desc: &str) -> Vec<DeclaredPermission> {
48    let lower = desc.to_lowercase();
49    let mut perms = Vec::new();
50
51    if lower.contains("file") || lower.contains("read") || lower.contains("directory") {
52        perms.push(DeclaredPermission {
53            permission_type: PermissionType::FileRead,
54            target: None,
55            description: Some("Inferred from description".into()),
56        });
57    }
58    if lower.contains("write") || lower.contains("save") || lower.contains("create file") {
59        perms.push(DeclaredPermission {
60            permission_type: PermissionType::FileWrite,
61            target: None,
62            description: Some("Inferred from description".into()),
63        });
64    }
65    if lower.contains("http")
66        || lower.contains("url")
67        || lower.contains("fetch")
68        || lower.contains("request")
69        || lower.contains("network")
70    {
71        perms.push(DeclaredPermission {
72            permission_type: PermissionType::NetworkAccess,
73            target: None,
74            description: Some("Inferred from description".into()),
75        });
76    }
77    if lower.contains("exec")
78        || lower.contains("run")
79        || lower.contains("command")
80        || lower.contains("shell")
81        || lower.contains("subprocess")
82    {
83        perms.push(DeclaredPermission {
84            permission_type: PermissionType::ProcessExec,
85            target: None,
86            description: Some("Inferred from description".into()),
87        });
88    }
89
90    perms
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn parses_mcp_tools_list() {
99        let json = serde_json::json!({
100            "tools": [
101                {
102                    "name": "calculator_add",
103                    "description": "Add two numbers",
104                    "inputSchema": {
105                        "type": "object",
106                        "properties": {
107                            "a": {"type": "number"},
108                            "b": {"type": "number"}
109                        }
110                    }
111                },
112                {
113                    "name": "fetch_url",
114                    "description": "Fetch content from a URL",
115                    "inputSchema": {
116                        "type": "object",
117                        "properties": {
118                            "url": {"type": "string"}
119                        }
120                    }
121                }
122            ]
123        });
124        let tools = parse_tools_from_json(&json);
125        assert_eq!(tools.len(), 2);
126        assert_eq!(tools[0].name, "calculator_add");
127        assert!(tools[0].declared_permissions.is_empty());
128        assert_eq!(tools[1].name, "fetch_url");
129        assert!(!tools[1].declared_permissions.is_empty());
130    }
131}