feat: provide blanket implementations for ClientHandler and ServerHandler traits (#609)

* feat!: implement ServerHandler for Box<H> and Arc<H> where H is a ServerHandler

* feat!: implement ClientHandler for Box<H> and Arc<H> where H is a ClientHandler

* test: test Box and Arc have blanket implementations for handler traits

* refactor: deduplicate blanket implementations with macros
This commit is contained in:
Taylor Ninesling 2026-01-14 10:53:05 -05:00 • committed by GitHub
commit 81f858836d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 348 additions and 1 deletions

View file

@ -1,4 +1,6 @@
pub mod progress;
use std::sync::Arc;
use crate::{
error::ErrorData as McpError,
model::*,
@ -210,3 +212,115 @@ impl ClientHandler for ClientInfo {
self.clone()
}
}
macro_rules! impl_client_handler_for_wrapper {
($wrapper:ident) => {
impl<T: ClientHandler> ClientHandler for $wrapper<T> {
fn ping(
&self,
context: RequestContext<RoleClient>,
) -> impl Future<Output = Result<(), McpError>> + Send + '_ {
(**self).ping(context)
}
fn create_message(
&self,
params: CreateMessageRequestParam,
context: RequestContext<RoleClient>,
) -> impl Future<Output = Result<CreateMessageResult, McpError>> + Send + '_ {
(**self).create_message(params, context)
}
fn list_roots(
&self,
context: RequestContext<RoleClient>,
) -> impl Future<Output = Result<ListRootsResult, McpError>> + Send + '_ {
(**self).list_roots(context)
}
fn create_elicitation(
&self,
request: CreateElicitationRequestParam,
context: RequestContext<RoleClient>,
) -> impl Future<Output = Result<CreateElicitationResult, McpError>> + Send + '_ {
(**self).create_elicitation(request, context)
}
fn on_custom_request(
&self,
request: CustomRequest,
context: RequestContext<RoleClient>,
) -> impl Future<Output = Result<CustomResult, McpError>> + Send + '_ {
(**self).on_custom_request(request, context)
}
fn on_cancelled(
&self,
params: CancelledNotificationParam,
context: NotificationContext<RoleClient>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_cancelled(params, context)
}
fn on_progress(
&self,
params: ProgressNotificationParam,
context: NotificationContext<RoleClient>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_progress(params, context)
}
fn on_logging_message(
&self,
params: LoggingMessageNotificationParam,
context: NotificationContext<RoleClient>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_logging_message(params, context)
}
fn on_resource_updated(
&self,
params: ResourceUpdatedNotificationParam,
context: NotificationContext<RoleClient>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_resource_updated(params, context)
}
fn on_resource_list_changed(
&self,
context: NotificationContext<RoleClient>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_resource_list_changed(context)
}
fn on_tool_list_changed(
&self,
context: NotificationContext<RoleClient>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_tool_list_changed(context)
}
fn on_prompt_list_changed(
&self,
context: NotificationContext<RoleClient>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_prompt_list_changed(context)
}
fn on_custom_notification(
&self,
notification: CustomNotification,
context: NotificationContext<RoleClient>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_custom_notification(notification, context)
}
fn get_info(&self) -> ClientInfo {
(**self).get_info()
}
}
};
}
impl_client_handler_for_wrapper!(Box);
impl_client_handler_for_wrapper!(Arc);

View file

@ -1,3 +1,5 @@
use std::sync::Arc;
use crate::{
error::ErrorData as McpError,
model::*,
@ -327,3 +329,206 @@ pub trait ServerHandler: Sized + Send + Sync + 'static {
std::future::ready(Err(McpError::method_not_found::<CancelTaskMethod>()))
}
}
macro_rules! impl_server_handler_for_wrapper {
($wrapper:ident) => {
impl<T: ServerHandler> ServerHandler for $wrapper<T> {
fn enqueue_task(
&self,
request: CallToolRequestParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<CreateTaskResult, McpError>> + Send + '_ {
(**self).enqueue_task(request, context)
}
fn ping(
&self,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<(), McpError>> + Send + '_ {
(**self).ping(context)
}
fn initialize(
&self,
request: InitializeRequestParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<InitializeResult, McpError>> + Send + '_ {
(**self).initialize(request, context)
}
fn complete(
&self,
request: CompleteRequestParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<CompleteResult, McpError>> + Send + '_ {
(**self).complete(request, context)
}
fn set_level(
&self,
request: SetLevelRequestParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<(), McpError>> + Send + '_ {
(**self).set_level(request, context)
}
fn get_prompt(
&self,
request: GetPromptRequestParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<GetPromptResult, McpError>> + Send + '_ {
(**self).get_prompt(request, context)
}
fn list_prompts(
&self,
request: Option<PaginatedRequestParam>,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ListPromptsResult, McpError>> + Send + '_ {
(**self).list_prompts(request, context)
}
fn list_resources(
&self,
request: Option<PaginatedRequestParam>,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ListResourcesResult, McpError>> + Send + '_ {
(**self).list_resources(request, context)
}
fn list_resource_templates(
&self,
request: Option<PaginatedRequestParam>,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ListResourceTemplatesResult, McpError>> + Send + '_
{
(**self).list_resource_templates(request, context)
}
fn read_resource(
&self,
request: ReadResourceRequestParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ReadResourceResult, McpError>> + Send + '_ {
(**self).read_resource(request, context)
}
fn subscribe(
&self,
request: SubscribeRequestParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<(), McpError>> + Send + '_ {
(**self).subscribe(request, context)
}
fn unsubscribe(
&self,
request: UnsubscribeRequestParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<(), McpError>> + Send + '_ {
(**self).unsubscribe(request, context)
}
fn call_tool(
&self,
request: CallToolRequestParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<CallToolResult, McpError>> + Send + '_ {
(**self).call_tool(request, context)
}
fn list_tools(
&self,
request: Option<PaginatedRequestParam>,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ListToolsResult, McpError>> + Send + '_ {
(**self).list_tools(request, context)
}
fn on_custom_request(
&self,
request: CustomRequest,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<CustomResult, McpError>> + Send + '_ {
(**self).on_custom_request(request, context)
}
fn on_cancelled(
&self,
notification: CancelledNotificationParam,
context: NotificationContext<RoleServer>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_cancelled(notification, context)
}
fn on_progress(
&self,
notification: ProgressNotificationParam,
context: NotificationContext<RoleServer>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_progress(notification, context)
}
fn on_initialized(
&self,
context: NotificationContext<RoleServer>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_initialized(context)
}
fn on_roots_list_changed(
&self,
context: NotificationContext<RoleServer>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_roots_list_changed(context)
}
fn on_custom_notification(
&self,
notification: CustomNotification,
context: NotificationContext<RoleServer>,
) -> impl Future<Output = ()> + Send + '_ {
(**self).on_custom_notification(notification, context)
}
fn get_info(&self) -> ServerInfo {
(**self).get_info()
}
fn list_tasks(
&self,
request: Option<PaginatedRequestParam>,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ListTasksResult, McpError>> + Send + '_ {
(**self).list_tasks(request, context)
}
fn get_task_info(
&self,
request: GetTaskInfoParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<GetTaskInfoResult, McpError>> + Send + '_ {
(**self).get_task_info(request, context)
}
fn get_task_result(
&self,
request: GetTaskResultParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<TaskResult, McpError>> + Send + '_ {
(**self).get_task_result(request, context)
}
fn cancel_task(
&self,
request: CancelTaskParam,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<(), McpError>> + Send + '_ {
(**self).cancel_task(request, context)
}
}
};
}
impl_server_handler_for_wrapper!(Box);
impl_server_handler_for_wrapper!(Arc);

View file

@ -133,6 +133,6 @@ where
}
fn get_info(&self) -> <RoleServer as crate::service::ServiceRole>::Info {
self.service.get_info()
ServerHandler::get_info(&self.service)
}
}

View file

@ -0,0 +1,28 @@
// cargo test --test test_handler_wrappers --features "client server"
mod common;
use std::sync::Arc;
use common::handlers::{TestClientHandler, TestServer};
use rmcp::{ClientHandler, ServerHandler};
#[test]
fn test_wrapped_server_handlers() {
// This test asserts that, when T: ServerHandler, both Box<T> and Arc<T> also implement ServerHandler.
fn accepts_server_handler<H: ServerHandler>(_handler: H) {}
accepts_server_handler(Box::new(TestServer::new()));
accepts_server_handler(Arc::new(TestServer::new()));
}
#[test]
fn test_wrapped_client_handlers() {
// This test asserts that, when T: ClientHandler, both Box<T> and Arc<T> also implement ClientHandler.
fn accepts_client_handler<H: ClientHandler>(_handler: H) {}
let client = TestClientHandler::new(false, false);
accepts_client_handler(Box::new(client.clone()));
accepts_client_handler(Arc::new(client));
}