aether_project/
mcp_config_source_config.rs1use mcp_utils::client::RawMcpServerConfig;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::collections::BTreeMap;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum McpSourceSpec {
7 File { path: String, proxy: bool },
8 Inline { servers: BTreeMap<String, RawMcpServerConfig> },
9}
10
11#[derive(serde::Deserialize)]
12#[serde(untagged)]
13enum McpSourceSpecInput {
14 Path(String),
15 Object(McpSourceSpecObject),
16}
17
18#[derive(schemars::JsonSchema, serde::Deserialize, serde::Serialize)]
19#[serde(tag = "type", rename_all = "camelCase", deny_unknown_fields)]
20enum McpSourceSpecObject {
21 File {
22 path: String,
23 #[serde(default)]
24 proxy: bool,
25 },
26 Inline {
27 servers: BTreeMap<String, RawMcpServerConfig>,
28 },
29}
30
31impl<'de> Deserialize<'de> for McpSourceSpec {
32 fn deserialize<T: Deserializer<'de>>(deserializer: T) -> Result<Self, T::Error> {
33 match Deserialize::deserialize(deserializer)? {
34 McpSourceSpecInput::Path(path) => Ok(Self::File { path, proxy: false }),
35 McpSourceSpecInput::Object(McpSourceSpecObject::File { path, proxy }) => Ok(Self::File { path, proxy }),
36 McpSourceSpecInput::Object(McpSourceSpecObject::Inline { servers }) => Ok(Self::Inline { servers }),
37 }
38 }
39}
40
41impl Serialize for McpSourceSpec {
42 fn serialize<T: Serializer>(&self, serializer: T) -> Result<T::Ok, T::Error> {
43 match self {
44 Self::File { path, proxy: false } => serializer.serialize_str(path),
45 Self::File { path, proxy } => {
46 Serialize::serialize(&McpSourceSpecObject::File { path: path.clone(), proxy: *proxy }, serializer)
47 }
48 Self::Inline { servers } => {
49 Serialize::serialize(&McpSourceSpecObject::Inline { servers: servers.clone() }, serializer)
50 }
51 }
52 }
53}
54
55impl schemars::JsonSchema for McpSourceSpec {
56 fn schema_name() -> std::borrow::Cow<'static, str> {
57 "McpSourceSpec".into()
58 }
59
60 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
61 let object_schema = generator.subschema_for::<McpSourceSpecObject>().to_value();
62 schemars::Schema::try_from(serde_json::json!({
63 "description": "MCP config source — either a file path string or a typed file or inline object.",
64 "oneOf": [
65 { "type": "string" },
66 object_schema
67 ]
68 }))
69 .expect("mcp source schema must be valid")
70 }
71}
72
73impl McpSourceSpec {
74 pub fn file(path: impl Into<String>) -> Self {
75 Self::File { path: path.into(), proxy: false }
76 }
77
78 pub fn path(&self) -> Option<&str> {
79 match self {
80 Self::File { path, .. } => Some(path.as_str()),
81 Self::Inline { .. } => None,
82 }
83 }
84}