use rmcp::model::{CallToolResult, ContentBlock};
use serde_json::{Value, json};
pub const SCHEMA_VERSION: &str = "prview.mcp.v1";
pub mod error_class {
pub const REPO_NOT_FOUND: &str = "repo_not_found";
pub const NOT_A_GIT_REPO: &str = "not_a_git_repo";
pub const RUN_FAILED: &str = "run_failed";
pub const RUN_TIMEOUT: &str = "run_timeout";
pub const RUN_NOT_FOUND: &str = "run_not_found";
pub const ARTIFACT_MISSING: &str = "artifact_missing";
pub const TOOL_MISSING: &str = "tool_missing";
pub const STORAGE_LOCKED: &str = "storage_locked";
pub const STORAGE_CORRUPT: &str = "storage_corrupt";
pub const STALE_RUN: &str = "stale_run";
pub const UNIMPLEMENTED: &str = "unimplemented";
}
#[derive(Debug, Clone)]
pub struct ToolError {
pub class: String,
pub message: String,
pub extra: Value,
}
impl ToolError {
pub fn new(class: &str, message: impl Into<String>) -> Self {
Self {
class: class.to_string(),
message: message.into(),
extra: json!({}),
}
}
pub fn with_extra(class: &str, message: impl Into<String>, extra: Value) -> Self {
Self {
class: class.to_string(),
message: message.into(),
extra,
}
}
pub fn into_result(self) -> CallToolResult {
tool_error(&self.class, &self.message, self.extra)
}
}
fn stamp_schema(mut value: Value) -> Value {
if let Value::Object(map) = &mut value {
map.insert(
"schema_version".to_string(),
Value::String(SCHEMA_VERSION.to_string()),
);
}
value
}
fn json_content(value: &Value) -> Vec<ContentBlock> {
let text = serde_json::to_string(value).unwrap_or_else(|_| "{}".to_string());
vec![ContentBlock::text(text)]
}
pub fn tool_error(class: &str, message: &str, extra: Value) -> CallToolResult {
let mut body = json!({
"error_class": class,
"message": message,
});
if let (Value::Object(dst), Value::Object(src)) = (&mut body, extra) {
for (k, v) in src {
dst.insert(k, v);
}
}
let body = stamp_schema(body);
CallToolResult::error(json_content(&body))
}
pub fn tool_success(value: Value) -> CallToolResult {
let body = stamp_schema(value);
CallToolResult::success(json_content(&body))
}