Skip to main content

tower_mcp/
context.rs

1//! Request context for MCP handlers
2//!
3//! Provides progress reporting, cancellation support, and client request capabilities
4//! for long-running operations.
5//!
6//! # Example
7//!
8//! ```rust,ignore
9//! use tower_mcp::context::RequestContext;
10//!
11//! async fn long_running_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
12//!     for i in 0..100 {
13//!         // Check if cancelled
14//!         if ctx.is_cancelled() {
15//!             return Err(Error::tool("Operation cancelled"));
16//!         }
17//!
18//!         // Report progress
19//!         ctx.report_progress(i as f64, Some(100.0), Some("Processing...")).await;
20//!
21//!         do_work(i).await;
22//!     }
23//!     Ok(CallToolResult::text("Done!"))
24//! }
25//! ```
26//!
27//! # Sampling (LLM requests to client)
28//!
29//! ```rust,ignore
30//! use tower_mcp::context::RequestContext;
31//! use tower_mcp::{CreateMessageParams, SamplingMessage};
32//!
33//! async fn ai_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
34//!     // Request LLM completion from the client
35//!     let params = CreateMessageParams::new(
36//!         vec![SamplingMessage::user("Summarize this text...")],
37//!         500,
38//!     );
39//!
40//!     let result = ctx.sample(params).await?;
41//!     Ok(CallToolResult::text(format!("Summary: {:?}", result.content)))
42//! }
43//! ```
44//!
45//! # Elicitation (requesting user input)
46//!
47//! ```rust,ignore
48//! use tower_mcp::context::RequestContext;
49//! use tower_mcp::{ElicitFormParams, ElicitFormSchema, ElicitMode, ElicitAction};
50//!
51//! async fn interactive_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
52//!     // Request user input via form
53//!     let params = ElicitFormParams {
54//!         mode: Some(ElicitMode::Form),
55//!         message: "Please provide additional details".to_string(),
56//!         requested_schema: ElicitFormSchema::new()
57//!             .string_field("name", Some("Your name"), true)
58//!             .number_field("age", Some("Your age"), false),
59//!         meta: None,
60//!     };
61//!
62//!     let result = ctx.elicit_form(params).await?;
63//!     if result.action == ElicitAction::Accept {
64//!         // Use the form data
65//!         Ok(CallToolResult::text(format!("Got: {:?}", result.content)))
66//!     } else {
67//!         Ok(CallToolResult::text("User declined"))
68//!     }
69//! }
70//! ```
71//!
72//! # Stateless mode: per-request metadata (`stateless` feature)
73//!
74//! With the 2026-07-28 protocol, clients do not run an initialize handshake.
75//! Instead, every request carries the client's protocol version, identity, and
76//! capabilities in a `_meta` object. JSON-RPC transports extract these fields
77//! and stash them as a [`StatelessRequestMeta`](crate::stateless::StatelessRequestMeta)
78//! extension on the [`RequestContext`], accessible via
79//! [`ctx.per_request_meta()`](RequestContext::per_request_meta).
80//!
81//! `per_request_meta()` returns `Some` when:
82//! - The `stateless` feature is compiled in, AND
83//! - The request was dispatched by a JSON-RPC transport, AND
84//! - The request's `_meta` contained at least one recognized field.
85//!
86//! It returns `None` for 2025-11-25 session-based requests and when the request
87//! carried no modern protocol metadata.
88//!
89//! The [`StatelessRequestMeta`](crate::stateless::StatelessRequestMeta) struct
90//! provides:
91//!
92//! - `protocol_version` -- the `io.modelcontextprotocol/protocolVersion` field
93//! - `client_info` -- the `io.modelcontextprotocol/clientInfo` field (name, version)
94//! - `client_capabilities` -- the `io.modelcontextprotocol/clientCapabilities` field
95//! - `log_level` -- optional per-request log level override
96//! - `progress_token` -- optional progress token for progress notifications
97//!
98//! ```rust,ignore
99//! // Requires feature = ["stateless"]
100//! use tower_mcp::context::RequestContext;
101//!
102//! async fn my_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
103//!     if let Some(meta) = ctx.per_request_meta() {
104//!         // Available for 2026-07-28 clients on JSON-RPC transports
105//!         if let Some(ref info) = meta.client_info {
106//!             tracing::info!(client = %info.name, version = %info.version, "request from");
107//!         }
108//!         if let Some(ref version) = meta.protocol_version {
109//!             tracing::debug!(protocol_version = %version);
110//!         }
111//!     }
112//!     Ok(CallToolResult::text("ok"))
113//! }
114//! ```
115
116use std::sync::atomic::{AtomicI64, Ordering};
117use std::sync::{Arc, RwLock};
118
119use async_trait::async_trait;
120use tokio::sync::mpsc;
121
122use crate::error::{Error, Result};
123use crate::protocol::{
124    CallToolResult, CancelTaskParams, CreateMessageParams, CreateMessageResult, ElicitFormParams,
125    ElicitRequestParams, ElicitResult, ElicitUrlParams, GetTaskInfoParams, GetTaskResultParams,
126    ListTasksParams, ListTasksResult, LogLevel, LoggingMessageParams, ProgressParams,
127    ProgressToken, RequestId, TaskObject, TaskStatus,
128};
129
130/// A notification to be sent to the client
131#[derive(Debug, Clone)]
132#[non_exhaustive]
133pub enum ServerNotification {
134    /// Progress update for a request
135    Progress(ProgressParams),
136    /// Log message notification
137    LogMessage(LoggingMessageParams),
138    /// A subscribed resource has been updated
139    ResourceUpdated {
140        /// The URI of the updated resource
141        uri: String,
142    },
143    /// The list of available resources has changed
144    ResourcesListChanged,
145    /// The list of available tools has changed
146    ToolsListChanged,
147    /// The list of available prompts has changed
148    PromptsListChanged,
149    /// Task status has changed, in the legacy flat shape.
150    TaskStatusChanged(crate::protocol::TaskStatusParams),
151    /// Task status has changed, in the final SEP-2663 shape.
152    ///
153    /// Carries the complete status-discriminated task, identical to what
154    /// `tasks/get` would have returned at that moment. Delivered only on
155    /// `subscriptions/listen` streams that named this task ID.
156    FinalTaskStatusChanged(crate::tasks::TaskStatusNotificationParams),
157}
158
159/// Sender for server notifications
160pub type NotificationSender = mpsc::Sender<ServerNotification>;
161
162/// Receiver for server notifications
163pub type NotificationReceiver = mpsc::Receiver<ServerNotification>;
164
165/// Create a new notification channel
166pub fn notification_channel(buffer: usize) -> (NotificationSender, NotificationReceiver) {
167    mpsc::channel(buffer)
168}
169
170// =============================================================================
171// Client Requests (Server -> Client)
172// =============================================================================
173
174/// Trait for sending requests from server to client
175///
176/// This enables bidirectional communication where the server can request
177/// actions from the client, such as sampling (LLM requests), elicitation
178/// (user input requests), and task polling (per SEP-1686).
179#[async_trait]
180pub trait ClientRequester: Send + Sync {
181    /// Send a sampling request to the client
182    ///
183    /// Returns the LLM completion result from the client.
184    async fn sample(&self, params: CreateMessageParams) -> Result<CreateMessageResult>;
185
186    /// Send an elicitation request to the client
187    ///
188    /// This requests user input from the client. The request can be either
189    /// form-based (structured input) or URL-based (redirect to external URL).
190    ///
191    /// Returns the elicitation result with the user's action and any submitted data.
192    async fn elicit(&self, params: ElicitRequestParams) -> Result<ElicitResult>;
193
194    /// Send a generic JSON-RPC request to the client.
195    ///
196    /// Used by typed helpers ([`RequestContext::get_task_info`] etc.) to
197    /// dispatch arbitrary request methods. The default implementation returns
198    /// an error so existing custom implementations of this trait keep
199    /// compiling; they only need to override this if they want to support
200    /// methods beyond `sample` and `elicit`.
201    async fn request(
202        &self,
203        method: String,
204        params: serde_json::Value,
205    ) -> Result<serde_json::Value> {
206        let _ = (method, params);
207        Err(Error::Internal(
208            "ClientRequester does not support arbitrary requests".to_string(),
209        ))
210    }
211}
212
213/// A clonable handle to a client requester
214pub type ClientRequesterHandle = Arc<dyn ClientRequester>;
215
216/// Outgoing request to be sent to the client
217#[derive(Debug)]
218pub struct OutgoingRequest {
219    /// The JSON-RPC request ID
220    pub id: RequestId,
221    /// The method name
222    pub method: String,
223    /// The request parameters as JSON
224    pub params: serde_json::Value,
225    /// Channel to send the response back
226    pub response_tx: tokio::sync::oneshot::Sender<Result<serde_json::Value>>,
227}
228
229/// Sender for outgoing requests to the client
230pub type OutgoingRequestSender = mpsc::Sender<OutgoingRequest>;
231
232/// Receiver for outgoing requests (used by transport)
233pub type OutgoingRequestReceiver = mpsc::Receiver<OutgoingRequest>;
234
235/// Create a new outgoing request channel
236pub fn outgoing_request_channel(buffer: usize) -> (OutgoingRequestSender, OutgoingRequestReceiver) {
237    mpsc::channel(buffer)
238}
239
240/// A client requester implementation that sends requests through a channel
241#[derive(Clone)]
242pub struct ChannelClientRequester {
243    request_tx: OutgoingRequestSender,
244    next_id: Arc<AtomicI64>,
245}
246
247impl ChannelClientRequester {
248    /// Create a new channel-based client requester
249    pub fn new(request_tx: OutgoingRequestSender) -> Self {
250        Self {
251            request_tx,
252            next_id: Arc::new(AtomicI64::new(1)),
253        }
254    }
255
256    /// Create a requester that draws IDs from a transport-owned allocator.
257    ///
258    /// HTTP uses one allocator per session while giving each originating POST
259    /// its own request channel. This keeps server-to-client request IDs unique
260    /// without allowing those requests to escape onto an unrelated SSE stream.
261    #[cfg(feature = "http")]
262    pub(crate) fn with_id_allocator(
263        request_tx: OutgoingRequestSender,
264        next_id: Arc<AtomicI64>,
265    ) -> Self {
266        Self {
267            request_tx,
268            next_id,
269        }
270    }
271
272    fn next_request_id(&self) -> RequestId {
273        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
274        RequestId::Number(id)
275    }
276}
277
278impl ChannelClientRequester {
279    async fn dispatch(&self, method: &str, params: serde_json::Value) -> Result<serde_json::Value> {
280        let id = self.next_request_id();
281        let (response_tx, response_rx) = tokio::sync::oneshot::channel();
282
283        let request = OutgoingRequest {
284            id,
285            method: method.to_string(),
286            params,
287            response_tx,
288        };
289
290        self.request_tx
291            .send(request)
292            .await
293            .map_err(|_| Error::Internal("Failed to send request: channel closed".to_string()))?;
294
295        response_rx.await.map_err(|_| {
296            Error::Internal("Failed to receive response: channel closed".to_string())
297        })?
298    }
299}
300
301#[async_trait]
302impl ClientRequester for ChannelClientRequester {
303    async fn sample(&self, params: CreateMessageParams) -> Result<CreateMessageResult> {
304        let params_json = serde_json::to_value(&params)
305            .map_err(|e| Error::Internal(format!("Failed to serialize params: {}", e)))?;
306        let response = self.dispatch("sampling/createMessage", params_json).await?;
307        serde_json::from_value(response)
308            .map_err(|e| Error::Internal(format!("Failed to deserialize response: {}", e)))
309    }
310
311    async fn elicit(&self, params: ElicitRequestParams) -> Result<ElicitResult> {
312        let params_json = serde_json::to_value(&params)
313            .map_err(|e| Error::Internal(format!("Failed to serialize params: {}", e)))?;
314        let response = self.dispatch("elicitation/create", params_json).await?;
315        serde_json::from_value(response)
316            .map_err(|e| Error::Internal(format!("Failed to deserialize response: {}", e)))
317    }
318
319    async fn request(
320        &self,
321        method: String,
322        params: serde_json::Value,
323    ) -> Result<serde_json::Value> {
324        self.dispatch(&method, params).await
325    }
326}
327
328/// Context for a request, providing progress, cancellation, and client request support
329#[derive(Clone)]
330pub struct RequestContext {
331    /// The request ID
332    request_id: RequestId,
333    /// Progress token (if provided by client)
334    progress_token: Option<ProgressToken>,
335    /// Cancellation signal for this request
336    cancellation: tokio_util::sync::CancellationToken,
337    /// Channel for sending notifications
338    notification_tx: Option<NotificationSender>,
339    /// Handle for sending requests to the client (for sampling, etc.)
340    client_requester: Option<ClientRequesterHandle>,
341    /// Extensions for passing data from router/middleware to handlers
342    extensions: Arc<Extensions>,
343    /// Minimum log level set by the client (shared with router for dynamic updates)
344    min_log_level: Option<Arc<RwLock<LogLevel>>>,
345}
346
347/// Type-erased extensions map for passing data to handlers.
348///
349/// Extensions allow router-level state and middleware-injected data to flow
350/// to tool handlers via the `Extension<T>` extractor.
351#[derive(Clone, Default)]
352pub struct Extensions {
353    map: std::collections::HashMap<std::any::TypeId, Arc<dyn std::any::Any + Send + Sync>>,
354}
355
356impl Extensions {
357    /// Create an empty extensions map.
358    pub fn new() -> Self {
359        Self::default()
360    }
361
362    /// Insert a value into the extensions map.
363    ///
364    /// If a value of the same type already exists, it is replaced.
365    pub fn insert<T: Send + Sync + 'static>(&mut self, val: T) {
366        self.map.insert(std::any::TypeId::of::<T>(), Arc::new(val));
367    }
368
369    /// Get a reference to a value in the extensions map.
370    ///
371    /// Returns `None` if no value of the given type has been inserted.
372    pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
373        self.map
374            .get(&std::any::TypeId::of::<T>())
375            .and_then(|val| val.downcast_ref::<T>())
376    }
377
378    /// Check if the extensions map contains a value of the given type.
379    pub fn contains<T: Send + Sync + 'static>(&self) -> bool {
380        self.map.contains_key(&std::any::TypeId::of::<T>())
381    }
382
383    /// Merge another extensions map into this one.
384    ///
385    /// Values from `other` will overwrite existing values of the same type.
386    pub fn merge(&mut self, other: &Extensions) {
387        for (k, v) in &other.map {
388            self.map.insert(*k, v.clone());
389        }
390    }
391
392    /// Returns the number of entries in the extensions map.
393    pub fn len(&self) -> usize {
394        self.map.len()
395    }
396
397    /// Returns `true` if the extensions map contains no entries.
398    pub fn is_empty(&self) -> bool {
399        self.map.is_empty()
400    }
401}
402
403impl std::fmt::Debug for Extensions {
404    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405        f.debug_struct("Extensions")
406            .field("len", &self.map.len())
407            .finish()
408    }
409}
410
411impl std::fmt::Debug for RequestContext {
412    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413        f.debug_struct("RequestContext")
414            .field("request_id", &self.request_id)
415            .field("progress_token", &self.progress_token)
416            .field("cancelled", &self.cancellation.is_cancelled())
417            .finish()
418    }
419}
420
421impl RequestContext {
422    /// Create a new request context
423    pub fn new(request_id: RequestId) -> Self {
424        Self {
425            request_id,
426            progress_token: None,
427            cancellation: tokio_util::sync::CancellationToken::new(),
428            notification_tx: None,
429            client_requester: None,
430            extensions: Arc::new(Extensions::new()),
431            min_log_level: None,
432        }
433    }
434
435    /// Set the progress token
436    pub fn with_progress_token(mut self, token: ProgressToken) -> Self {
437        self.progress_token = Some(token);
438        self
439    }
440
441    /// Set the notification sender
442    pub fn with_notification_sender(mut self, tx: NotificationSender) -> Self {
443        self.notification_tx = Some(tx);
444        self
445    }
446
447    /// Set the minimum log level for filtering outgoing log notifications
448    ///
449    /// This is shared with the router so that `logging/setLevel` updates
450    /// are immediately visible to all request contexts.
451    pub fn with_min_log_level(mut self, level: Arc<RwLock<LogLevel>>) -> Self {
452        self.min_log_level = Some(level);
453        self
454    }
455
456    /// Set the client requester for server-to-client requests
457    pub fn with_client_requester(mut self, requester: ClientRequesterHandle) -> Self {
458        self.client_requester = Some(requester);
459        self
460    }
461
462    /// Set the extensions for this request context.
463    ///
464    /// Extensions allow router-level state and middleware data to flow to handlers.
465    pub fn with_extensions(mut self, extensions: Arc<Extensions>) -> Self {
466        self.extensions = extensions;
467        self
468    }
469
470    /// Get a reference to a value from the extensions map.
471    ///
472    /// Returns `None` if no value of the given type has been inserted.
473    ///
474    /// # Example
475    ///
476    /// ```rust,ignore
477    /// #[derive(Clone)]
478    /// struct CurrentUser { id: String }
479    ///
480    /// // In a handler:
481    /// if let Some(user) = ctx.extension::<CurrentUser>() {
482    ///     println!("User: {}", user.id);
483    /// }
484    /// ```
485    pub fn extension<T: Send + Sync + 'static>(&self) -> Option<&T> {
486        self.extensions.get::<T>()
487    }
488
489    /// Protocol extensions declared by both the client and server.
490    ///
491    /// Unknown or one-sided declarations are not included. The returned view
492    /// preserves each peer's settings object for extension-specific policy.
493    pub fn negotiated_extensions(&self) -> Option<&crate::NegotiatedExtensions> {
494        self.extension()
495    }
496
497    /// Get a mutable reference to the extensions.
498    ///
499    /// This allows middleware to insert data that handlers can access via
500    /// the `Extension<T>` extractor.
501    pub fn extensions_mut(&mut self) -> &mut Extensions {
502        Arc::make_mut(&mut self.extensions)
503    }
504
505    /// Get a reference to the extensions.
506    pub fn extensions(&self) -> &Extensions {
507        &self.extensions
508    }
509
510    /// SEP-2575 per-request `_meta` (protocol version, client info, client
511    /// capabilities, log level) if the transport extracted it.
512    ///
513    /// Returns `Some` for 2026-07-28 clients on JSON-RPC transports when the
514    /// request carried a `_meta` object with recognized fields. Returns `None`
515    /// when:
516    /// - The request had no `_meta` field, or
517    /// - The transport does not use [`crate::jsonrpc::JsonRpcService`], or
518    /// - The `stateless` feature is not compiled in.
519    ///
520    /// # Example
521    ///
522    /// ```rust,ignore
523    /// async fn my_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
524    ///     if let Some(meta) = ctx.per_request_meta() {
525    ///         // protocol_version, client_info, client_capabilities are all Option<_>
526    ///         if let Some(ref version) = meta.protocol_version {
527    ///             tracing::debug!(protocol_version = %version);
528    ///         }
529    ///         if let Some(ref info) = meta.client_info {
530    ///             tracing::info!(client = %info.name, version = %info.version);
531    ///         }
532    ///     }
533    ///     Ok(CallToolResult::text("ok"))
534    /// }
535    /// ```
536    #[cfg(feature = "stateless")]
537    pub fn per_request_meta(&self) -> Option<&crate::stateless::StatelessRequestMeta> {
538        self.extension::<crate::stateless::StatelessRequestMeta>()
539    }
540
541    /// SEP-2322 continuation values supplied by the client on this attempt.
542    #[cfg(feature = "stateless")]
543    pub fn mrtr(&self) -> Option<&crate::mrtr::MrtrRequest> {
544        self.extension::<crate::mrtr::MrtrRequest>()
545    }
546
547    /// Client responses from the prior MRTR round, if any.
548    #[cfg(feature = "stateless")]
549    pub fn input_responses(&self) -> Option<&crate::protocol::InputResponses> {
550        self.mrtr()
551            .and_then(crate::mrtr::MrtrRequest::input_responses)
552    }
553
554    /// Opaque request state echoed by the client, if any.
555    #[cfg(feature = "stateless")]
556    pub fn request_state(&self) -> Option<&str> {
557        self.mrtr()
558            .and_then(crate::mrtr::MrtrRequest::request_state)
559    }
560
561    /// Router-configured request-state codec shared by this handler.
562    #[cfg(feature = "stateless")]
563    pub fn request_state_codec(&self) -> Option<&crate::mrtr::RequestStateCodec> {
564        self.extension::<crate::mrtr::RequestStateCodec>()
565    }
566
567    /// Get the request ID
568    pub fn request_id(&self) -> &RequestId {
569        &self.request_id
570    }
571
572    /// Get the progress token (if any)
573    pub fn progress_token(&self) -> Option<&ProgressToken> {
574        self.progress_token.as_ref()
575    }
576
577    /// Check if the request has been cancelled
578    pub fn is_cancelled(&self) -> bool {
579        self.cancellation.is_cancelled()
580    }
581
582    /// Mark the request as cancelled
583    pub fn cancel(&self) {
584        self.cancellation.cancel();
585    }
586
587    /// Wait until the request is cancelled.
588    ///
589    /// Completes when [`cancel`](Self::cancel) is called -- by a
590    /// `notifications/cancelled` message, or by the transport when the
591    /// client disconnects before the response is delivered (HTTP
592    /// stateless mode). Useful in `tokio::select!` to abandon work early:
593    ///
594    /// ```rust,ignore
595    /// tokio::select! {
596    ///     result = do_work() => { /* ... */ }
597    ///     _ = ctx.cancelled() => return Err(Error::tool("cancelled")),
598    /// }
599    /// ```
600    pub async fn cancelled(&self) {
601        self.cancellation.cancelled().await
602    }
603
604    /// Get a cancellation token that can be shared
605    pub fn cancellation_token(&self) -> CancellationToken {
606        CancellationToken {
607            inner: self.cancellation.clone(),
608        }
609    }
610
611    /// Replace this context's cancellation source with an existing token.
612    ///
613    /// Used by transports to link a request's lifetime to an external
614    /// signal (e.g. client disconnect on the HTTP stateless path). After
615    /// this call, `is_cancelled()`, `cancelled()`, and tokens returned by
616    /// [`cancellation_token`](Self::cancellation_token) all observe the
617    /// given token.
618    pub fn with_cancellation_token(mut self, token: CancellationToken) -> Self {
619        self.cancellation = token.inner;
620        self
621    }
622
623    /// Report progress to the client
624    ///
625    /// This is a no-op if no progress token was provided or no notification sender is configured.
626    pub async fn report_progress(&self, progress: f64, total: Option<f64>, message: Option<&str>) {
627        let Some(token) = &self.progress_token else {
628            return;
629        };
630        let Some(tx) = &self.notification_tx else {
631            return;
632        };
633
634        let params = ProgressParams {
635            progress_token: token.clone(),
636            progress,
637            total,
638            message: message.map(|s| s.to_string()),
639            meta: None,
640        };
641
642        // Best effort - don't block if channel is full
643        let _ = tx.try_send(ServerNotification::Progress(params));
644    }
645
646    /// Report progress synchronously (non-async version)
647    ///
648    /// This is a no-op if no progress token was provided or no notification sender is configured.
649    pub fn report_progress_sync(&self, progress: f64, total: Option<f64>, message: Option<&str>) {
650        let Some(token) = &self.progress_token else {
651            return;
652        };
653        let Some(tx) = &self.notification_tx else {
654            return;
655        };
656
657        let params = ProgressParams {
658            progress_token: token.clone(),
659            progress,
660            total,
661            message: message.map(|s| s.to_string()),
662            meta: None,
663        };
664
665        let _ = tx.try_send(ServerNotification::Progress(params));
666    }
667
668    /// Notify subscribed clients that the tool list changed.
669    pub fn notify_tools_list_changed(&self) -> bool {
670        self.notification_tx
671            .as_ref()
672            .is_some_and(|tx| tx.try_send(ServerNotification::ToolsListChanged).is_ok())
673    }
674
675    /// Notify subscribed clients that the prompt list changed.
676    pub fn notify_prompts_list_changed(&self) -> bool {
677        self.notification_tx
678            .as_ref()
679            .is_some_and(|tx| tx.try_send(ServerNotification::PromptsListChanged).is_ok())
680    }
681
682    /// Notify subscribed clients that the resource list changed.
683    pub fn notify_resources_list_changed(&self) -> bool {
684        self.notification_tx.as_ref().is_some_and(|tx| {
685            tx.try_send(ServerNotification::ResourcesListChanged)
686                .is_ok()
687        })
688    }
689
690    /// Notify subscribed clients that one resource changed.
691    pub fn notify_resource_updated(&self, uri: impl Into<String>) -> bool {
692        self.notification_tx.as_ref().is_some_and(|tx| {
693            tx.try_send(ServerNotification::ResourceUpdated { uri: uri.into() })
694                .is_ok()
695        })
696    }
697
698    /// Notify the client that a final-protocol task changed status.
699    ///
700    /// This is useful when a handler drives a task transition through a custom
701    /// [`crate::TaskStore`] path rather than through the router directly.
702    pub fn notify_task_status_changed(
703        &self,
704        params: crate::tasks::TaskStatusNotificationParams,
705    ) -> bool {
706        self.notification_tx.as_ref().is_some_and(|tx| {
707            tx.try_send(ServerNotification::FinalTaskStatusChanged(params))
708                .is_ok()
709        })
710    }
711
712    /// Send a log message notification to the client
713    ///
714    /// This is a no-op if no notification sender is configured.
715    ///
716    /// # Example
717    ///
718    /// ```rust,ignore
719    /// use tower_mcp::protocol::{LoggingMessageParams, LogLevel};
720    ///
721    /// async fn my_tool(ctx: RequestContext) {
722    ///     ctx.send_log(
723    ///         LoggingMessageParams::new(LogLevel::Info, serde_json::json!("Processing..."))
724    ///             .with_logger("my-tool")
725    ///     );
726    /// }
727    /// ```
728    pub fn send_log(&self, params: LoggingMessageParams) {
729        let Some(tx) = &self.notification_tx else {
730            return;
731        };
732
733        // The final protocol removed logging/setLevel. Log delivery is instead
734        // authorized per request: no logLevel means no log notifications.
735        #[cfg(feature = "stateless")]
736        if let Some(meta) = self.per_request_meta()
737            && meta.protocol_version.as_deref()
738                == Some(crate::protocol::PROTOCOL_VERSION_2026_07_28)
739        {
740            let Some(request_level) = meta.log_level else {
741                return;
742            };
743            let request_level = match request_level {
744                crate::stateless::LogLevel::Debug => LogLevel::Debug,
745                crate::stateless::LogLevel::Info => LogLevel::Info,
746                crate::stateless::LogLevel::Notice => LogLevel::Notice,
747                crate::stateless::LogLevel::Warning => LogLevel::Warning,
748                crate::stateless::LogLevel::Error => LogLevel::Error,
749                crate::stateless::LogLevel::Critical => LogLevel::Critical,
750                crate::stateless::LogLevel::Alert => LogLevel::Alert,
751                crate::stateless::LogLevel::Emergency => LogLevel::Emergency,
752            };
753            if params.level > request_level {
754                return;
755            }
756            let _ = tx.try_send(ServerNotification::LogMessage(params));
757            return;
758        }
759
760        // Filter by minimum log level set via logging/setLevel
761        // LogLevel derives Ord with Emergency < Alert < ... < Debug,
762        // so a message passes if its severity is at least the minimum
763        // (i.e., its ordinal is <= the minimum level's ordinal).
764        if let Some(min_level) = &self.min_log_level
765            && let Ok(min) = min_level.read()
766            && params.level > *min
767        {
768            return;
769        }
770
771        let _ = tx.try_send(ServerNotification::LogMessage(params));
772    }
773
774    /// Check if sampling is available
775    ///
776    /// Returns true if a client requester is configured and the transport
777    /// supports bidirectional communication.
778    pub fn can_sample(&self) -> bool {
779        self.client_requester.is_some()
780    }
781
782    /// Request an LLM completion from the client
783    ///
784    /// This sends a `sampling/createMessage` request to the client and waits
785    /// for the response. The client is expected to forward this to an LLM
786    /// and return the result.
787    ///
788    /// Returns an error if sampling is not available (no client requester configured).
789    ///
790    /// # Example
791    ///
792    /// ```rust,ignore
793    /// use tower_mcp::{CreateMessageParams, SamplingMessage};
794    ///
795    /// async fn my_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
796    ///     let params = CreateMessageParams::new(
797    ///         vec![SamplingMessage::user("Summarize: ...")],
798    ///         500,
799    ///     );
800    ///
801    ///     let result = ctx.sample(params).await?;
802    ///     Ok(CallToolResult::text(format!("{:?}", result.content)))
803    /// }
804    /// ```
805    pub async fn sample(&self, params: CreateMessageParams) -> Result<CreateMessageResult> {
806        let requester = self.client_requester.as_ref().ok_or_else(|| {
807            Error::Internal("Sampling not available: no client requester configured".to_string())
808        })?;
809
810        requester.sample(params).await
811    }
812
813    /// Check if elicitation is available
814    ///
815    /// Returns true if a client requester is configured and the transport
816    /// supports bidirectional communication. Note that this only checks if
817    /// the mechanism is available, not whether the client supports elicitation.
818    pub fn can_elicit(&self) -> bool {
819        self.client_requester.is_some()
820    }
821
822    /// Request user input via a form from the client
823    ///
824    /// This sends an `elicitation/create` request to the client with a form schema.
825    /// The client renders the form to the user and returns their response.
826    ///
827    /// Returns an error if elicitation is not available (no client requester configured).
828    ///
829    /// # Example
830    ///
831    /// ```rust,ignore
832    /// use tower_mcp::{ElicitFormParams, ElicitFormSchema, ElicitMode, ElicitAction};
833    ///
834    /// async fn my_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
835    ///     let params = ElicitFormParams {
836    ///         mode: Some(ElicitMode::Form),
837    ///         message: "Please enter your details".to_string(),
838    ///         requested_schema: ElicitFormSchema::new()
839    ///             .string_field("name", Some("Your name"), true),
840    ///         meta: None,
841    ///     };
842    ///
843    ///     let result = ctx.elicit_form(params).await?;
844    ///     match result.action {
845    ///         ElicitAction::Accept => {
846    ///             // Use result.content
847    ///             Ok(CallToolResult::text("Got your input!"))
848    ///         }
849    ///         _ => Ok(CallToolResult::text("User declined"))
850    ///     }
851    /// }
852    /// ```
853    pub async fn elicit_form(&self, params: ElicitFormParams) -> Result<ElicitResult> {
854        let requester = self.client_requester.as_ref().ok_or_else(|| {
855            Error::Internal("Elicitation not available: no client requester configured".to_string())
856        })?;
857
858        requester.elicit(ElicitRequestParams::Form(params)).await
859    }
860
861    /// Request user input via URL redirect from the client
862    ///
863    /// This sends an `elicitation/create` request to the client with a URL.
864    /// The client directs the user to the URL for out-of-band input collection.
865    /// The server receives the result via a callback notification.
866    ///
867    /// Returns an error if elicitation is not available (no client requester configured).
868    ///
869    /// **Protocol note:** `ElicitUrlParams::elicitation_id` and the callback
870    /// notification it correlates with are a 2025-11-25-and-earlier pattern.
871    /// The final 2026-07-28 schema removes both in favor of MRTR (SEP-2322).
872    /// Use an MRTR-capable tool, prompt, or resource handler there; the client
873    /// learns the outcome by retrying the original request instead of receiving
874    /// a completion notification.
875    ///
876    /// # Example
877    ///
878    /// ```rust,ignore
879    /// use tower_mcp::{ElicitUrlParams, ElicitMode, ElicitAction};
880    ///
881    /// async fn my_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
882    ///     let params = ElicitUrlParams {
883    ///         mode: Some(ElicitMode::Url),
884    ///         elicitation_id: "unique-id-123".to_string(),
885    ///         message: "Please authorize via the link".to_string(),
886    ///         url: "https://example.com/auth?id=unique-id-123".to_string(),
887    ///         meta: None,
888    ///     };
889    ///
890    ///     let result = ctx.elicit_url(params).await?;
891    ///     match result.action {
892    ///         ElicitAction::Accept => Ok(CallToolResult::text("Authorization complete!")),
893    ///         _ => Ok(CallToolResult::text("Authorization cancelled"))
894    ///     }
895    /// }
896    /// ```
897    pub async fn elicit_url(&self, params: ElicitUrlParams) -> Result<ElicitResult> {
898        let requester = self.client_requester.as_ref().ok_or_else(|| {
899            Error::Internal("Elicitation not available: no client requester configured".to_string())
900        })?;
901
902        requester.elicit(ElicitRequestParams::Url(params)).await
903    }
904
905    /// Request simple confirmation from the user.
906    ///
907    /// This is a convenience method for simple yes/no confirmation dialogs.
908    /// It creates an elicitation form with a single boolean "confirm" field
909    /// and returns `true` if the user accepts, `false` otherwise.
910    ///
911    /// Returns an error if elicitation is not available (no client requester configured).
912    ///
913    /// # Example
914    ///
915    /// ```rust,ignore
916    /// use tower_mcp::{RequestContext, CallToolResult};
917    ///
918    /// async fn delete_item(ctx: RequestContext) -> Result<CallToolResult> {
919    ///     let confirmed = ctx.confirm("Are you sure you want to delete this item?").await?;
920    ///     if confirmed {
921    ///         // Perform deletion
922    ///         Ok(CallToolResult::text("Item deleted"))
923    ///     } else {
924    ///         Ok(CallToolResult::text("Deletion cancelled"))
925    ///     }
926    /// }
927    /// ```
928    pub async fn confirm(&self, message: impl Into<String>) -> Result<bool> {
929        use crate::protocol::{ElicitAction, ElicitFormParams, ElicitFormSchema, ElicitMode};
930
931        let params = ElicitFormParams {
932            mode: Some(ElicitMode::Form),
933            message: message.into(),
934            requested_schema: ElicitFormSchema::new().boolean_field_with_default(
935                "confirm",
936                Some("Confirm this action"),
937                true,
938                false,
939            ),
940            meta: None,
941        };
942
943        let result = self.elicit_form(params).await?;
944        Ok(result.action == ElicitAction::Accept)
945    }
946
947    /// List tasks tracked by the connected client (legacy SEP-1686).
948    ///
949    /// Sends a `tasks/list` request to the client and returns the result.
950    /// Pass `Some(status)` to filter to a single status, or `None` for all
951    /// tasks. Pagination is exposed via [`ListTasksResult::next_cursor`];
952    /// use [`request_raw`](Self::request_raw) for cursor-driven calls.
953    ///
954    /// Returns an error if no client requester is configured or the client
955    /// does not advertise task support.
956    #[deprecated(
957        since = "0.13.0",
958        note = "final SEP-2663 removes tasks/list; a conforming peer answers \
959                MethodNotFound (-32601). Only useful against legacy SEP-1686 \
960                clients."
961    )]
962    pub async fn list_tasks(&self, status: Option<TaskStatus>) -> Result<ListTasksResult> {
963        let params = ListTasksParams {
964            status,
965            cursor: None,
966            meta: None,
967        };
968        let value = self
969            .request_raw("tasks/list", serde_json::to_value(&params)?)
970            .await?;
971        serde_json::from_value(value)
972            .map_err(|e| Error::Internal(format!("Failed to deserialize tasks/list: {e}")))
973    }
974
975    /// Fetch metadata for a single task tracked by the client (SEP-1686).
976    ///
977    /// Sends a `tasks/get` request and returns the task object, including
978    /// the current status, timestamps, and TTL.
979    pub async fn get_task_info(&self, task_id: impl Into<String>) -> Result<TaskObject> {
980        let params = GetTaskInfoParams {
981            task_id: task_id.into(),
982            meta: None,
983        };
984        let value = self
985            .request_raw("tasks/get", serde_json::to_value(&params)?)
986            .await?;
987        serde_json::from_value(value)
988            .map_err(|e| Error::Internal(format!("Failed to deserialize tasks/get: {e}")))
989    }
990
991    /// Fetch the terminal result for a task tracked by the client (legacy
992    /// SEP-1686).
993    ///
994    /// Sends a `tasks/result` request. The client is expected to block until
995    /// the task reaches a terminal state and then return the underlying
996    /// `CallToolResult`. For long-running tasks, prefer polling with
997    /// [`get_task_info`](Self::get_task_info) and only call this once the
998    /// status is terminal.
999    #[deprecated(
1000        since = "0.13.0",
1001        note = "final SEP-2663 removes tasks/result (results are inlined in \
1002                the tasks/get DetailedTask); a conforming peer answers \
1003                MethodNotFound (-32601). Only useful against legacy SEP-1686 \
1004                clients."
1005    )]
1006    pub async fn get_task_result(&self, task_id: impl Into<String>) -> Result<CallToolResult> {
1007        let params = GetTaskResultParams {
1008            task_id: task_id.into(),
1009            meta: None,
1010        };
1011        let value = self
1012            .request_raw("tasks/result", serde_json::to_value(&params)?)
1013            .await?;
1014        serde_json::from_value(value)
1015            .map_err(|e| Error::Internal(format!("Failed to deserialize tasks/result: {e}")))
1016    }
1017
1018    /// Cancel a task tracked by the client.
1019    ///
1020    /// Sends a `tasks/cancel` request. Per final SEP-2663 the acknowledgment
1021    /// is an empty result and the observable task status is polled via
1022    /// [`get_task_info`](Self::get_task_info); the ack body is discarded, so
1023    /// this also tolerates legacy SEP-1686 peers that return the task object.
1024    pub async fn cancel_task(
1025        &self,
1026        task_id: impl Into<String>,
1027        reason: Option<String>,
1028    ) -> Result<()> {
1029        let params = CancelTaskParams {
1030            task_id: task_id.into(),
1031            reason,
1032            meta: None,
1033        };
1034        self.request_raw("tasks/cancel", serde_json::to_value(&params)?)
1035            .await?;
1036        Ok(())
1037    }
1038
1039    /// Send an arbitrary JSON-RPC request to the client.
1040    ///
1041    /// Escape hatch for methods not covered by the typed helpers (e.g. when
1042    /// a `tasks/list` cursor needs to be passed). Most callers should prefer
1043    /// the typed methods.
1044    pub async fn request_raw(
1045        &self,
1046        method: &str,
1047        params: serde_json::Value,
1048    ) -> Result<serde_json::Value> {
1049        let requester = self.client_requester.as_ref().ok_or_else(|| {
1050            Error::Internal(
1051                "Client request not available: no client requester configured".to_string(),
1052            )
1053        })?;
1054        requester.request(method.to_string(), params).await
1055    }
1056}
1057
1058/// A token that can be used to check for, wait on, or request cancellation
1059///
1060/// Cloned tokens share the same underlying signal: cancelling any clone
1061/// cancels them all. Backed by [`tokio_util::sync::CancellationToken`],
1062/// so cancellation can also be awaited via [`cancelled`](Self::cancelled).
1063#[derive(Clone, Debug, Default)]
1064pub struct CancellationToken {
1065    inner: tokio_util::sync::CancellationToken,
1066}
1067
1068impl CancellationToken {
1069    /// Create a new, un-cancelled token
1070    pub fn new() -> Self {
1071        Self::default()
1072    }
1073
1074    /// Check if cancellation has been requested
1075    pub fn is_cancelled(&self) -> bool {
1076        self.inner.is_cancelled()
1077    }
1078
1079    /// Request cancellation
1080    pub fn cancel(&self) {
1081        self.inner.cancel();
1082    }
1083
1084    /// Wait until cancellation is requested
1085    ///
1086    /// Completes immediately if the token is already cancelled.
1087    pub async fn cancelled(&self) {
1088        self.inner.cancelled().await
1089    }
1090}
1091
1092/// Builder for creating request contexts
1093#[derive(Default)]
1094pub struct RequestContextBuilder {
1095    request_id: Option<RequestId>,
1096    progress_token: Option<ProgressToken>,
1097    notification_tx: Option<NotificationSender>,
1098    client_requester: Option<ClientRequesterHandle>,
1099    min_log_level: Option<Arc<RwLock<LogLevel>>>,
1100}
1101
1102impl RequestContextBuilder {
1103    /// Create a new builder
1104    pub fn new() -> Self {
1105        Self::default()
1106    }
1107
1108    /// Set the request ID
1109    pub fn request_id(mut self, id: RequestId) -> Self {
1110        self.request_id = Some(id);
1111        self
1112    }
1113
1114    /// Set the progress token
1115    pub fn progress_token(mut self, token: ProgressToken) -> Self {
1116        self.progress_token = Some(token);
1117        self
1118    }
1119
1120    /// Set the notification sender
1121    pub fn notification_sender(mut self, tx: NotificationSender) -> Self {
1122        self.notification_tx = Some(tx);
1123        self
1124    }
1125
1126    /// Set the client requester for server-to-client requests
1127    pub fn client_requester(mut self, requester: ClientRequesterHandle) -> Self {
1128        self.client_requester = Some(requester);
1129        self
1130    }
1131
1132    /// Set the minimum log level for filtering
1133    pub fn min_log_level(mut self, level: Arc<RwLock<LogLevel>>) -> Self {
1134        self.min_log_level = Some(level);
1135        self
1136    }
1137
1138    /// Build the request context
1139    ///
1140    /// Panics if request_id is not set.
1141    pub fn build(self) -> RequestContext {
1142        let mut ctx = RequestContext::new(self.request_id.expect("request_id is required"));
1143        if let Some(token) = self.progress_token {
1144            ctx = ctx.with_progress_token(token);
1145        }
1146        if let Some(tx) = self.notification_tx {
1147            ctx = ctx.with_notification_sender(tx);
1148        }
1149        if let Some(requester) = self.client_requester {
1150            ctx = ctx.with_client_requester(requester);
1151        }
1152        if let Some(level) = self.min_log_level {
1153            ctx = ctx.with_min_log_level(level);
1154        }
1155        ctx
1156    }
1157}
1158
1159#[cfg(test)]
1160mod tests {
1161    use super::*;
1162
1163    #[test]
1164    fn test_cancellation() {
1165        let ctx = RequestContext::new(RequestId::Number(1));
1166        assert!(!ctx.is_cancelled());
1167
1168        let token = ctx.cancellation_token();
1169        assert!(!token.is_cancelled());
1170
1171        ctx.cancel();
1172        assert!(ctx.is_cancelled());
1173        assert!(token.is_cancelled());
1174    }
1175
1176    #[tokio::test]
1177    async fn test_progress_reporting() {
1178        let (tx, mut rx) = notification_channel(10);
1179
1180        let ctx = RequestContext::new(RequestId::Number(1))
1181            .with_progress_token(ProgressToken::Number(42))
1182            .with_notification_sender(tx);
1183
1184        ctx.report_progress(50.0, Some(100.0), Some("Halfway"))
1185            .await;
1186
1187        let notification = rx.recv().await.unwrap();
1188        match notification {
1189            ServerNotification::Progress(params) => {
1190                assert_eq!(params.progress, 50.0);
1191                assert_eq!(params.total, Some(100.0));
1192                assert_eq!(params.message.as_deref(), Some("Halfway"));
1193            }
1194            _ => panic!("Expected Progress notification"),
1195        }
1196    }
1197
1198    #[tokio::test]
1199    async fn test_progress_no_token() {
1200        let (tx, mut rx) = notification_channel(10);
1201
1202        // No progress token - should be a no-op
1203        let ctx = RequestContext::new(RequestId::Number(1)).with_notification_sender(tx);
1204
1205        ctx.report_progress(50.0, Some(100.0), None).await;
1206
1207        // Channel should be empty
1208        assert!(rx.try_recv().is_err());
1209    }
1210
1211    #[test]
1212    fn test_builder() {
1213        let (tx, _rx) = notification_channel(10);
1214
1215        let ctx = RequestContextBuilder::new()
1216            .request_id(RequestId::String("req-1".to_string()))
1217            .progress_token(ProgressToken::String("prog-1".to_string()))
1218            .notification_sender(tx)
1219            .build();
1220
1221        assert_eq!(ctx.request_id(), &RequestId::String("req-1".to_string()));
1222        assert!(ctx.progress_token().is_some());
1223    }
1224
1225    #[test]
1226    fn test_can_sample_without_requester() {
1227        let ctx = RequestContext::new(RequestId::Number(1));
1228        assert!(!ctx.can_sample());
1229    }
1230
1231    #[test]
1232    fn test_can_sample_with_requester() {
1233        let (request_tx, _rx) = outgoing_request_channel(10);
1234        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(request_tx));
1235
1236        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1237        assert!(ctx.can_sample());
1238    }
1239
1240    #[tokio::test]
1241    async fn test_sample_without_requester_fails() {
1242        use crate::protocol::{CreateMessageParams, SamplingMessage};
1243
1244        let ctx = RequestContext::new(RequestId::Number(1));
1245        let params = CreateMessageParams::new(vec![SamplingMessage::user("test")], 100);
1246
1247        let result = ctx.sample(params).await;
1248        assert!(result.is_err());
1249        assert!(
1250            result
1251                .unwrap_err()
1252                .to_string()
1253                .contains("Sampling not available")
1254        );
1255    }
1256
1257    #[test]
1258    fn test_builder_with_client_requester() {
1259        let (request_tx, _rx) = outgoing_request_channel(10);
1260        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(request_tx));
1261
1262        let ctx = RequestContextBuilder::new()
1263            .request_id(RequestId::Number(1))
1264            .client_requester(requester)
1265            .build();
1266
1267        assert!(ctx.can_sample());
1268    }
1269
1270    #[test]
1271    fn test_can_elicit_without_requester() {
1272        let ctx = RequestContext::new(RequestId::Number(1));
1273        assert!(!ctx.can_elicit());
1274    }
1275
1276    #[test]
1277    fn test_can_elicit_with_requester() {
1278        let (request_tx, _rx) = outgoing_request_channel(10);
1279        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(request_tx));
1280
1281        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1282        assert!(ctx.can_elicit());
1283    }
1284
1285    #[tokio::test]
1286    async fn test_elicit_form_without_requester_fails() {
1287        use crate::protocol::{ElicitFormSchema, ElicitMode};
1288
1289        let ctx = RequestContext::new(RequestId::Number(1));
1290        let params = ElicitFormParams {
1291            mode: Some(ElicitMode::Form),
1292            message: "Enter details".to_string(),
1293            requested_schema: ElicitFormSchema::new().string_field("name", None, true),
1294            meta: None,
1295        };
1296
1297        let result = ctx.elicit_form(params).await;
1298        assert!(result.is_err());
1299        assert!(
1300            result
1301                .unwrap_err()
1302                .to_string()
1303                .contains("Elicitation not available")
1304        );
1305    }
1306
1307    #[tokio::test]
1308    async fn test_elicit_url_without_requester_fails() {
1309        use crate::protocol::ElicitMode;
1310
1311        let ctx = RequestContext::new(RequestId::Number(1));
1312        let params = ElicitUrlParams {
1313            mode: Some(ElicitMode::Url),
1314            elicitation_id: "test-123".to_string(),
1315            message: "Please authorize".to_string(),
1316            url: "https://example.com/auth".to_string(),
1317            meta: None,
1318        };
1319
1320        let result = ctx.elicit_url(params).await;
1321        assert!(result.is_err());
1322        assert!(
1323            result
1324                .unwrap_err()
1325                .to_string()
1326                .contains("Elicitation not available")
1327        );
1328    }
1329
1330    #[tokio::test]
1331    async fn test_confirm_without_requester_fails() {
1332        let ctx = RequestContext::new(RequestId::Number(1));
1333
1334        let result = ctx.confirm("Are you sure?").await;
1335        assert!(result.is_err());
1336        assert!(
1337            result
1338                .unwrap_err()
1339                .to_string()
1340                .contains("Elicitation not available")
1341        );
1342    }
1343
1344    #[tokio::test]
1345    async fn test_send_log_filtered_by_level() {
1346        let (tx, mut rx) = notification_channel(10);
1347        let min_level = Arc::new(RwLock::new(LogLevel::Warning));
1348
1349        let ctx = RequestContext::new(RequestId::Number(1))
1350            .with_notification_sender(tx)
1351            .with_min_log_level(min_level.clone());
1352
1353        // Error is more severe than Warning — should pass through
1354        ctx.send_log(LoggingMessageParams::new(
1355            LogLevel::Error,
1356            serde_json::Value::Null,
1357        ));
1358        let msg = rx.try_recv();
1359        assert!(msg.is_ok(), "Error should pass through Warning filter");
1360
1361        // Warning is equal to min level — should pass through
1362        ctx.send_log(LoggingMessageParams::new(
1363            LogLevel::Warning,
1364            serde_json::Value::Null,
1365        ));
1366        let msg = rx.try_recv();
1367        assert!(msg.is_ok(), "Warning should pass through Warning filter");
1368
1369        // Info is less severe than Warning — should be filtered
1370        ctx.send_log(LoggingMessageParams::new(
1371            LogLevel::Info,
1372            serde_json::Value::Null,
1373        ));
1374        let msg = rx.try_recv();
1375        assert!(msg.is_err(), "Info should be filtered by Warning filter");
1376
1377        // Debug is less severe than Warning — should be filtered
1378        ctx.send_log(LoggingMessageParams::new(
1379            LogLevel::Debug,
1380            serde_json::Value::Null,
1381        ));
1382        let msg = rx.try_recv();
1383        assert!(msg.is_err(), "Debug should be filtered by Warning filter");
1384    }
1385
1386    #[tokio::test]
1387    async fn test_send_log_level_updates_dynamically() {
1388        let (tx, mut rx) = notification_channel(10);
1389        let min_level = Arc::new(RwLock::new(LogLevel::Error));
1390
1391        let ctx = RequestContext::new(RequestId::Number(1))
1392            .with_notification_sender(tx)
1393            .with_min_log_level(min_level.clone());
1394
1395        // Info should be filtered at Error level
1396        ctx.send_log(LoggingMessageParams::new(
1397            LogLevel::Info,
1398            serde_json::Value::Null,
1399        ));
1400        assert!(
1401            rx.try_recv().is_err(),
1402            "Info should be filtered at Error level"
1403        );
1404
1405        // Dynamically update to Debug (most permissive)
1406        *min_level.write().unwrap() = LogLevel::Debug;
1407
1408        // Now Info should pass through
1409        ctx.send_log(LoggingMessageParams::new(
1410            LogLevel::Info,
1411            serde_json::Value::Null,
1412        ));
1413        assert!(
1414            rx.try_recv().is_ok(),
1415            "Info should pass through after level changed to Debug"
1416        );
1417    }
1418
1419    #[tokio::test]
1420    async fn test_send_log_no_min_level_sends_all() {
1421        let (tx, mut rx) = notification_channel(10);
1422
1423        // No min_log_level set — all messages should pass through
1424        let ctx = RequestContext::new(RequestId::Number(1)).with_notification_sender(tx);
1425
1426        ctx.send_log(LoggingMessageParams::new(
1427            LogLevel::Debug,
1428            serde_json::Value::Null,
1429        ));
1430        assert!(
1431            rx.try_recv().is_ok(),
1432            "Debug should pass when no min level is set"
1433        );
1434    }
1435
1436    #[tokio::test]
1437    #[cfg(feature = "stateless")]
1438    async fn final_request_log_level_is_required_and_filters_per_request() {
1439        let (tx, mut rx) = notification_channel(10);
1440        let mut extensions = Extensions::new();
1441        extensions.insert(crate::stateless::StatelessRequestMeta {
1442            protocol_version: Some(crate::protocol::PROTOCOL_VERSION_2026_07_28.to_string()),
1443            client_capabilities: Some(Default::default()),
1444            ..Default::default()
1445        });
1446        let ctx = RequestContext::new(RequestId::Number(1))
1447            .with_notification_sender(tx.clone())
1448            .with_extensions(Arc::new(extensions));
1449        ctx.send_log(LoggingMessageParams::new(
1450            LogLevel::Emergency,
1451            serde_json::Value::Null,
1452        ));
1453        assert!(
1454            rx.try_recv().is_err(),
1455            "final requests without logLevel must receive no logs"
1456        );
1457
1458        let mut extensions = Extensions::new();
1459        extensions.insert(crate::stateless::StatelessRequestMeta {
1460            protocol_version: Some(crate::protocol::PROTOCOL_VERSION_2026_07_28.to_string()),
1461            client_capabilities: Some(Default::default()),
1462            log_level: Some(crate::stateless::LogLevel::Warning),
1463            ..Default::default()
1464        });
1465        let ctx = RequestContext::new(RequestId::Number(2))
1466            .with_notification_sender(tx)
1467            .with_extensions(Arc::new(extensions));
1468        ctx.send_log(LoggingMessageParams::new(
1469            LogLevel::Info,
1470            serde_json::Value::Null,
1471        ));
1472        assert!(rx.try_recv().is_err(), "Info must be filtered at Warning");
1473        ctx.send_log(LoggingMessageParams::new(
1474            LogLevel::Error,
1475            serde_json::Value::Null,
1476        ));
1477        assert!(rx.try_recv().is_ok(), "Error must pass at Warning");
1478    }
1479
1480    fn make_task_object(id: &str, status: TaskStatus) -> serde_json::Value {
1481        serde_json::json!({
1482            "taskId": id,
1483            "status": status,
1484            "createdAt": "2026-04-24T00:00:00Z",
1485            "lastUpdatedAt": "2026-04-24T00:00:00Z",
1486            "ttl": null
1487        })
1488    }
1489
1490    fn spawn_mock_client(
1491        mut rx: OutgoingRequestReceiver,
1492        responder: impl Fn(&str, serde_json::Value) -> serde_json::Value + Send + 'static,
1493    ) {
1494        tokio::spawn(async move {
1495            while let Some(req) = rx.recv().await {
1496                let response = responder(&req.method, req.params);
1497                let _ = req.response_tx.send(Ok(response));
1498            }
1499        });
1500    }
1501
1502    #[tokio::test]
1503    async fn test_get_task_info_round_trips() {
1504        let (tx, rx) = outgoing_request_channel(10);
1505        spawn_mock_client(rx, |method, params| {
1506            assert_eq!(method, "tasks/get");
1507            let task_id = params["taskId"].as_str().unwrap().to_string();
1508            make_task_object(&task_id, TaskStatus::Working)
1509        });
1510        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(tx));
1511        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1512
1513        let info = ctx.get_task_info("task-123").await.unwrap();
1514        assert_eq!(info.task_id, "task-123");
1515        assert!(matches!(info.status, TaskStatus::Working));
1516    }
1517
1518    #[tokio::test]
1519    #[allow(deprecated)] // exercises the legacy SEP-1686 helper
1520    async fn test_list_tasks_round_trips() {
1521        let (tx, rx) = outgoing_request_channel(10);
1522        spawn_mock_client(rx, |method, params| {
1523            assert_eq!(method, "tasks/list");
1524            // Status filter should be forwarded
1525            assert_eq!(params["status"], serde_json::json!("working"));
1526            serde_json::json!({
1527                "tasks": [
1528                    make_task_object("task-1", TaskStatus::Working),
1529                    make_task_object("task-2", TaskStatus::Working),
1530                ]
1531            })
1532        });
1533        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(tx));
1534        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1535
1536        let result = ctx.list_tasks(Some(TaskStatus::Working)).await.unwrap();
1537        assert_eq!(result.tasks.len(), 2);
1538        assert_eq!(result.tasks[0].task_id, "task-1");
1539    }
1540
1541    #[tokio::test]
1542    async fn test_cancel_task_forwards_reason() {
1543        let (tx, rx) = outgoing_request_channel(10);
1544        spawn_mock_client(rx, |method, params| {
1545            assert_eq!(method, "tasks/cancel");
1546            assert_eq!(params["reason"], serde_json::json!("user requested"));
1547            // SEP-2663 (final): the cancel acknowledgment is an empty result.
1548            serde_json::json!({})
1549        });
1550        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(tx));
1551        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1552
1553        ctx.cancel_task("task-99", Some("user requested".into()))
1554            .await
1555            .expect("empty ack should succeed");
1556    }
1557
1558    #[tokio::test]
1559    async fn test_cancel_task_tolerates_legacy_task_object_ack() {
1560        // Legacy SEP-1686 peers return the task object from tasks/cancel;
1561        // the helper discards the body either way.
1562        let (tx, rx) = outgoing_request_channel(10);
1563        spawn_mock_client(rx, |method, _params| {
1564            assert_eq!(method, "tasks/cancel");
1565            make_task_object("task-99", TaskStatus::Cancelled)
1566        });
1567        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(tx));
1568        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1569
1570        ctx.cancel_task("task-99", None)
1571            .await
1572            .expect("legacy task-object ack should also succeed");
1573    }
1574
1575    #[tokio::test]
1576    async fn test_get_task_info_without_requester_fails() {
1577        let ctx = RequestContext::new(RequestId::Number(1));
1578        let result = ctx.get_task_info("task-1").await;
1579        assert!(result.is_err());
1580        assert!(
1581            result
1582                .unwrap_err()
1583                .to_string()
1584                .contains("Client request not available")
1585        );
1586    }
1587
1588    #[tokio::test]
1589    async fn test_default_request_impl_errors() {
1590        // A custom requester that only implements sample/elicit (not request)
1591        // should reject task helpers.
1592        struct OnlySampleAndElicit;
1593
1594        #[async_trait]
1595        impl ClientRequester for OnlySampleAndElicit {
1596            async fn sample(&self, _: CreateMessageParams) -> Result<CreateMessageResult> {
1597                unreachable!()
1598            }
1599            async fn elicit(&self, _: ElicitRequestParams) -> Result<ElicitResult> {
1600                unreachable!()
1601            }
1602        }
1603
1604        let requester: ClientRequesterHandle = Arc::new(OnlySampleAndElicit);
1605        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1606
1607        let err = ctx.get_task_info("x").await.unwrap_err();
1608        assert!(err.to_string().contains("does not support arbitrary"));
1609    }
1610}