Skip to main content

codei_config/
mcp.rs

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}