fn fmt_error_chain(err: &dyn std::error::Error) -> String {
let mut out = err.to_string();
let mut current = err.source();
while let Some(source) = current {
let s = source.to_string();
if !out.ends_with(&s) {
out.push_str(": ");
out.push_str(&s);
}
current = source.source();
}
out
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("connection error to {url}: {}", fmt_error_chain(source))]
Connection {
url: String,
source: reqwest::Error,
},
#[error("request error to {url}: {}", fmt_error_chain(source))]
Request {
url: String,
source: reqwest::Error,
},
#[error("bad status from {url} ({code}): {body}")]
BadStatus {
url: String,
code: reqwest::StatusCode,
body: String,
},
#[error("json-rpc error from {url} ({code}): {message}{}", data.as_ref().map(|d| format!("; data: {d}")).unwrap_or_default())]
JsonRpc {
url: String,
code: i64,
message: String,
data: Option<serde_json::Value>,
},
#[error("session expired at {url}")]
SessionExpired {
url: String,
},
#[error("server did not return Mcp-Session-Id header at {url}; body: {body}")]
NoSessionId {
url: String,
body: String,
},
#[error("missing authorization for MCP server: {0}")]
MissingAuthorization(String),
#[error("malformed JSON-RPC response from {url}: {message}")]
MalformedResponse {
url: String,
message: String,
},
}