Skip to content

Commit a77203f

Browse files
committed
feat(task): route server-originated tasks/* to ClientHandler (SEP-1686)
Tasks are bidirectional per SEP-1686: either party can be the requestor or the receiver. The ServerHandler side is already wired for client→server task flow (tools/call augmentation). This patch mirrors the same wiring on the client side so servers that initiate task-augmented requests (notably sampling/createMessage and elicitation/create) can follow up with tasks/get, tasks/list, tasks/result, and tasks/cancel directed at the client. Changes, purely additive: * ServerRequest: add GetTaskInfoRequest | ListTasksRequest | GetTaskResultRequest | CancelTaskRequest variants. Add a ServerRequest::method() accessor mirroring ClientRequest::method(). Update the variant_extension! invocation so the existing GetExtensions / GetMeta impls cover the new variants. * ClientResult: add ListTasksResult | GetTaskResult | GetTaskPayloadResult | CancelTaskResult response variants. GetTaskPayloadResult retains its existing custom Deserialize-fails behavior, so payload responses are still observed on the wire as CustomResult (matching the server-side pattern). * ClientHandler: add list_tasks, get_task_info, get_task_result, and cancel_task methods with default -32601 Method-not-found impls, mirroring the server-side signatures. Propagate via the Box/Arc wrapper macro. Dispatch all four from the handle_request match. This unblocks clients that want to advertise capabilities.tasks.requests.sampling.createMessage, capabilities.tasks.requests.elicitation.create, or the client-side tasks.list / tasks.cancel capabilities: previously, servers had no way to reach the client's task methods through the typed request enum, and such capabilities couldn't be honored end-to-end. Tests: new test_task_client_receiver.rs exercises a full bidirectional roundtrip for each of the four methods (server → client RPC → ClientHandler → response → server), plus a default-impl test that confirms the unit () client returns -32601 for tasks/get. Existing message-schema golden files regenerated to include the new ServerRequest and ClientResult variants; no other tests affected. Related: #528, #536 (which added the server-side half of SEP-1686).
1 parent 020a38b commit a77203f

9 files changed

Lines changed: 1346 additions & 0 deletions

crates/rmcp/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,11 @@ name = "test_custom_request"
283283
required-features = ["server", "client"]
284284
path = "tests/test_custom_request.rs"
285285

286+
[[test]]
287+
name = "test_task_client_receiver"
288+
required-features = ["server", "client"]
289+
path = "tests/test_task_client_receiver.rs"
290+
286291
[[test]]
287292
name = "test_prompt_macros"
288293
required-features = ["server", "client"]

crates/rmcp/src/handler/client.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ impl<H: ClientHandler> Service<RoleClient> for H {
3030
.create_elicitation(request.params, context)
3131
.await
3232
.map(ClientResult::CreateElicitationResult),
33+
ServerRequest::ListTasksRequest(request) => self
34+
.list_tasks(request.params, context)
35+
.await
36+
.map(ClientResult::ListTasksResult),
37+
ServerRequest::GetTaskInfoRequest(request) => self
38+
.get_task_info(request.params, context)
39+
.await
40+
.map(ClientResult::GetTaskResult),
41+
ServerRequest::GetTaskResultRequest(request) => self
42+
.get_task_result(request.params, context)
43+
.await
44+
.map(ClientResult::GetTaskPayloadResult),
45+
ServerRequest::CancelTaskRequest(request) => self
46+
.cancel_task(request.params, context)
47+
.await
48+
.map(ClientResult::CancelTaskResult),
3349
ServerRequest::CustomRequest(request) => self
3450
.on_custom_request(request, context)
3551
.await
@@ -191,6 +207,68 @@ pub trait ClientHandler: Sized + Send + Sync + 'static {
191207
)))
192208
}
193209

210+
/// Handle a `tasks/list` request from a server. Only relevant when the
211+
/// client is also a task *receiver* (e.g. it accepted a task-augmented
212+
/// `sampling/createMessage` or `elicitation/create` request).
213+
///
214+
/// # Default Behavior
215+
/// Returns `-32601` (Method not found). Clients that advertise
216+
/// `capabilities.tasks.list` must override this.
217+
fn list_tasks(
218+
&self,
219+
request: Option<PaginatedRequestParams>,
220+
context: RequestContext<RoleClient>,
221+
) -> impl Future<Output = Result<ListTasksResult, McpError>> + MaybeSendFuture + '_ {
222+
let _ = (request, context);
223+
std::future::ready(Err(McpError::method_not_found::<ListTasksMethod>()))
224+
}
225+
226+
/// Handle a `tasks/get` request from a server. Only relevant when the
227+
/// client is also a task *receiver* (e.g. it accepted a task-augmented
228+
/// `sampling/createMessage` or `elicitation/create` request).
229+
///
230+
/// # Default Behavior
231+
/// Returns `-32601` (Method not found). Clients that advertise
232+
/// `capabilities.tasks.requests.sampling.createMessage` or
233+
/// `capabilities.tasks.requests.elicitation.create` must override this.
234+
fn get_task_info(
235+
&self,
236+
request: GetTaskInfoParams,
237+
context: RequestContext<RoleClient>,
238+
) -> impl Future<Output = Result<GetTaskResult, McpError>> + MaybeSendFuture + '_ {
239+
let _ = (request, context);
240+
std::future::ready(Err(McpError::method_not_found::<GetTaskInfoMethod>()))
241+
}
242+
243+
/// Handle a `tasks/result` request from a server. Only relevant when
244+
/// the client is also a task *receiver*.
245+
///
246+
/// # Default Behavior
247+
/// Returns `-32601` (Method not found).
248+
fn get_task_result(
249+
&self,
250+
request: GetTaskResultParams,
251+
context: RequestContext<RoleClient>,
252+
) -> impl Future<Output = Result<GetTaskPayloadResult, McpError>> + MaybeSendFuture + '_ {
253+
let _ = (request, context);
254+
std::future::ready(Err(McpError::method_not_found::<GetTaskResultMethod>()))
255+
}
256+
257+
/// Handle a `tasks/cancel` request from a server. Only relevant when
258+
/// the client is also a task *receiver*.
259+
///
260+
/// # Default Behavior
261+
/// Returns `-32601` (Method not found). Clients that advertise
262+
/// `capabilities.tasks.cancel` must override this.
263+
fn cancel_task(
264+
&self,
265+
request: CancelTaskParams,
266+
context: RequestContext<RoleClient>,
267+
) -> impl Future<Output = Result<CancelTaskResult, McpError>> + MaybeSendFuture + '_ {
268+
let _ = (request, context);
269+
std::future::ready(Err(McpError::method_not_found::<CancelTaskMethod>()))
270+
}
271+
194272
fn on_cancelled(
195273
&self,
196274
params: CancelledNotificationParam,
@@ -310,6 +388,38 @@ macro_rules! impl_client_handler_for_wrapper {
310388
(**self).on_custom_request(request, context)
311389
}
312390

391+
fn list_tasks(
392+
&self,
393+
request: Option<PaginatedRequestParams>,
394+
context: RequestContext<RoleClient>,
395+
) -> impl Future<Output = Result<ListTasksResult, McpError>> + MaybeSendFuture + '_ {
396+
(**self).list_tasks(request, context)
397+
}
398+
399+
fn get_task_info(
400+
&self,
401+
request: GetTaskInfoParams,
402+
context: RequestContext<RoleClient>,
403+
) -> impl Future<Output = Result<GetTaskResult, McpError>> + MaybeSendFuture + '_ {
404+
(**self).get_task_info(request, context)
405+
}
406+
407+
fn get_task_result(
408+
&self,
409+
request: GetTaskResultParams,
410+
context: RequestContext<RoleClient>,
411+
) -> impl Future<Output = Result<GetTaskPayloadResult, McpError>> + MaybeSendFuture + '_ {
412+
(**self).get_task_result(request, context)
413+
}
414+
415+
fn cancel_task(
416+
&self,
417+
request: CancelTaskParams,
418+
context: RequestContext<RoleClient>,
419+
) -> impl Future<Output = Result<CancelTaskResult, McpError>> + MaybeSendFuture + '_ {
420+
(**self).cancel_task(request, context)
421+
}
422+
313423
fn on_cancelled(
314424
&self,
315425
params: CancelledNotificationParam,

crates/rmcp/src/model.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3323,6 +3323,10 @@ ts_union!(
33233323
box CreateMessageResult
33243324
| ListRootsResult
33253325
| CreateElicitationResult
3326+
| ListTasksResult
3327+
| GetTaskResult
3328+
| GetTaskPayloadResult
3329+
| CancelTaskResult
33263330
| EmptyResult
33273331
| CustomResult;
33283332
);
@@ -3341,9 +3345,29 @@ ts_union!(
33413345
| CreateMessageRequest
33423346
| ListRootsRequest
33433347
| CreateElicitationRequest
3348+
| GetTaskInfoRequest
3349+
| ListTasksRequest
3350+
| GetTaskResultRequest
3351+
| CancelTaskRequest
33443352
| CustomRequest;
33453353
);
33463354

3355+
impl ServerRequest {
3356+
pub fn method(&self) -> &str {
3357+
match &self {
3358+
ServerRequest::PingRequest(r) => r.method.as_str(),
3359+
ServerRequest::CreateMessageRequest(r) => r.method.as_str(),
3360+
ServerRequest::ListRootsRequest(r) => r.method.as_str(),
3361+
ServerRequest::CreateElicitationRequest(r) => r.method.as_str(),
3362+
ServerRequest::GetTaskInfoRequest(r) => r.method.as_str(),
3363+
ServerRequest::ListTasksRequest(r) => r.method.as_str(),
3364+
ServerRequest::GetTaskResultRequest(r) => r.method.as_str(),
3365+
ServerRequest::CancelTaskRequest(r) => r.method.as_str(),
3366+
ServerRequest::CustomRequest(r) => r.method.as_str(),
3367+
}
3368+
}
3369+
}
3370+
33473371
ts_union!(
33483372
export type ServerNotification =
33493373
| CancelledNotification

crates/rmcp/src/model/meta.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ variant_extension! {
166166
ListRootsRequest
167167
CreateElicitationRequest
168168
CustomRequest
169+
GetTaskInfoRequest
170+
ListTasksRequest
171+
GetTaskResultRequest
172+
CancelTaskRequest
169173
}
170174
}
171175

0 commit comments

Comments
 (0)