Skip to main content

systemprompt_models/validators/
mcp.rs

1//! MCP configuration validator.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use super::ValidationConfigProvider;
7use crate::ServicesConfig;
8use crate::mcp::deployment::McpServerType;
9use std::collections::HashMap;
10use systemprompt_traits::validation_report::{ValidationIssue, ValidationReport};
11use systemprompt_traits::{ConfigProvider, DomainConfig, DomainConfigError};
12
13#[derive(Debug, Default)]
14pub struct McpConfigValidator {
15    services_config: Option<ServicesConfig>,
16}
17
18impl McpConfigValidator {
19    pub fn new() -> Self {
20        Self::default()
21    }
22}
23
24impl DomainConfig for McpConfigValidator {
25    fn domain_id(&self) -> &'static str {
26        "mcp"
27    }
28
29    fn priority(&self) -> u32 {
30        40
31    }
32
33    fn dependencies(&self) -> &[&'static str] {
34        &["agents"]
35    }
36
37    fn load(&mut self, config: &dyn ConfigProvider) -> Result<(), DomainConfigError> {
38        let provider = config
39            .as_any()
40            .downcast_ref::<ValidationConfigProvider>()
41            .ok_or_else(|| DomainConfigError::LoadError {
42                message: "Expected ValidationConfigProvider with pre-loaded configs".into(),
43            })?;
44
45        self.services_config = Some(provider.services_config().clone());
46        Ok(())
47    }
48
49    fn validate(&self) -> Result<ValidationReport, DomainConfigError> {
50        let mut report = ValidationReport::new("mcp");
51
52        let Some(config) = self.services_config.as_ref() else {
53            return Ok(report);
54        };
55
56        let mut used_ports: HashMap<u16, String> = HashMap::new();
57
58        for (name, deployment) in &config.mcp_servers {
59            if deployment.server_type == McpServerType::External {
60                continue;
61            }
62            let Some(port) = deployment.port else {
63                continue;
64            };
65
66            if let Some(existing) = used_ports.get(&port) {
67                report.add_error(
68                    ValidationIssue::new(
69                        format!("mcp_servers.{}.port", name),
70                        format!("Port {} already used by server '{}'", port, existing),
71                    )
72                    .with_suggestion("Assign unique ports to each MCP server"),
73                );
74            } else {
75                used_ports.insert(port, name.clone());
76            }
77        }
78
79        Ok(report)
80    }
81}