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