Skip to main content

aether_project/
mcp_config_source_config.rs

1use mcp_utils::client::McpServerConfig;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::collections::BTreeMap;
4use utils::{PathOrObject, ResourcePath, is_false, string_or_object_schema};
5
6#[derive(Debug, Clone, PartialEq)]
7pub enum McpSourceSpec {
8    File(McpFileSpec),
9    Inline { servers: BTreeMap<String, McpServerConfig> },
10}
11
12#[derive(Debug, Clone, PartialEq)]
13pub struct McpFileSpec {
14    pub path: ResourcePath,
15    pub proxy: bool,
16    pub optional: bool,
17}
18
19impl McpFileSpec {
20    pub fn new(path: impl Into<ResourcePath>) -> Self {
21        Self { path: path.into(), proxy: false, optional: false }
22    }
23
24    #[must_use]
25    pub fn proxy(mut self) -> Self {
26        self.proxy = true;
27        self
28    }
29
30    #[must_use]
31    pub fn optional(mut self) -> Self {
32        self.optional = true;
33        self
34    }
35}
36
37impl McpSourceSpec {
38    pub fn file(path: impl Into<ResourcePath>) -> Self {
39        Self::File(McpFileSpec::new(path))
40    }
41
42    pub fn path(&self) -> Option<&str> {
43        match self {
44            Self::File(file) => Some(file.path.as_authored()),
45            Self::Inline { .. } => None,
46        }
47    }
48}
49
50impl From<McpFileSpec> for McpSourceSpec {
51    fn from(spec: McpFileSpec) -> Self {
52        Self::File(spec)
53    }
54}
55
56impl<'de> Deserialize<'de> for McpSourceSpec {
57    fn deserialize<T: Deserializer<'de>>(deserializer: T) -> Result<Self, T::Error> {
58        Ok(match PathOrObject::<McpSourceSpecObject>::deserialize(deserializer)? {
59            PathOrObject::Path(path) => Self::File(McpFileSpec::new(path)),
60            PathOrObject::Object(McpSourceSpecObject::File { path, proxy, optional }) => {
61                Self::File(McpFileSpec { path, proxy, optional })
62            }
63            PathOrObject::Object(McpSourceSpecObject::Inline { servers }) => Self::Inline { servers },
64        })
65    }
66}
67
68impl Serialize for McpSourceSpec {
69    fn serialize<T: Serializer>(&self, serializer: T) -> Result<T::Ok, T::Error> {
70        match self {
71            Self::File(McpFileSpec { path, proxy: false, optional: false }) => path.serialize(serializer),
72            Self::File(McpFileSpec { path, proxy, optional }) => Serialize::serialize(
73                &McpSourceSpecObject::File { path: path.clone(), proxy: *proxy, optional: *optional },
74                serializer,
75            ),
76            Self::Inline { servers } => {
77                Serialize::serialize(&McpSourceSpecObject::Inline { servers: servers.clone() }, serializer)
78            }
79        }
80    }
81}
82
83impl schemars::JsonSchema for McpSourceSpec {
84    fn schema_name() -> std::borrow::Cow<'static, str> {
85        "McpSourceSpec".into()
86    }
87
88    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
89        string_or_object_schema(
90            "MCP config source — either a file path string or a typed file or inline object.",
91            &generator.subschema_for::<McpSourceSpecObject>().to_value(),
92        )
93    }
94}
95
96#[derive(schemars::JsonSchema, serde::Deserialize, serde::Serialize)]
97#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
98enum McpSourceSpecObject {
99    File {
100        path: ResourcePath,
101        #[serde(default, skip_serializing_if = "is_false")]
102        proxy: bool,
103        #[serde(default, skip_serializing_if = "is_false")]
104        optional: bool,
105    },
106    Inline {
107        servers: BTreeMap<String, McpServerConfig>,
108    },
109}