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 declared_capabilities: Default::default(),
42 capability_declarations: Vec::new(),
43 observed_capabilities: Default::default(),
44 capability_observation_complete: false,
45 capability_evidence: Vec::new(),
46 });
47 }
48
49 tools
50}
51
52fn infer_permissions_from_description(desc: &str) -> Vec<DeclaredPermission> {
53 let lower = desc.to_lowercase();
54 let mut perms = Vec::new();
55
56 if lower.contains("file") || lower.contains("read") || lower.contains("directory") {
57 perms.push(DeclaredPermission {
58 permission_type: PermissionType::FileRead,
59 target: None,
60 description: Some("Inferred from description".into()),
61 });
62 }
63 if lower.contains("write") || lower.contains("save") || lower.contains("create file") {
64 perms.push(DeclaredPermission {
65 permission_type: PermissionType::FileWrite,
66 target: None,
67 description: Some("Inferred from description".into()),
68 });
69 }
70 if lower.contains("http")
71 || lower.contains("url")
72 || lower.contains("fetch")
73 || lower.contains("request")
74 || lower.contains("network")
75 {
76 perms.push(DeclaredPermission {
77 permission_type: PermissionType::NetworkAccess,
78 target: None,
79 description: Some("Inferred from description".into()),
80 });
81 }
82 if lower.contains("exec")
83 || lower.contains("run")
84 || lower.contains("command")
85 || lower.contains("shell")
86 || lower.contains("subprocess")
87 {
88 perms.push(DeclaredPermission {
89 permission_type: PermissionType::ProcessExec,
90 target: None,
91 description: Some("Inferred from description".into()),
92 });
93 }
94
95 perms
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn parses_mcp_tools_list() {
104 let json = serde_json::json!({
105 "tools": [
106 {
107 "name": "calculator_add",
108 "description": "Add two numbers",
109 "inputSchema": {
110 "type": "object",
111 "properties": {
112 "a": {"type": "number"},
113 "b": {"type": "number"}
114 }
115 }
116 },
117 {
118 "name": "fetch_url",
119 "description": "Fetch content from a URL",
120 "inputSchema": {
121 "type": "object",
122 "properties": {
123 "url": {"type": "string"}
124 }
125 }
126 }
127 ]
128 });
129 let tools = parse_tools_from_json(&json);
130 assert_eq!(tools.len(), 2);
131 assert_eq!(tools[0].name, "calculator_add");
132 assert!(tools[0].declared_permissions.is_empty());
133 assert_eq!(tools[1].name, "fetch_url");
134 assert!(!tools[1].declared_permissions.is_empty());
135 }
136}