magi-code 0.96.2

Repository-aware CLI coding agent for terminal work
Documentation
use super::{McpError, McpResult};
use std::{
    borrow::Borrow,
    cmp::Ordering,
    fmt,
    hash::{Hash, Hasher},
};

pub(crate) const MCP_QUALIFIED_NAME_MAX_BYTES: usize = 64;
const PREFIX: &str = "mcp__";
const SEPARATOR: &str = "__";

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct QualifiedMcpToolName {
    server: String,
    tool: String,
    qualified: String,
}
impl QualifiedMcpToolName {
    pub(crate) fn new(server: &str, tool: &str) -> McpResult<Self> {
        validate_component("server", server)?;
        if server.ends_with('_') {
            return Err(McpError::Config(format!(
                "MCP server name '{server}' must not end with '_' because mcp__<server>__<tool> would be ambiguous; choose a name that does not end with '_'"
            )));
        }
        validate_component("tool", tool)?;
        let qualified = format!("{PREFIX}{server}{SEPARATOR}{tool}");
        if qualified.len() > MCP_QUALIFIED_NAME_MAX_BYTES {
            return Err(McpError::Config(format!(
                "MCP qualified tool name exceeds {MCP_QUALIFIED_NAME_MAX_BYTES} bytes"
            )));
        }
        Ok(Self {
            server: server.to_string(),
            tool: tool.to_string(),
            qualified,
        })
    }
    pub(crate) fn parse(name: &str) -> McpResult<Self> {
        let rest = name.strip_prefix(PREFIX).ok_or_else(|| invalid(name))?;
        let (server, tool) = rest.split_once(SEPARATOR).ok_or_else(|| invalid(name))?;
        if tool.contains(SEPARATOR) {
            return Err(invalid(name));
        }
        let parsed = Self::new(server, tool).map_err(|error| invalid_with_detail(name, error))?;
        if parsed.as_str() != name {
            return Err(invalid(name));
        }
        Ok(parsed)
    }
    pub(crate) fn server(&self) -> &str {
        &self.server
    }
    pub(crate) fn tool(&self) -> &str {
        &self.tool
    }
    pub(crate) fn as_str(&self) -> &str {
        &self.qualified
    }
}
impl Hash for QualifiedMcpToolName {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.as_str().hash(state);
    }
}
impl Borrow<str> for QualifiedMcpToolName {
    fn borrow(&self) -> &str {
        self.as_str()
    }
}
impl Ord for QualifiedMcpToolName {
    fn cmp(&self, other: &Self) -> Ordering {
        self.as_str().cmp(other.as_str())
    }
}
impl PartialOrd for QualifiedMcpToolName {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
impl fmt::Display for QualifiedMcpToolName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}
fn validate_component(kind: &str, value: &str) -> McpResult<()> {
    if value.is_empty() {
        return Err(McpError::Config(format!(
            "MCP {kind} name must not be empty"
        )));
    }
    if value.contains(SEPARATOR) {
        return Err(McpError::Config(format!(
            "MCP {kind} name '{value}' must not contain '__'"
        )));
    }
    if !value
        .bytes()
        .all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
    {
        return Err(McpError::Config(format!(
            "MCP {kind} name '{value}' must contain only ASCII letters, digits, '_' or '-'"
        )));
    }
    Ok(())
}
fn invalid(name: &str) -> McpError {
    McpError::Config(format!(
        "invalid MCP qualified tool name '{name}'; expected exactly mcp__<server>__<tool>"
    ))
}
fn invalid_with_detail(name: &str, error: McpError) -> McpError {
    McpError::Config(format!("invalid MCP qualified tool name '{name}': {error}"))
}