use super::{catalog::McpPrompt, McpCatalog, McpCatalogError};
const MCP_PROMPT_PREFIX: &str = "mcp:";
#[derive(Debug, PartialEq)]
pub(crate) enum McpPromptCommand {
MissingArguments(Vec<String>),
Failed(McpCatalogError),
Expanded(String),
}
pub(crate) fn find(catalog: &McpCatalog, command: &str) -> Option<McpPrompt> {
let (server, name) = parse_command(command)?;
catalog
.prompts()
.into_iter()
.find(|prompt| prompt.server == server && prompt.name == name)
}
pub(crate) async fn expand(
catalog: &McpCatalog,
prompt: &McpPrompt,
trailing: &str,
max_output_bytes: usize,
) -> McpPromptCommand {
let arguments = prompt.parse_arguments(trailing);
let missing = prompt.missing_arguments(&arguments);
if !missing.is_empty() {
return McpPromptCommand::MissingArguments(
missing.into_iter().map(str::to_owned).collect(),
);
}
match catalog
.get_prompt(&prompt.server, &prompt.name, arguments, max_output_bytes)
.await
{
Ok(expansion) => McpPromptCommand::Expanded(prompt_turn_text(
expansion.description.as_deref(),
&expansion.text,
max_output_bytes,
)),
Err(error) => McpPromptCommand::Failed(error),
}
}
pub(crate) fn parse_command(command: &str) -> Option<(String, String)> {
let rest = command
.get(..MCP_PROMPT_PREFIX.len())
.filter(|prefix| prefix.eq_ignore_ascii_case(MCP_PROMPT_PREFIX))
.and_then(|_| command.get(MCP_PROMPT_PREFIX.len()..))?;
let (server, prompt) = rest.split_once(':')?;
if server.is_empty() || prompt.is_empty() {
return None;
}
Some((server.to_string(), prompt.to_string()))
}
fn prompt_turn_text(description: Option<&str>, body: &str, max_output_bytes: usize) -> String {
match description {
Some(description) if !description.trim().is_empty() => {
rho_tools::tool::truncate(format!("{description}\n\n{body}"), max_output_bytes)
}
_ => body.to_string(),
}
}
#[cfg(test)]
#[path = "prompt_command_tests.rs"]
mod tests;