Skip to main content

mcp_proxy_args/
load.rs

1use std::fs;
2use std::path::PathBuf;
3
4use serde_json::Value;
5use thiserror::Error;
6
7#[derive(Debug, Clone, Default, PartialEq, Eq)]
8pub struct RawFallbackImportSpec {
9    pub initialize_inline: Option<String>,
10    pub initialize_file: Option<PathBuf>,
11    pub tools_inline: Option<String>,
12    pub tools_file: Option<PathBuf>,
13}
14
15impl RawFallbackImportSpec {
16    pub fn has_any(&self) -> bool {
17        self.initialize_inline.is_some()
18            || self.initialize_file.is_some()
19            || self.tools_inline.is_some()
20            || self.tools_file.is_some()
21    }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum ImportSource {
26    Inline,
27    File(PathBuf),
28}
29
30impl std::fmt::Display for ImportSource {
31    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            Self::Inline => formatter.write_str("inline"),
34            Self::File(path) => write!(formatter, "{}", path.display()),
35        }
36    }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct LoadedJson {
41    pub json: String,
42    pub source: ImportSource,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct LoadedFallbackJson {
47    pub initialize: LoadedJson,
48    pub tools: LoadedJson,
49}
50
51#[derive(Debug, Error)]
52pub enum LoadError {
53    #[error("fallback import must include both initialize and tools metadata")]
54    IncompletePair,
55    #[error("both inline and file sources were provided for {kind}")]
56    ConflictingSources { kind: &'static str },
57    #[error("failed to read {kind} fallback file {path}: {source}")]
58    ReadFile {
59        kind: &'static str,
60        path: PathBuf,
61        #[source]
62        source: std::io::Error,
63    },
64    #[error("invalid JSON syntax for {kind} fallback from {source_name}: {source}")]
65    InvalidJsonSyntax {
66        kind: &'static str,
67        source_name: String,
68        #[source]
69        source: serde_json::Error,
70    },
71}
72
73fn load_one(
74    kind: &'static str,
75    inline: &Option<String>,
76    file: &Option<PathBuf>,
77) -> Result<Option<LoadedJson>, LoadError> {
78    let loaded = match (inline, file) {
79        (Some(_), Some(_)) => return Err(LoadError::ConflictingSources { kind }),
80        (None, None) => return Ok(None),
81        (Some(json), None) => LoadedJson {
82            json: json.clone(),
83            source: ImportSource::Inline,
84        },
85        (None, Some(path)) => {
86            let json = fs::read_to_string(path).map_err(|source| LoadError::ReadFile {
87                kind,
88                path: path.clone(),
89                source,
90            })?;
91            LoadedJson {
92                json,
93                source: ImportSource::File(path.clone()),
94            }
95        }
96    };
97
98    serde_json::from_str::<Value>(&loaded.json).map_err(|source| {
99        let source_name = loaded.source.to_string();
100        LoadError::InvalidJsonSyntax {
101            kind,
102            source_name,
103            source,
104        }
105    })?;
106
107    Ok(Some(loaded))
108}
109
110pub fn try_load_fallback(
111    spec: &RawFallbackImportSpec,
112) -> Result<Option<LoadedFallbackJson>, LoadError> {
113    if !spec.has_any() {
114        return Ok(None);
115    }
116
117    let initialize = load_one("initialize", &spec.initialize_inline, &spec.initialize_file)?;
118    let tools = load_one("tools", &spec.tools_inline, &spec.tools_file)?;
119
120    match (initialize, tools) {
121        (Some(initialize), Some(tools)) => Ok(Some(LoadedFallbackJson { initialize, tools })),
122        _ => Err(LoadError::IncompletePair),
123    }
124}