Files
codex/codex-rs/rmcp-client/src/service_error.rs
T
Anthony Tafoya 12fd621f62 Preserve specific MCP failure codes in metrics and spans (#54094)
## Why

MCP request failures and tool errors reported only through `codex/telemetry` metadata previously received an `unknown` error code, obscuring the failure category.

## What changed

- Classify typed request failures into bounded codes such as `request_timeout`, `transport_closed`, and `protocol_error`, and carry them into MCP call metrics and spans before errors become strings.
- Preserve typed timeouts when a prepared tool call exhausts its deadline during trusted access lookup.
- Accept allowlisted `error_code` values from `codex/telemetry` as a fallback after structured content and connector authentication metadata. Keep model output unchanged and ignore unrecognized metadata codes.

## Testing

Add coverage for typed errors wrapped in context, prepared-call timeout classification, metadata precedence and rejection, unchanged model output, and exported span error attributes.

GitOrigin-RevId: d7a440a6c9119b8a07a7f1fad697eb25af506f00
2026-10-14 20:53:59 +00:00

49 lines
1.8 KiB
Rust

//! Access structured MCP errors without dropping their original protocol data.
use anyhow::Error;
use rmcp::ErrorData;
use rmcp::service::ServiceError;
use std::time::Duration;
use crate::rmcp_client::ClientOperationError;
/// Returns the server's structured protocol error, including its original data.
pub fn mcp_error(error: &Error) -> Option<&ErrorData> {
let ClientOperationError::Service(ServiceError::McpError(error)) = error.downcast_ref()? else {
return None;
};
Some(error)
}
/// Preserves a request deadline as a typed error for caller-side telemetry.
pub fn request_timeout_error(label: &str, duration: Duration) -> Error {
ClientOperationError::Timeout {
label: label.to_string(),
duration,
}
.into()
}
/// Returns a bounded caller-side failure category without parsing error text.
pub fn request_error_code(error: &Error) -> Option<&'static str> {
let error = error.downcast_ref::<ClientOperationError>()?;
Some(match error {
ClientOperationError::Timeout { .. } => "request_timeout",
ClientOperationError::Service(error) => match error {
ServiceError::Timeout { .. } => "request_timeout",
ServiceError::TransportClosed => "transport_closed",
ServiceError::TransportSend(_) => "transport_error",
ServiceError::McpError(_) => "protocol_error",
ServiceError::UnexpectedResponse => "unexpected_response",
ServiceError::Cancelled { .. } => "cancelled",
ServiceError::SubscriptionLagged { .. } => "subscription_lagged",
ServiceError::InputRequiredRoundsExceeded { .. } => "input_required_rounds_exceeded",
_ => return None,
},
})
}
#[cfg(test)]
#[path = "service_error_tests.rs"]
mod tests;