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