feat: ToolError migration to ErrorData (#4051)

This commit is contained in:
Alex Hancock
2025-08-12 16:18:41 -04:00
committed by GitHub
parent 88b013194c
commit bd1eff52a4
35 changed files with 2459 additions and 1336 deletions
+189 -93
View File
@@ -6,8 +6,8 @@
use std::sync::Arc;
use chrono::Utc;
use mcp_core::{ToolError, ToolResult};
use rmcp::model::Content;
use mcp_core::ToolResult;
use rmcp::model::{Content, ErrorCode, ErrorData};
use crate::recipe::Recipe;
use crate::scheduler_trait::SchedulerTrait;
@@ -24,8 +24,10 @@ impl Agent {
let scheduler = match self.scheduler_service.lock().await.as_ref() {
Some(s) => s.clone(),
None => {
return Err(ToolError::ExecutionError(
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Scheduler not available. This tool only works in server mode.".to_string(),
None,
))
}
};
@@ -33,7 +35,13 @@ impl Agent {
let action = arguments
.get("action")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'action' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Missing 'action' parameter".to_string(),
None,
)
})?;
match action {
"list" => self.handle_list_jobs(scheduler).await,
@@ -46,10 +54,11 @@ impl Agent {
"inspect" => self.handle_inspect_job(scheduler, arguments).await,
"sessions" => self.handle_list_sessions(scheduler, arguments).await,
"session_content" => self.handle_session_content(arguments).await,
_ => Err(ToolError::ExecutionError(format!(
"Unknown action: {}",
action
))),
_ => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Unknown action: {}", action),
None,
)),
}
}
@@ -61,17 +70,22 @@ impl Agent {
match scheduler.list_scheduled_jobs().await {
Ok(jobs) => {
let jobs_json = serde_json::to_string_pretty(&jobs).map_err(|e| {
ToolError::ExecutionError(format!("Failed to serialize jobs: {}", e))
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to serialize jobs: {}", e),
None,
)
})?;
Ok(vec![Content::text(format!(
"Scheduled Jobs:\n{}",
jobs_json
))])
}
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to list jobs: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to list jobs: {}", e),
None,
)),
}
}
@@ -85,14 +99,22 @@ impl Agent {
.get("recipe_path")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::ExecutionError("Missing 'recipe_path' parameter".to_string())
ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Missing 'recipe_path' parameter".to_string(),
None,
)
})?;
let cron_expression = arguments
.get("cron_expression")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::ExecutionError("Missing 'cron_expression' parameter".to_string())
ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Missing 'cron_expression' parameter".to_string(),
None,
)
})?;
// Get the execution_mode parameter, defaulting to "background" if not provided
@@ -103,18 +125,23 @@ impl Agent {
// Validate execution_mode is either "foreground" or "background"
if execution_mode != "foreground" && execution_mode != "background" {
return Err(ToolError::ExecutionError(format!(
"Invalid execution_mode: {}. Must be 'foreground' or 'background'",
execution_mode
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!(
"Invalid execution_mode: {}. Must be 'foreground' or 'background'",
execution_mode
),
None,
));
}
// Validate recipe file exists and is readable
if !std::path::Path::new(recipe_path).exists() {
return Err(ToolError::ExecutionError(format!(
"Recipe file not found: {}",
recipe_path
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Recipe file not found: {}", recipe_path),
None,
));
}
// Validate it's a valid recipe by trying to parse it
@@ -122,19 +149,28 @@ impl Agent {
Ok(content) => {
if recipe_path.ends_with(".json") {
serde_json::from_str::<Recipe>(&content).map_err(|e| {
ToolError::ExecutionError(format!("Invalid JSON recipe: {}", e))
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Invalid JSON recipe: {}", e),
None,
)
})?;
} else {
serde_yaml::from_str::<Recipe>(&content).map_err(|e| {
ToolError::ExecutionError(format!("Invalid YAML recipe: {}", e))
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Invalid YAML recipe: {}", e),
None,
)
})?;
}
}
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Cannot read recipe file: {}",
e
)))
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Cannot read recipe file: {}", e),
None,
))
}
}
@@ -158,10 +194,11 @@ impl Agent {
"Successfully created scheduled job '{}' for recipe '{}' with cron expression '{}' in {} mode",
job_id, recipe_path, cron_expression, execution_mode
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to create job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to create job: {}", e),
None,
)),
}
}
@@ -174,17 +211,24 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.run_now(job_id).await {
Ok(session_id) => Ok(vec![Content::text(format!(
"Successfully started job '{}'. Session ID: {}",
job_id, session_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to run job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to run job: {}", e),
None,
)),
}
}
@@ -197,17 +241,24 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.pause_schedule(job_id).await {
Ok(()) => Ok(vec![Content::text(format!(
"Successfully paused job '{}'",
job_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to pause job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to pause job: {}", e),
None,
)),
}
}
@@ -220,17 +271,24 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.unpause_schedule(job_id).await {
Ok(()) => Ok(vec![Content::text(format!(
"Successfully unpaused job '{}'",
job_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to unpause job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to unpause job: {}", e),
None,
)),
}
}
@@ -243,17 +301,24 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.remove_scheduled_job(job_id).await {
Ok(()) => Ok(vec![Content::text(format!(
"Successfully deleted job '{}'",
job_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to delete job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to delete job: {}", e),
None,
)),
}
}
@@ -266,17 +331,24 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.kill_running_job(job_id).await {
Ok(()) => Ok(vec![Content::text(format!(
"Successfully killed running job '{}'",
job_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to kill job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to kill job: {}", e),
None,
)),
}
}
@@ -289,7 +361,13 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
match scheduler.get_running_job_info(job_id).await {
Ok(Some((session_id, start_time))) => {
@@ -303,10 +381,11 @@ impl Agent {
"Job '{}' is not currently running",
job_id
))]),
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to inspect job: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to inspect job: {}", e),
None,
)),
}
}
@@ -319,7 +398,13 @@ impl Agent {
let job_id = arguments
.get("job_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::ExecutionError("Missing 'job_id' parameter".to_string()))?;
.ok_or_else(|| {
ErrorData::new(
ErrorCode::INVALID_PARAMS,
"Missing 'job_id' parameter".to_string(),
None,
)
})?;
let limit = arguments
.get("limit")
@@ -353,10 +438,11 @@ impl Agent {
))])
}
}
Err(e) => Err(ToolError::ExecutionError(format!(
"Failed to list sessions: {}",
e
))),
Err(e) => Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to list sessions: {}", e),
None,
)),
}
}
@@ -369,7 +455,11 @@ impl Agent {
.get("session_id")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::ExecutionError("Missing 'session_id' parameter".to_string())
ErrorData::new(
ErrorCode::INTERNAL_ERROR,
"Missing 'session_id' parameter".to_string(),
None,
)
})?;
// Get the session file path
@@ -378,29 +468,32 @@ impl Agent {
) {
Ok(path) => path,
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Invalid session ID '{}': {}",
session_id, e
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Invalid session ID '{}': {}", session_id, e),
None,
));
}
};
// Check if session file exists
if !session_path.exists() {
return Err(ToolError::ExecutionError(format!(
"Session '{}' not found",
session_id
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Session '{}' not found", session_id),
None,
));
}
// Read session metadata
let metadata = match crate::session::storage::read_metadata(&session_path) {
Ok(metadata) => metadata,
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Failed to read session metadata: {}",
e
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to read session metadata: {}", e),
None,
));
}
};
@@ -408,10 +501,11 @@ impl Agent {
let messages = match crate::session::storage::read_messages(&session_path) {
Ok(messages) => messages,
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Failed to read session messages: {}",
e
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to read session messages: {}", e),
None,
));
}
};
@@ -419,20 +513,22 @@ impl Agent {
let metadata_json = match serde_json::to_string_pretty(&metadata) {
Ok(json) => json,
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Failed to serialize metadata: {}",
e
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to serialize metadata: {}", e),
None,
));
}
};
let messages_json = match serde_json::to_string_pretty(&messages) {
Ok(json) => json,
Err(e) => {
return Err(ToolError::ExecutionError(format!(
"Failed to serialize messages: {}",
e
)));
return Err(ErrorData::new(
ErrorCode::INTERNAL_ERROR,
format!("Failed to serialize messages: {}", e),
None,
));
}
};