feat: unified error type (#308)

* feat: unified error type

* fix: use type id to compare transport type
This commit is contained in:
4t145 2025-07-09 11:23:39 +08:00 • committed by GitHub
commit e615300fea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 286 additions and 161 deletions

View file

@ -134,7 +134,7 @@ impl ServerHandler for MyToolHandler {
&self,
request: CallToolRequestParam,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, rmcp::Error> {
) -> Result<CallToolResult, rmcp::ErrorData> {
let tcc = ToolCallContext::new(self, request, context);
self.tool_router.call(tcc).await
}
@ -143,7 +143,7 @@ impl ServerHandler for MyToolHandler {
&self,
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, rmcp::Error> {
) -> Result<ListToolsResult, rmcp::ErrorData> {
let items = self.tool_router.list_all();
Ok(ListToolsResult::with_all_items(items))
}

View file

@ -139,7 +139,7 @@ pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
/// &self,
/// request: CallToolRequestParam,
/// context: RequestContext<RoleServer>,
/// ) -> Result<CallToolResult, rmcp::Error> {
/// ) -> Result<CallToolResult, rmcp::ErrorData> {
/// let tcc = ToolCallContext::new(self, request, context);
/// self.tool_router.call(tcc).await
/// }
@ -148,7 +148,7 @@ pub fn tool_router(attr: TokenStream, input: TokenStream) -> TokenStream {
/// &self,
/// _request: Option<PaginatedRequestParam>,
/// _context: RequestContext<RoleServer>,
/// ) -> Result<ListToolsResult, rmcp::Error> {
/// ) -> Result<ListToolsResult, rmcp::ErrorData> {
/// let items = self.tool_router.list_all();
/// Ok(ListToolsResult::with_all_items(items))
/// }

View file

@ -29,7 +29,7 @@ pub fn tool_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
&self,
request: rmcp::model::CallToolRequestParam,
context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<rmcp::model::CallToolResult, rmcp::Error> {
) -> Result<rmcp::model::CallToolResult, rmcp::ErrorData> {
let tcc = rmcp::handler::server::tool::ToolCallContext::new(self, request, context);
#router.call(tcc).await
}
@ -39,7 +39,7 @@ pub fn tool_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
&self,
_request: Option<rmcp::model::PaginatedRequestParam>,
_context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<rmcp::model::ListToolsResult, rmcp::Error> {
) -> Result<rmcp::model::ListToolsResult, rmcp::ErrorData> {
Ok(rmcp::model::ListToolsResult::with_all_items(#router.list_all()))
}
};

View file

@ -15,7 +15,7 @@ wait for the first release.
Creating a server with tools is simple using the `#[tool]` macro:
```rust, ignore
use rmcp::{Error as McpError, ServiceExt, model::*, tool, tool_handler, tool_router, transport::stdio, handler::server::router::tool::ToolRouter};
use rmcp::{ErrorData as McpError, ServiceExt, model::*, tool, tool_router, transport::stdio, handler::server::tool::ToolCallContext, handler::server::router::tool::ToolRouter};
use std::sync::Arc;
use tokio::sync::Mutex;

View file

@ -1,9 +1,11 @@
use std::fmt::Display;
use crate::model::ErrorData;
use std::{borrow::Cow, fmt::Display};
use crate::ServiceError;
pub use crate::model::ErrorData;
#[deprecated(
note = "Use `rmcp::ErrorData` instead, `rmcp::ErrorData` could become `RmcpError` in the future."
)]
pub type Error = ErrorData;
impl Display for ErrorData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.code.0, self.message)?;
@ -15,3 +17,40 @@ impl Display for ErrorData {
}
impl std::error::Error for ErrorData {}
/// This is an unified error type for the errors could be returned by the service.
#[derive(Debug, thiserror::Error)]
pub enum RmcpError {
#[error("Service error: {0}")]
Service(#[from] ServiceError),
#[cfg(feature = "client")]
#[error("Client initialization error: {0}")]
ClientInitialize(#[from] crate::service::ClientInitializeError),
#[cfg(feature = "server")]
#[error("Server initialization error: {0}")]
ServerInitialize(#[from] crate::service::ServerInitializeError),
#[error("Runtime error: {0}")]
Runtime(#[from] tokio::task::JoinError),
#[error("Transport creation error: {error}")]
// TODO: Maybe we can introduce something like `TryIntoTransport` to auto wrap transport type,
// but it could be an breaking change, so we could do it in the future.
TransportCreation {
into_transport_type_name: Cow<'static, str>,
into_transport_type_id: std::any::TypeId,
#[source]
error: Box<dyn std::error::Error + Send + Sync>,
},
// and cancellation shouldn't be an error?
}
impl RmcpError {
pub fn transport_creation<T: 'static>(
error: impl Into<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
RmcpError::TransportCreation {
into_transport_type_id: std::any::TypeId::of::<T>(),
into_transport_type_name: std::any::type_name::<T>().into(),
error: error.into(),
}
}
}

View file

@ -1,6 +1,6 @@
pub mod progress;
use crate::{
error::Error as McpError,
error::ErrorData as McpError,
model::*,
service::{NotificationContext, RequestContext, RoleClient, Service, ServiceRole},
};

View file

@ -1,5 +1,5 @@
use crate::{
error::Error as McpError,
error::ErrorData as McpError,
model::*,
service::{NotificationContext, RequestContext, RoleServer, Service, ServiceRole},
};

View file

@ -51,7 +51,7 @@ where
&self,
notification: <RoleServer as crate::service::ServiceRole>::PeerNot,
context: NotificationContext<RoleServer>,
) -> Result<(), crate::Error> {
) -> Result<(), crate::ErrorData> {
self.service
.handle_notification(notification, context)
.await
@ -60,7 +60,7 @@ where
&self,
request: <RoleServer as crate::service::ServiceRole>::PeerReq,
context: crate::service::RequestContext<RoleServer>,
) -> Result<<RoleServer as crate::service::ServiceRole>::Resp, crate::Error> {
) -> Result<<RoleServer as crate::service::ServiceRole>::Resp, crate::ErrorData> {
match request {
ClientRequest::CallToolRequest(request) => {
if self.tool_router.has_route(request.params.name.as_ref())

View file

@ -52,7 +52,7 @@ impl<S: Send + Sync + 'static> ToolRoute<S> {
where
C: for<'a> Fn(
ToolCallContext<'a, S>,
) -> BoxFuture<'a, Result<CallToolResult, crate::Error>>
) -> BoxFuture<'a, Result<CallToolResult, crate::ErrorData>>
+ Send
+ Sync
+ 'static,
@ -237,11 +237,11 @@ where
pub async fn call(
&self,
context: ToolCallContext<'_, S>,
) -> Result<CallToolResult, crate::Error> {
) -> Result<CallToolResult, crate::ErrorData> {
let item = self
.map
.get(context.name())
.ok_or_else(|| crate::Error::invalid_params("tool not found", None))?;
.ok_or_else(|| crate::ErrorData::invalid_params("tool not found", None))?;
(item.call)(context).await
}

View file

@ -56,9 +56,9 @@ pub fn cached_schema_for_type<T: JsonSchema + std::any::Any>() -> Arc<JsonObject
}
/// Deserialize a JSON object into a type
pub fn parse_json_object<T: DeserializeOwned>(input: JsonObject) -> Result<T, crate::Error> {
pub fn parse_json_object<T: DeserializeOwned>(input: JsonObject) -> Result<T, crate::ErrorData> {
serde_json::from_value(serde_json::Value::Object(input)).map_err(|e| {
crate::Error::invalid_params(
crate::ErrorData::invalid_params(
format!("failed to deserialize parameters: {error}", error = e),
None,
)
@ -93,21 +93,23 @@ impl<'s, S> ToolCallContext<'s, S> {
}
pub trait FromToolCallContextPart<S>: Sized {
fn from_tool_call_context_part(context: &mut ToolCallContext<S>) -> Result<Self, crate::Error>;
fn from_tool_call_context_part(
context: &mut ToolCallContext<S>,
) -> Result<Self, crate::ErrorData>;
}
pub trait IntoCallToolResult {
fn into_call_tool_result(self) -> Result<CallToolResult, crate::Error>;
fn into_call_tool_result(self) -> Result<CallToolResult, crate::ErrorData>;
}
impl<T: IntoContents> IntoCallToolResult for T {
fn into_call_tool_result(self) -> Result<CallToolResult, crate::Error> {
fn into_call_tool_result(self) -> Result<CallToolResult, crate::ErrorData> {
Ok(CallToolResult::success(self.into_contents()))
}
}
impl<T: IntoContents, E: IntoContents> IntoCallToolResult for Result<T, E> {
fn into_call_tool_result(self) -> Result<CallToolResult, crate::Error> {
fn into_call_tool_result(self) -> Result<CallToolResult, crate::ErrorData> {
match self {
Ok(value) => Ok(CallToolResult::success(value.into_contents())),
Err(error) => Ok(CallToolResult::error(error.into_contents())),
@ -115,8 +117,8 @@ impl<T: IntoContents, E: IntoContents> IntoCallToolResult for Result<T, E> {
}
}
impl<T: IntoCallToolResult> IntoCallToolResult for Result<T, crate::Error> {
fn into_call_tool_result(self) -> Result<CallToolResult, crate::Error> {
impl<T: IntoCallToolResult> IntoCallToolResult for Result<T, crate::ErrorData> {
fn into_call_tool_result(self) -> Result<CallToolResult, crate::ErrorData> {
match self {
Ok(value) => value.into_call_tool_result(),
Err(error) => Err(error),
@ -134,7 +136,7 @@ pin_project_lite::pin_project! {
},
Ready {
#[pin]
result: Ready<Result<CallToolResult, crate::Error>>,
result: Ready<Result<CallToolResult, crate::ErrorData>>,
}
}
}
@ -144,7 +146,7 @@ where
F: Future<Output = R>,
R: IntoCallToolResult,
{
type Output = Result<CallToolResult, crate::Error>;
type Output = Result<CallToolResult, crate::ErrorData>;
fn poll(
self: std::pin::Pin<&mut Self>,
@ -159,8 +161,8 @@ where
}
}
impl IntoCallToolResult for Result<CallToolResult, crate::Error> {
fn into_call_tool_result(self) -> Result<CallToolResult, crate::Error> {
impl IntoCallToolResult for Result<CallToolResult, crate::ErrorData> {
fn into_call_tool_result(self) -> Result<CallToolResult, crate::ErrorData> {
self
}
}
@ -169,10 +171,10 @@ pub trait CallToolHandler<S, A> {
fn call(
self,
context: ToolCallContext<'_, S>,
) -> BoxFuture<'_, Result<CallToolResult, crate::Error>>;
) -> BoxFuture<'_, Result<CallToolResult, crate::ErrorData>>;
}
pub type DynCallToolHandler<S> = dyn for<'s> Fn(ToolCallContext<'s, S>) -> BoxFuture<'s, Result<CallToolResult, crate::Error>>
pub type DynCallToolHandler<S> = dyn for<'s> Fn(ToolCallContext<'s, S>) -> BoxFuture<'s, Result<CallToolResult, crate::ErrorData>>
+ Send
+ Sync;
@ -193,7 +195,9 @@ impl<P: JsonSchema> JsonSchema for Parameters<P> {
}
impl<S> FromToolCallContextPart<S> for CancellationToken {
fn from_tool_call_context_part(context: &mut ToolCallContext<S>) -> Result<Self, crate::Error> {
fn from_tool_call_context_part(
context: &mut ToolCallContext<S>,
) -> Result<Self, crate::ErrorData> {
Ok(context.request_context.ct.clone())
}
}
@ -201,7 +205,9 @@ impl<S> FromToolCallContextPart<S> for CancellationToken {
pub struct ToolName(pub Cow<'static, str>);
impl<S> FromToolCallContextPart<S> for ToolName {
fn from_tool_call_context_part(context: &mut ToolCallContext<S>) -> Result<Self, crate::Error> {
fn from_tool_call_context_part(
context: &mut ToolCallContext<S>,
) -> Result<Self, crate::ErrorData> {
Ok(Self(context.name.clone()))
}
}
@ -210,11 +216,13 @@ impl<S, P> FromToolCallContextPart<S> for Parameters<P>
where
P: DeserializeOwned,
{
fn from_tool_call_context_part(context: &mut ToolCallContext<S>) -> Result<Self, crate::Error> {
fn from_tool_call_context_part(
context: &mut ToolCallContext<S>,
) -> Result<Self, crate::ErrorData> {
let arguments = context.arguments.take().unwrap_or_default();
let value: P =
serde_json::from_value(serde_json::Value::Object(arguments)).map_err(|e| {
crate::Error::invalid_params(
crate::ErrorData::invalid_params(
format!("failed to deserialize parameters: {error}", error = e),
None,
)
@ -224,14 +232,18 @@ where
}
impl<S> FromToolCallContextPart<S> for JsonObject {
fn from_tool_call_context_part(context: &mut ToolCallContext<S>) -> Result<Self, crate::Error> {
fn from_tool_call_context_part(
context: &mut ToolCallContext<S>,
) -> Result<Self, crate::ErrorData> {
let object = context.arguments.take().unwrap_or_default();
Ok(object)
}
}
impl<S> FromToolCallContextPart<S> for crate::model::Extensions {
fn from_tool_call_context_part(context: &mut ToolCallContext<S>) -> Result<Self, crate::Error> {
fn from_tool_call_context_part(
context: &mut ToolCallContext<S>,
) -> Result<Self, crate::ErrorData> {
let extensions = context.request_context.extensions.clone();
Ok(extensions)
}
@ -243,14 +255,16 @@ impl<S, T> FromToolCallContextPart<S> for Extension<T>
where
T: Send + Sync + 'static + Clone,
{
fn from_tool_call_context_part(context: &mut ToolCallContext<S>) -> Result<Self, crate::Error> {
fn from_tool_call_context_part(
context: &mut ToolCallContext<S>,
) -> Result<Self, crate::ErrorData> {
let extension = context
.request_context
.extensions
.get::<T>()
.cloned()
.ok_or_else(|| {
crate::Error::invalid_params(
crate::ErrorData::invalid_params(
format!("missing extension {}", std::any::type_name::<T>()),
None,
)
@ -260,14 +274,18 @@ where
}
impl<S> FromToolCallContextPart<S> for crate::Peer<RoleServer> {
fn from_tool_call_context_part(context: &mut ToolCallContext<S>) -> Result<Self, crate::Error> {
fn from_tool_call_context_part(
context: &mut ToolCallContext<S>,
) -> Result<Self, crate::ErrorData> {
let peer = context.request_context.peer.clone();
Ok(peer)
}
}
impl<S> FromToolCallContextPart<S> for crate::model::Meta {
fn from_tool_call_context_part(context: &mut ToolCallContext<S>) -> Result<Self, crate::Error> {
fn from_tool_call_context_part(
context: &mut ToolCallContext<S>,
) -> Result<Self, crate::ErrorData> {
let mut meta = crate::model::Meta::default();
std::mem::swap(&mut meta, &mut context.request_context.meta);
Ok(meta)
@ -276,19 +294,23 @@ impl<S> FromToolCallContextPart<S> for crate::model::Meta {
pub struct RequestId(pub crate::model::RequestId);
impl<S> FromToolCallContextPart<S> for RequestId {
fn from_tool_call_context_part(context: &mut ToolCallContext<S>) -> Result<Self, crate::Error> {
fn from_tool_call_context_part(
context: &mut ToolCallContext<S>,
) -> Result<Self, crate::ErrorData> {
Ok(RequestId(context.request_context.id.clone()))
}
}
impl<S> FromToolCallContextPart<S> for RequestContext<RoleServer> {
fn from_tool_call_context_part(context: &mut ToolCallContext<S>) -> Result<Self, crate::Error> {
fn from_tool_call_context_part(
context: &mut ToolCallContext<S>,
) -> Result<Self, crate::ErrorData> {
Ok(context.request_context.clone())
}
}
impl<'s, S> ToolCallContext<'s, S> {
pub fn invoke<H, A>(self, h: H) -> BoxFuture<'s, Result<CallToolResult, crate::Error>>
pub fn invoke<H, A>(self, h: H) -> BoxFuture<'s, Result<CallToolResult, crate::ErrorData>>
where
H: CallToolHandler<S, A>,
{
@ -331,7 +353,7 @@ macro_rules! impl_for {
fn call(
self,
mut context: ToolCallContext<'_, S>,
) -> BoxFuture<'_, Result<CallToolResult, crate::Error>>{
) -> BoxFuture<'_, Result<CallToolResult, crate::ErrorData>>{
$(
let result = $Tn::from_tool_call_context_part(&mut context);
let $Tn = match result {
@ -362,7 +384,7 @@ macro_rules! impl_for {
fn call(
self,
mut context: ToolCallContext<S>,
) -> BoxFuture<'static, Result<CallToolResult, crate::Error>>{
) -> BoxFuture<'static, Result<CallToolResult, crate::ErrorData>>{
$(
let result = $Tn::from_tool_call_context_part(&mut context);
let $Tn = match result {
@ -391,7 +413,7 @@ macro_rules! impl_for {
fn call(
self,
mut context: ToolCallContext<S>,
) -> BoxFuture<'static, Result<CallToolResult, crate::Error>> {
) -> BoxFuture<'static, Result<CallToolResult, crate::ErrorData>> {
$(
let result = $Tn::from_tool_call_context_part(&mut context);
let $Tn = match result {
@ -416,7 +438,7 @@ macro_rules! impl_for {
fn call(
self,
mut context: ToolCallContext<S>,
) -> BoxFuture<'static, Result<CallToolResult, crate::Error>> {
) -> BoxFuture<'static, Result<CallToolResult, crate::ErrorData>> {
$(
let result = $Tn::from_tool_call_context_part(&mut context);
let $Tn = match result {

View file

@ -19,7 +19,7 @@
//!
//! ```rust
//! use std::sync::Arc;
//! use rmcp::{Error as McpError, model::*, tool, tool_router, handler::server::tool::ToolRouter};
//! use rmcp::{ErrorData as McpError, model::*, tool, tool_router, handler::server::tool::ToolRouter};
//! use tokio::sync::Mutex;
//!
//! #[derive(Clone)]
@ -90,7 +90,8 @@
//! }
//! ```
mod error;
pub use error::Error;
#[allow(deprecated)]
pub use error::{Error, ErrorData, RmcpError};
/// Basic data types in MCP specification
pub mod model;

View file

@ -63,9 +63,9 @@ pub enum RawContent {
pub type Content = Annotated<RawContent>;
impl RawContent {
pub fn json<S: Serialize>(json: S) -> Result<Self, crate::Error> {
pub fn json<S: Serialize>(json: S) -> Result<Self, crate::ErrorData> {
let json = serde_json::to_string(&json).map_err(|e| {
crate::Error::internal_error(
crate::ErrorData::internal_error(
"fail to serialize response to json",
Some(json!(
{"reason": e.to_string()}
@ -142,7 +142,7 @@ impl Content {
RawContent::embedded_text(uri, content).no_annotation()
}
pub fn json<S: Serialize>(json: S) -> Result<Self, crate::Error> {
pub fn json<S: Serialize>(json: S) -> Result<Self, crate::ErrorData> {
RawContent::json(json).map(|c| c.no_annotation())
}
}

View file

@ -2,14 +2,14 @@ use futures::{FutureExt, future::BoxFuture};
use thiserror::Error;
use crate::{
error::Error as McpError,
error::ErrorData as McpError,
model::{
CancelledNotification, CancelledNotificationParam, Extensions, GetExtensions, GetMeta,
JsonRpcBatchRequestItem, JsonRpcBatchResponseItem, JsonRpcError, JsonRpcMessage,
JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, Meta, NumberOrString, ProgressToken,
RequestId, ServerJsonRpcMessage,
},
transport::{IntoTransport, Transport},
transport::{DynamicTransportError, IntoTransport, Transport},
};
#[cfg(feature = "client")]
#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
@ -37,7 +37,7 @@ pub enum ServiceError {
#[error("Mcp error: {0}")]
McpError(McpError),
#[error("Transport send error: {0}")]
TransportSend(Box<dyn std::error::Error + Send + Sync>),
TransportSend(DynamicTransportError),
#[error("Transport closed")]
TransportClosed,
#[error("Unexpected response type")]
@ -48,7 +48,6 @@ pub enum ServiceError {
Timeout { timeout: Duration },
}
impl ServiceError {}
trait TransferObject:
std::fmt::Debug + Clone + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static
{
@ -79,7 +78,7 @@ pub trait ServiceRole: std::fmt::Debug + Send + Sync + 'static + Copy + Clone {
+ TransferObject
+ GetMeta
+ GetExtensions;
type InitializeError<E>;
type InitializeError;
const IS_CLIENT: bool;
type Info: TransferObject;
type PeerInfo: TransferObject;
@ -117,10 +116,10 @@ pub trait ServiceExt<R: ServiceRole>: Service<R> + Sized {
fn serve<T, E, A>(
self,
transport: T,
) -> impl Future<Output = Result<RunningService<R, Self>, R::InitializeError<E>>> + Send
) -> impl Future<Output = Result<RunningService<R, Self>, R::InitializeError>> + Send
where
T: IntoTransport<R, E, A>,
E: std::error::Error + From<std::io::Error> + Send + Sync + 'static,
E: std::error::Error + Send + Sync + 'static,
Self: Sized,
{
Self::serve_with_ct(self, transport, Default::default())
@ -129,10 +128,10 @@ pub trait ServiceExt<R: ServiceRole>: Service<R> + Sized {
self,
transport: T,
ct: CancellationToken,
) -> impl Future<Output = Result<RunningService<R, Self>, R::InitializeError<E>>> + Send
) -> impl Future<Output = Result<RunningService<R, Self>, R::InitializeError>> + Send
where
T: IntoTransport<R, E, A>,
E: std::error::Error + From<std::io::Error> + Send + Sync + 'static,
E: std::error::Error + Send + Sync + 'static,
Self: Sized;
}
@ -537,11 +536,11 @@ where
E: std::error::Error + Send + Sync + 'static,
{
let (peer, peer_rx) = Peer::new(Arc::new(AtomicU32RequestIdProvider::default()), peer_info);
serve_inner(service, transport, peer, peer_rx, ct)
serve_inner(service, transport.into_transport(), peer, peer_rx, ct)
}
#[instrument(skip_all)]
fn serve_inner<R, S, T, E, A>(
fn serve_inner<R, S, T>(
service: S,
transport: T,
peer: Peer<R>,
@ -551,8 +550,7 @@ fn serve_inner<R, S, T, E, A>(
where
R: ServiceRole,
S: Service<R>,
T: IntoTransport<R, E, A>,
E: std::error::Error + Send + Sync + 'static,
T: Transport<R> + 'static,
{
const SINK_PROXY_BUFFER_SIZE: usize = 64;
let (sink_proxy_tx, mut sink_proxy_rx) =
@ -578,25 +576,25 @@ where
let handle = tokio::spawn(async move {
let mut transport = transport.into_transport();
let mut batch_messages = VecDeque::<RxJsonRpcMessage<R>>::new();
let mut send_task_set = tokio::task::JoinSet::<SendTaskResult<E>>::new();
let mut send_task_set = tokio::task::JoinSet::<SendTaskResult>::new();
#[derive(Debug)]
enum SendTaskResult<E> {
enum SendTaskResult {
Request {
id: RequestId,
result: Result<(), E>,
result: Result<(), DynamicTransportError>,
},
Notification {
responder: Responder<Result<(), ServiceError>>,
cancellation_param: Option<CancelledNotificationParam>,
result: Result<(), E>,
result: Result<(), DynamicTransportError>,
},
}
#[derive(Debug)]
enum Event<R: ServiceRole, E> {
enum Event<R: ServiceRole> {
ProxyMessage(PeerSinkMessage<R>),
PeerMessage(RxJsonRpcMessage<R>),
ToSink(TxJsonRpcMessage<R>),
SendTaskResult(SendTaskResult<E>),
SendTaskResult(SendTaskResult),
}
let quit_reason = loop {
@ -654,7 +652,7 @@ where
Event::SendTaskResult(SendTaskResult::Request { id, result }) => {
if let Err(e) = result {
if let Some(responder) = local_responder_pool.remove(&id) {
let _ = responder.send(Err(ServiceError::TransportSend(Box::new(e))));
let _ = responder.send(Err(ServiceError::TransportSend(e)));
}
}
}
@ -664,7 +662,7 @@ where
cancellation_param,
}) => {
let response = if let Err(e) = result {
Err(ServiceError::TransportSend(Box::new(e)))
Err(ServiceError::TransportSend(e))
} else {
Ok(())
};
@ -706,8 +704,10 @@ where
let send = transport.send(JsonRpcMessage::request(request, id.clone()));
{
let id = id.clone();
send_task_set
.spawn(send.map(move |r| SendTaskResult::Request { id, result: r }));
send_task_set.spawn(send.map(move |r| SendTaskResult::Request {
id,
result: r.map_err(DynamicTransportError::new::<T, R>),
}));
}
}
Event::ProxyMessage(PeerSinkMessage::Notification {
@ -727,7 +727,7 @@ where
send_task_set.spawn(send.map(move |result| SendTaskResult::Notification {
responder,
cancellation_param,
result,
result: result.map_err(DynamicTransportError::new::<T, R>),
}));
}
Event::PeerMessage(JsonRpcMessage::Request(JsonRpcRequest {

View file

@ -3,25 +3,29 @@ use std::borrow::Cow;
use thiserror::Error;
use super::*;
use crate::model::{
CallToolRequest, CallToolRequestParam, CallToolResult, CancelledNotification,
CancelledNotificationParam, ClientInfo, ClientJsonRpcMessage, ClientNotification,
ClientRequest, ClientResult, CompleteRequest, CompleteRequestParam, CompleteResult,
GetPromptRequest, GetPromptRequestParam, GetPromptResult, InitializeRequest,
InitializedNotification, JsonRpcResponse, ListPromptsRequest, ListPromptsResult,
ListResourceTemplatesRequest, ListResourceTemplatesResult, ListResourcesRequest,
ListResourcesResult, ListToolsRequest, ListToolsResult, PaginatedRequestParam,
ProgressNotification, ProgressNotificationParam, ReadResourceRequest, ReadResourceRequestParam,
ReadResourceResult, RequestId, RootsListChangedNotification, ServerInfo, ServerJsonRpcMessage,
ServerNotification, ServerRequest, ServerResult, SetLevelRequest, SetLevelRequestParam,
SubscribeRequest, SubscribeRequestParam, UnsubscribeRequest, UnsubscribeRequestParam,
use crate::{
model::{
CallToolRequest, CallToolRequestParam, CallToolResult, CancelledNotification,
CancelledNotificationParam, ClientInfo, ClientJsonRpcMessage, ClientNotification,
ClientRequest, ClientResult, CompleteRequest, CompleteRequestParam, CompleteResult,
GetPromptRequest, GetPromptRequestParam, GetPromptResult, InitializeRequest,
InitializedNotification, JsonRpcResponse, ListPromptsRequest, ListPromptsResult,
ListResourceTemplatesRequest, ListResourceTemplatesResult, ListResourcesRequest,
ListResourcesResult, ListToolsRequest, ListToolsResult, PaginatedRequestParam,
ProgressNotification, ProgressNotificationParam, ReadResourceRequest,
ReadResourceRequestParam, ReadResourceResult, RequestId, RootsListChangedNotification,
ServerInfo, ServerJsonRpcMessage, ServerNotification, ServerRequest, ServerResult,
SetLevelRequest, SetLevelRequestParam, SubscribeRequest, SubscribeRequestParam,
UnsubscribeRequest, UnsubscribeRequestParam,
},
transport::DynamicTransportError,
};
/// It represents the error that may occur when serving the client.
///
/// if you want to handle the error, you can use `serve_client_with_ct` or `serve_client` with `Result<RunningService<RoleClient, S>, ClientError>`
#[derive(Error, Debug)]
pub enum ClientInitializeError<E> {
pub enum ClientInitializeError {
#[error("expect initialized response, but received: {0:?}")]
ExpectedInitResponse(Option<ServerJsonRpcMessage>),
@ -36,7 +40,7 @@ pub enum ClientInitializeError<E> {
#[error("Send message error {error}, when {context}")]
TransportError {
error: E,
error: DynamicTransportError,
context: Cow<'static, str>,
},
@ -44,11 +48,23 @@ pub enum ClientInitializeError<E> {
Cancelled,
}
impl ClientInitializeError {
pub fn transport<T: Transport<RoleClient> + 'static>(
error: T::Error,
context: impl Into<Cow<'static, str>>,
) -> Self {
Self::TransportError {
error: DynamicTransportError::new::<T, _>(error),
context: context.into(),
}
}
}
/// Helper function to get the next message from the stream
async fn expect_next_message<T, E>(
async fn expect_next_message<T>(
transport: &mut T,
context: &str,
) -> Result<ServerJsonRpcMessage, ClientInitializeError<E>>
) -> Result<ServerJsonRpcMessage, ClientInitializeError>
where
T: Transport<RoleClient>,
{
@ -59,10 +75,10 @@ where
}
/// Helper function to expect a response from the stream
async fn expect_response<T, E>(
async fn expect_response<T>(
transport: &mut T,
context: &str,
) -> Result<(ServerResult, RequestId), ClientInitializeError<E>>
) -> Result<(ServerResult, RequestId), ClientInitializeError>
where
T: Transport<RoleClient>,
{
@ -86,7 +102,7 @@ impl ServiceRole for RoleClient {
type PeerNot = ServerNotification;
type Info = ClientInfo;
type PeerInfo = ServerInfo;
type InitializeError<E> = ClientInitializeError<E>;
type InitializeError = ClientInitializeError;
const IS_CLIENT: bool = true;
}
@ -97,10 +113,10 @@ impl<S: Service<RoleClient>> ServiceExt<RoleClient> for S {
self,
transport: T,
ct: CancellationToken,
) -> impl Future<Output = Result<RunningService<RoleClient, Self>, ClientInitializeError<E>>> + Send
) -> impl Future<Output = Result<RunningService<RoleClient, Self>, ClientInitializeError>> + Send
where
T: IntoTransport<RoleClient, E, A>,
E: std::error::Error + From<std::io::Error> + Send + Sync + 'static,
E: std::error::Error + Send + Sync + 'static,
Self: Sized,
{
serve_client_with_ct(self, transport, ct)
@ -110,7 +126,7 @@ impl<S: Service<RoleClient>> ServiceExt<RoleClient> for S {
pub async fn serve_client<S, T, E, A>(
service: S,
transport: T,
) -> Result<RunningService<RoleClient, S>, ClientInitializeError<E>>
) -> Result<RunningService<RoleClient, S>, ClientInitializeError>
where
S: Service<RoleClient>,
T: IntoTransport<RoleClient, E, A>,
@ -123,29 +139,28 @@ pub async fn serve_client_with_ct<S, T, E, A>(
service: S,
transport: T,
ct: CancellationToken,
) -> Result<RunningService<RoleClient, S>, ClientInitializeError<E>>
) -> Result<RunningService<RoleClient, S>, ClientInitializeError>
where
S: Service<RoleClient>,
T: IntoTransport<RoleClient, E, A>,
E: std::error::Error + Send + Sync + 'static,
{
tokio::select! {
result = serve_client_with_ct_inner(service, transport, ct.clone()) => { result }
result = serve_client_with_ct_inner(service, transport.into_transport(), ct.clone()) => { result }
_ = ct.cancelled() => {
Err(ClientInitializeError::Cancelled)
}
}
}
async fn serve_client_with_ct_inner<S, T, E, A>(
async fn serve_client_with_ct_inner<S, T>(
service: S,
transport: T,
ct: CancellationToken,
) -> Result<RunningService<RoleClient, S>, ClientInitializeError<E>>
) -> Result<RunningService<RoleClient, S>, ClientInitializeError>
where
S: Service<RoleClient>,
T: IntoTransport<RoleClient, E, A>,
E: std::error::Error + Send + Sync + 'static,
T: Transport<RoleClient> + 'static,
{
let mut transport = transport.into_transport();
let id_provider = <Arc<AtomicU32RequestIdProvider>>::default();
@ -164,7 +179,7 @@ where
))
.await
.map_err(|error| ClientInitializeError::TransportError {
error,
error: DynamicTransportError::new::<T, _>(error),
context: "send initialize request".into(),
})?;
@ -188,13 +203,9 @@ where
extensions: Default::default(),
}),
);
transport
.send(notification)
.await
.map_err(|error| ClientInitializeError::TransportError {
error,
context: "send initialized notification".into(),
})?;
transport.send(notification).await.map_err(|error| {
ClientInitializeError::transport::<T>(error, "send initialized notification")
})?;
let (peer, peer_rx) = Peer::new(id_provider, Some(initialize_result));
Ok(serve_inner(service, transport, peer, peer_rx, ct))
}

View file

@ -3,14 +3,18 @@ use std::borrow::Cow;
use thiserror::Error;
use super::*;
use crate::model::{
CancelledNotification, CancelledNotificationParam, ClientInfo, ClientJsonRpcMessage,
ClientNotification, ClientRequest, ClientResult, CreateMessageRequest,
CreateMessageRequestParam, CreateMessageResult, ErrorData, ListRootsRequest, ListRootsResult,
LoggingMessageNotification, LoggingMessageNotificationParam, ProgressNotification,
ProgressNotificationParam, PromptListChangedNotification, ProtocolVersion,
ResourceListChangedNotification, ResourceUpdatedNotification, ResourceUpdatedNotificationParam,
ServerInfo, ServerNotification, ServerRequest, ServerResult, ToolListChangedNotification,
use crate::{
model::{
CancelledNotification, CancelledNotificationParam, ClientInfo, ClientJsonRpcMessage,
ClientNotification, ClientRequest, ClientResult, CreateMessageRequest,
CreateMessageRequestParam, CreateMessageResult, ErrorData, ListRootsRequest,
ListRootsResult, LoggingMessageNotification, LoggingMessageNotificationParam,
ProgressNotification, ProgressNotificationParam, PromptListChangedNotification,
ProtocolVersion, ResourceListChangedNotification, ResourceUpdatedNotification,
ResourceUpdatedNotificationParam, ServerInfo, ServerNotification, ServerRequest,
ServerResult, ToolListChangedNotification,
},
transport::DynamicTransportError,
};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
@ -26,7 +30,7 @@ impl ServiceRole for RoleServer {
type Info = ServerInfo;
type PeerInfo = ClientInfo;
type InitializeError<E> = ServerInitializeError<E>;
type InitializeError = ServerInitializeError;
const IS_CLIENT: bool = false;
}
@ -34,7 +38,7 @@ impl ServiceRole for RoleServer {
///
/// if you want to handle the error, you can use `serve_server_with_ct` or `serve_server` with `Result<RunningService<RoleServer, S>, ServerError>`
#[derive(Error, Debug)]
pub enum ServerInitializeError<E> {
pub enum ServerInitializeError {
#[error("expect initialized request, but received: {0:?}")]
ExpectedInitializeRequest(Option<ClientJsonRpcMessage>),
@ -55,7 +59,7 @@ pub enum ServerInitializeError<E> {
#[error("Send message error {error}, when {context}")]
TransportError {
error: E,
error: DynamicTransportError,
context: Cow<'static, str>,
},
@ -63,6 +67,17 @@ pub enum ServerInitializeError<E> {
Cancelled,
}
impl ServerInitializeError {
pub fn transport<T: Transport<RoleServer> + 'static>(
error: T::Error,
context: impl Into<Cow<'static, str>>,
) -> Self {
Self::TransportError {
error: DynamicTransportError::new::<T, _>(error),
context: context.into(),
}
}
}
pub type ClientSink = Peer<RoleServer>;
impl<S: Service<RoleServer>> ServiceExt<RoleServer> for S {
@ -70,7 +85,7 @@ impl<S: Service<RoleServer>> ServiceExt<RoleServer> for S {
self,
transport: T,
ct: CancellationToken,
) -> impl Future<Output = Result<RunningService<RoleServer, Self>, ServerInitializeError<E>>> + Send
) -> impl Future<Output = Result<RunningService<RoleServer, Self>, ServerInitializeError>> + Send
where
T: IntoTransport<RoleServer, E, A>,
E: std::error::Error + Send + Sync + 'static,
@ -83,7 +98,7 @@ impl<S: Service<RoleServer>> ServiceExt<RoleServer> for S {
pub async fn serve_server<S, T, E, A>(
service: S,
transport: T,
) -> Result<RunningService<RoleServer, S>, ServerInitializeError<E>>
) -> Result<RunningService<RoleServer, S>, ServerInitializeError>
where
S: Service<RoleServer>,
T: IntoTransport<RoleServer, E, A>,
@ -93,10 +108,10 @@ where
}
/// Helper function to get the next message from the stream
async fn expect_next_message<T, E>(
async fn expect_next_message<T>(
transport: &mut T,
context: &str,
) -> Result<ClientJsonRpcMessage, ServerInitializeError<E>>
) -> Result<ClientJsonRpcMessage, ServerInitializeError>
where
T: Transport<RoleServer>,
{
@ -107,10 +122,10 @@ where
}
/// Helper function to expect a request from the stream
async fn expect_request<T, E>(
async fn expect_request<T>(
transport: &mut T,
context: &str,
) -> Result<(ClientRequest, RequestId), ServerInitializeError<E>>
) -> Result<(ClientRequest, RequestId), ServerInitializeError>
where
T: Transport<RoleServer>,
{
@ -123,10 +138,10 @@ where
}
/// Helper function to expect a notification from the stream
async fn expect_notification<T, E>(
async fn expect_notification<T>(
transport: &mut T,
context: &str,
) -> Result<ClientNotification, ServerInitializeError<E>>
) -> Result<ClientNotification, ServerInitializeError>
where
T: Transport<RoleServer>,
{
@ -142,29 +157,28 @@ pub async fn serve_server_with_ct<S, T, E, A>(
service: S,
transport: T,
ct: CancellationToken,
) -> Result<RunningService<RoleServer, S>, ServerInitializeError<E>>
) -> Result<RunningService<RoleServer, S>, ServerInitializeError>
where
S: Service<RoleServer>,
T: IntoTransport<RoleServer, E, A>,
E: std::error::Error + Send + Sync + 'static,
{
tokio::select! {
result = serve_server_with_ct_inner(service, transport, ct.clone()) => { result }
result = serve_server_with_ct_inner(service, transport.into_transport(), ct.clone()) => { result }
_ = ct.cancelled() => {
Err(ServerInitializeError::Cancelled)
}
}
}
async fn serve_server_with_ct_inner<S, T, E, A>(
async fn serve_server_with_ct_inner<S, T>(
service: S,
transport: T,
ct: CancellationToken,
) -> Result<RunningService<RoleServer, S>, ServerInitializeError<E>>
) -> Result<RunningService<RoleServer, S>, ServerInitializeError>
where
S: Service<RoleServer>,
T: IntoTransport<RoleServer, E, A>,
E: std::error::Error + Send + Sync + 'static,
T: Transport<RoleServer> + 'static,
{
let mut transport = transport.into_transport();
let id_provider = <Arc<AtomicU32RequestIdProvider>>::default();
@ -196,9 +210,8 @@ where
transport
.send(ServerJsonRpcMessage::error(e.clone(), id))
.await
.map_err(|error| ServerInitializeError::TransportError {
error,
context: "sending error response".into(),
.map_err(|error| {
ServerInitializeError::transport::<T>(error, "sending error response")
})?;
return Err(ServerInitializeError::InitializeFailed(e));
}
@ -219,9 +232,8 @@ where
id,
))
.await
.map_err(|error| ServerInitializeError::TransportError {
error,
context: "sending initialize response".into(),
.map_err(|error| {
ServerInitializeError::transport::<T>(error, "sending initialize response")
})?;
// Wait for initialize notification

View file

@ -24,14 +24,14 @@ impl<S, R: ServiceRole> TowerHandler<S, R> {
impl<S, R: ServiceRole> Service<R> for TowerHandler<S, R>
where
S: TowerService<R::PeerReq, Response = R::Resp> + Sync + Send + Clone + 'static,
S::Error: Into<crate::Error>,
S::Error: Into<crate::ErrorData>,
S::Future: Send,
{
async fn handle_request(
&self,
request: R::PeerReq,
_context: RequestContext<R>,
) -> Result<R::Resp, crate::Error> {
) -> Result<R::Resp, crate::ErrorData> {
let mut service = self.service.clone();
poll_fn(|cx| service.poll_ready(cx))
.await
@ -44,7 +44,7 @@ where
&self,
_notification: R::PeerNot,
_context: NotificationContext<R>,
) -> impl Future<Output = Result<(), crate::Error>> + Send + '_ {
) -> impl Future<Output = Result<(), crate::ErrorData>> + Send + '_ {
std::future::ready(Ok(()))
}

View file

@ -64,7 +64,7 @@
//! }
//! ```
use std::sync::Arc;
use std::{borrow::Cow, sync::Arc};
use crate::service::{RxJsonRpcMessage, ServiceRole, TxJsonRpcMessage};
@ -141,6 +141,9 @@ where
R: ServiceRole,
{
type Error: std::error::Error + Send + Sync + 'static;
fn name() -> Cow<'static, str> {
std::any::type_name::<Self>().into()
}
/// Send a message to the transport
///
/// Notice that the future returned by this function should be `Send` and `'static`.
@ -241,3 +244,36 @@ where
std::future::ready(Ok(()))
}
}
#[derive(Debug, thiserror::Error)]
#[error("Transport [{transport_name}] error: {error}")]
pub struct DynamicTransportError {
pub transport_name: Cow<'static, str>,
pub transport_type_id: std::any::TypeId,
#[source]
pub error: Box<dyn std::error::Error + Send + Sync>,
}
impl DynamicTransportError {
pub fn new<T: Transport<R> + 'static, R: ServiceRole>(e: T::Error) -> Self {
Self {
transport_name: T::name(),
transport_type_id: std::any::TypeId::of::<T>(),
error: Box::new(e),
}
}
pub fn downcast<T: Transport<R> + 'static, R: ServiceRole>(self) -> Result<T::Error, Self> {
if !self.is::<T, R>() {
Err(self)
} else {
Ok(self
.error
.downcast::<T::Error>()
.map(|e| *e)
.expect("type is checked"))
}
}
pub fn is<T: Transport<R> + 'static, R: ServiceRole>(&self) -> bool {
self.error.is::<T::Error>() && self.transport_type_id == std::any::TypeId::of::<T>()
}
}

View file

@ -4,7 +4,7 @@ use std::{
};
use rmcp::{
ClientHandler, Error as McpError, RoleClient, RoleServer, ServerHandler,
ClientHandler, ErrorData as McpError, RoleClient, RoleServer, ServerHandler,
model::*,
service::{NotificationContext, RequestContext},
};

View file

@ -1,5 +1,5 @@
use rmcp::{
Error as McpError, handler::server::tool::Parameters, model::*, schemars, tool, tool_router,
ErrorData as McpError, handler::server::tool::Parameters, model::*, schemars, tool, tool_router,
};
use serde::{Deserialize, Serialize};

View file

@ -27,7 +27,7 @@ impl ServerHandler for Server {
&self,
request: rmcp::model::SubscribeRequestParam,
context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<(), rmcp::Error> {
) -> Result<(), rmcp::ErrorData> {
let uri = request.uri;
let peer = context.peer;

Some files were not shown because too many files have changed in this diff Show more