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:
parent
acb06ea819
commit
81f858836d
4 changed files with 348 additions and 1 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
pub mod progress;
|
pub mod progress;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::ErrorData as McpError,
|
error::ErrorData as McpError,
|
||||||
model::*,
|
model::*,
|
||||||
|
|
@ -210,3 +212,115 @@ impl ClientHandler for ClientInfo {
|
||||||
self.clone()
|
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);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::ErrorData as McpError,
|
error::ErrorData as McpError,
|
||||||
model::*,
|
model::*,
|
||||||
|
|
@ -327,3 +329,206 @@ pub trait ServerHandler: Sized + Send + Sync + 'static {
|
||||||
std::future::ready(Err(McpError::method_not_found::<CancelTaskMethod>()))
|
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);
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,6 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_info(&self) -> <RoleServer as crate::service::ServiceRole>::Info {
|
fn get_info(&self) -> <RoleServer as crate::service::ServiceRole>::Info {
|
||||||
self.service.get_info()
|
ServerHandler::get_info(&self.service)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
crates/rmcp/tests/test_handler_wrappers.rs
Normal file
28
crates/rmcp/tests/test_handler_wrappers.rs
Normal 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));
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue