use std::future::Future;
use miette::Diagnostic;
use rmcp::{ErrorData as McpError, model::*};
use serde_json::json;
use tracing::Instrument;
use crate::core::error::{Error, Result};
fn error_data(e: &Error) -> serde_json::Value {
let code = e.code().map(|c| c.to_string());
let help = e.help().map(|h| h.to_string());
json!({
"code": code,
"help": help,
"retryable": is_retryable(e),
})
}
fn is_retryable(e: &Error) -> bool {
matches!(
e,
Error::Network(_) | Error::Mcp { .. } | Error::InvalidJson(_)
)
}
fn error_message(e: &Error) -> String {
let mut msg = e.to_string();
if let Some(code) = e.code() {
msg = format!("[{code}] {msg}");
}
if let Some(help) = e.help() {
msg = format!("{msg}\n\nhelp: {help}");
}
msg
}
pub fn mcp_err(e: Error) -> McpError {
let msg = error_message(&e);
let data = error_data(&e);
McpError::internal_error(msg, Some(data))
}
pub fn json_result(value: serde_json::Value) -> CallToolResult {
CallToolResult::success(vec![Content::text(
serde_json::to_string_pretty(&value).unwrap_or_else(|_| value.to_string()),
)])
}
pub fn text_result(s: String) -> CallToolResult {
CallToolResult::success(vec![Content::text(s)])
}
fn error_tool_result(e: &Error) -> CallToolResult {
let msg = error_message(e);
let data = error_data(e);
let body = json!({
"error": msg,
"data": data,
});
let text = serde_json::to_string_pretty(&body).unwrap_or_else(|_| body.to_string());
CallToolResult::error(vec![Content::text(text)])
}
fn outcome_for(e: &Error) -> &'static str {
match e {
Error::PolicyViolation { .. } | Error::Forbidden { .. } => "permission_denied",
_ => "error",
}
}
pub async fn run_json<F>(tool: &str, check: Result<()>, fut: F) -> CallToolResult
where
F: Future<Output = Result<serde_json::Value>>,
{
let span = tracing::info_span!("mcp_tool", tool = tool, outcome = tracing::field::Empty);
async move {
match check {
Err(e) => {
tracing::warn!(tool = tool, error = %e, "MCP tool error");
tracing::Span::current().record("outcome", outcome_for(&e));
error_tool_result(&e)
}
Ok(()) => match fut.await {
Ok(v) => {
tracing::Span::current().record("outcome", "success");
json_result(v)
}
Err(e) => {
tracing::warn!(tool = tool, error = %e, "MCP tool error");
tracing::Span::current().record("outcome", outcome_for(&e));
error_tool_result(&e)
}
},
}
}
.instrument(span)
.await
}
pub async fn run_text<F>(tool: &str, check: Result<()>, fut: F) -> CallToolResult
where
F: Future<Output = Result<String>>,
{
let span = tracing::info_span!("mcp_tool", tool = tool, outcome = tracing::field::Empty);
async move {
match check {
Err(e) => {
tracing::warn!(tool = tool, error = %e, "MCP tool error");
tracing::Span::current().record("outcome", outcome_for(&e));
error_tool_result(&e)
}
Ok(()) => match fut.await {
Ok(s) => {
tracing::Span::current().record("outcome", "success");
text_result(s)
}
Err(e) => {
tracing::warn!(tool = tool, error = %e, "MCP tool error");
tracing::Span::current().record("outcome", outcome_for(&e));
error_tool_result(&e)
}
},
}
}
.instrument(span)
.await
}