1use std::fs;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5
6use crate::error::ConfigError;
7use crate::paths::expand_tilde;
8
9#[derive(Debug, Clone, Serialize, Deserialize, Default)]
10pub struct McpConfig {
11 #[serde(default)]
12 pub servers: Vec<McpServer>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct McpServer {
17 pub name: String,
18 pub command: String,
19 #[serde(default)]
20 pub args: Vec<String>,
21 #[serde(default)]
22 pub env: Vec<(String, String)>,
23}
24
25pub fn mcp_config_path() -> PathBuf {
26 expand_tilde("~/.config/codei/mcp-servers.toml")
27}
28
29pub fn load_mcp_config() -> Result<McpConfig, ConfigError> {
30 let path = mcp_config_path();
31 if !path.is_file() {
32 return Ok(McpConfig::default());
33 }
34 let text = fs::read_to_string(&path).map_err(|source| ConfigError::Read {
35 path: path.clone(),
36 source,
37 })?;
38 parse_mcp_config(&text)
39}
40
41pub fn parse_mcp_config(text: &str) -> Result<McpConfig, ConfigError> {
42 let config: McpConfig =
43 toml::from_str(text).map_err(|e| ConfigError::McpParse(e.to_string()))?;
44 Ok(config)
45}
46
47pub fn save_mcp_config(config: &McpConfig) -> Result<(), ConfigError> {
48 let path = mcp_config_path();
49 if let Some(parent) = path.parent() {
50 fs::create_dir_all(parent).map_err(|source| ConfigError::Read {
51 path: parent.to_path_buf(),
52 source,
53 })?;
54 }
55 let text = toml::to_string_pretty(config).map_err(|e| ConfigError::McpParse(e.to_string()))?;
56 fs::write(&path, text).map_err(|source| ConfigError::Read { path, source })
57}
58
59pub fn init_mcp_config() -> Result<(PathBuf, bool), ConfigError> {
60 let path = mcp_config_path();
61 if path.is_file() {
62 return Ok((path, false));
63 }
64 if let Some(parent) = path.parent() {
65 fs::create_dir_all(parent).map_err(|source| ConfigError::Read {
66 path: parent.to_path_buf(),
67 source,
68 })?;
69 }
70 fs::write(&path, default_mcp_template()).map_err(|source| ConfigError::Read {
71 path: path.clone(),
72 source,
73 })?;
74 Ok((path, true))
75}
76
77pub fn default_mcp_template() -> &'static str {
78 r#"# MCP server configuration for CodeI
79# Docs: https://modelcontextprotocol.io/
80
81[[servers]]
82name = "example"
83command = "node"
84args = ["/path/to/mcp-server.js"]
85"#
86}