adk_tool/mcp/manager/
config.rs1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
31#[serde(rename_all = "camelCase")]
32pub struct McpServerConfig {
33 pub command: String,
35
36 #[serde(default)]
38 pub args: Vec<String>,
39
40 #[serde(default)]
42 pub env: HashMap<String, String>,
43
44 #[serde(default)]
46 pub disabled: bool,
47
48 #[serde(default)]
50 pub auto_approve: Vec<String>,
51
52 #[serde(default)]
54 pub restart_policy: Option<RestartPolicy>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
85#[serde(rename_all = "camelCase")]
86pub struct RestartPolicy {
87 #[serde(default = "default_initial_delay")]
89 pub initial_delay_ms: u64,
90
91 #[serde(default = "default_max_delay")]
93 pub max_delay_ms: u64,
94
95 #[serde(default = "default_backoff_multiplier")]
97 pub backoff_multiplier: f64,
98
99 #[serde(default = "default_max_restart_attempts")]
101 pub max_restart_attempts: u32,
102}
103
104fn default_initial_delay() -> u64 {
105 1000
106}
107
108fn default_max_delay() -> u64 {
109 30000
110}
111
112fn default_backoff_multiplier() -> f64 {
113 2.0
114}
115
116fn default_max_restart_attempts() -> u32 {
117 10
118}
119
120impl Default for RestartPolicy {
121 fn default() -> Self {
122 Self {
123 initial_delay_ms: default_initial_delay(),
124 max_delay_ms: default_max_delay(),
125 backoff_multiplier: default_backoff_multiplier(),
126 max_restart_attempts: default_max_restart_attempts(),
127 }
128 }
129}
130
131#[derive(Deserialize)]
136#[serde(rename_all = "camelCase")]
137#[allow(dead_code)] pub(crate) struct McpJsonFile {
139 pub mcp_servers: HashMap<String, McpServerConfig>,
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_default_restart_policy() {
149 let policy = RestartPolicy::default();
150 assert_eq!(policy.initial_delay_ms, 1000);
151 assert_eq!(policy.max_delay_ms, 30000);
152 assert!((policy.backoff_multiplier - 2.0).abs() < f64::EPSILON);
153 assert_eq!(policy.max_restart_attempts, 10);
154 }
155
156 #[test]
157 fn test_mcp_server_config_defaults() {
158 let json = r#"{"command": "echo"}"#;
159 let config: McpServerConfig = serde_json::from_str(json).unwrap();
160 assert_eq!(config.command, "echo");
161 assert!(config.args.is_empty());
162 assert!(config.env.is_empty());
163 assert!(!config.disabled);
164 assert!(config.auto_approve.is_empty());
165 assert!(config.restart_policy.is_none());
166 }
167
168 #[test]
169 fn test_mcp_json_file_parsing() {
170 let json = r#"{
171 "mcpServers": {
172 "filesystem": {
173 "command": "npx",
174 "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
175 "env": {},
176 "disabled": false,
177 "autoApprove": ["read_file", "list_directory"]
178 }
179 }
180 }"#;
181 let file: McpJsonFile = serde_json::from_str(json).unwrap();
182 assert_eq!(file.mcp_servers.len(), 1);
183 let fs_config = &file.mcp_servers["filesystem"];
184 assert_eq!(fs_config.command, "npx");
185 assert_eq!(fs_config.auto_approve, vec!["read_file", "list_directory"]);
186 }
187
188 #[test]
189 fn test_restart_policy_serde_defaults() {
190 let json = r#"{}"#;
191 let policy: RestartPolicy = serde_json::from_str(json).unwrap();
192 assert_eq!(policy.initial_delay_ms, 1000);
193 assert_eq!(policy.max_delay_ms, 30000);
194 assert!((policy.backoff_multiplier - 2.0).abs() < f64::EPSILON);
195 assert_eq!(policy.max_restart_attempts, 10);
196 }
197
198 #[test]
199 fn test_config_round_trip() {
200 let config = McpServerConfig {
201 command: "npx".to_string(),
202 args: vec!["-y".to_string(), "server".to_string()],
203 env: HashMap::from([("KEY".to_string(), "value".to_string())]),
204 disabled: true,
205 auto_approve: vec!["tool1".to_string()],
206 restart_policy: Some(RestartPolicy {
207 initial_delay_ms: 500,
208 max_delay_ms: 10000,
209 backoff_multiplier: 1.5,
210 max_restart_attempts: 3,
211 }),
212 };
213 let json = serde_json::to_string(&config).unwrap();
214 let deserialized: McpServerConfig = serde_json::from_str(&json).unwrap();
215 assert_eq!(config, deserialized);
216 }
217}