use std::{collections::HashMap, path::Path, process::Stdio}; use anyhow::Result; use rmcp::{RoleClient, ServiceExt, service::RunningService, transport::ConfigureCommandExt}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct Config { pub openai_key: Option, pub chat_url: Option, pub mcp: Option, pub model_name: Option, pub proxy: Option, pub support_tool: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct McpConfig { pub server: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct McpServerConfig { pub name: String, #[serde(flatten)] pub transport: McpServerTransportConfig, } #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(tag = "protocol", rename_all = "lowercase")] pub enum McpServerTransportConfig { Streamable { url: String, }, Stdio { command: String, #[serde(default)] args: Vec, #[serde(default)] envs: HashMap, }, } impl McpServerTransportConfig { pub async fn start(&self) -> Result> { let client = match self { McpServerTransportConfig::Streamable { url } => { let transport = rmcp::transport::StreamableHttpClientTransport::from_uri(url.to_string()); ().serve(transport).await? } McpServerTransportConfig::Stdio { command, args, envs, } => { let transport = rmcp::transport::child_process::TokioChildProcess::new( tokio::process::Command::new(command).configure(|cmd| { cmd.args(args) .envs(envs) .stderr(Stdio::inherit()) .stdout(Stdio::inherit()); }), )?; ().serve(transport).await? } }; Ok(client) } } impl Config { pub async fn load(path: impl AsRef) -> Result { let content = tokio::fs::read_to_string(path).await?; let config: Self = toml::from_str(&content)?; Ok(config) } pub async fn create_mcp_clients( &self, ) -> Result>> { let mut clients = HashMap::new(); if let Some(mcp_config) = &self.mcp { for server in &mcp_config.server { let client = server.transport.start().await?; clients.insert(server.name.clone(), client); } } Ok(clients) } }