pub(crate) const MCP_TOOL_NAME_GRAMMAR: &str = "valid mcp(server), mcp(server/tool), mcp(server/*), mcp(*/tool), or mcp(*/*) reference (* is the only wildcard; segments non-empty)";
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum McpSegment {
Any,
Named(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct McpRef {
pub server: McpSegment,
pub tool: McpSegment,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum McpRefParseError {
Empty,
EmptyServerSegment,
EmptyToolSegment,
TooManySegments,
WhitespaceOnly,
}
impl std::fmt::Display for McpRefParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.message())
}
}
impl McpRefParseError {
pub fn message(&self) -> &'static str {
match self {
Self::Empty => "MCP reference payload is empty",
Self::EmptyServerSegment => "MCP reference has an empty server segment",
Self::EmptyToolSegment => "MCP reference has an empty tool segment",
Self::TooManySegments => "MCP reference must have at most one '/' separator",
Self::WhitespaceOnly => "MCP reference payload is whitespace only",
}
}
}
impl std::fmt::Display for McpRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_canonical())
}
}
impl McpRef {
pub fn to_canonical(&self) -> String {
format!("mcp({})", self.to_pattern())
}
pub(crate) fn to_pattern(&self) -> String {
format!(
"{}/{}",
segment_display(&self.server),
segment_display(&self.tool)
)
}
}
pub fn parse_foreign_mcp_token(raw: &str, dialect: ForeignMcpDialect) -> Option<McpRef> {
match dialect {
ForeignMcpDialect::Claude => parse_claude_mcp_wire_token(raw.trim()),
ForeignMcpDialect::Cursor => parse_cursor_mcp_token(raw.trim()),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ForeignMcpDialect {
Claude,
Cursor,
}
fn parse_claude_mcp_wire_token(raw: &str) -> Option<McpRef> {
const PREFIX: &str = "mcp__";
let remainder = match raw.get(..PREFIX.len()) {
Some(p) if p.eq_ignore_ascii_case(PREFIX) => &raw[PREFIX.len()..],
_ => return None,
};
if remainder.is_empty() {
return None;
}
match remainder.split_once("__") {
None => {
let server = parse_segment(remainder).ok()?;
Some(McpRef {
server,
tool: McpSegment::Any,
})
}
Some((server_part, tool_part)) => {
let server = parse_segment(server_part).ok()?;
let tool = parse_segment(tool_part).ok()?;
Some(McpRef { server, tool })
}
}
}
fn parse_cursor_mcp_token(raw: &str) -> Option<McpRef> {
let open = raw.find('(')?;
let head = raw[..open].trim();
if !head.eq_ignore_ascii_case("mcp") {
return None;
}
let inner = extract_scoped_payload(&raw[open..])?;
let (server_part, tool_part) = inner.rsplit_once(':')?;
let server = parse_segment(server_part).ok()?;
let tool = parse_segment(tool_part).ok()?;
Some(McpRef { server, tool })
}
fn parse_segment(segment: &str) -> Result<McpSegment, McpRefParseError> {
let trimmed = segment.trim();
if trimmed.is_empty() {
return Err(McpRefParseError::Empty);
}
if trimmed == "*" {
Ok(McpSegment::Any)
} else {
Ok(McpSegment::Named(trimmed.to_string()))
}
}
pub fn try_parse_mcp_tool_name(raw: &str) -> Option<McpRef> {
let trimmed = raw.trim();
let open = trimmed.find('(')?;
let head = trimmed[..open].trim();
if !head.eq_ignore_ascii_case("mcp") {
return None;
}
let payload = &trimmed[open..];
let inner = extract_scoped_payload(payload)?;
parse_mcp_ref(inner).ok()
}
fn extract_scoped_payload(payload: &str) -> Option<&str> {
let trimmed = payload.trim();
if trimmed.len() < 2 || !trimmed.starts_with('(') || !trimmed.ends_with(')') {
return None;
}
Some(&trimmed[1..trimmed.len() - 1])
}
fn segment_display(segment: &McpSegment) -> String {
match segment {
McpSegment::Any => "*".to_string(),
McpSegment::Named(name) => name.clone(),
}
}
pub fn parse_mcp_ref(payload: &str) -> Result<McpRef, McpRefParseError> {
let trimmed = payload.trim();
if trimmed.is_empty() {
return Err(McpRefParseError::WhitespaceOnly);
}
match trimmed.split_once('/') {
None => {
let server = parse_segment(trimmed)?;
Ok(McpRef {
server,
tool: McpSegment::Any,
})
}
Some((server_part, tool_part)) => {
if trimmed.matches('/').count() > 1 {
return Err(McpRefParseError::TooManySegments);
}
let server = parse_segment(server_part).map_err(|err| match err {
McpRefParseError::Empty => McpRefParseError::EmptyServerSegment,
other => other,
})?;
let tool = parse_segment(tool_part).map_err(|err| match err {
McpRefParseError::Empty => McpRefParseError::EmptyToolSegment,
other => other,
})?;
Ok(McpRef { server, tool })
}
}
}