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    /// Whether this request arrived on the 2026-07-28 lifecycle, where the
346    /// server never initiates JSON-RPC requests. Distinguishes "the protocol
347    /// has no route for this" from "a transport did not wire one up", which
348    /// are the same absent requester but very different problems (#1201).
349    final_lifecycle: bool,
350}
351
352/// Type-erased extensions map for passing data to handlers.
353///
354/// Extensions allow router-level state and middleware-injected data to flow
355/// to tool handlers via the `Extension<T>` extractor.
356#[derive(Clone, Default)]
357pub struct Extensions {
358    map: std::collections::HashMap<std::any::TypeId, Arc<dyn std::any::Any + Send + Sync>>,
359}
360
361impl Extensions {
362    /// Create an empty extensions map.
363    pub fn new() -> Self {
364        Self::default()
365    }
366
367    /// Insert a value into the extensions map.
368    ///
369    /// If a value of the same type already exists, it is replaced.
370    pub fn insert<T: Send + Sync + 'static>(&mut self, val: T) {
371        self.map.insert(std::any::TypeId::of::<T>(), Arc::new(val));
372    }
373
374    /// Get a reference to a value in the extensions map.
375    ///
376    /// Returns `None` if no value of the given type has been inserted.
377    pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
378        self.map
379            .get(&std::any::TypeId::of::<T>())
380            .and_then(|val| val.downcast_ref::<T>())
381    }
382
383    /// Check if the extensions map contains a value of the given type.
384    pub fn contains<T: Send + Sync + 'static>(&self) -> bool {
385        self.map.contains_key(&std::any::TypeId::of::<T>())
386    }
387
388    /// Merge another extensions map into this one.
389    ///
390    /// Values from `other` will overwrite existing values of the same type.
391    pub fn merge(&mut self, other: &Extensions) {
392        for (k, v) in &other.map {
393            self.map.insert(*k, v.clone());
394        }
395    }
396
397    /// Returns the number of entries in the extensions map.
398    pub fn len(&self) -> usize {
399        self.map.len()
400    }
401
402    /// Returns `true` if the extensions map contains no entries.
403    pub fn is_empty(&self) -> bool {
404        self.map.is_empty()
405    }
406}
407
408impl std::fmt::Debug for Extensions {
409    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410        f.debug_struct("Extensions")
411            .field("len", &self.map.len())
412            .finish()
413    }
414}
415
416impl std::fmt::Debug for RequestContext {
417    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418        f.debug_struct("RequestContext")
419            .field("request_id", &self.request_id)
420            .field("progress_token", &self.progress_token)
421            .field("cancelled", &self.cancellation.is_cancelled())
422            .finish()
423    }
424}
425
426impl RequestContext {
427    /// Create a new request context
428    pub fn new(request_id: RequestId) -> Self {
429        Self {
430            request_id,
431            progress_token: None,
432            cancellation: tokio_util::sync::CancellationToken::new(),
433            notification_tx: None,
434            client_requester: None,
435            final_lifecycle: false,
436            extensions: Arc::new(Extensions::new()),
437            min_log_level: None,
438        }
439    }
440
441    /// Set the progress token
442    pub fn with_progress_token(mut self, token: ProgressToken) -> Self {
443        self.progress_token = Some(token);
444        self
445    }
446
447    /// Set the notification sender
448    pub fn with_notification_sender(mut self, tx: NotificationSender) -> Self {
449        self.notification_tx = Some(tx);
450        self
451    }
452
453    /// Set the minimum log level for filtering outgoing log notifications
454    ///
455    /// This is shared with the router so that `logging/setLevel` updates
456    /// are immediately visible to all request contexts.
457    pub fn with_min_log_level(mut self, level: Arc<RwLock<LogLevel>>) -> Self {
458        self.min_log_level = Some(level);
459        self
460    }
461
462    /// Mark this context as serving a 2026-07-28 request.
463    ///
464    /// Only affects diagnostics: the final lifecycle has no server-initiated
465    /// requests, so the router does not attach a requester, and this lets the
466    /// resulting error say why rather than blaming configuration.
467    pub(crate) fn with_final_lifecycle(mut self, final_lifecycle: bool) -> Self {
468        self.final_lifecycle = final_lifecycle;
469        self
470    }
471
472    /// The error for a server-initiated request that has no route to the client.
473    fn no_requester(&self, what: &str, replacement: &str) -> Error {
474        if self.final_lifecycle {
475            Error::Internal(format!(
476                "{what} is not available on the 2026-07-28 lifecycle: servers do not \
477                 initiate JSON-RPC requests. Return {replacement} from the handler \
478                 instead, so the client fulfils the request and retries (SEP-2322 \
479                 Multi Round-Trip Requests)."
480            ))
481        } else {
482            Error::Internal(format!(
483                "{what} is not available: no client requester is configured. The \
484                 transport must provide one; stdio, HTTP, WebSocket, and the \
485                 in-process channel transport all do."
486            ))
487        }
488    }
489
490    /// Set the client requester for server-to-client requests.
491    ///
492    /// Only the 2025-11-25 lifecycle uses this: the router does not attach a
493    /// requester to a 2026-07-28 request, because that protocol has no
494    /// server-initiated JSON-RPC requests.
495    pub fn with_client_requester(mut self, requester: ClientRequesterHandle) -> Self {
496        self.client_requester = Some(requester);
497        self
498    }
499
500    /// Set the extensions for this request context.
501    ///
502    /// Extensions allow router-level state and middleware data to flow to handlers.
503    pub fn with_extensions(mut self, extensions: Arc<Extensions>) -> Self {
504        self.extensions = extensions;
505        self
506    }
507
508    /// Get a reference to a value from the extensions map.
509    ///
510    /// Returns `None` if no value of the given type has been inserted.
511    ///
512    /// # Example
513    ///
514    /// ```rust,ignore
515    /// #[derive(Clone)]
516    /// struct CurrentUser { id: String }
517    ///
518    /// // In a handler:
519    /// if let Some(user) = ctx.extension::<CurrentUser>() {
520    ///     println!("User: {}", user.id);
521    /// }
522    /// ```
523    pub fn extension<T: Send + Sync + 'static>(&self) -> Option<&T> {
524        self.extensions.get::<T>()
525    }
526
527    /// Protocol extensions declared by both the client and server.
528    ///
529    /// Unknown or one-sided declarations are not included. The returned view
530    /// preserves each peer's settings object for extension-specific policy.
531    pub fn negotiated_extensions(&self) -> Option<&crate::NegotiatedExtensions> {
532        self.extension()
533    }
534
535    /// Get a mutable reference to the extensions.
536    ///
537    /// This allows middleware to insert data that handlers can access via
538    /// the `Extension<T>` extractor.
539    pub fn extensions_mut(&mut self) -> &mut Extensions {
540        Arc::make_mut(&mut self.extensions)
541    }
542
543    /// Get a reference to the extensions.
544    pub fn extensions(&self) -> &Extensions {
545        &self.extensions
546    }
547
548    /// SEP-2575 per-request `_meta` (protocol version, client info, client
549    /// capabilities, log level) if the transport extracted it.
550    ///
551    /// Returns `Some` for 2026-07-28 clients on JSON-RPC transports when the
552    /// request carried a `_meta` object with recognized fields. Returns `None`
553    /// when:
554    /// - The request had no `_meta` field, or
555    /// - The transport does not use [`crate::jsonrpc::JsonRpcService`], or
556    /// - The `stateless` feature is not compiled in.
557    ///
558    /// # Example
559    ///
560    /// ```rust,ignore
561    /// async fn my_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
562    ///     if let Some(meta) = ctx.per_request_meta() {
563    ///         // protocol_version, client_info, client_capabilities are all Option<_>
564    ///         if let Some(ref version) = meta.protocol_version {
565    ///             tracing::debug!(protocol_version = %version);
566    ///         }
567    ///         if let Some(ref info) = meta.client_info {
568    ///             tracing::info!(client = %info.name, version = %info.version);
569    ///         }
570    ///     }
571    ///     Ok(CallToolResult::text("ok"))
572    /// }
573    /// ```
574    #[cfg(feature = "stateless")]
575    pub fn per_request_meta(&self) -> Option<&crate::stateless::StatelessRequestMeta> {
576        self.extension::<crate::stateless::StatelessRequestMeta>()
577    }
578
579    /// SEP-2322 continuation values supplied by the client on this attempt.
580    #[cfg(feature = "stateless")]
581    pub fn mrtr(&self) -> Option<&crate::mrtr::MrtrRequest> {
582        self.extension::<crate::mrtr::MrtrRequest>()
583    }
584
585    /// Client responses from the prior MRTR round, if any.
586    #[cfg(feature = "stateless")]
587    pub fn input_responses(&self) -> Option<&crate::protocol::InputResponses> {
588        self.mrtr()
589            .and_then(crate::mrtr::MrtrRequest::input_responses)
590    }
591
592    /// Opaque request state echoed by the client, if any.
593    #[cfg(feature = "stateless")]
594    pub fn request_state(&self) -> Option<&str> {
595        self.mrtr()
596            .and_then(crate::mrtr::MrtrRequest::request_state)
597    }
598
599    /// Router-configured request-state codec shared by this handler.
600    #[cfg(feature = "stateless")]
601    pub fn request_state_codec(&self) -> Option<&crate::mrtr::RequestStateCodec> {
602        self.extension::<crate::mrtr::RequestStateCodec>()
603    }
604
605    /// Get the request ID
606    pub fn request_id(&self) -> &RequestId {
607        &self.request_id
608    }
609
610    /// Get the progress token (if any)
611    pub fn progress_token(&self) -> Option<&ProgressToken> {
612        self.progress_token.as_ref()
613    }
614
615    /// Check if the request has been cancelled
616    pub fn is_cancelled(&self) -> bool {
617        self.cancellation.is_cancelled()
618    }
619
620    /// Mark the request as cancelled
621    pub fn cancel(&self) {
622        self.cancellation.cancel();
623    }
624
625    /// Wait until the request is cancelled.
626    ///
627    /// Completes when [`cancel`](Self::cancel) is called -- by a
628    /// `notifications/cancelled` message, or by the transport when the
629    /// client disconnects before the response is delivered (HTTP
630    /// stateless mode). Useful in `tokio::select!` to abandon work early:
631    ///
632    /// ```rust,ignore
633    /// tokio::select! {
634    ///     result = do_work() => { /* ... */ }
635    ///     _ = ctx.cancelled() => return Err(Error::tool("cancelled")),
636    /// }
637    /// ```
638    pub async fn cancelled(&self) {
639        self.cancellation.cancelled().await
640    }
641
642    /// Get a cancellation token that can be shared
643    pub fn cancellation_token(&self) -> CancellationToken {
644        CancellationToken {
645            inner: self.cancellation.clone(),
646        }
647    }
648
649    /// Replace this context's cancellation source with an existing token.
650    ///
651    /// Used by transports to link a request's lifetime to an external
652    /// signal (e.g. client disconnect on the HTTP stateless path). After
653    /// this call, `is_cancelled()`, `cancelled()`, and tokens returned by
654    /// [`cancellation_token`](Self::cancellation_token) all observe the
655    /// given token.
656    pub fn with_cancellation_token(mut self, token: CancellationToken) -> Self {
657        self.cancellation = token.inner;
658        self
659    }
660
661    /// Report progress to the client
662    ///
663    /// This is a no-op if no progress token was provided or no notification sender is configured.
664    pub async fn report_progress(&self, progress: f64, total: Option<f64>, message: Option<&str>) {
665        let Some(token) = &self.progress_token else {
666            return;
667        };
668        let Some(tx) = &self.notification_tx else {
669            return;
670        };
671
672        let params = ProgressParams {
673            progress_token: token.clone(),
674            progress,
675            total,
676            message: message.map(|s| s.to_string()),
677            meta: None,
678        };
679
680        // Best effort - don't block if channel is full
681        let _ = tx.try_send(ServerNotification::Progress(params));
682    }
683
684    /// Report progress synchronously (non-async version)
685    ///
686    /// This is a no-op if no progress token was provided or no notification sender is configured.
687    pub fn report_progress_sync(&self, progress: f64, total: Option<f64>, message: Option<&str>) {
688        let Some(token) = &self.progress_token else {
689            return;
690        };
691        let Some(tx) = &self.notification_tx else {
692            return;
693        };
694
695        let params = ProgressParams {
696            progress_token: token.clone(),
697            progress,
698            total,
699            message: message.map(|s| s.to_string()),
700            meta: None,
701        };
702
703        let _ = tx.try_send(ServerNotification::Progress(params));
704    }
705
706    /// Notify subscribed clients that the tool list changed.
707    pub fn notify_tools_list_changed(&self) -> bool {
708        self.notification_tx
709            .as_ref()
710            .is_some_and(|tx| tx.try_send(ServerNotification::ToolsListChanged).is_ok())
711    }
712
713    /// Notify subscribed clients that the prompt list changed.
714    pub fn notify_prompts_list_changed(&self) -> bool {
715        self.notification_tx
716            .as_ref()
717            .is_some_and(|tx| tx.try_send(ServerNotification::PromptsListChanged).is_ok())
718    }
719
720    /// Notify subscribed clients that the resource list changed.
721    pub fn notify_resources_list_changed(&self) -> bool {
722        self.notification_tx.as_ref().is_some_and(|tx| {
723            tx.try_send(ServerNotification::ResourcesListChanged)
724                .is_ok()
725        })
726    }
727
728    /// Notify subscribed clients that one resource changed.
729    pub fn notify_resource_updated(&self, uri: impl Into<String>) -> bool {
730        self.notification_tx.as_ref().is_some_and(|tx| {
731            tx.try_send(ServerNotification::ResourceUpdated { uri: uri.into() })
732                .is_ok()
733        })
734    }
735
736    /// Notify the client that a final-protocol task changed status.
737    ///
738    /// This is useful when a handler drives a task transition through a custom
739    /// [`crate::TaskStore`] path rather than through the router directly.
740    pub fn notify_task_status_changed(
741        &self,
742        params: crate::tasks::TaskStatusNotificationParams,
743    ) -> bool {
744        self.notification_tx.as_ref().is_some_and(|tx| {
745            tx.try_send(ServerNotification::FinalTaskStatusChanged(params))
746                .is_ok()
747        })
748    }
749
750    /// Send a log message notification to the client
751    ///
752    /// This is a no-op if no notification sender is configured.
753    ///
754    /// # Example
755    ///
756    /// ```rust,ignore
757    /// use tower_mcp::protocol::{LoggingMessageParams, LogLevel};
758    ///
759    /// async fn my_tool(ctx: RequestContext) {
760    ///     ctx.send_log(
761    ///         LoggingMessageParams::new(LogLevel::Info, serde_json::json!("Processing..."))
762    ///             .with_logger("my-tool")
763    ///     );
764    /// }
765    /// ```
766    pub fn send_log(&self, params: LoggingMessageParams) {
767        let Some(tx) = &self.notification_tx else {
768            return;
769        };
770
771        // The final protocol removed logging/setLevel. Log delivery is instead
772        // authorized per request: no logLevel means no log notifications.
773        #[cfg(feature = "stateless")]
774        if let Some(meta) = self.per_request_meta()
775            && meta.protocol_version.as_deref()
776                == Some(crate::protocol::PROTOCOL_VERSION_2026_07_28)
777        {
778            let Some(request_level) = meta.log_level else {
779                return;
780            };
781            let request_level = match request_level {
782                crate::stateless::LogLevel::Debug => LogLevel::Debug,
783                crate::stateless::LogLevel::Info => LogLevel::Info,
784                crate::stateless::LogLevel::Notice => LogLevel::Notice,
785                crate::stateless::LogLevel::Warning => LogLevel::Warning,
786                crate::stateless::LogLevel::Error => LogLevel::Error,
787                crate::stateless::LogLevel::Critical => LogLevel::Critical,
788                crate::stateless::LogLevel::Alert => LogLevel::Alert,
789                crate::stateless::LogLevel::Emergency => LogLevel::Emergency,
790            };
791            if params.level > request_level {
792                return;
793            }
794            let _ = tx.try_send(ServerNotification::LogMessage(params));
795            return;
796        }
797
798        // Filter by minimum log level set via logging/setLevel
799        // LogLevel derives Ord with Emergency < Alert < ... < Debug,
800        // so a message passes if its severity is at least the minimum
801        // (i.e., its ordinal is <= the minimum level's ordinal).
802        if let Some(min_level) = &self.min_log_level
803            && let Ok(min) = min_level.read()
804            && params.level > *min
805        {
806            return;
807        }
808
809        let _ = tx.try_send(ServerNotification::LogMessage(params));
810    }
811
812    /// Check if sampling is available
813    ///
814    /// Returns true if a client requester is configured and the transport
815    /// supports bidirectional communication.
816    pub fn can_sample(&self) -> bool {
817        self.client_requester.is_some()
818    }
819
820    /// Request an LLM completion from the client
821    ///
822    /// This sends a `sampling/createMessage` request to the client and waits
823    /// for the response. The client is expected to forward this to an LLM
824    /// and return the result.
825    ///
826    /// Returns an error if sampling is not available (no client requester configured).
827    ///
828    /// # Example
829    ///
830    /// ```rust,ignore
831    /// use tower_mcp::{CreateMessageParams, SamplingMessage};
832    ///
833    /// async fn my_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
834    ///     let params = CreateMessageParams::new(
835    ///         vec![SamplingMessage::user("Summarize: ...")],
836    ///         500,
837    ///     );
838    ///
839    ///     let result = ctx.sample(params).await?;
840    ///     Ok(CallToolResult::text(format!("{:?}", result.content)))
841    /// }
842    /// ```
843    /// # Protocol lifecycle
844    ///
845    /// This is a 2025-11-25 mechanism. The 2026-07-28 lifecycle has no
846    /// server-initiated JSON-RPC requests: `ElicitRequest` and
847    /// `CreateMessageRequest` survive only as members of `InputRequest`,
848    /// carried inside an [`InputRequiredResult`](crate::protocol::InputRequiredResult)
849    /// that the client fulfils and retries. Calling this on a 2026-07-28
850    /// request therefore fails; return
851    /// [`RequestOutcome::input_required`](crate::protocol::RequestOutcome::input_required)
852    /// from the handler instead (SEP-2322 Multi Round-Trip Requests).
853    ///
854    /// [`can_sample`](Self::can_sample) and [`can_elicit`](Self::can_elicit)
855    /// both report `false` on that lifecycle, so a handler serving both eras
856    /// can branch on them rather than on the protocol version.
857    ///
858    pub async fn sample(&self, params: CreateMessageParams) -> Result<CreateMessageResult> {
859        let requester = self.client_requester.as_ref().ok_or_else(|| {
860            self.no_requester(
861                "Sampling",
862                "`RequestOutcome::input_required` carrying an \
863                 `InputRequest::CreateMessage`",
864            )
865        })?;
866
867        requester.sample(params).await
868    }
869
870    /// Check if elicitation is available
871    ///
872    /// Returns true if a client requester is configured and the transport
873    /// supports bidirectional communication. Note that this only checks if
874    /// the mechanism is available, not whether the client supports elicitation.
875    pub fn can_elicit(&self) -> bool {
876        self.client_requester.is_some()
877    }
878
879    /// Request user input via a form from the client
880    ///
881    /// This sends an `elicitation/create` request to the client with a form schema.
882    /// The client renders the form to the user and returns their response.
883    ///
884    /// Returns an error if elicitation is not available (no client requester configured).
885    ///
886    /// # Example
887    ///
888    /// ```rust,ignore
889    /// use tower_mcp::{ElicitFormParams, ElicitFormSchema, ElicitMode, ElicitAction};
890    ///
891    /// async fn my_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
892    ///     let params = ElicitFormParams {
893    ///         mode: Some(ElicitMode::Form),
894    ///         message: "Please enter your details".to_string(),
895    ///         requested_schema: ElicitFormSchema::new()
896    ///             .string_field("name", Some("Your name"), true),
897    ///         meta: None,
898    ///     };
899    ///
900    ///     let result = ctx.elicit_form(params).await?;
901    ///     match result.action {
902    ///         ElicitAction::Accept => {
903    ///             // Use result.content
904    ///             Ok(CallToolResult::text("Got your input!"))
905    ///         }
906    ///         _ => Ok(CallToolResult::text("User declined"))
907    ///     }
908    /// }
909    /// ```
910    /// # Protocol lifecycle
911    ///
912    /// This is a 2025-11-25 mechanism. The 2026-07-28 lifecycle has no
913    /// server-initiated JSON-RPC requests: `ElicitRequest` and
914    /// `CreateMessageRequest` survive only as members of `InputRequest`,
915    /// carried inside an [`InputRequiredResult`](crate::protocol::InputRequiredResult)
916    /// that the client fulfils and retries. Calling this on a 2026-07-28
917    /// request therefore fails; return
918    /// [`RequestOutcome::input_required`](crate::protocol::RequestOutcome::input_required)
919    /// from the handler instead (SEP-2322 Multi Round-Trip Requests).
920    ///
921    /// [`can_sample`](Self::can_sample) and [`can_elicit`](Self::can_elicit)
922    /// both report `false` on that lifecycle, so a handler serving both eras
923    /// can branch on them rather than on the protocol version.
924    ///
925    pub async fn elicit_form(&self, params: ElicitFormParams) -> Result<ElicitResult> {
926        let requester = self.client_requester.as_ref().ok_or_else(|| {
927            self.no_requester(
928                "Elicitation",
929                "`RequestOutcome::input_required` carrying an `InputRequest::Elicit`",
930            )
931        })?;
932
933        requester.elicit(ElicitRequestParams::Form(params)).await
934    }
935
936    /// Request user input via URL redirect from the client
937    ///
938    /// This sends an `elicitation/create` request to the client with a URL.
939    /// The client directs the user to the URL for out-of-band input collection.
940    /// The server receives the result via a callback notification.
941    ///
942    /// Returns an error if elicitation is not available (no client requester configured).
943    ///
944    /// **Protocol note:** `ElicitUrlParams::elicitation_id` and the callback
945    /// notification it correlates with are a 2025-11-25-and-earlier pattern.
946    /// The final 2026-07-28 schema removes both in favor of MRTR (SEP-2322).
947    /// Use an MRTR-capable tool, prompt, or resource handler there; the client
948    /// learns the outcome by retrying the original request instead of receiving
949    /// a completion notification.
950    ///
951    /// # Example
952    ///
953    /// ```rust,ignore
954    /// use tower_mcp::{ElicitUrlParams, ElicitMode, ElicitAction};
955    ///
956    /// async fn my_tool(ctx: RequestContext, input: MyInput) -> Result<CallToolResult> {
957    ///     let params = ElicitUrlParams {
958    ///         mode: Some(ElicitMode::Url),
959    ///         elicitation_id: "unique-id-123".to_string(),
960    ///         message: "Please authorize via the link".to_string(),
961    ///         url: "https://example.com/auth?id=unique-id-123".to_string(),
962    ///         meta: None,
963    ///     };
964    ///
965    ///     let result = ctx.elicit_url(params).await?;
966    ///     match result.action {
967    ///         ElicitAction::Accept => Ok(CallToolResult::text("Authorization complete!")),
968    ///         _ => Ok(CallToolResult::text("Authorization cancelled"))
969    ///     }
970    /// }
971    /// ```
972    /// # Protocol lifecycle
973    ///
974    /// This is a 2025-11-25 mechanism. The 2026-07-28 lifecycle has no
975    /// server-initiated JSON-RPC requests: `ElicitRequest` and
976    /// `CreateMessageRequest` survive only as members of `InputRequest`,
977    /// carried inside an [`InputRequiredResult`](crate::protocol::InputRequiredResult)
978    /// that the client fulfils and retries. Calling this on a 2026-07-28
979    /// request therefore fails; return
980    /// [`RequestOutcome::input_required`](crate::protocol::RequestOutcome::input_required)
981    /// from the handler instead (SEP-2322 Multi Round-Trip Requests).
982    ///
983    /// [`can_sample`](Self::can_sample) and [`can_elicit`](Self::can_elicit)
984    /// both report `false` on that lifecycle, so a handler serving both eras
985    /// can branch on them rather than on the protocol version.
986    ///
987    pub async fn elicit_url(&self, params: ElicitUrlParams) -> Result<ElicitResult> {
988        let requester = self.client_requester.as_ref().ok_or_else(|| {
989            self.no_requester(
990                "Elicitation",
991                "`RequestOutcome::input_required` carrying an `InputRequest::Elicit`",
992            )
993        })?;
994
995        requester.elicit(ElicitRequestParams::Url(params)).await
996    }
997
998    /// Request simple confirmation from the user.
999    ///
1000    /// This is a convenience method for simple yes/no confirmation dialogs.
1001    /// It creates an elicitation form with a single boolean "confirm" field
1002    /// and returns `true` if the user accepts, `false` otherwise.
1003    ///
1004    /// Returns an error if elicitation is not available (no client requester configured).
1005    ///
1006    /// # Example
1007    ///
1008    /// ```rust,ignore
1009    /// use tower_mcp::{RequestContext, CallToolResult};
1010    ///
1011    /// async fn delete_item(ctx: RequestContext) -> Result<CallToolResult> {
1012    ///     let confirmed = ctx.confirm("Are you sure you want to delete this item?").await?;
1013    ///     if confirmed {
1014    ///         // Perform deletion
1015    ///         Ok(CallToolResult::text("Item deleted"))
1016    ///     } else {
1017    ///         Ok(CallToolResult::text("Deletion cancelled"))
1018    ///     }
1019    /// }
1020    /// ```
1021    pub async fn confirm(&self, message: impl Into<String>) -> Result<bool> {
1022        use crate::protocol::{ElicitAction, ElicitFormParams, ElicitFormSchema, ElicitMode};
1023
1024        let params = ElicitFormParams {
1025            mode: Some(ElicitMode::Form),
1026            message: message.into(),
1027            requested_schema: ElicitFormSchema::new().boolean_field_with_default(
1028                "confirm",
1029                Some("Confirm this action"),
1030                true,
1031                false,
1032            ),
1033            meta: None,
1034        };
1035
1036        let result = self.elicit_form(params).await?;
1037        Ok(result.action == ElicitAction::Accept)
1038    }
1039
1040    /// List tasks tracked by the connected client (legacy SEP-1686).
1041    ///
1042    /// Sends a `tasks/list` request to the client and returns the result.
1043    /// Pass `Some(status)` to filter to a single status, or `None` for all
1044    /// tasks. Pagination is exposed via [`ListTasksResult::next_cursor`];
1045    /// use [`request_raw`](Self::request_raw) for cursor-driven calls.
1046    ///
1047    /// Returns an error if no client requester is configured or the client
1048    /// does not advertise task support.
1049    #[deprecated(
1050        since = "0.13.0",
1051        note = "final SEP-2663 removes tasks/list; a conforming peer answers \
1052                MethodNotFound (-32601). Only useful against legacy SEP-1686 \
1053                clients."
1054    )]
1055    pub async fn list_tasks(&self, status: Option<TaskStatus>) -> Result<ListTasksResult> {
1056        let params = ListTasksParams {
1057            status,
1058            cursor: None,
1059            meta: None,
1060        };
1061        let value = self
1062            .request_raw("tasks/list", serde_json::to_value(&params)?)
1063            .await?;
1064        serde_json::from_value(value)
1065            .map_err(|e| Error::Internal(format!("Failed to deserialize tasks/list: {e}")))
1066    }
1067
1068    /// Fetch metadata for a single task tracked by the client (SEP-1686).
1069    ///
1070    /// Sends a `tasks/get` request and returns the task object, including
1071    /// the current status, timestamps, and TTL.
1072    pub async fn get_task_info(&self, task_id: impl Into<String>) -> Result<TaskObject> {
1073        let params = GetTaskInfoParams {
1074            task_id: task_id.into(),
1075            meta: None,
1076        };
1077        let value = self
1078            .request_raw("tasks/get", serde_json::to_value(&params)?)
1079            .await?;
1080        serde_json::from_value(value)
1081            .map_err(|e| Error::Internal(format!("Failed to deserialize tasks/get: {e}")))
1082    }
1083
1084    /// Fetch the terminal result for a task tracked by the client (legacy
1085    /// SEP-1686).
1086    ///
1087    /// Sends a `tasks/result` request. The client is expected to block until
1088    /// the task reaches a terminal state and then return the underlying
1089    /// `CallToolResult`. For long-running tasks, prefer polling with
1090    /// [`get_task_info`](Self::get_task_info) and only call this once the
1091    /// status is terminal.
1092    #[deprecated(
1093        since = "0.13.0",
1094        note = "final SEP-2663 removes tasks/result (results are inlined in \
1095                the tasks/get DetailedTask); a conforming peer answers \
1096                MethodNotFound (-32601). Only useful against legacy SEP-1686 \
1097                clients."
1098    )]
1099    pub async fn get_task_result(&self, task_id: impl Into<String>) -> Result<CallToolResult> {
1100        let params = GetTaskResultParams {
1101            task_id: task_id.into(),
1102            meta: None,
1103        };
1104        let value = self
1105            .request_raw("tasks/result", serde_json::to_value(&params)?)
1106            .await?;
1107        serde_json::from_value(value)
1108            .map_err(|e| Error::Internal(format!("Failed to deserialize tasks/result: {e}")))
1109    }
1110
1111    /// Cancel a task tracked by the client.
1112    ///
1113    /// Sends a `tasks/cancel` request. Per final SEP-2663 the acknowledgment
1114    /// is an empty result and the observable task status is polled via
1115    /// [`get_task_info`](Self::get_task_info); the ack body is discarded, so
1116    /// this also tolerates legacy SEP-1686 peers that return the task object.
1117    pub async fn cancel_task(
1118        &self,
1119        task_id: impl Into<String>,
1120        reason: Option<String>,
1121    ) -> Result<()> {
1122        let params = CancelTaskParams {
1123            task_id: task_id.into(),
1124            reason,
1125            meta: None,
1126        };
1127        self.request_raw("tasks/cancel", serde_json::to_value(&params)?)
1128            .await?;
1129        Ok(())
1130    }
1131
1132    /// Send an arbitrary JSON-RPC request to the client.
1133    ///
1134    /// Escape hatch for methods not covered by the typed helpers (e.g. when
1135    /// a `tasks/list` cursor needs to be passed). Most callers should prefer
1136    /// the typed methods.
1137    pub async fn request_raw(
1138        &self,
1139        method: &str,
1140        params: serde_json::Value,
1141    ) -> Result<serde_json::Value> {
1142        let requester = self.client_requester.as_ref().ok_or_else(|| {
1143            self.no_requester(
1144                "A server-initiated client request",
1145                "`RequestOutcome::input_required`",
1146            )
1147        })?;
1148        requester.request(method.to_string(), params).await
1149    }
1150}
1151
1152/// A token that can be used to check for, wait on, or request cancellation
1153///
1154/// Cloned tokens share the same underlying signal: cancelling any clone
1155/// cancels them all. Backed by [`tokio_util::sync::CancellationToken`],
1156/// so cancellation can also be awaited via [`cancelled`](Self::cancelled).
1157#[derive(Clone, Debug, Default)]
1158pub struct CancellationToken {
1159    inner: tokio_util::sync::CancellationToken,
1160}
1161
1162impl CancellationToken {
1163    /// Create a new, un-cancelled token
1164    pub fn new() -> Self {
1165        Self::default()
1166    }
1167
1168    /// Check if cancellation has been requested
1169    pub fn is_cancelled(&self) -> bool {
1170        self.inner.is_cancelled()
1171    }
1172
1173    /// Request cancellation
1174    pub fn cancel(&self) {
1175        self.inner.cancel();
1176    }
1177
1178    /// Wait until cancellation is requested
1179    ///
1180    /// Completes immediately if the token is already cancelled.
1181    pub async fn cancelled(&self) {
1182        self.inner.cancelled().await
1183    }
1184}
1185
1186/// Builder for creating request contexts
1187#[derive(Default)]
1188pub struct RequestContextBuilder {
1189    request_id: Option<RequestId>,
1190    progress_token: Option<ProgressToken>,
1191    notification_tx: Option<NotificationSender>,
1192    client_requester: Option<ClientRequesterHandle>,
1193    min_log_level: Option<Arc<RwLock<LogLevel>>>,
1194}
1195
1196impl RequestContextBuilder {
1197    /// Create a new builder
1198    pub fn new() -> Self {
1199        Self::default()
1200    }
1201
1202    /// Set the request ID
1203    pub fn request_id(mut self, id: RequestId) -> Self {
1204        self.request_id = Some(id);
1205        self
1206    }
1207
1208    /// Set the progress token
1209    pub fn progress_token(mut self, token: ProgressToken) -> Self {
1210        self.progress_token = Some(token);
1211        self
1212    }
1213
1214    /// Set the notification sender
1215    pub fn notification_sender(mut self, tx: NotificationSender) -> Self {
1216        self.notification_tx = Some(tx);
1217        self
1218    }
1219
1220    /// Set the client requester for server-to-client requests
1221    pub fn client_requester(mut self, requester: ClientRequesterHandle) -> Self {
1222        self.client_requester = Some(requester);
1223        self
1224    }
1225
1226    /// Set the minimum log level for filtering
1227    pub fn min_log_level(mut self, level: Arc<RwLock<LogLevel>>) -> Self {
1228        self.min_log_level = Some(level);
1229        self
1230    }
1231
1232    /// Build the request context
1233    ///
1234    /// Panics if request_id is not set.
1235    pub fn build(self) -> RequestContext {
1236        let mut ctx = RequestContext::new(self.request_id.expect("request_id is required"));
1237        if let Some(token) = self.progress_token {
1238            ctx = ctx.with_progress_token(token);
1239        }
1240        if let Some(tx) = self.notification_tx {
1241            ctx = ctx.with_notification_sender(tx);
1242        }
1243        if let Some(requester) = self.client_requester {
1244            ctx = ctx.with_client_requester(requester);
1245        }
1246        if let Some(level) = self.min_log_level {
1247            ctx = ctx.with_min_log_level(level);
1248        }
1249        ctx
1250    }
1251}
1252
1253#[cfg(test)]
1254mod tests {
1255    use super::*;
1256
1257    #[test]
1258    fn test_cancellation() {
1259        let ctx = RequestContext::new(RequestId::Number(1));
1260        assert!(!ctx.is_cancelled());
1261
1262        let token = ctx.cancellation_token();
1263        assert!(!token.is_cancelled());
1264
1265        ctx.cancel();
1266        assert!(ctx.is_cancelled());
1267        assert!(token.is_cancelled());
1268    }
1269
1270    #[tokio::test]
1271    async fn test_progress_reporting() {
1272        let (tx, mut rx) = notification_channel(10);
1273
1274        let ctx = RequestContext::new(RequestId::Number(1))
1275            .with_progress_token(ProgressToken::Number(42))
1276            .with_notification_sender(tx);
1277
1278        ctx.report_progress(50.0, Some(100.0), Some("Halfway"))
1279            .await;
1280
1281        let notification = rx.recv().await.unwrap();
1282        match notification {
1283            ServerNotification::Progress(params) => {
1284                assert_eq!(params.progress, 50.0);
1285                assert_eq!(params.total, Some(100.0));
1286                assert_eq!(params.message.as_deref(), Some("Halfway"));
1287            }
1288            _ => panic!("Expected Progress notification"),
1289        }
1290    }
1291
1292    #[tokio::test]
1293    async fn test_progress_no_token() {
1294        let (tx, mut rx) = notification_channel(10);
1295
1296        // No progress token - should be a no-op
1297        let ctx = RequestContext::new(RequestId::Number(1)).with_notification_sender(tx);
1298
1299        ctx.report_progress(50.0, Some(100.0), None).await;
1300
1301        // Channel should be empty
1302        assert!(rx.try_recv().is_err());
1303    }
1304
1305    #[test]
1306    fn test_builder() {
1307        let (tx, _rx) = notification_channel(10);
1308
1309        let ctx = RequestContextBuilder::new()
1310            .request_id(RequestId::String("req-1".to_string()))
1311            .progress_token(ProgressToken::String("prog-1".to_string()))
1312            .notification_sender(tx)
1313            .build();
1314
1315        assert_eq!(ctx.request_id(), &RequestId::String("req-1".to_string()));
1316        assert!(ctx.progress_token().is_some());
1317    }
1318
1319    #[test]
1320    fn test_can_sample_without_requester() {
1321        let ctx = RequestContext::new(RequestId::Number(1));
1322        assert!(!ctx.can_sample());
1323    }
1324
1325    #[test]
1326    fn test_can_sample_with_requester() {
1327        let (request_tx, _rx) = outgoing_request_channel(10);
1328        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(request_tx));
1329
1330        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1331        assert!(ctx.can_sample());
1332    }
1333
1334    #[tokio::test]
1335    async fn test_sample_without_requester_fails() {
1336        use crate::protocol::{CreateMessageParams, SamplingMessage};
1337
1338        let ctx = RequestContext::new(RequestId::Number(1));
1339        let params = CreateMessageParams::new(vec![SamplingMessage::user("test")], 100);
1340
1341        let result = ctx.sample(params).await;
1342        assert!(result.is_err());
1343        assert!(
1344            result
1345                .unwrap_err()
1346                .to_string()
1347                .contains("Sampling is not available: no client requester is configured")
1348        );
1349    }
1350
1351    #[test]
1352    fn test_builder_with_client_requester() {
1353        let (request_tx, _rx) = outgoing_request_channel(10);
1354        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(request_tx));
1355
1356        let ctx = RequestContextBuilder::new()
1357            .request_id(RequestId::Number(1))
1358            .client_requester(requester)
1359            .build();
1360
1361        assert!(ctx.can_sample());
1362    }
1363
1364    #[test]
1365    fn test_can_elicit_without_requester() {
1366        let ctx = RequestContext::new(RequestId::Number(1));
1367        assert!(!ctx.can_elicit());
1368    }
1369
1370    #[test]
1371    fn test_can_elicit_with_requester() {
1372        let (request_tx, _rx) = outgoing_request_channel(10);
1373        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(request_tx));
1374
1375        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1376        assert!(ctx.can_elicit());
1377    }
1378
1379    #[tokio::test]
1380    async fn test_elicit_form_without_requester_fails() {
1381        use crate::protocol::{ElicitFormSchema, ElicitMode};
1382
1383        let ctx = RequestContext::new(RequestId::Number(1));
1384        let params = ElicitFormParams {
1385            mode: Some(ElicitMode::Form),
1386            message: "Enter details".to_string(),
1387            requested_schema: ElicitFormSchema::new().string_field("name", None, true),
1388            meta: None,
1389        };
1390
1391        let result = ctx.elicit_form(params).await;
1392        assert!(result.is_err());
1393        assert!(
1394            result
1395                .unwrap_err()
1396                .to_string()
1397                .contains("Elicitation is not available: no client requester is configured")
1398        );
1399    }
1400
1401    #[tokio::test]
1402    async fn test_elicit_url_without_requester_fails() {
1403        use crate::protocol::ElicitMode;
1404
1405        let ctx = RequestContext::new(RequestId::Number(1));
1406        let params = ElicitUrlParams {
1407            mode: Some(ElicitMode::Url),
1408            elicitation_id: "test-123".to_string(),
1409            message: "Please authorize".to_string(),
1410            url: "https://example.com/auth".to_string(),
1411            meta: None,
1412        };
1413
1414        let result = ctx.elicit_url(params).await;
1415        assert!(result.is_err());
1416        assert!(
1417            result
1418                .unwrap_err()
1419                .to_string()
1420                .contains("Elicitation is not available: no client requester is configured")
1421        );
1422    }
1423
1424    #[tokio::test]
1425    async fn test_confirm_without_requester_fails() {
1426        let ctx = RequestContext::new(RequestId::Number(1));
1427
1428        let result = ctx.confirm("Are you sure?").await;
1429        assert!(result.is_err());
1430        assert!(
1431            result
1432                .unwrap_err()
1433                .to_string()
1434                .contains("Elicitation is not available: no client requester is configured")
1435        );
1436    }
1437
1438    #[tokio::test]
1439    async fn test_send_log_filtered_by_level() {
1440        let (tx, mut rx) = notification_channel(10);
1441        let min_level = Arc::new(RwLock::new(LogLevel::Warning));
1442
1443        let ctx = RequestContext::new(RequestId::Number(1))
1444            .with_notification_sender(tx)
1445            .with_min_log_level(min_level.clone());
1446
1447        // Error is more severe than Warning — should pass through
1448        ctx.send_log(LoggingMessageParams::new(
1449            LogLevel::Error,
1450            serde_json::Value::Null,
1451        ));
1452        let msg = rx.try_recv();
1453        assert!(msg.is_ok(), "Error should pass through Warning filter");
1454
1455        // Warning is equal to min level — should pass through
1456        ctx.send_log(LoggingMessageParams::new(
1457            LogLevel::Warning,
1458            serde_json::Value::Null,
1459        ));
1460        let msg = rx.try_recv();
1461        assert!(msg.is_ok(), "Warning should pass through Warning filter");
1462
1463        // Info is less severe than Warning — should be filtered
1464        ctx.send_log(LoggingMessageParams::new(
1465            LogLevel::Info,
1466            serde_json::Value::Null,
1467        ));
1468        let msg = rx.try_recv();
1469        assert!(msg.is_err(), "Info should be filtered by Warning filter");
1470
1471        // Debug is less severe than Warning — should be filtered
1472        ctx.send_log(LoggingMessageParams::new(
1473            LogLevel::Debug,
1474            serde_json::Value::Null,
1475        ));
1476        let msg = rx.try_recv();
1477        assert!(msg.is_err(), "Debug should be filtered by Warning filter");
1478    }
1479
1480    #[tokio::test]
1481    async fn test_send_log_level_updates_dynamically() {
1482        let (tx, mut rx) = notification_channel(10);
1483        let min_level = Arc::new(RwLock::new(LogLevel::Error));
1484
1485        let ctx = RequestContext::new(RequestId::Number(1))
1486            .with_notification_sender(tx)
1487            .with_min_log_level(min_level.clone());
1488
1489        // Info should be filtered at Error level
1490        ctx.send_log(LoggingMessageParams::new(
1491            LogLevel::Info,
1492            serde_json::Value::Null,
1493        ));
1494        assert!(
1495            rx.try_recv().is_err(),
1496            "Info should be filtered at Error level"
1497        );
1498
1499        // Dynamically update to Debug (most permissive)
1500        *min_level.write().unwrap() = LogLevel::Debug;
1501
1502        // Now Info should pass through
1503        ctx.send_log(LoggingMessageParams::new(
1504            LogLevel::Info,
1505            serde_json::Value::Null,
1506        ));
1507        assert!(
1508            rx.try_recv().is_ok(),
1509            "Info should pass through after level changed to Debug"
1510        );
1511    }
1512
1513    #[tokio::test]
1514    async fn test_send_log_no_min_level_sends_all() {
1515        let (tx, mut rx) = notification_channel(10);
1516
1517        // No min_log_level set — all messages should pass through
1518        let ctx = RequestContext::new(RequestId::Number(1)).with_notification_sender(tx);
1519
1520        ctx.send_log(LoggingMessageParams::new(
1521            LogLevel::Debug,
1522            serde_json::Value::Null,
1523        ));
1524        assert!(
1525            rx.try_recv().is_ok(),
1526            "Debug should pass when no min level is set"
1527        );
1528    }
1529
1530    #[tokio::test]
1531    #[cfg(feature = "stateless")]
1532    async fn final_request_log_level_is_required_and_filters_per_request() {
1533        let (tx, mut rx) = notification_channel(10);
1534        let mut extensions = Extensions::new();
1535        extensions.insert(crate::stateless::StatelessRequestMeta {
1536            protocol_version: Some(crate::protocol::PROTOCOL_VERSION_2026_07_28.to_string()),
1537            client_capabilities: Some(Default::default()),
1538            ..Default::default()
1539        });
1540        let ctx = RequestContext::new(RequestId::Number(1))
1541            .with_notification_sender(tx.clone())
1542            .with_extensions(Arc::new(extensions));
1543        ctx.send_log(LoggingMessageParams::new(
1544            LogLevel::Emergency,
1545            serde_json::Value::Null,
1546        ));
1547        assert!(
1548            rx.try_recv().is_err(),
1549            "final requests without logLevel must receive no logs"
1550        );
1551
1552        let mut extensions = Extensions::new();
1553        extensions.insert(crate::stateless::StatelessRequestMeta {
1554            protocol_version: Some(crate::protocol::PROTOCOL_VERSION_2026_07_28.to_string()),
1555            client_capabilities: Some(Default::default()),
1556            log_level: Some(crate::stateless::LogLevel::Warning),
1557            ..Default::default()
1558        });
1559        let ctx = RequestContext::new(RequestId::Number(2))
1560            .with_notification_sender(tx)
1561            .with_extensions(Arc::new(extensions));
1562        ctx.send_log(LoggingMessageParams::new(
1563            LogLevel::Info,
1564            serde_json::Value::Null,
1565        ));
1566        assert!(rx.try_recv().is_err(), "Info must be filtered at Warning");
1567        ctx.send_log(LoggingMessageParams::new(
1568            LogLevel::Error,
1569            serde_json::Value::Null,
1570        ));
1571        assert!(rx.try_recv().is_ok(), "Error must pass at Warning");
1572    }
1573
1574    fn make_task_object(id: &str, status: TaskStatus) -> serde_json::Value {
1575        serde_json::json!({
1576            "taskId": id,
1577            "status": status,
1578            "createdAt": "2026-04-24T00:00:00Z",
1579            "lastUpdatedAt": "2026-04-24T00:00:00Z",
1580            "ttl": null
1581        })
1582    }
1583
1584    fn spawn_mock_client(
1585        mut rx: OutgoingRequestReceiver,
1586        responder: impl Fn(&str, serde_json::Value) -> serde_json::Value + Send + 'static,
1587    ) {
1588        tokio::spawn(async move {
1589            while let Some(req) = rx.recv().await {
1590                let response = responder(&req.method, req.params);
1591                let _ = req.response_tx.send(Ok(response));
1592            }
1593        });
1594    }
1595
1596    #[tokio::test]
1597    async fn test_get_task_info_round_trips() {
1598        let (tx, rx) = outgoing_request_channel(10);
1599        spawn_mock_client(rx, |method, params| {
1600            assert_eq!(method, "tasks/get");
1601            let task_id = params["taskId"].as_str().unwrap().to_string();
1602            make_task_object(&task_id, TaskStatus::Working)
1603        });
1604        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(tx));
1605        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1606
1607        let info = ctx.get_task_info("task-123").await.unwrap();
1608        assert_eq!(info.task_id, "task-123");
1609        assert!(matches!(info.status, TaskStatus::Working));
1610    }
1611
1612    #[tokio::test]
1613    #[allow(deprecated)] // exercises the legacy SEP-1686 helper
1614    async fn test_list_tasks_round_trips() {
1615        let (tx, rx) = outgoing_request_channel(10);
1616        spawn_mock_client(rx, |method, params| {
1617            assert_eq!(method, "tasks/list");
1618            // Status filter should be forwarded
1619            assert_eq!(params["status"], serde_json::json!("working"));
1620            serde_json::json!({
1621                "tasks": [
1622                    make_task_object("task-1", TaskStatus::Working),
1623                    make_task_object("task-2", TaskStatus::Working),
1624                ]
1625            })
1626        });
1627        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(tx));
1628        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1629
1630        let result = ctx.list_tasks(Some(TaskStatus::Working)).await.unwrap();
1631        assert_eq!(result.tasks.len(), 2);
1632        assert_eq!(result.tasks[0].task_id, "task-1");
1633    }
1634
1635    #[tokio::test]
1636    async fn test_cancel_task_forwards_reason() {
1637        let (tx, rx) = outgoing_request_channel(10);
1638        spawn_mock_client(rx, |method, params| {
1639            assert_eq!(method, "tasks/cancel");
1640            assert_eq!(params["reason"], serde_json::json!("user requested"));
1641            // SEP-2663 (final): the cancel acknowledgment is an empty result.
1642            serde_json::json!({})
1643        });
1644        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(tx));
1645        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1646
1647        ctx.cancel_task("task-99", Some("user requested".into()))
1648            .await
1649            .expect("empty ack should succeed");
1650    }
1651
1652    #[tokio::test]
1653    async fn test_cancel_task_tolerates_legacy_task_object_ack() {
1654        // Legacy SEP-1686 peers return the task object from tasks/cancel;
1655        // the helper discards the body either way.
1656        let (tx, rx) = outgoing_request_channel(10);
1657        spawn_mock_client(rx, |method, _params| {
1658            assert_eq!(method, "tasks/cancel");
1659            make_task_object("task-99", TaskStatus::Cancelled)
1660        });
1661        let requester: ClientRequesterHandle = Arc::new(ChannelClientRequester::new(tx));
1662        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1663
1664        ctx.cancel_task("task-99", None)
1665            .await
1666            .expect("legacy task-object ack should also succeed");
1667    }
1668
1669    #[tokio::test]
1670    async fn test_get_task_info_without_requester_fails() {
1671        let ctx = RequestContext::new(RequestId::Number(1));
1672        let result = ctx.get_task_info("task-1").await;
1673        assert!(result.is_err());
1674        assert!(
1675            result
1676                .unwrap_err()
1677                .to_string()
1678                .contains("no client requester is configured")
1679        );
1680    }
1681
1682    #[tokio::test]
1683    async fn test_default_request_impl_errors() {
1684        // A custom requester that only implements sample/elicit (not request)
1685        // should reject task helpers.
1686        struct OnlySampleAndElicit;
1687
1688        #[async_trait]
1689        impl ClientRequester for OnlySampleAndElicit {
1690            async fn sample(&self, _: CreateMessageParams) -> Result<CreateMessageResult> {
1691                unreachable!()
1692            }
1693            async fn elicit(&self, _: ElicitRequestParams) -> Result<ElicitResult> {
1694                unreachable!()
1695            }
1696        }
1697
1698        let requester: ClientRequesterHandle = Arc::new(OnlySampleAndElicit);
1699        let ctx = RequestContext::new(RequestId::Number(1)).with_client_requester(requester);
1700
1701        let err = ctx.get_task_info("x").await.unwrap_err();
1702        assert!(err.to_string().contains("does not support arbitrary"));
1703    }
1704}
1705
1706#[cfg(test)]
1707mod final_lifecycle_diagnostics_tests {
1708    use super::*;
1709    use crate::protocol::{ElicitFormParams, ElicitFormSchema};
1710
1711    fn params() -> ElicitFormParams {
1712        ElicitFormParams {
1713            mode: None,
1714            message: "confirm?".to_string(),
1715            requested_schema: ElicitFormSchema::new(),
1716            meta: None,
1717        }
1718    }
1719
1720    fn sampling_params() -> CreateMessageParams {
1721        CreateMessageParams {
1722            messages: Vec::new(),
1723            max_tokens: 1,
1724            system_prompt: None,
1725            temperature: None,
1726            stop_sequences: Vec::new(),
1727            model_preferences: None,
1728            include_context: None,
1729            metadata: None,
1730            tools: None,
1731            tool_choice: None,
1732            task: None,
1733            meta: None,
1734        }
1735    }
1736
1737    /// #1201: the final lifecycle has no server-initiated requests, so the
1738    /// absent requester is a protocol fact rather than missing configuration.
1739    /// The message must say so and name the replacement, because the generic
1740    /// text sent a reporter looking at transport wiring that was correct.
1741    #[tokio::test]
1742    async fn final_lifecycle_elicitation_error_names_the_replacement() {
1743        let ctx = RequestContext::new(RequestId::Number(1)).with_final_lifecycle(true);
1744
1745        let error = ctx.elicit_form(params()).await.unwrap_err().to_string();
1746        assert!(
1747            error.contains("2026-07-28"),
1748            "must name the lifecycle: {error}"
1749        );
1750        assert!(
1751            error.contains("do not \ninitiate JSON-RPC requests")
1752                || error.contains("do not initiate JSON-RPC requests"),
1753            "must explain the cause: {error}"
1754        );
1755        assert!(
1756            error.contains("RequestOutcome::input_required"),
1757            "must name the replacement API: {error}"
1758        );
1759        assert!(
1760            error.contains("SEP-2322"),
1761            "must cite the mechanism: {error}"
1762        );
1763        assert!(
1764            !error.contains("no client requester is configured"),
1765            "must not blame configuration: {error}"
1766        );
1767    }
1768
1769    #[tokio::test]
1770    async fn final_lifecycle_sampling_error_names_the_replacement() {
1771        let ctx = RequestContext::new(RequestId::Number(1)).with_final_lifecycle(true);
1772        let error = ctx.sample(sampling_params()).await.unwrap_err().to_string();
1773        assert!(error.contains("2026-07-28"), "{error}");
1774        assert!(error.contains("RequestOutcome::input_required"), "{error}");
1775    }
1776
1777    /// A legacy request with no requester is still a configuration problem,
1778    /// and must not be mislabelled as a protocol restriction.
1779    #[tokio::test]
1780    async fn legacy_lifecycle_keeps_the_configuration_error() {
1781        let ctx = RequestContext::new(RequestId::Number(1));
1782
1783        let error = ctx.elicit_form(params()).await.unwrap_err().to_string();
1784        assert!(
1785            error.contains("no client requester is configured"),
1786            "a legacy transport without a requester is misconfigured: {error}"
1787        );
1788        assert!(
1789            !error.contains("2026-07-28"),
1790            "must not blame the protocol: {error}"
1791        );
1792    }
1793
1794    /// The capability probes report the restriction too, so a handler serving
1795    /// both eras can branch without inspecting the protocol version.
1796    #[tokio::test]
1797    async fn capability_probes_report_false_on_the_final_lifecycle() {
1798        let ctx = RequestContext::new(RequestId::Number(1)).with_final_lifecycle(true);
1799        assert!(!ctx.can_elicit());
1800        assert!(!ctx.can_sample());
1801    }
1802}