aether_project/
mcp_config_source_config.rs1use 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}