agentshield/parser/
json_schema.rs1use crate::ir::tool_surface::{DeclaredPermission, PermissionType, ToolSurface};
2
3pub 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 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}