systemprompt_runtime/startup_validation/
mcp_validator.rs1use std::path::Path;
7use systemprompt_loader::ExtensionRegistry as McpExtensionRegistry;
8use systemprompt_models::mcp::McpServerType;
9use systemprompt_models::{Config, ServicesConfig};
10use systemprompt_traits::validation_report::ValidationError;
11use systemprompt_traits::{StartupValidationReport, ValidationReport};
12
13pub(super) fn validate_mcp_manifests(
14 config: &Config,
15 services_config: &ServicesConfig,
16 report: &mut StartupValidationReport,
17) {
18 let registry = McpExtensionRegistry::build(
19 Path::new(&config.system_path),
20 config.is_cloud,
21 &config.bin_path,
22 );
23
24 let mcp_errors = collect_manifest_errors(services_config, config.is_cloud, |binary| {
25 registry
26 .get_path(binary)
27 .map(|_| ())
28 .map_err(|e| e.to_string())
29 });
30
31 merge_mcp_errors(report, mcp_errors);
32}
33
34pub fn collect_manifest_errors<F>(
43 services_config: &ServicesConfig,
44 is_cloud: bool,
45 resolve: F,
46) -> Vec<ValidationError>
47where
48 F: Fn(&str) -> Result<(), String>,
49{
50 let mut mcp_errors: Vec<ValidationError> = Vec::new();
51
52 for (name, deployment) in &services_config.mcp_servers {
53 if !deployment.enabled {
54 continue;
55 }
56 if deployment.dev_only && is_cloud {
57 continue;
58 }
59 if !matches!(deployment.server_type, McpServerType::Internal) {
62 continue;
63 }
64
65 if let Err(e) = resolve(&deployment.binary) {
66 mcp_errors.push(
67 ValidationError::new(
68 format!("mcp_servers.{}.binary", name),
69 format!(
70 "Manifest not found for binary '{}': {}",
71 deployment.binary, e
72 ),
73 )
74 .with_suggestion(format!(
75 "Ensure manifest.yaml exists at extensions/mcp/{}/manifest.yaml",
76 deployment.binary
77 )),
78 );
79 }
80 }
81
82 mcp_errors
83}
84
85pub fn merge_mcp_errors(report: &mut StartupValidationReport, mcp_errors: Vec<ValidationError>) {
91 if mcp_errors.is_empty() {
92 return;
93 }
94
95 if let Some(mcp_report) = report.domains.iter_mut().find(|d| d.domain == "mcp") {
96 for error in mcp_errors {
97 mcp_report.add_error(error);
98 }
99 } else {
100 let mut mcp_report = ValidationReport::new("mcp");
101 for error in mcp_errors {
102 mcp_report.add_error(error);
103 }
104 report.add_domain(mcp_report);
105 }
106}