1use std::collections::HashMap;
5
6use serde::{Deserialize, Serialize};
7
8use crate::defaults::default_true;
9
10pub use zeph_mcp::{McpTrustLevel, tool::ToolSecurityMeta};
11
12fn default_slack_port() -> u16 {
13 3000
14}
15
16fn default_slack_webhook_host() -> String {
17 "127.0.0.1".into()
18}
19
20fn default_a2a_host() -> String {
21 "0.0.0.0".into()
22}
23
24fn default_a2a_port() -> u16 {
25 8080
26}
27
28fn default_a2a_rate_limit() -> u32 {
29 60
30}
31
32fn default_a2a_max_body() -> usize {
33 1_048_576
34}
35
36fn default_drain_timeout_ms() -> u64 {
37 30_000
38}
39
40fn default_max_dynamic_servers() -> usize {
41 10
42}
43
44fn default_mcp_timeout() -> u64 {
45 30
46}
47
48fn default_oauth_callback_port() -> u16 {
49 18766
50}
51
52fn default_oauth_client_name() -> String {
53 "Zeph".into()
54}
55
56#[derive(Clone, Deserialize, Serialize)]
57pub struct TelegramConfig {
58 pub token: Option<String>,
59 #[serde(default)]
60 pub allowed_users: Vec<String>,
61}
62
63impl std::fmt::Debug for TelegramConfig {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 f.debug_struct("TelegramConfig")
66 .field("token", &self.token.as_ref().map(|_| "[REDACTED]"))
67 .field("allowed_users", &self.allowed_users)
68 .finish()
69 }
70}
71
72#[derive(Clone, Deserialize, Serialize)]
73pub struct DiscordConfig {
74 pub token: Option<String>,
75 pub application_id: Option<String>,
76 #[serde(default)]
77 pub allowed_user_ids: Vec<String>,
78 #[serde(default)]
79 pub allowed_role_ids: Vec<String>,
80 #[serde(default)]
81 pub allowed_channel_ids: Vec<String>,
82}
83
84impl std::fmt::Debug for DiscordConfig {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 f.debug_struct("DiscordConfig")
87 .field("token", &self.token.as_ref().map(|_| "[REDACTED]"))
88 .field("application_id", &self.application_id)
89 .field("allowed_user_ids", &self.allowed_user_ids)
90 .field("allowed_role_ids", &self.allowed_role_ids)
91 .field("allowed_channel_ids", &self.allowed_channel_ids)
92 .finish()
93 }
94}
95
96#[derive(Clone, Deserialize, Serialize)]
97pub struct SlackConfig {
98 pub bot_token: Option<String>,
99 pub signing_secret: Option<String>,
100 #[serde(default = "default_slack_webhook_host")]
101 pub webhook_host: String,
102 #[serde(default = "default_slack_port")]
103 pub port: u16,
104 #[serde(default)]
105 pub allowed_user_ids: Vec<String>,
106 #[serde(default)]
107 pub allowed_channel_ids: Vec<String>,
108}
109
110impl std::fmt::Debug for SlackConfig {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.debug_struct("SlackConfig")
113 .field("bot_token", &self.bot_token.as_ref().map(|_| "[REDACTED]"))
114 .field(
115 "signing_secret",
116 &self.signing_secret.as_ref().map(|_| "[REDACTED]"), )
118 .field("webhook_host", &self.webhook_host)
119 .field("port", &self.port)
120 .field("allowed_user_ids", &self.allowed_user_ids)
121 .field("allowed_channel_ids", &self.allowed_channel_ids)
122 .finish()
123 }
124}
125
126#[derive(Debug, Clone, Deserialize, Serialize)]
130pub struct IbctKeyConfig {
131 pub key_id: String,
133 pub key_hex: String,
135}
136
137fn default_ibct_ttl() -> u64 {
138 300
139}
140
141#[derive(Deserialize, Serialize)]
142#[allow(clippy::struct_excessive_bools)] pub struct A2aServerConfig {
144 #[serde(default)]
145 pub enabled: bool,
146 #[serde(default = "default_a2a_host")]
147 pub host: String,
148 #[serde(default = "default_a2a_port")]
149 pub port: u16,
150 #[serde(default)]
151 pub public_url: String,
152 #[serde(default)]
153 pub auth_token: Option<String>,
154 #[serde(default = "default_a2a_rate_limit")]
155 pub rate_limit: u32,
156 #[serde(default = "default_true")]
157 pub require_tls: bool,
158 #[serde(default = "default_true")]
159 pub ssrf_protection: bool,
160 #[serde(default = "default_a2a_max_body")]
161 pub max_body_size: usize,
162 #[serde(default = "default_drain_timeout_ms")]
163 pub drain_timeout_ms: u64,
164 #[serde(default)]
168 pub require_auth: bool,
169 #[serde(default)]
174 pub ibct_keys: Vec<IbctKeyConfig>,
175 #[serde(default)]
181 pub ibct_signing_key_vault_ref: Option<String>,
182 #[serde(default = "default_ibct_ttl")]
184 pub ibct_ttl_secs: u64,
185}
186
187impl std::fmt::Debug for A2aServerConfig {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 f.debug_struct("A2aServerConfig")
190 .field("enabled", &self.enabled)
191 .field("host", &self.host)
192 .field("port", &self.port)
193 .field("public_url", &self.public_url)
194 .field(
195 "auth_token",
196 &self.auth_token.as_ref().map(|_| "[REDACTED]"),
197 )
198 .field("rate_limit", &self.rate_limit)
199 .field("require_tls", &self.require_tls)
200 .field("ssrf_protection", &self.ssrf_protection)
201 .field("max_body_size", &self.max_body_size)
202 .field("drain_timeout_ms", &self.drain_timeout_ms)
203 .field("require_auth", &self.require_auth)
204 .field("ibct_keys_count", &self.ibct_keys.len())
205 .field(
206 "ibct_signing_key_vault_ref",
207 &self.ibct_signing_key_vault_ref,
208 )
209 .field("ibct_ttl_secs", &self.ibct_ttl_secs)
210 .finish()
211 }
212}
213
214impl Default for A2aServerConfig {
215 fn default() -> Self {
216 Self {
217 enabled: false,
218 host: default_a2a_host(),
219 port: default_a2a_port(),
220 public_url: String::new(),
221 auth_token: None,
222 rate_limit: default_a2a_rate_limit(),
223 require_tls: true,
224 ssrf_protection: true,
225 max_body_size: default_a2a_max_body(),
226 drain_timeout_ms: default_drain_timeout_ms(),
227 require_auth: false,
228 ibct_keys: Vec::new(),
229 ibct_signing_key_vault_ref: None,
230 ibct_ttl_secs: default_ibct_ttl(),
231 }
232 }
233}
234
235#[derive(Debug, Clone, Deserialize, Serialize)]
241#[serde(default)]
242pub struct ToolPruningConfig {
243 pub enabled: bool,
245 pub max_tools: usize,
247 pub pruning_provider: String,
250 pub min_tools_to_prune: usize,
252 pub always_include: Vec<String>,
254}
255
256impl Default for ToolPruningConfig {
257 fn default() -> Self {
258 Self {
259 enabled: false,
260 max_tools: 15,
261 pruning_provider: String::new(),
262 min_tools_to_prune: 10,
263 always_include: Vec::new(),
264 }
265 }
266}
267
268#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
273#[serde(rename_all = "lowercase")]
274pub enum ToolDiscoveryStrategyConfig {
275 Embedding,
277 Llm,
279 #[default]
281 None,
282}
283
284#[derive(Debug, Clone, Deserialize, Serialize)]
290#[serde(default)]
291pub struct ToolDiscoveryConfig {
292 pub strategy: ToolDiscoveryStrategyConfig,
294 pub top_k: usize,
296 pub min_similarity: f32,
298 pub embedding_provider: String,
302 pub always_include: Vec<String>,
304 pub min_tools_to_filter: usize,
306 pub strict: bool,
309}
310
311impl Default for ToolDiscoveryConfig {
312 fn default() -> Self {
313 Self {
314 strategy: ToolDiscoveryStrategyConfig::None,
315 top_k: 10,
316 min_similarity: 0.2,
317 embedding_provider: String::new(),
318 always_include: Vec::new(),
319 min_tools_to_filter: 10,
320 strict: false,
321 }
322 }
323}
324
325#[derive(Debug, Clone, Deserialize, Serialize)]
327#[allow(clippy::struct_excessive_bools)]
328pub struct TrustCalibrationConfig {
329 #[serde(default)]
331 pub enabled: bool,
332 #[serde(default = "default_true")]
334 pub probe_on_connect: bool,
335 #[serde(default = "default_true")]
337 pub monitor_invocations: bool,
338 #[serde(default = "default_true")]
340 pub persist_scores: bool,
341 #[serde(default = "default_decay_rate")]
343 pub decay_rate_per_day: f64,
344 #[serde(default = "default_injection_penalty")]
346 pub injection_penalty: f64,
347 #[serde(default)]
349 pub verifier_provider: String,
350}
351
352fn default_decay_rate() -> f64 {
353 0.01
354}
355
356fn default_injection_penalty() -> f64 {
357 0.25
358}
359
360impl Default for TrustCalibrationConfig {
361 fn default() -> Self {
362 Self {
363 enabled: false,
364 probe_on_connect: true,
365 monitor_invocations: true,
366 persist_scores: true,
367 decay_rate_per_day: default_decay_rate(),
368 injection_penalty: default_injection_penalty(),
369 verifier_provider: String::new(),
370 }
371 }
372}
373
374fn default_max_description_bytes() -> usize {
375 2048
376}
377
378fn default_max_instructions_bytes() -> usize {
379 2048
380}
381
382fn default_elicitation_timeout() -> u64 {
383 120
384}
385
386fn default_elicitation_queue_capacity() -> usize {
387 16
388}
389
390#[allow(clippy::struct_excessive_bools)]
391#[derive(Debug, Clone, Deserialize, Serialize)]
392pub struct McpConfig {
393 #[serde(default)]
394 pub servers: Vec<McpServerConfig>,
395 #[serde(default)]
396 pub allowed_commands: Vec<String>,
397 #[serde(default = "default_max_dynamic_servers")]
398 pub max_dynamic_servers: usize,
399 #[serde(default)]
401 pub pruning: ToolPruningConfig,
402 #[serde(default)]
404 pub trust_calibration: TrustCalibrationConfig,
405 #[serde(default)]
407 pub tool_discovery: ToolDiscoveryConfig,
408 #[serde(default = "default_max_description_bytes")]
410 pub max_description_bytes: usize,
411 #[serde(default = "default_max_instructions_bytes")]
413 pub max_instructions_bytes: usize,
414 #[serde(default)]
418 pub elicitation_enabled: bool,
419 #[serde(default = "default_elicitation_timeout")]
421 pub elicitation_timeout: u64,
422 #[serde(default = "default_elicitation_queue_capacity")]
426 pub elicitation_queue_capacity: usize,
427 #[serde(default = "default_true")]
430 pub elicitation_warn_sensitive_fields: bool,
431 #[serde(default)]
437 pub lock_tool_list: bool,
438 #[serde(default)]
443 pub default_env_isolation: bool,
444}
445
446impl Default for McpConfig {
447 fn default() -> Self {
448 Self {
449 servers: Vec::new(),
450 allowed_commands: Vec::new(),
451 max_dynamic_servers: default_max_dynamic_servers(),
452 pruning: ToolPruningConfig::default(),
453 trust_calibration: TrustCalibrationConfig::default(),
454 tool_discovery: ToolDiscoveryConfig::default(),
455 max_description_bytes: default_max_description_bytes(),
456 max_instructions_bytes: default_max_instructions_bytes(),
457 elicitation_enabled: false,
458 elicitation_timeout: default_elicitation_timeout(),
459 elicitation_queue_capacity: default_elicitation_queue_capacity(),
460 elicitation_warn_sensitive_fields: true,
461 lock_tool_list: false,
462 default_env_isolation: false,
463 }
464 }
465}
466
467#[derive(Clone, Deserialize, Serialize)]
468pub struct McpServerConfig {
469 pub id: String,
470 pub command: Option<String>,
472 #[serde(default)]
473 pub args: Vec<String>,
474 #[serde(default)]
475 pub env: HashMap<String, String>,
476 pub url: Option<String>,
478 #[serde(default = "default_mcp_timeout")]
479 pub timeout: u64,
480 #[serde(default)]
482 pub policy: zeph_mcp::McpPolicy,
483 #[serde(default)]
486 pub headers: HashMap<String, String>,
487 #[serde(default)]
489 pub oauth: Option<McpOAuthConfig>,
490 #[serde(default)]
492 pub trust_level: McpTrustLevel,
493 #[serde(default)]
497 pub tool_allowlist: Option<Vec<String>>,
498 #[serde(default)]
504 pub expected_tools: Vec<String>,
505 #[serde(default)]
509 pub roots: Vec<McpRootEntry>,
510 #[serde(default)]
513 pub tool_metadata: HashMap<String, ToolSecurityMeta>,
514 #[serde(default)]
518 pub elicitation_enabled: Option<bool>,
519 #[serde(default)]
526 pub env_isolation: Option<bool>,
527}
528
529#[derive(Debug, Clone, Deserialize, Serialize)]
531pub struct McpRootEntry {
532 pub uri: String,
534 #[serde(default)]
536 pub name: Option<String>,
537}
538
539#[derive(Debug, Clone, Deserialize, Serialize)]
541pub struct McpOAuthConfig {
542 #[serde(default)]
544 pub enabled: bool,
545 #[serde(default)]
547 pub token_storage: OAuthTokenStorage,
548 #[serde(default)]
550 pub scopes: Vec<String>,
551 #[serde(default = "default_oauth_callback_port")]
553 pub callback_port: u16,
554 #[serde(default = "default_oauth_client_name")]
556 pub client_name: String,
557}
558
559impl Default for McpOAuthConfig {
560 fn default() -> Self {
561 Self {
562 enabled: false,
563 token_storage: OAuthTokenStorage::default(),
564 scopes: Vec::new(),
565 callback_port: default_oauth_callback_port(),
566 client_name: default_oauth_client_name(),
567 }
568 }
569}
570
571#[derive(Debug, Clone, Default, Deserialize, Serialize)]
573#[serde(rename_all = "lowercase")]
574pub enum OAuthTokenStorage {
575 #[default]
577 Vault,
578 Memory,
580}
581
582impl std::fmt::Debug for McpServerConfig {
583 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584 let redacted_env: HashMap<&str, &str> = self
585 .env
586 .keys()
587 .map(|k| (k.as_str(), "[REDACTED]"))
588 .collect();
589 let redacted_headers: HashMap<&str, &str> = self
591 .headers
592 .keys()
593 .map(|k| (k.as_str(), "[REDACTED]"))
594 .collect();
595 f.debug_struct("McpServerConfig")
596 .field("id", &self.id)
597 .field("command", &self.command)
598 .field("args", &self.args)
599 .field("env", &redacted_env)
600 .field("url", &self.url)
601 .field("timeout", &self.timeout)
602 .field("policy", &self.policy)
603 .field("headers", &redacted_headers)
604 .field("oauth", &self.oauth)
605 .field("trust_level", &self.trust_level)
606 .field("tool_allowlist", &self.tool_allowlist)
607 .field("expected_tools", &self.expected_tools)
608 .field("roots", &self.roots)
609 .field(
610 "tool_metadata_keys",
611 &self.tool_metadata.keys().collect::<Vec<_>>(),
612 )
613 .field("elicitation_enabled", &self.elicitation_enabled)
614 .field("env_isolation", &self.env_isolation)
615 .finish()
616 }
617}