Skip to main content

tower_mcp/
resource.rs

1//! Resource definition and builder API
2//!
3//! Provides ergonomic ways to define MCP resources:
4//!
5//! 1. **Builder pattern** - Fluent API for defining resources
6//! 2. **Trait-based** - Implement `McpResource` for full control
7//! 3. **Resource templates** - Parameterized resources using URI templates (RFC 6570)
8//!
9//! ## Per-Resource Middleware
10//!
11//! Resources are implemented as Tower services internally, enabling middleware
12//! composition via the `.layer()` method:
13//!
14//! ```rust
15//! use std::time::Duration;
16//! use tower::timeout::TimeoutLayer;
17//! use tower_mcp::resource::ResourceBuilder;
18//! use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
19//!
20//! let resource = ResourceBuilder::new("file:///large-file.txt")
21//!     .name("Large File")
22//!     .description("A large file that may take time to read")
23//!     .handler(|| async {
24//!         // Simulate slow read
25//!         Ok(ReadResourceResult {
26//!             contents: vec![ResourceContent {
27//!                 uri: "file:///large-file.txt".to_string(),
28//!                 mime_type: Some("text/plain".to_string()),
29//!                 text: Some("content".to_string()),
30//!                 blob: None,
31//!                 meta: None,
32//!             }],
33//!             meta: None,
34//!             ..Default::default()
35//!         })
36//!     })
37//!     .layer(TimeoutLayer::new(Duration::from_secs(30)))
38//!     .build();
39//! ```
40//!
41//! # Resource Templates
42//!
43//! Resource templates allow servers to expose parameterized resources using URI templates.
44//! When a client requests `resources/read` with a URI matching a template, the server
45//! extracts the variables and passes them to the handler.
46//!
47//! ```rust
48//! use tower_mcp::resource::ResourceTemplateBuilder;
49//! use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
50//! use std::collections::HashMap;
51//!
52//! let template = ResourceTemplateBuilder::new("file:///{path}")
53//!     .name("Project Files")
54//!     .description("Access files in the project directory")
55//!     .handler(|uri: String, vars: HashMap<String, String>| async move {
56//!         let path = vars.get("path").unwrap_or(&String::new()).clone();
57//!         Ok(ReadResourceResult {
58//!             contents: vec![ResourceContent {
59//!                 uri,
60//!                 mime_type: Some("text/plain".to_string()),
61//!                 text: Some(format!("Contents of {}", path)),
62//!                 blob: None,
63//!                 meta: None,
64//!             }],
65//!             meta: None,
66//!             ..Default::default()
67//!         })
68//!     });
69//! ```
70
71use std::collections::HashMap;
72use std::convert::Infallible;
73use std::fmt;
74use std::future::Future;
75use std::pin::Pin;
76use std::sync::Arc;
77use std::task::{Context, Poll};
78
79use pin_project_lite::pin_project;
80use serde_json::Value;
81
82#[cfg(feature = "stateless")]
83use tower::ServiceExt;
84use tower::util::BoxCloneService;
85use tower_service::Service;
86
87#[cfg(feature = "stateless")]
88use tokio::sync::Mutex;
89
90use crate::context::RequestContext;
91use crate::error::{Error, Result};
92use crate::protocol::{
93    ContentAnnotations, ReadResourceResult, RequestOutcome, ResourceContent, ResourceDefinition,
94    ResourceTemplateDefinition, ToolIcon,
95};
96
97// =============================================================================
98// Service Types for Per-Resource Middleware
99// =============================================================================
100
101/// Request type for resource services.
102///
103/// Contains the request context (for progress reporting, cancellation, etc.)
104/// and the resource URI being read.
105#[derive(Debug, Clone)]
106pub struct ResourceRequest {
107    /// Request context for progress reporting, cancellation, and client requests
108    pub ctx: RequestContext,
109    /// The URI of the resource being read
110    pub uri: String,
111}
112
113impl ResourceRequest {
114    /// Create a new resource request
115    pub fn new(ctx: RequestContext, uri: String) -> Self {
116        Self { ctx, uri }
117    }
118}
119
120/// A boxed, cloneable resource service with `Error = Infallible`.
121///
122/// This is the internal service type that resources use. Middleware errors are
123/// caught and converted to error results, so the service never fails at the Tower level.
124pub type BoxResourceService = BoxCloneService<ResourceRequest, ReadResourceResult, Infallible>;
125
126#[cfg(feature = "stateless")]
127type BoxMrtrResourceService =
128    BoxCloneService<ResourceRequest, RequestOutcome<ReadResourceResult>, Infallible>;
129
130/// Catches errors from the inner service and converts them to error results.
131///
132/// This wrapper ensures that middleware errors (e.g., timeouts, rate limits)
133/// and handler errors are converted to `Err(Error)` responses wrapped in
134/// `Ok`, rather than propagating as Tower service errors.
135#[doc(hidden)]
136pub struct ResourceCatchError<S> {
137    inner: S,
138}
139
140impl<S> ResourceCatchError<S> {
141    /// Create a new `ResourceCatchError` wrapping the given service.
142    pub fn new(inner: S) -> Self {
143        Self { inner }
144    }
145}
146
147impl<S: Clone> Clone for ResourceCatchError<S> {
148    fn clone(&self) -> Self {
149        Self {
150            inner: self.inner.clone(),
151        }
152    }
153}
154
155impl<S: fmt::Debug> fmt::Debug for ResourceCatchError<S> {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        f.debug_struct("ResourceCatchError")
158            .field("inner", &self.inner)
159            .finish()
160    }
161}
162
163pin_project! {
164    /// Future for [`ResourceCatchError`].
165    #[doc(hidden)]
166    pub struct ResourceCatchErrorFuture<F> {
167        #[pin]
168        inner: F,
169        uri: Option<String>,
170    }
171}
172
173impl<F, E> Future for ResourceCatchErrorFuture<F>
174where
175    F: Future<Output = std::result::Result<ReadResourceResult, E>>,
176    E: fmt::Display,
177{
178    type Output = std::result::Result<ReadResourceResult, Infallible>;
179
180    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
181        let this = self.project();
182        match this.inner.poll(cx) {
183            Poll::Pending => Poll::Pending,
184            Poll::Ready(Ok(result)) => Poll::Ready(Ok(result)),
185            Poll::Ready(Err(err)) => {
186                let uri = this.uri.take().unwrap_or_default();
187                Poll::Ready(Ok(ReadResourceResult {
188                    contents: vec![ResourceContent {
189                        uri,
190                        mime_type: Some("text/plain".to_string()),
191                        text: Some(format!("Error reading resource: {}", err)),
192                        blob: None,
193                        meta: None,
194                    }],
195                    meta: None,
196                    ..Default::default()
197                }))
198            }
199        }
200    }
201}
202
203impl<S> Service<ResourceRequest> for ResourceCatchError<S>
204where
205    S: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
206    S::Error: fmt::Display + Send,
207    S::Future: Send,
208{
209    type Response = ReadResourceResult;
210    type Error = Infallible;
211    type Future = ResourceCatchErrorFuture<S::Future>;
212
213    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
214        // Map any readiness error to Infallible (we catch it on call)
215        match self.inner.poll_ready(cx) {
216            Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
217            Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
218            Poll::Pending => Poll::Pending,
219        }
220    }
221
222    fn call(&mut self, req: ResourceRequest) -> Self::Future {
223        let uri = req.uri.clone();
224        let fut = self.inner.call(req);
225
226        ResourceCatchErrorFuture {
227            inner: fut,
228            uri: Some(uri),
229        }
230    }
231}
232
233#[cfg(feature = "stateless")]
234#[derive(Clone)]
235struct MrtrResourceCatchError<S> {
236    inner: S,
237}
238
239#[cfg(feature = "stateless")]
240impl<S> MrtrResourceCatchError<S> {
241    fn new(inner: S) -> Self {
242        Self { inner }
243    }
244}
245
246#[cfg(feature = "stateless")]
247impl<S> Service<ResourceRequest> for MrtrResourceCatchError<S>
248where
249    S: Service<ResourceRequest, Response = RequestOutcome<ReadResourceResult>>
250        + Clone
251        + Send
252        + 'static,
253    S::Error: fmt::Display + Send + 'static,
254    S::Future: Send + 'static,
255{
256    type Response = RequestOutcome<ReadResourceResult>;
257    type Error = Infallible;
258    type Future =
259        Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
260
261    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
262        match self.inner.poll_ready(cx) {
263            Poll::Ready(Ok(())) | Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
264            Poll::Pending => Poll::Pending,
265        }
266    }
267
268    fn call(&mut self, req: ResourceRequest) -> Self::Future {
269        let uri = req.uri.clone();
270        let future = self.inner.call(req);
271        Box::pin(async move {
272            Ok(match future.await {
273                Ok(outcome) => outcome,
274                Err(error) => RequestOutcome::Complete(ReadResourceResult {
275                    contents: vec![ResourceContent {
276                        uri,
277                        mime_type: Some("text/plain".to_string()),
278                        text: Some(format!("Error reading resource: {error}")),
279                        blob: None,
280                        meta: None,
281                    }],
282                    meta: None,
283                    ..Default::default()
284                }),
285            })
286        })
287    }
288}
289
290/// A boxed future for resource handlers
291pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
292
293/// Resource handler trait - the core abstraction for resource reading
294pub trait ResourceHandler: Send + Sync {
295    /// Read the resource contents
296    fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>>;
297
298    /// Read the resource with request context for progress/cancellation support
299    ///
300    /// The default implementation ignores the context and calls `read`.
301    /// Override this to receive progress/cancellation context.
302    fn read_with_context(&self, _ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
303        self.read()
304    }
305
306    /// Returns true if this handler uses context (for optimization)
307    fn uses_context(&self) -> bool {
308        false
309    }
310}
311
312/// Resource handler that may return an SEP-2322 input-required continuation.
313#[cfg(feature = "stateless")]
314pub trait MrtrResourceHandler: Send + Sync {
315    /// Read a resource attempt with continuation values in the context.
316    fn read(
317        &self,
318        ctx: RequestContext,
319    ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>>;
320}
321
322#[cfg(feature = "stateless")]
323struct MrtrResourceHandlerService<H> {
324    handler: Arc<H>,
325}
326
327#[cfg(feature = "stateless")]
328impl<H> MrtrResourceHandlerService<H> {
329    fn new(handler: H) -> Self {
330        Self {
331            handler: Arc::new(handler),
332        }
333    }
334}
335
336#[cfg(feature = "stateless")]
337impl<H> Clone for MrtrResourceHandlerService<H> {
338    fn clone(&self) -> Self {
339        Self {
340            handler: self.handler.clone(),
341        }
342    }
343}
344
345#[cfg(feature = "stateless")]
346impl<H> Service<ResourceRequest> for MrtrResourceHandlerService<H>
347where
348    H: MrtrResourceHandler + 'static,
349{
350    type Response = RequestOutcome<ReadResourceResult>;
351    type Error = Error;
352    type Future =
353        Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
354
355    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
356        Poll::Ready(Ok(()))
357    }
358
359    fn call(&mut self, req: ResourceRequest) -> Self::Future {
360        let handler = self.handler.clone();
361        Box::pin(async move { handler.read(req.ctx).await })
362    }
363}
364
365#[cfg(feature = "stateless")]
366struct ServiceMrtrResourceHandler {
367    service: Mutex<BoxMrtrResourceService>,
368    uri: String,
369}
370
371#[cfg(feature = "stateless")]
372impl MrtrResourceHandler for ServiceMrtrResourceHandler {
373    fn read(
374        &self,
375        ctx: RequestContext,
376    ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>> {
377        Box::pin(async move {
378            let request = ResourceRequest::new(ctx, self.uri.clone());
379            let mut service = self.service.lock().await.clone();
380            let outcome = service
381                .ready()
382                .await
383                .expect("MRTR resource service is infallible")
384                .call(request)
385                .await
386                .expect("MRTR resource service is infallible");
387            Ok(outcome)
388        })
389    }
390}
391
392/// Adapts a `ResourceHandler` to a Tower `Service<ResourceRequest>`.
393///
394/// This is an internal adapter that bridges the handler abstraction to the
395/// service abstraction, enabling middleware composition.
396struct ResourceHandlerService<H> {
397    handler: Arc<H>,
398}
399
400impl<H> ResourceHandlerService<H> {
401    fn new(handler: H) -> Self {
402        Self {
403            handler: Arc::new(handler),
404        }
405    }
406}
407
408impl<H> Clone for ResourceHandlerService<H> {
409    fn clone(&self) -> Self {
410        Self {
411            handler: self.handler.clone(),
412        }
413    }
414}
415
416impl<H> fmt::Debug for ResourceHandlerService<H> {
417    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418        f.debug_struct("ResourceHandlerService")
419            .finish_non_exhaustive()
420    }
421}
422
423impl<H> Service<ResourceRequest> for ResourceHandlerService<H>
424where
425    H: ResourceHandler + 'static,
426{
427    type Response = ReadResourceResult;
428    type Error = Error;
429    type Future =
430        Pin<Box<dyn Future<Output = std::result::Result<ReadResourceResult, Error>> + Send>>;
431
432    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
433        Poll::Ready(Ok(()))
434    }
435
436    fn call(&mut self, req: ResourceRequest) -> Self::Future {
437        let handler = self.handler.clone();
438        Box::pin(async move { handler.read_with_context(req.ctx).await })
439    }
440}
441
442/// A complete resource definition with service-based execution.
443///
444/// Resources are implemented as Tower services internally, enabling middleware
445/// composition via the builder's `.layer()` method. The service is wrapped
446/// in [`ResourceCatchError`] to convert any errors (from handlers or middleware)
447/// into error result responses.
448pub struct Resource {
449    /// Resource URI
450    pub uri: String,
451    /// Human-readable name
452    pub name: String,
453    /// Human-readable title for display purposes
454    pub title: Option<String>,
455    /// Optional description
456    pub description: Option<String>,
457    /// Optional MIME type
458    pub mime_type: Option<String>,
459    /// Optional icons for display in user interfaces
460    pub icons: Option<Vec<ToolIcon>>,
461    /// Optional size in bytes
462    pub size: Option<u64>,
463    /// Optional annotations (audience, priority hints)
464    pub annotations: Option<ContentAnnotations>,
465    /// Validated protocol metadata included in `resources/list`.
466    pub meta: Option<Value>,
467    /// The boxed service that reads the resource
468    service: Option<BoxResourceService>,
469    #[cfg(feature = "stateless")]
470    mrtr_handler: Option<Arc<dyn MrtrResourceHandler>>,
471}
472
473impl Clone for Resource {
474    fn clone(&self) -> Self {
475        Self {
476            uri: self.uri.clone(),
477            name: self.name.clone(),
478            title: self.title.clone(),
479            description: self.description.clone(),
480            mime_type: self.mime_type.clone(),
481            icons: self.icons.clone(),
482            size: self.size,
483            annotations: self.annotations.clone(),
484            meta: self.meta.clone(),
485            service: self.service.clone(),
486            #[cfg(feature = "stateless")]
487            mrtr_handler: self.mrtr_handler.clone(),
488        }
489    }
490}
491
492impl std::fmt::Debug for Resource {
493    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
494        f.debug_struct("Resource")
495            .field("uri", &self.uri)
496            .field("name", &self.name)
497            .field("title", &self.title)
498            .field("description", &self.description)
499            .field("mime_type", &self.mime_type)
500            .field("icons", &self.icons)
501            .field("size", &self.size)
502            .field("annotations", &self.annotations)
503            .field("meta", &self.meta)
504            .finish_non_exhaustive()
505    }
506}
507
508// SAFETY: BoxCloneService is Send + Sync (tower provides unsafe impl Sync),
509// and all other fields in Resource are Send + Sync.
510unsafe impl Send for Resource {}
511unsafe impl Sync for Resource {}
512
513impl Resource {
514    /// Create a new resource builder
515    pub fn builder(uri: impl Into<String>) -> ResourceBuilder {
516        ResourceBuilder::new(uri)
517    }
518
519    /// Get the resource definition for resources/list
520    pub fn definition(&self) -> ResourceDefinition {
521        ResourceDefinition {
522            uri: self.uri.clone(),
523            name: self.name.clone(),
524            title: self.title.clone(),
525            description: self.description.clone(),
526            mime_type: self.mime_type.clone(),
527            icons: self.icons.clone(),
528            size: self.size,
529            annotations: self.annotations.clone(),
530            meta: self.meta.clone(),
531        }
532    }
533
534    /// Attach validated protocol metadata to this resource definition.
535    pub fn with_meta(
536        mut self,
537        meta: Value,
538    ) -> std::result::Result<Self, crate::protocol::MetaValidationError> {
539        crate::protocol::validate_meta_object(&meta)?;
540        self.meta = Some(meta);
541        Ok(self)
542    }
543
544    /// Read the resource without context
545    ///
546    /// Creates a dummy request context. For full context support, use
547    /// [`read_with_context`](Self::read_with_context).
548    pub fn read(&self) -> BoxFuture<'static, ReadResourceResult> {
549        let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
550        self.read_with_context(ctx)
551    }
552
553    /// Read the resource with request context
554    ///
555    /// The context provides progress reporting, cancellation support, and
556    /// access to client requests (for sampling, etc.).
557    ///
558    /// # Note
559    ///
560    /// This method returns `ReadResourceResult` directly (not `Result<ReadResourceResult>`).
561    /// Any errors from the handler or middleware are converted to error responses
562    /// in the result contents.
563    pub fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'static, ReadResourceResult> {
564        let resource = self.clone();
565        let uri = self.uri.clone();
566        Box::pin(async move {
567            match resource.read_outcome_with_context(ctx).await {
568                Ok(RequestOutcome::Complete(result)) => result,
569                Ok(RequestOutcome::InputRequired(_)) => ReadResourceResult {
570                    contents: vec![ResourceContent {
571                        uri,
572                        mime_type: Some("text/plain".into()),
573                        text: Some(
574                            "resource requires additional client input; use read_outcome_with_context"
575                                .into(),
576                        ),
577                        blob: None,
578                        meta: None,
579                    }],
580                    ..ReadResourceResult::default()
581                },
582                Err(error) => ReadResourceResult {
583                    contents: vec![ResourceContent {
584                        uri,
585                        mime_type: Some("text/plain".into()),
586                        text: Some(error.to_string()),
587                        blob: None,
588                        meta: None,
589                    }],
590                    ..ReadResourceResult::default()
591                },
592            }
593        })
594    }
595
596    /// Read the resource while preserving an SEP-2322 continuation.
597    pub fn read_outcome_with_context(
598        &self,
599        ctx: RequestContext,
600    ) -> BoxFuture<'static, Result<RequestOutcome<ReadResourceResult>>> {
601        use tower::ServiceExt;
602        #[cfg(feature = "stateless")]
603        if let Some(handler) = self.mrtr_handler.clone() {
604            return Box::pin(async move { handler.read(ctx).await });
605        }
606        let service = self
607            .service
608            .clone()
609            .expect("resource must have a complete or MRTR handler");
610        let uri = self.uri.clone();
611        Box::pin(async move {
612            let result = service
613                .oneshot(ResourceRequest::new(ctx, uri))
614                .await
615                .unwrap();
616            Ok(RequestOutcome::Complete(result))
617        })
618    }
619
620    /// Create a resource from a handler (internal helper)
621    #[allow(clippy::too_many_arguments)]
622    fn from_handler<H: ResourceHandler + 'static>(
623        uri: String,
624        name: String,
625        title: Option<String>,
626        description: Option<String>,
627        mime_type: Option<String>,
628        icons: Option<Vec<ToolIcon>>,
629        size: Option<u64>,
630        annotations: Option<ContentAnnotations>,
631        handler: H,
632    ) -> Self {
633        let handler_service = ResourceHandlerService::new(handler);
634        let catch_error = ResourceCatchError::new(handler_service);
635        let service = BoxCloneService::new(catch_error);
636
637        Self {
638            uri,
639            name,
640            title,
641            description,
642            mime_type,
643            icons,
644            size,
645            annotations,
646            meta: None,
647            service: Some(service),
648            #[cfg(feature = "stateless")]
649            mrtr_handler: None,
650        }
651    }
652
653    #[cfg(feature = "stateless")]
654    #[allow(clippy::too_many_arguments)]
655    fn from_mrtr_handler<H: MrtrResourceHandler + 'static>(
656        uri: String,
657        name: String,
658        title: Option<String>,
659        description: Option<String>,
660        mime_type: Option<String>,
661        icons: Option<Vec<ToolIcon>>,
662        size: Option<u64>,
663        annotations: Option<ContentAnnotations>,
664        handler: H,
665    ) -> Self {
666        Self {
667            uri,
668            name,
669            title,
670            description,
671            mime_type,
672            icons,
673            size,
674            annotations,
675            meta: None,
676            service: None,
677            mrtr_handler: Some(Arc::new(handler)),
678        }
679    }
680}
681
682// =============================================================================
683// Builder API
684// =============================================================================
685
686/// Builder for creating resources with a fluent API
687///
688/// # Example
689///
690/// ```rust
691/// use tower_mcp::resource::ResourceBuilder;
692/// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
693///
694/// let resource = ResourceBuilder::new("file:///config.json")
695///     .name("Configuration")
696///     .description("Application configuration file")
697///     .mime_type("application/json")
698///     .handler(|| async {
699///         Ok(ReadResourceResult {
700///             contents: vec![ResourceContent {
701///                 uri: "file:///config.json".to_string(),
702///                 mime_type: Some("application/json".to_string()),
703///                 text: Some(r#"{"setting": "value"}"#.to_string()),
704///                 blob: None,
705///                 meta: None,
706///             }],
707///             meta: None,
708///             ..Default::default()
709///         })
710///     })
711///     .build();
712///
713/// assert_eq!(resource.uri, "file:///config.json");
714/// ```
715pub struct ResourceBuilder {
716    uri: String,
717    name: Option<String>,
718    title: Option<String>,
719    description: Option<String>,
720    mime_type: Option<String>,
721    icons: Option<Vec<ToolIcon>>,
722    size: Option<u64>,
723    annotations: Option<ContentAnnotations>,
724}
725
726impl ResourceBuilder {
727    /// Create a new resource builder with the given URI.
728    pub fn new(uri: impl Into<String>) -> Self {
729        Self {
730            uri: uri.into(),
731            name: None,
732            title: None,
733            description: None,
734            mime_type: None,
735            icons: None,
736            size: None,
737            annotations: None,
738        }
739    }
740
741    /// Set the resource name (human-readable)
742    pub fn name(mut self, name: impl Into<String>) -> Self {
743        self.name = Some(name.into());
744        self
745    }
746
747    /// Set a human-readable title for the resource
748    pub fn title(mut self, title: impl Into<String>) -> Self {
749        self.title = Some(title.into());
750        self
751    }
752
753    /// Set the resource description
754    pub fn description(mut self, description: impl Into<String>) -> Self {
755        self.description = Some(description.into());
756        self
757    }
758
759    /// Set the MIME type of the resource
760    pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
761        self.mime_type = Some(mime_type.into());
762        self
763    }
764
765    /// Add an icon for the resource
766    pub fn icon(mut self, src: impl Into<String>) -> Self {
767        self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
768            src: src.into(),
769            mime_type: None,
770            sizes: None,
771            theme: None,
772        });
773        self
774    }
775
776    /// Add an icon with metadata
777    pub fn icon_with_meta(
778        mut self,
779        src: impl Into<String>,
780        mime_type: Option<String>,
781        sizes: Option<Vec<String>>,
782    ) -> Self {
783        self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
784            src: src.into(),
785            mime_type,
786            sizes,
787            theme: None,
788        });
789        self
790    }
791
792    /// Set the size of the resource in bytes
793    pub fn size(mut self, size: u64) -> Self {
794        self.size = Some(size);
795        self
796    }
797
798    /// Set annotations (audience, priority hints) for this resource
799    pub fn annotations(mut self, annotations: ContentAnnotations) -> Self {
800        self.annotations = Some(annotations);
801        self
802    }
803
804    /// Set the handler function for reading the resource.
805    ///
806    /// Returns a [`ResourceBuilderWithHandler`] that can be used to apply
807    /// middleware layers via `.layer()` or build the resource directly via `.build()`.
808    ///
809    /// # Sharing State
810    ///
811    /// Capture an [`Arc`] in the closure to share state across handler
812    /// invocations or with other parts of your application:
813    ///
814    /// ```rust
815    /// use std::sync::Arc;
816    /// use tokio::sync::RwLock;
817    /// use tower_mcp::resource::ResourceBuilder;
818    /// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
819    ///
820    /// let db = Arc::new(RwLock::new(vec!["initial".to_string()]));
821    ///
822    /// let db_clone = Arc::clone(&db);
823    /// let resource = ResourceBuilder::new("app://entries")
824    ///     .name("Entries")
825    ///     .handler(move || {
826    ///         let db = Arc::clone(&db_clone);
827    ///         async move {
828    ///             let entries = db.read().await;
829    ///             Ok(ReadResourceResult {
830    ///                 contents: vec![ResourceContent {
831    ///                     uri: "app://entries".to_string(),
832    ///                     mime_type: Some("text/plain".to_string()),
833    ///                     text: Some(entries.join("\n")),
834    ///                     blob: None,
835    ///                     meta: None,
836    ///                 }],
837    ///                 meta: None,
838    ///                 ..Default::default()
839    ///             })
840    ///         }
841    ///     })
842    ///     .build();
843    /// ```
844    ///
845    /// [`Arc`]: std::sync::Arc
846    pub fn handler<F, Fut>(self, handler: F) -> ResourceBuilderWithHandler<F>
847    where
848        F: Fn() -> Fut + Send + Sync + 'static,
849        Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
850    {
851        ResourceBuilderWithHandler {
852            uri: self.uri,
853            name: self.name,
854            title: self.title,
855            description: self.description,
856            mime_type: self.mime_type,
857            icons: self.icons,
858            size: self.size,
859            annotations: self.annotations,
860            handler,
861        }
862    }
863
864    /// Set a context-aware handler for reading the resource.
865    ///
866    /// The handler receives a `RequestContext` for progress reporting and
867    /// cancellation checking.
868    ///
869    /// Returns a [`ResourceBuilderWithContextHandler`] that can be used to apply
870    /// middleware layers via `.layer()` or build the resource directly via `.build()`.
871    pub fn handler_with_context<F, Fut>(self, handler: F) -> ResourceBuilderWithContextHandler<F>
872    where
873        F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
874        Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
875    {
876        ResourceBuilderWithContextHandler {
877            uri: self.uri,
878            name: self.name,
879            title: self.title,
880            description: self.description,
881            mime_type: self.mime_type,
882            icons: self.icons,
883            size: self.size,
884            annotations: self.annotations,
885            handler,
886        }
887    }
888
889    /// Set an SEP-2322 resource handler that may return input-required.
890    #[cfg(feature = "stateless")]
891    pub fn mrtr_handler<F, Fut>(self, handler: F) -> ResourceBuilderWithMrtrHandler<F>
892    where
893        F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
894        Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
895    {
896        ResourceBuilderWithMrtrHandler {
897            uri: self.uri,
898            name: self.name,
899            title: self.title,
900            description: self.description,
901            mime_type: self.mime_type,
902            icons: self.icons,
903            size: self.size,
904            annotations: self.annotations,
905            handler,
906        }
907    }
908
909    /// Create a static text resource (convenience method)
910    pub fn text(self, content: impl Into<String>) -> Resource {
911        let uri = self.uri.clone();
912        let content = content.into();
913        let mime_type = self.mime_type.clone();
914
915        self.handler(move || {
916            let uri = uri.clone();
917            let content = content.clone();
918            let mime_type = mime_type.clone();
919            async move {
920                Ok(ReadResourceResult {
921                    contents: vec![ResourceContent {
922                        uri,
923                        mime_type,
924                        text: Some(content),
925                        blob: None,
926                        meta: None,
927                    }],
928                    meta: None,
929                    ..Default::default()
930                })
931            }
932        })
933        .build()
934    }
935
936    /// Create a static JSON resource (convenience method)
937    pub fn json(mut self, value: serde_json::Value) -> Resource {
938        let uri = self.uri.clone();
939        self.mime_type = Some("application/json".to_string());
940        let text = serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{}".to_string());
941
942        self.handler(move || {
943            let uri = uri.clone();
944            let text = text.clone();
945            async move {
946                Ok(ReadResourceResult {
947                    contents: vec![ResourceContent {
948                        uri,
949                        mime_type: Some("application/json".to_string()),
950                        text: Some(text),
951                        blob: None,
952                        meta: None,
953                    }],
954                    meta: None,
955                    ..Default::default()
956                })
957            }
958        })
959        .build()
960    }
961}
962
963/// Builder state after handler is specified.
964///
965/// This builder allows applying middleware layers via `.layer()` or building
966/// the resource directly via `.build()`.
967#[doc(hidden)]
968pub struct ResourceBuilderWithHandler<F> {
969    uri: String,
970    name: Option<String>,
971    title: Option<String>,
972    description: Option<String>,
973    mime_type: Option<String>,
974    icons: Option<Vec<ToolIcon>>,
975    size: Option<u64>,
976    annotations: Option<ContentAnnotations>,
977    handler: F,
978}
979
980#[cfg(feature = "stateless")]
981#[doc(hidden)]
982pub struct ResourceBuilderWithMrtrHandler<F> {
983    uri: String,
984    name: Option<String>,
985    title: Option<String>,
986    description: Option<String>,
987    mime_type: Option<String>,
988    icons: Option<Vec<ToolIcon>>,
989    size: Option<u64>,
990    annotations: Option<ContentAnnotations>,
991    handler: F,
992}
993
994#[cfg(feature = "stateless")]
995#[doc(hidden)]
996pub struct ResourceBuilderWithMrtrLayer<F, L> {
997    uri: String,
998    name: Option<String>,
999    title: Option<String>,
1000    description: Option<String>,
1001    mime_type: Option<String>,
1002    icons: Option<Vec<ToolIcon>>,
1003    size: Option<u64>,
1004    annotations: Option<ContentAnnotations>,
1005    handler: F,
1006    layer: L,
1007}
1008
1009#[cfg(feature = "stateless")]
1010impl<F, Fut> ResourceBuilderWithMrtrHandler<F>
1011where
1012    F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1013    Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
1014{
1015    /// Build the resource with an SEP-2322-aware handler.
1016    pub fn build(self) -> Resource {
1017        let name = self.name.unwrap_or_else(|| self.uri.clone());
1018
1019        Resource::from_mrtr_handler(
1020            self.uri,
1021            name,
1022            self.title,
1023            self.description,
1024            self.mime_type,
1025            self.icons,
1026            self.size,
1027            self.annotations,
1028            MrtrContextHandler {
1029                handler: self.handler,
1030            },
1031        )
1032    }
1033
1034    /// Apply a Tower layer to every attempt at this MRTR-capable resource.
1035    ///
1036    /// Each retry is an independent request, so the layer runs once per
1037    /// round. Middleware failures become complete resource error results,
1038    /// matching non-MRTR resource middleware.
1039    pub fn layer<L>(self, layer: L) -> ResourceBuilderWithMrtrLayer<F, L> {
1040        ResourceBuilderWithMrtrLayer {
1041            uri: self.uri,
1042            name: self.name,
1043            title: self.title,
1044            description: self.description,
1045            mime_type: self.mime_type,
1046            icons: self.icons,
1047            size: self.size,
1048            annotations: self.annotations,
1049            handler: self.handler,
1050            layer,
1051        }
1052    }
1053}
1054
1055#[cfg(feature = "stateless")]
1056#[allow(private_bounds)]
1057impl<F, Fut, L> ResourceBuilderWithMrtrLayer<F, L>
1058where
1059    F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1060    Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
1061    L: tower::Layer<MrtrResourceHandlerService<MrtrContextHandler<F>>>
1062        + Clone
1063        + Send
1064        + Sync
1065        + 'static,
1066    L::Service: Service<ResourceRequest, Response = RequestOutcome<ReadResourceResult>>
1067        + Clone
1068        + Send
1069        + 'static,
1070    <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send + 'static,
1071    <L::Service as Service<ResourceRequest>>::Future: Send + 'static,
1072{
1073    /// Build the MRTR resource with the applied layer(s).
1074    pub fn build(self) -> Resource {
1075        let name = self.name.unwrap_or_else(|| self.uri.clone());
1076        let handler = MrtrContextHandler {
1077            handler: self.handler,
1078        };
1079        let service = MrtrResourceHandlerService::new(handler);
1080        let service = self.layer.layer(service);
1081        let service = BoxCloneService::new(MrtrResourceCatchError::new(service));
1082
1083        Resource {
1084            uri: self.uri.clone(),
1085            name,
1086            title: self.title,
1087            description: self.description,
1088            mime_type: self.mime_type,
1089            icons: self.icons,
1090            size: self.size,
1091            annotations: self.annotations,
1092            meta: None,
1093            service: None,
1094            mrtr_handler: Some(Arc::new(ServiceMrtrResourceHandler {
1095                service: Mutex::new(service),
1096                uri: self.uri,
1097            })),
1098        }
1099    }
1100
1101    /// Apply an additional Tower layer.
1102    pub fn layer<L2>(
1103        self,
1104        layer: L2,
1105    ) -> ResourceBuilderWithMrtrLayer<F, tower::layer::util::Stack<L2, L>> {
1106        ResourceBuilderWithMrtrLayer {
1107            uri: self.uri,
1108            name: self.name,
1109            title: self.title,
1110            description: self.description,
1111            mime_type: self.mime_type,
1112            icons: self.icons,
1113            size: self.size,
1114            annotations: self.annotations,
1115            handler: self.handler,
1116            layer: tower::layer::util::Stack::new(layer, self.layer),
1117        }
1118    }
1119}
1120
1121impl<F, Fut> ResourceBuilderWithHandler<F>
1122where
1123    F: Fn() -> Fut + Send + Sync + 'static,
1124    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1125{
1126    /// Build the resource without any middleware layers.
1127    pub fn build(self) -> Resource {
1128        let name = self.name.unwrap_or_else(|| self.uri.clone());
1129
1130        Resource::from_handler(
1131            self.uri,
1132            name,
1133            self.title,
1134            self.description,
1135            self.mime_type,
1136            self.icons,
1137            self.size,
1138            self.annotations,
1139            FnHandler {
1140                handler: self.handler,
1141            },
1142        )
1143    }
1144
1145    /// Apply a Tower layer (middleware) to this resource.
1146    ///
1147    /// The layer wraps the resource's handler service, enabling functionality like
1148    /// timeouts, rate limiting, and metrics collection at the per-resource level.
1149    ///
1150    /// # Example
1151    ///
1152    /// ```rust
1153    /// use std::time::Duration;
1154    /// use tower::timeout::TimeoutLayer;
1155    /// use tower_mcp::resource::ResourceBuilder;
1156    /// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
1157    ///
1158    /// let resource = ResourceBuilder::new("file:///slow.txt")
1159    ///     .name("Slow Resource")
1160    ///     .handler(|| async {
1161    ///         Ok(ReadResourceResult {
1162    ///             contents: vec![ResourceContent {
1163    ///                 uri: "file:///slow.txt".to_string(),
1164    ///                 mime_type: Some("text/plain".to_string()),
1165    ///                 text: Some("content".to_string()),
1166    ///                 blob: None,
1167    ///                 meta: None,
1168    ///             }],
1169    ///             meta: None,
1170    ///             ..Default::default()
1171    ///         })
1172    ///     })
1173    ///     .layer(TimeoutLayer::new(Duration::from_secs(30)))
1174    ///     .build();
1175    /// ```
1176    pub fn layer<L>(self, layer: L) -> ResourceBuilderWithLayer<F, L> {
1177        ResourceBuilderWithLayer {
1178            uri: self.uri,
1179            name: self.name,
1180            title: self.title,
1181            description: self.description,
1182            mime_type: self.mime_type,
1183            icons: self.icons,
1184            size: self.size,
1185            annotations: self.annotations,
1186            handler: self.handler,
1187            layer,
1188        }
1189    }
1190}
1191
1192/// Builder state after a layer has been applied to the handler.
1193///
1194/// This builder allows chaining additional layers and building the final resource.
1195#[doc(hidden)]
1196pub struct ResourceBuilderWithLayer<F, L> {
1197    uri: String,
1198    name: Option<String>,
1199    title: Option<String>,
1200    description: Option<String>,
1201    mime_type: Option<String>,
1202    icons: Option<Vec<ToolIcon>>,
1203    size: Option<u64>,
1204    annotations: Option<ContentAnnotations>,
1205    handler: F,
1206    layer: L,
1207}
1208
1209// Allow private_bounds because these internal types (ResourceHandlerService, FnHandler, etc.)
1210// are implementation details that users don't interact with directly.
1211#[allow(private_bounds)]
1212impl<F, Fut, L> ResourceBuilderWithLayer<F, L>
1213where
1214    F: Fn() -> Fut + Send + Sync + 'static,
1215    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1216    L: tower::Layer<ResourceHandlerService<FnHandler<F>>> + Clone + Send + Sync + 'static,
1217    L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
1218    <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
1219    <L::Service as Service<ResourceRequest>>::Future: Send,
1220{
1221    /// Build the resource with the applied layer(s).
1222    pub fn build(self) -> Resource {
1223        let name = self.name.unwrap_or_else(|| self.uri.clone());
1224
1225        let handler_service = ResourceHandlerService::new(FnHandler {
1226            handler: self.handler,
1227        });
1228        let layered = self.layer.layer(handler_service);
1229        let catch_error = ResourceCatchError::new(layered);
1230        let service = BoxCloneService::new(catch_error);
1231
1232        Resource {
1233            uri: self.uri,
1234            name,
1235            title: self.title,
1236            description: self.description,
1237            mime_type: self.mime_type,
1238            icons: self.icons,
1239            size: self.size,
1240            annotations: self.annotations,
1241            meta: None,
1242            service: Some(service),
1243            #[cfg(feature = "stateless")]
1244            mrtr_handler: None,
1245        }
1246    }
1247
1248    /// Apply an additional Tower layer (middleware).
1249    ///
1250    /// Layers are applied in order, with earlier layers wrapping later ones.
1251    /// This means the first layer added is the outermost middleware.
1252    pub fn layer<L2>(
1253        self,
1254        layer: L2,
1255    ) -> ResourceBuilderWithLayer<F, tower::layer::util::Stack<L2, L>> {
1256        ResourceBuilderWithLayer {
1257            uri: self.uri,
1258            name: self.name,
1259            title: self.title,
1260            description: self.description,
1261            mime_type: self.mime_type,
1262            icons: self.icons,
1263            size: self.size,
1264            annotations: self.annotations,
1265            handler: self.handler,
1266            layer: tower::layer::util::Stack::new(layer, self.layer),
1267        }
1268    }
1269}
1270
1271/// Builder state after context-aware handler is specified.
1272#[doc(hidden)]
1273pub struct ResourceBuilderWithContextHandler<F> {
1274    uri: String,
1275    name: Option<String>,
1276    title: Option<String>,
1277    description: Option<String>,
1278    mime_type: Option<String>,
1279    icons: Option<Vec<ToolIcon>>,
1280    size: Option<u64>,
1281    annotations: Option<ContentAnnotations>,
1282    handler: F,
1283}
1284
1285impl<F, Fut> ResourceBuilderWithContextHandler<F>
1286where
1287    F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1288    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1289{
1290    /// Build the resource without any middleware layers.
1291    pub fn build(self) -> Resource {
1292        let name = self.name.unwrap_or_else(|| self.uri.clone());
1293
1294        Resource::from_handler(
1295            self.uri,
1296            name,
1297            self.title,
1298            self.description,
1299            self.mime_type,
1300            self.icons,
1301            self.size,
1302            self.annotations,
1303            ContextAwareHandler {
1304                handler: self.handler,
1305            },
1306        )
1307    }
1308
1309    /// Apply a Tower layer (middleware) to this resource.
1310    ///
1311    /// Works the same as [`ResourceBuilderWithHandler::layer`].
1312    pub fn layer<L>(self, layer: L) -> ResourceBuilderWithContextLayer<F, L> {
1313        ResourceBuilderWithContextLayer {
1314            uri: self.uri,
1315            name: self.name,
1316            title: self.title,
1317            description: self.description,
1318            mime_type: self.mime_type,
1319            icons: self.icons,
1320            size: self.size,
1321            annotations: self.annotations,
1322            handler: self.handler,
1323            layer,
1324        }
1325    }
1326}
1327
1328/// Builder state after a layer has been applied to a context-aware handler.
1329#[doc(hidden)]
1330pub struct ResourceBuilderWithContextLayer<F, L> {
1331    uri: String,
1332    name: Option<String>,
1333    title: Option<String>,
1334    description: Option<String>,
1335    mime_type: Option<String>,
1336    icons: Option<Vec<ToolIcon>>,
1337    size: Option<u64>,
1338    annotations: Option<ContentAnnotations>,
1339    handler: F,
1340    layer: L,
1341}
1342
1343// Allow private_bounds because these internal types are implementation details.
1344#[allow(private_bounds)]
1345impl<F, Fut, L> ResourceBuilderWithContextLayer<F, L>
1346where
1347    F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1348    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1349    L: tower::Layer<ResourceHandlerService<ContextAwareHandler<F>>> + Clone + Send + Sync + 'static,
1350    L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
1351    <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
1352    <L::Service as Service<ResourceRequest>>::Future: Send,
1353{
1354    /// Build the resource with the applied layer(s).
1355    pub fn build(self) -> Resource {
1356        let name = self.name.unwrap_or_else(|| self.uri.clone());
1357
1358        let handler_service = ResourceHandlerService::new(ContextAwareHandler {
1359            handler: self.handler,
1360        });
1361        let layered = self.layer.layer(handler_service);
1362        let catch_error = ResourceCatchError::new(layered);
1363        let service = BoxCloneService::new(catch_error);
1364
1365        Resource {
1366            uri: self.uri,
1367            name,
1368            title: self.title,
1369            description: self.description,
1370            mime_type: self.mime_type,
1371            icons: self.icons,
1372            size: self.size,
1373            annotations: self.annotations,
1374            meta: None,
1375            service: Some(service),
1376            #[cfg(feature = "stateless")]
1377            mrtr_handler: None,
1378        }
1379    }
1380
1381    /// Apply an additional Tower layer (middleware).
1382    pub fn layer<L2>(
1383        self,
1384        layer: L2,
1385    ) -> ResourceBuilderWithContextLayer<F, tower::layer::util::Stack<L2, L>> {
1386        ResourceBuilderWithContextLayer {
1387            uri: self.uri,
1388            name: self.name,
1389            title: self.title,
1390            description: self.description,
1391            mime_type: self.mime_type,
1392            icons: self.icons,
1393            size: self.size,
1394            annotations: self.annotations,
1395            handler: self.handler,
1396            layer: tower::layer::util::Stack::new(layer, self.layer),
1397        }
1398    }
1399}
1400
1401// =============================================================================
1402// Handler implementations
1403// =============================================================================
1404
1405/// Handler wrapping a function
1406struct FnHandler<F> {
1407    handler: F,
1408}
1409
1410impl<F, Fut> ResourceHandler for FnHandler<F>
1411where
1412    F: Fn() -> Fut + Send + Sync + 'static,
1413    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1414{
1415    fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1416        Box::pin((self.handler)())
1417    }
1418}
1419
1420/// Handler that receives request context
1421struct ContextAwareHandler<F> {
1422    handler: F,
1423}
1424
1425impl<F, Fut> ResourceHandler for ContextAwareHandler<F>
1426where
1427    F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1428    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1429{
1430    fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1431        let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
1432        self.read_with_context(ctx)
1433    }
1434
1435    fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
1436        Box::pin((self.handler)(ctx))
1437    }
1438
1439    fn uses_context(&self) -> bool {
1440        true
1441    }
1442}
1443
1444#[cfg(feature = "stateless")]
1445struct MrtrContextHandler<F> {
1446    handler: F,
1447}
1448
1449#[cfg(feature = "stateless")]
1450impl<F, Fut> MrtrResourceHandler for MrtrContextHandler<F>
1451where
1452    F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1453    Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
1454{
1455    fn read(
1456        &self,
1457        ctx: RequestContext,
1458    ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>> {
1459        Box::pin((self.handler)(ctx))
1460    }
1461}
1462
1463// =============================================================================
1464// Trait-based resource definition
1465// =============================================================================
1466
1467/// Trait for defining resources with full control
1468///
1469/// Implement this trait when you need more control than the builder provides,
1470/// or when you want to define resources as standalone types.
1471///
1472/// # Example
1473///
1474/// ```rust
1475/// use tower_mcp::resource::McpResource;
1476/// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
1477/// use tower_mcp::error::Result;
1478///
1479/// struct ConfigResource {
1480///     config: String,
1481/// }
1482///
1483/// impl McpResource for ConfigResource {
1484///     const URI: &'static str = "file:///config.json";
1485///     const NAME: &'static str = "Configuration";
1486///     const DESCRIPTION: Option<&'static str> = Some("Application configuration");
1487///     const MIME_TYPE: Option<&'static str> = Some("application/json");
1488///
1489///     async fn read(&self) -> Result<ReadResourceResult> {
1490///         Ok(ReadResourceResult {
1491///             contents: vec![ResourceContent {
1492///                 uri: Self::URI.to_string(),
1493///                 mime_type: Self::MIME_TYPE.map(|s| s.to_string()),
1494///                 text: Some(self.config.clone()),
1495///                 blob: None,
1496///                 meta: None,
1497///             }],
1498///             meta: None,
1499///             ..Default::default()
1500///         })
1501///     }
1502/// }
1503///
1504/// let resource = ConfigResource { config: "{}".to_string() }.into_resource();
1505/// assert_eq!(resource.uri, "file:///config.json");
1506/// ```
1507pub trait McpResource: Send + Sync + 'static {
1508    /// The resource URI.
1509    const URI: &'static str;
1510    /// The resource name.
1511    const NAME: &'static str;
1512    /// Optional human-readable description.
1513    const DESCRIPTION: Option<&'static str> = None;
1514    /// Optional MIME type for the resource content.
1515    const MIME_TYPE: Option<&'static str> = None;
1516
1517    /// Read the resource content.
1518    fn read(&self) -> impl Future<Output = Result<ReadResourceResult>> + Send;
1519
1520    /// Convert to a Resource instance
1521    fn into_resource(self) -> Resource
1522    where
1523        Self: Sized,
1524    {
1525        let resource = Arc::new(self);
1526        Resource::from_handler(
1527            Self::URI.to_string(),
1528            Self::NAME.to_string(),
1529            None,
1530            Self::DESCRIPTION.map(|s| s.to_string()),
1531            Self::MIME_TYPE.map(|s| s.to_string()),
1532            None,
1533            None,
1534            None,
1535            McpResourceHandler { resource },
1536        )
1537    }
1538}
1539
1540/// Wrapper to make McpResource implement ResourceHandler
1541struct McpResourceHandler<T: McpResource> {
1542    resource: Arc<T>,
1543}
1544
1545impl<T: McpResource> ResourceHandler for McpResourceHandler<T> {
1546    fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1547        let resource = self.resource.clone();
1548        Box::pin(async move { resource.read().await })
1549    }
1550}
1551
1552// =============================================================================
1553// Resource Templates
1554// =============================================================================
1555
1556/// Handler trait for resource templates
1557///
1558/// Unlike [`ResourceHandler`], template handlers receive the extracted
1559/// URI variables as a parameter.
1560pub trait ResourceTemplateHandler: Send + Sync {
1561    /// Read a resource with the given URI variables extracted from the template
1562    fn read(
1563        &self,
1564        uri: &str,
1565        variables: HashMap<String, String>,
1566    ) -> BoxFuture<'_, Result<ReadResourceResult>>;
1567}
1568
1569/// Resource-template handler that may return an SEP-2322 continuation.
1570#[cfg(feature = "stateless")]
1571pub trait MrtrResourceTemplateHandler: Send + Sync {
1572    /// Read a matched template resource with retry values in the context.
1573    fn read(
1574        &self,
1575        ctx: RequestContext,
1576        uri: &str,
1577        variables: HashMap<String, String>,
1578    ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>>;
1579}
1580
1581/// A parameterized resource template
1582///
1583/// Resource templates use URI template syntax (RFC 6570) to match multiple URIs
1584/// and extract variable values. This allows servers to expose dynamic resources
1585/// like file systems or database records.
1586///
1587/// # Example
1588///
1589/// ```rust
1590/// use tower_mcp::resource::ResourceTemplateBuilder;
1591/// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
1592/// use std::collections::HashMap;
1593///
1594/// let template = ResourceTemplateBuilder::new("file:///{path}")
1595///     .name("Project Files")
1596///     .handler(|uri: String, vars: HashMap<String, String>| async move {
1597///         let path = vars.get("path").unwrap_or(&String::new()).clone();
1598///         Ok(ReadResourceResult {
1599///             contents: vec![ResourceContent {
1600///                 uri,
1601///                 mime_type: Some("text/plain".to_string()),
1602///                 text: Some(format!("Contents of {}", path)),
1603///                 blob: None,
1604///                 meta: None,
1605///             }],
1606///             meta: None,
1607///             ..Default::default()
1608///         })
1609///     });
1610/// ```
1611pub struct ResourceTemplate {
1612    /// The URI template pattern (e.g., `file:///{path}`)
1613    pub uri_template: String,
1614    /// Human-readable name
1615    pub name: String,
1616    /// Human-readable title for display purposes
1617    pub title: Option<String>,
1618    /// Optional description
1619    pub description: Option<String>,
1620    /// Optional MIME type hint
1621    pub mime_type: Option<String>,
1622    /// Optional icons for display in user interfaces
1623    pub icons: Option<Vec<ToolIcon>>,
1624    /// Optional annotations (audience, priority hints)
1625    pub annotations: Option<ContentAnnotations>,
1626    /// Compiled regex for matching URIs
1627    pattern: regex::Regex,
1628    /// Variable names in order of appearance
1629    variables: Vec<String>,
1630    /// Handler for reading matched resources
1631    handler: Option<Arc<dyn ResourceTemplateHandler>>,
1632    #[cfg(feature = "stateless")]
1633    mrtr_handler: Option<Arc<dyn MrtrResourceTemplateHandler>>,
1634}
1635
1636impl Clone for ResourceTemplate {
1637    fn clone(&self) -> Self {
1638        Self {
1639            uri_template: self.uri_template.clone(),
1640            name: self.name.clone(),
1641            title: self.title.clone(),
1642            description: self.description.clone(),
1643            mime_type: self.mime_type.clone(),
1644            icons: self.icons.clone(),
1645            annotations: self.annotations.clone(),
1646            pattern: self.pattern.clone(),
1647            variables: self.variables.clone(),
1648            handler: self.handler.clone(),
1649            #[cfg(feature = "stateless")]
1650            mrtr_handler: self.mrtr_handler.clone(),
1651        }
1652    }
1653}
1654
1655impl std::fmt::Debug for ResourceTemplate {
1656    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1657        f.debug_struct("ResourceTemplate")
1658            .field("uri_template", &self.uri_template)
1659            .field("name", &self.name)
1660            .field("title", &self.title)
1661            .field("description", &self.description)
1662            .field("mime_type", &self.mime_type)
1663            .field("icons", &self.icons)
1664            .field("variables", &self.variables)
1665            .finish_non_exhaustive()
1666    }
1667}
1668
1669impl ResourceTemplate {
1670    /// Create a new resource template builder
1671    pub fn builder(uri_template: impl Into<String>) -> ResourceTemplateBuilder {
1672        ResourceTemplateBuilder::new(uri_template)
1673    }
1674
1675    /// Get the template definition for resources/templates/list
1676    pub fn definition(&self) -> ResourceTemplateDefinition {
1677        ResourceTemplateDefinition {
1678            uri_template: self.uri_template.clone(),
1679            name: self.name.clone(),
1680            title: self.title.clone(),
1681            description: self.description.clone(),
1682            mime_type: self.mime_type.clone(),
1683            icons: self.icons.clone(),
1684            annotations: self.annotations.clone(),
1685            arguments: Vec::new(),
1686            meta: None,
1687        }
1688    }
1689
1690    /// Check if a URI matches this template and extract variables
1691    ///
1692    /// Returns `Some(HashMap)` with extracted variables if the URI matches,
1693    /// `None` if it doesn't match.
1694    pub fn match_uri(&self, uri: &str) -> Option<HashMap<String, String>> {
1695        self.pattern.captures(uri).map(|caps| {
1696            self.variables
1697                .iter()
1698                .enumerate()
1699                .filter_map(|(i, name)| {
1700                    caps.get(i + 1)
1701                        .map(|m| (name.clone(), m.as_str().to_string()))
1702                })
1703                .collect()
1704        })
1705    }
1706
1707    /// Read a resource at the given URI using this template's handler
1708    ///
1709    /// # Arguments
1710    ///
1711    /// * `uri` - The actual URI being read
1712    /// * `variables` - Variables extracted from matching the URI against the template
1713    pub fn read(
1714        &self,
1715        uri: &str,
1716        variables: HashMap<String, String>,
1717    ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1718        match &self.handler {
1719            Some(handler) => handler.read(uri, variables),
1720            None => Box::pin(async {
1721                Err(Error::invalid_params(
1722                    "MRTR resource template requires read_outcome_with_context",
1723                ))
1724            }),
1725        }
1726    }
1727
1728    /// Read a matched resource while preserving an SEP-2322 continuation.
1729    pub fn read_outcome_with_context(
1730        &self,
1731        ctx: RequestContext,
1732        uri: &str,
1733        variables: HashMap<String, String>,
1734    ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>> {
1735        let _ = &ctx;
1736        #[cfg(feature = "stateless")]
1737        if let Some(handler) = &self.mrtr_handler {
1738            return handler.read(ctx, uri, variables);
1739        }
1740        match &self.handler {
1741            Some(handler) => {
1742                let handler = handler.clone();
1743                let uri = uri.to_string();
1744                Box::pin(async move {
1745                    handler
1746                        .read(&uri, variables)
1747                        .await
1748                        .map(RequestOutcome::Complete)
1749                })
1750            }
1751            None => Box::pin(async {
1752                Err(Error::invalid_params(
1753                    "resource template has neither a complete nor MRTR handler",
1754                ))
1755            }),
1756        }
1757    }
1758}
1759
1760/// Builder for creating resource templates
1761///
1762/// # Example
1763///
1764/// ```rust
1765/// use tower_mcp::resource::ResourceTemplateBuilder;
1766/// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
1767/// use std::collections::HashMap;
1768///
1769/// let template = ResourceTemplateBuilder::new("db://users/{id}")
1770///     .name("User Records")
1771///     .description("Access user records by ID")
1772///     .handler(|uri: String, vars: HashMap<String, String>| async move {
1773///         let id = vars.get("id").unwrap();
1774///         Ok(ReadResourceResult {
1775///             contents: vec![ResourceContent {
1776///                 uri,
1777///                 mime_type: Some("application/json".to_string()),
1778///                 text: Some(format!(r#"{{"id": "{}"}}"#, id)),
1779///                 blob: None,
1780///                 meta: None,
1781///             }],
1782///             meta: None,
1783///             ..Default::default()
1784///         })
1785///     });
1786/// ```
1787pub struct ResourceTemplateBuilder {
1788    uri_template: String,
1789    name: Option<String>,
1790    title: Option<String>,
1791    description: Option<String>,
1792    mime_type: Option<String>,
1793    icons: Option<Vec<ToolIcon>>,
1794    annotations: Option<ContentAnnotations>,
1795}
1796
1797impl ResourceTemplateBuilder {
1798    /// Create a new builder with the given URI template
1799    ///
1800    /// # URI Template Syntax
1801    ///
1802    /// Templates use RFC 6570 Level 1 syntax with simple variable expansion:
1803    /// - `{varname}` - Matches any non-slash characters
1804    ///
1805    /// # Examples
1806    ///
1807    /// - `file:///{path}` - Matches `file:///README.md`
1808    /// - `db://users/{id}` - Matches `db://users/123`
1809    /// - `api://v1/{resource}/{id}` - Matches `api://v1/posts/456`
1810    pub fn new(uri_template: impl Into<String>) -> Self {
1811        Self {
1812            uri_template: uri_template.into(),
1813            name: None,
1814            title: None,
1815            description: None,
1816            mime_type: None,
1817            icons: None,
1818            annotations: None,
1819        }
1820    }
1821
1822    /// Set the human-readable name for this template
1823    pub fn name(mut self, name: impl Into<String>) -> Self {
1824        self.name = Some(name.into());
1825        self
1826    }
1827
1828    /// Set a human-readable title for the template
1829    pub fn title(mut self, title: impl Into<String>) -> Self {
1830        self.title = Some(title.into());
1831        self
1832    }
1833
1834    /// Set the description for this template
1835    pub fn description(mut self, description: impl Into<String>) -> Self {
1836        self.description = Some(description.into());
1837        self
1838    }
1839
1840    /// Set the MIME type hint for resources from this template
1841    pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
1842        self.mime_type = Some(mime_type.into());
1843        self
1844    }
1845
1846    /// Add an icon for the template
1847    pub fn icon(mut self, src: impl Into<String>) -> Self {
1848        self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1849            src: src.into(),
1850            mime_type: None,
1851            sizes: None,
1852            theme: None,
1853        });
1854        self
1855    }
1856
1857    /// Add an icon with metadata
1858    pub fn icon_with_meta(
1859        mut self,
1860        src: impl Into<String>,
1861        mime_type: Option<String>,
1862        sizes: Option<Vec<String>>,
1863    ) -> Self {
1864        self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1865            src: src.into(),
1866            mime_type,
1867            sizes,
1868            theme: None,
1869        });
1870        self
1871    }
1872
1873    /// Set annotations (audience, priority hints) for this resource template
1874    pub fn annotations(mut self, annotations: ContentAnnotations) -> Self {
1875        self.annotations = Some(annotations);
1876        self
1877    }
1878
1879    /// Set the handler function for reading template resources.
1880    ///
1881    /// The handler receives:
1882    /// - `uri`: The full URI being read
1883    /// - `variables`: A map of variable names to their values extracted from the URI
1884    ///
1885    /// # Panics
1886    ///
1887    /// Panics if the URI template produces an invalid regex pattern. For a
1888    /// non-panicking alternative (useful with dynamic/user-supplied templates),
1889    /// use [`try_handler`](Self::try_handler).
1890    pub fn handler<F, Fut>(self, handler: F) -> ResourceTemplate
1891    where
1892        F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1893        Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1894    {
1895        self.try_handler(handler).unwrap_or_else(|e| {
1896            panic!("Invalid URI template: {e}");
1897        })
1898    }
1899
1900    /// Set the handler function for reading template resources, returning an
1901    /// error if the URI template is invalid.
1902    ///
1903    /// This is the fallible version of [`handler`](Self::handler), suitable for
1904    /// use with dynamically created templates where the URI pattern may come
1905    /// from user input.
1906    ///
1907    /// The handler receives:
1908    /// - `uri`: The full URI being read
1909    /// - `variables`: A map of variable names to their values extracted from the URI
1910    pub fn try_handler<F, Fut>(self, handler: F) -> std::result::Result<ResourceTemplate, Error>
1911    where
1912        F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1913        Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1914    {
1915        let (pattern, variables) = compile_uri_template(&self.uri_template)?;
1916        let name = self.name.unwrap_or_else(|| self.uri_template.clone());
1917
1918        Ok(ResourceTemplate {
1919            uri_template: self.uri_template,
1920            name,
1921            title: self.title,
1922            description: self.description,
1923            mime_type: self.mime_type,
1924            icons: self.icons,
1925            annotations: self.annotations,
1926            pattern,
1927            variables,
1928            handler: Some(Arc::new(FnTemplateHandler { handler })),
1929            #[cfg(feature = "stateless")]
1930            mrtr_handler: None,
1931        })
1932    }
1933
1934    /// Set an SEP-2322-aware template handler that may require client input.
1935    ///
1936    /// # Panics
1937    ///
1938    /// Panics if the URI template produces an invalid regex pattern. Use
1939    /// [`try_mrtr_handler`](Self::try_mrtr_handler) for a fallible variant.
1940    #[cfg(feature = "stateless")]
1941    pub fn mrtr_handler<F, Fut>(self, handler: F) -> ResourceTemplate
1942    where
1943        F: Fn(RequestContext, String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1944        Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
1945    {
1946        self.try_mrtr_handler(handler)
1947            .unwrap_or_else(|error| panic!("Invalid URI template: {error}"))
1948    }
1949
1950    /// Fallible variant of [`mrtr_handler`](Self::mrtr_handler).
1951    #[cfg(feature = "stateless")]
1952    pub fn try_mrtr_handler<F, Fut>(
1953        self,
1954        handler: F,
1955    ) -> std::result::Result<ResourceTemplate, Error>
1956    where
1957        F: Fn(RequestContext, String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1958        Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
1959    {
1960        let (pattern, variables) = compile_uri_template(&self.uri_template)?;
1961        let name = self.name.unwrap_or_else(|| self.uri_template.clone());
1962
1963        Ok(ResourceTemplate {
1964            uri_template: self.uri_template,
1965            name,
1966            title: self.title,
1967            description: self.description,
1968            mime_type: self.mime_type,
1969            icons: self.icons,
1970            annotations: self.annotations,
1971            pattern,
1972            variables,
1973            handler: None,
1974            mrtr_handler: Some(Arc::new(MrtrFnTemplateHandler { handler })),
1975        })
1976    }
1977}
1978
1979/// Handler wrapping a function for templates
1980struct FnTemplateHandler<F> {
1981    handler: F,
1982}
1983
1984impl<F, Fut> ResourceTemplateHandler for FnTemplateHandler<F>
1985where
1986    F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1987    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1988{
1989    fn read(
1990        &self,
1991        uri: &str,
1992        variables: HashMap<String, String>,
1993    ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1994        let uri = uri.to_string();
1995        Box::pin((self.handler)(uri, variables))
1996    }
1997}
1998
1999#[cfg(feature = "stateless")]
2000struct MrtrFnTemplateHandler<F> {
2001    handler: F,
2002}
2003
2004#[cfg(feature = "stateless")]
2005impl<F, Fut> MrtrResourceTemplateHandler for MrtrFnTemplateHandler<F>
2006where
2007    F: Fn(RequestContext, String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
2008    Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
2009{
2010    fn read(
2011        &self,
2012        ctx: RequestContext,
2013        uri: &str,
2014        variables: HashMap<String, String>,
2015    ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>> {
2016        Box::pin((self.handler)(ctx, uri.to_string(), variables))
2017    }
2018}
2019
2020/// Compile a URI template into a regex pattern and extract variable names.
2021///
2022/// Supports RFC 6570 Level 1 (simple expansion):
2023/// - `{var}` matches any characters except `/`
2024/// - `{+var}` matches any characters including `/` (reserved expansion)
2025///
2026/// Returns the compiled regex and a list of variable names in order,
2027/// or an error if the template produces an invalid regex pattern.
2028fn compile_uri_template(template: &str) -> std::result::Result<(regex::Regex, Vec<String>), Error> {
2029    let mut pattern = String::from("^");
2030    let mut variables = Vec::new();
2031
2032    let mut chars = template.chars().peekable();
2033    while let Some(c) = chars.next() {
2034        if c == '{' {
2035            // Check for + prefix (reserved expansion)
2036            let is_reserved = chars.peek() == Some(&'+');
2037            if is_reserved {
2038                chars.next();
2039            }
2040
2041            // Collect variable name
2042            let var_name: String = chars.by_ref().take_while(|&c| c != '}').collect();
2043            variables.push(var_name);
2044
2045            // Choose pattern based on expansion type
2046            if is_reserved {
2047                // Reserved expansion - match anything
2048                pattern.push_str("(.+)");
2049            } else {
2050                // Simple expansion - match non-slash characters
2051                pattern.push_str("([^/]+)");
2052            }
2053        } else {
2054            // Escape regex special characters
2055            match c {
2056                '.' | '+' | '*' | '?' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|'
2057                | '\\' => {
2058                    pattern.push('\\');
2059                    pattern.push(c);
2060                }
2061                _ => pattern.push(c),
2062            }
2063        }
2064    }
2065
2066    pattern.push('$');
2067
2068    let regex = regex::Regex::new(&pattern)
2069        .map_err(|e| Error::Internal(format!("Invalid URI template '{}': {}", template, e)))?;
2070
2071    Ok((regex, variables))
2072}
2073
2074#[cfg(test)]
2075mod tests {
2076    use super::*;
2077    use std::time::Duration;
2078    use tower::timeout::TimeoutLayer;
2079
2080    #[tokio::test]
2081    async fn test_builder_resource() {
2082        let resource = ResourceBuilder::new("file:///test.txt")
2083            .name("Test File")
2084            .description("A test file")
2085            .text("Hello, World!");
2086
2087        assert_eq!(resource.uri, "file:///test.txt");
2088        assert_eq!(resource.name, "Test File");
2089        assert_eq!(resource.description.as_deref(), Some("A test file"));
2090
2091        let result = resource.read().await;
2092        assert_eq!(result.contents.len(), 1);
2093        assert_eq!(result.contents[0].text.as_deref(), Some("Hello, World!"));
2094    }
2095
2096    #[cfg(feature = "stateless")]
2097    #[tokio::test]
2098    async fn test_mrtr_builder_preserves_input_required_outcome() {
2099        let resource = ResourceBuilder::new("test://continue")
2100            .mrtr_handler(|_ctx| async move {
2101                Ok(RequestOutcome::input_required(
2102                    crate::protocol::InputRequiredResult::new().with_request_state("signed-state"),
2103                ))
2104            })
2105            .build();
2106
2107        let outcome = resource
2108            .read_outcome_with_context(RequestContext::new(crate::protocol::RequestId::Number(1)))
2109            .await
2110            .unwrap();
2111        assert_eq!(
2112            outcome
2113                .as_input_required()
2114                .and_then(|result| result.request_state.as_deref()),
2115            Some("signed-state")
2116        );
2117    }
2118
2119    #[cfg(feature = "stateless")]
2120    #[tokio::test]
2121    async fn mrtr_resource_composes_middleware() {
2122        let resource = ResourceBuilder::new("test://layered-continue")
2123            .mrtr_handler(|_ctx| async move {
2124                Ok(RequestOutcome::input_required(
2125                    crate::protocol::InputRequiredResult::new().with_request_state("layered-state"),
2126                ))
2127            })
2128            .layer(TimeoutLayer::new(Duration::from_secs(1)))
2129            .build();
2130
2131        let outcome = resource
2132            .read_outcome_with_context(RequestContext::new(crate::protocol::RequestId::Number(2)))
2133            .await
2134            .unwrap();
2135        assert_eq!(
2136            outcome
2137                .as_input_required()
2138                .and_then(|result| result.request_state.as_deref()),
2139            Some("layered-state")
2140        );
2141    }
2142
2143    #[cfg(feature = "stateless")]
2144    #[tokio::test]
2145    async fn test_mrtr_template_preserves_context_and_input_required_outcome() {
2146        let template = ResourceTemplateBuilder::new("test://items/{id}").mrtr_handler(
2147            |ctx, uri, variables| async move {
2148                assert_eq!(ctx.request_state(), Some("prior-state"));
2149                assert_eq!(uri, "test://items/42");
2150                assert_eq!(variables.get("id").map(String::as_str), Some("42"));
2151                Ok(RequestOutcome::input_required(
2152                    crate::protocol::InputRequiredResult::new().with_request_state("next-state"),
2153                ))
2154            },
2155        );
2156        let variables = template.match_uri("test://items/42").unwrap();
2157        let mut ctx = RequestContext::new(crate::protocol::RequestId::Number(1));
2158        ctx.extensions_mut().insert(crate::mrtr::MrtrRequest::new(
2159            None,
2160            Some("prior-state".into()),
2161        ));
2162
2163        let outcome = template
2164            .read_outcome_with_context(ctx, "test://items/42", variables)
2165            .await
2166            .unwrap();
2167        assert_eq!(
2168            outcome
2169                .as_input_required()
2170                .and_then(|result| result.request_state.as_deref()),
2171            Some("next-state")
2172        );
2173    }
2174
2175    #[tokio::test]
2176    async fn test_json_resource() {
2177        let resource = ResourceBuilder::new("file:///config.json")
2178            .name("Config")
2179            .json(serde_json::json!({"key": "value"}));
2180
2181        assert_eq!(resource.mime_type.as_deref(), Some("application/json"));
2182
2183        let result = resource.read().await;
2184        assert!(result.contents[0].text.as_ref().unwrap().contains("key"));
2185    }
2186
2187    #[tokio::test]
2188    async fn test_handler_resource() {
2189        let resource = ResourceBuilder::new("memory://counter")
2190            .name("Counter")
2191            .handler(|| async {
2192                Ok(ReadResourceResult {
2193                    contents: vec![ResourceContent {
2194                        uri: "memory://counter".to_string(),
2195                        mime_type: Some("text/plain".to_string()),
2196                        text: Some("42".to_string()),
2197                        blob: None,
2198                        meta: None,
2199                    }],
2200                    meta: None,
2201                    ..Default::default()
2202                })
2203            })
2204            .build();
2205
2206        let result = resource.read().await;
2207        assert_eq!(result.contents[0].text.as_deref(), Some("42"));
2208    }
2209
2210    #[tokio::test]
2211    async fn test_handler_resource_with_layer() {
2212        let resource = ResourceBuilder::new("file:///with-timeout.txt")
2213            .name("Resource with Timeout")
2214            .handler(|| async {
2215                Ok(ReadResourceResult {
2216                    contents: vec![ResourceContent {
2217                        uri: "file:///with-timeout.txt".to_string(),
2218                        mime_type: Some("text/plain".to_string()),
2219                        text: Some("content".to_string()),
2220                        blob: None,
2221                        meta: None,
2222                    }],
2223                    meta: None,
2224                    ..Default::default()
2225                })
2226            })
2227            .layer(TimeoutLayer::new(Duration::from_secs(30)))
2228            .build();
2229
2230        let result = resource.read().await;
2231        assert_eq!(result.contents[0].text.as_deref(), Some("content"));
2232    }
2233
2234    #[tokio::test]
2235    async fn test_handler_resource_with_timeout_error() {
2236        let resource = ResourceBuilder::new("file:///slow.txt")
2237            .name("Slow Resource")
2238            .handler(|| async {
2239                // Sleep much longer than timeout to ensure timeout fires reliably in CI
2240                tokio::time::sleep(Duration::from_secs(1)).await;
2241                Ok(ReadResourceResult {
2242                    contents: vec![ResourceContent {
2243                        uri: "file:///slow.txt".to_string(),
2244                        mime_type: Some("text/plain".to_string()),
2245                        text: Some("content".to_string()),
2246                        blob: None,
2247                        meta: None,
2248                    }],
2249                    meta: None,
2250                    ..Default::default()
2251                })
2252            })
2253            .layer(TimeoutLayer::new(Duration::from_millis(50)))
2254            .build();
2255
2256        let result = resource.read().await;
2257        // Timeout error should be caught and converted to error content
2258        assert!(
2259            result.contents[0]
2260                .text
2261                .as_ref()
2262                .unwrap()
2263                .contains("Error reading resource")
2264        );
2265    }
2266
2267    #[tokio::test]
2268    async fn test_context_aware_handler() {
2269        let resource = ResourceBuilder::new("file:///ctx.txt")
2270            .name("Context Resource")
2271            .handler_with_context(|_ctx: RequestContext| async {
2272                Ok(ReadResourceResult {
2273                    contents: vec![ResourceContent {
2274                        uri: "file:///ctx.txt".to_string(),
2275                        mime_type: Some("text/plain".to_string()),
2276                        text: Some("context aware".to_string()),
2277                        blob: None,
2278                        meta: None,
2279                    }],
2280                    meta: None,
2281                    ..Default::default()
2282                })
2283            })
2284            .build();
2285
2286        let result = resource.read().await;
2287        assert_eq!(result.contents[0].text.as_deref(), Some("context aware"));
2288    }
2289
2290    #[tokio::test]
2291    async fn test_context_aware_handler_with_layer() {
2292        let resource = ResourceBuilder::new("file:///ctx-layer.txt")
2293            .name("Context Resource with Layer")
2294            .handler_with_context(|_ctx: RequestContext| async {
2295                Ok(ReadResourceResult {
2296                    contents: vec![ResourceContent {
2297                        uri: "file:///ctx-layer.txt".to_string(),
2298                        mime_type: Some("text/plain".to_string()),
2299                        text: Some("context with layer".to_string()),
2300                        blob: None,
2301                        meta: None,
2302                    }],
2303                    meta: None,
2304                    ..Default::default()
2305                })
2306            })
2307            .layer(TimeoutLayer::new(Duration::from_secs(30)))
2308            .build();
2309
2310        let result = resource.read().await;
2311        assert_eq!(
2312            result.contents[0].text.as_deref(),
2313            Some("context with layer")
2314        );
2315    }
2316
2317    #[tokio::test]
2318    async fn test_trait_resource() {
2319        struct TestResource;
2320
2321        impl McpResource for TestResource {
2322            const URI: &'static str = "test://resource";
2323            const NAME: &'static str = "Test";
2324            const DESCRIPTION: Option<&'static str> = Some("A test resource");
2325            const MIME_TYPE: Option<&'static str> = Some("text/plain");
2326
2327            async fn read(&self) -> Result<ReadResourceResult> {
2328                Ok(ReadResourceResult {
2329                    contents: vec![ResourceContent {
2330                        uri: Self::URI.to_string(),
2331                        mime_type: Self::MIME_TYPE.map(|s| s.to_string()),
2332                        text: Some("test content".to_string()),
2333                        blob: None,
2334                        meta: None,
2335                    }],
2336                    meta: None,
2337                    ..Default::default()
2338                })
2339            }
2340        }
2341
2342        let resource = TestResource.into_resource();
2343        assert_eq!(resource.uri, "test://resource");
2344        assert_eq!(resource.name, "Test");
2345
2346        let result = resource.read().await;
2347        assert_eq!(result.contents[0].text.as_deref(), Some("test content"));
2348    }
2349
2350    #[test]
2351    fn test_resource_definition() {
2352        let resource = ResourceBuilder::new("file:///test.txt")
2353            .name("Test")
2354            .description("Description")
2355            .mime_type("text/plain")
2356            .text("content");
2357
2358        let def = resource.definition();
2359        assert_eq!(def.uri, "file:///test.txt");
2360        assert_eq!(def.name, "Test");
2361        assert_eq!(def.description.as_deref(), Some("Description"));
2362        assert_eq!(def.mime_type.as_deref(), Some("text/plain"));
2363    }
2364
2365    #[test]
2366    fn test_resource_request_new() {
2367        let ctx = RequestContext::new(crate::protocol::RequestId::Number(1));
2368        let req = ResourceRequest::new(ctx, "file:///test.txt".to_string());
2369        assert_eq!(req.uri, "file:///test.txt");
2370    }
2371
2372    #[test]
2373    fn test_resource_catch_error_clone() {
2374        let handler = FnHandler {
2375            handler: || async {
2376                Ok::<_, Error>(ReadResourceResult {
2377                    contents: vec![],
2378                    meta: None,
2379                    ..Default::default()
2380                })
2381            },
2382        };
2383        let service = ResourceHandlerService::new(handler);
2384        let catch_error = ResourceCatchError::new(service);
2385        let _clone = catch_error.clone();
2386    }
2387
2388    #[test]
2389    fn test_resource_catch_error_debug() {
2390        let handler = FnHandler {
2391            handler: || async {
2392                Ok::<_, Error>(ReadResourceResult {
2393                    contents: vec![],
2394                    meta: None,
2395                    ..Default::default()
2396                })
2397            },
2398        };
2399        let service = ResourceHandlerService::new(handler);
2400        let catch_error = ResourceCatchError::new(service);
2401        let debug = format!("{:?}", catch_error);
2402        assert!(debug.contains("ResourceCatchError"));
2403    }
2404
2405    // =========================================================================
2406    // Resource Template Tests
2407    // =========================================================================
2408
2409    #[test]
2410    fn test_compile_uri_template_simple() {
2411        let (regex, vars) = compile_uri_template("file:///{path}").unwrap();
2412        assert_eq!(vars, vec!["path"]);
2413        assert!(regex.is_match("file:///README.md"));
2414        assert!(!regex.is_match("file:///foo/bar")); // no slashes in simple expansion
2415    }
2416
2417    #[test]
2418    fn test_compile_uri_template_multiple_vars() {
2419        let (regex, vars) = compile_uri_template("api://v1/{resource}/{id}").unwrap();
2420        assert_eq!(vars, vec!["resource", "id"]);
2421        assert!(regex.is_match("api://v1/users/123"));
2422        assert!(regex.is_match("api://v1/posts/abc"));
2423        assert!(!regex.is_match("api://v1/users")); // missing id
2424    }
2425
2426    #[test]
2427    fn test_compile_uri_template_reserved_expansion() {
2428        let (regex, vars) = compile_uri_template("file:///{+path}").unwrap();
2429        assert_eq!(vars, vec!["path"]);
2430        assert!(regex.is_match("file:///README.md"));
2431        assert!(regex.is_match("file:///foo/bar/baz.txt")); // slashes allowed
2432    }
2433
2434    #[test]
2435    fn test_compile_uri_template_special_chars() {
2436        let (regex, vars) = compile_uri_template("http://example.com/api?query={q}").unwrap();
2437        assert_eq!(vars, vec!["q"]);
2438        assert!(regex.is_match("http://example.com/api?query=hello"));
2439    }
2440
2441    #[test]
2442    fn test_resource_template_match_uri() {
2443        let template = ResourceTemplateBuilder::new("db://users/{id}")
2444            .name("User Records")
2445            .handler(|uri: String, vars: HashMap<String, String>| async move {
2446                Ok(ReadResourceResult {
2447                    contents: vec![ResourceContent {
2448                        uri,
2449                        mime_type: None,
2450                        text: Some(format!("User {}", vars.get("id").unwrap())),
2451                        blob: None,
2452                        meta: None,
2453                    }],
2454                    meta: None,
2455                    ..Default::default()
2456                })
2457            });
2458
2459        // Test matching
2460        let vars = template.match_uri("db://users/123").unwrap();
2461        assert_eq!(vars.get("id"), Some(&"123".to_string()));
2462
2463        // Test non-matching
2464        assert!(template.match_uri("db://posts/123").is_none());
2465        assert!(template.match_uri("db://users").is_none());
2466    }
2467
2468    #[test]
2469    fn test_resource_template_match_multiple_vars() {
2470        let template = ResourceTemplateBuilder::new("api://{version}/{resource}/{id}")
2471            .name("API Resources")
2472            .handler(|uri: String, _vars: HashMap<String, String>| async move {
2473                Ok(ReadResourceResult {
2474                    contents: vec![ResourceContent {
2475                        uri,
2476                        mime_type: None,
2477                        text: None,
2478                        blob: None,
2479                        meta: None,
2480                    }],
2481                    meta: None,
2482                    ..Default::default()
2483                })
2484            });
2485
2486        let vars = template.match_uri("api://v2/users/abc-123").unwrap();
2487        assert_eq!(vars.get("version"), Some(&"v2".to_string()));
2488        assert_eq!(vars.get("resource"), Some(&"users".to_string()));
2489        assert_eq!(vars.get("id"), Some(&"abc-123".to_string()));
2490    }
2491
2492    #[tokio::test]
2493    async fn test_resource_template_read() {
2494        let template = ResourceTemplateBuilder::new("file:///{path}")
2495            .name("Files")
2496            .mime_type("text/plain")
2497            .handler(|uri: String, vars: HashMap<String, String>| async move {
2498                let path = vars.get("path").unwrap().clone();
2499                Ok(ReadResourceResult {
2500                    contents: vec![ResourceContent {
2501                        uri,
2502                        mime_type: Some("text/plain".to_string()),
2503                        text: Some(format!("Contents of {}", path)),
2504                        blob: None,
2505                        meta: None,
2506                    }],
2507                    meta: None,
2508                    ..Default::default()
2509                })
2510            });
2511
2512        let vars = template.match_uri("file:///README.md").unwrap();
2513        let result = template.read("file:///README.md", vars).await.unwrap();
2514
2515        assert_eq!(result.contents.len(), 1);
2516        assert_eq!(result.contents[0].uri, "file:///README.md");
2517        assert_eq!(
2518            result.contents[0].text.as_deref(),
2519            Some("Contents of README.md")
2520        );
2521    }
2522
2523    #[test]
2524    fn test_resource_template_definition() {
2525        let template = ResourceTemplateBuilder::new("db://records/{id}")
2526            .name("Database Records")
2527            .description("Access database records by ID")
2528            .mime_type("application/json")
2529            .handler(|uri: String, _vars: HashMap<String, String>| async move {
2530                Ok(ReadResourceResult {
2531                    contents: vec![ResourceContent {
2532                        uri,
2533                        mime_type: None,
2534                        text: None,
2535                        blob: None,
2536                        meta: None,
2537                    }],
2538                    meta: None,
2539                    ..Default::default()
2540                })
2541            });
2542
2543        let def = template.definition();
2544        assert_eq!(def.uri_template, "db://records/{id}");
2545        assert_eq!(def.name, "Database Records");
2546        assert_eq!(
2547            def.description.as_deref(),
2548            Some("Access database records by ID")
2549        );
2550        assert_eq!(def.mime_type.as_deref(), Some("application/json"));
2551    }
2552
2553    #[test]
2554    fn test_resource_template_reserved_path() {
2555        let template = ResourceTemplateBuilder::new("file:///{+path}")
2556            .name("Files with subpaths")
2557            .handler(|uri: String, _vars: HashMap<String, String>| async move {
2558                Ok(ReadResourceResult {
2559                    contents: vec![ResourceContent {
2560                        uri,
2561                        mime_type: None,
2562                        text: None,
2563                        blob: None,
2564                        meta: None,
2565                    }],
2566                    meta: None,
2567                    ..Default::default()
2568                })
2569            });
2570
2571        // Reserved expansion should match slashes
2572        let vars = template.match_uri("file:///src/lib/utils.rs").unwrap();
2573        assert_eq!(vars.get("path"), Some(&"src/lib/utils.rs".to_string()));
2574    }
2575
2576    #[test]
2577    fn test_resource_annotations() {
2578        use crate::protocol::{ContentAnnotations, ContentRole};
2579
2580        let annotations = ContentAnnotations {
2581            audience: Some(vec![ContentRole::User]),
2582            priority: Some(0.8),
2583            last_modified: None,
2584        };
2585
2586        let resource = ResourceBuilder::new("file:///important.txt")
2587            .name("Important File")
2588            .annotations(annotations.clone())
2589            .text("content");
2590
2591        let def = resource.definition();
2592        assert!(def.annotations.is_some());
2593        let ann = def.annotations.unwrap();
2594        assert_eq!(ann.priority, Some(0.8));
2595        assert_eq!(ann.audience.unwrap(), vec![ContentRole::User]);
2596    }
2597
2598    #[test]
2599    fn test_resource_template_annotations() {
2600        use crate::protocol::{ContentAnnotations, ContentRole};
2601
2602        let annotations = ContentAnnotations {
2603            audience: Some(vec![ContentRole::Assistant]),
2604            priority: Some(0.5),
2605            last_modified: None,
2606        };
2607
2608        let template = ResourceTemplateBuilder::new("db://users/{id}")
2609            .name("Users")
2610            .annotations(annotations)
2611            .handler(|uri: String, _vars: HashMap<String, String>| async move {
2612                Ok(ReadResourceResult {
2613                    contents: vec![ResourceContent {
2614                        uri,
2615                        mime_type: None,
2616                        text: Some("data".to_string()),
2617                        blob: None,
2618                        meta: None,
2619                    }],
2620                    meta: None,
2621                    ..Default::default()
2622                })
2623            });
2624
2625        let def = template.definition();
2626        assert!(def.annotations.is_some());
2627        let ann = def.annotations.unwrap();
2628        assert_eq!(ann.priority, Some(0.5));
2629        assert_eq!(ann.audience.unwrap(), vec![ContentRole::Assistant]);
2630    }
2631
2632    #[test]
2633    fn test_resource_no_annotations_by_default() {
2634        let resource = ResourceBuilder::new("file:///test.txt")
2635            .name("Test")
2636            .text("content");
2637
2638        let def = resource.definition();
2639        assert!(def.annotations.is_none());
2640    }
2641
2642    #[test]
2643    fn test_try_handler_success() {
2644        let result = ResourceTemplateBuilder::new("db://users/{id}")
2645            .name("Users")
2646            .try_handler(|uri: String, _vars: HashMap<String, String>| async move {
2647                Ok(ReadResourceResult {
2648                    contents: vec![ResourceContent {
2649                        uri,
2650                        mime_type: None,
2651                        text: Some("ok".to_string()),
2652                        blob: None,
2653                        meta: None,
2654                    }],
2655                    meta: None,
2656                    ..Default::default()
2657                })
2658            });
2659
2660        assert!(result.is_ok());
2661        let template = result.unwrap();
2662        assert_eq!(template.uri_template, "db://users/{id}");
2663    }
2664
2665    #[test]
2666    fn test_compile_uri_template_returns_result() {
2667        // Valid templates should succeed
2668        assert!(compile_uri_template("file:///{path}").is_ok());
2669        assert!(compile_uri_template("api://v1/{resource}/{id}").is_ok());
2670        assert!(compile_uri_template("file:///{+path}").is_ok());
2671        assert!(compile_uri_template("no-vars").is_ok());
2672        assert!(compile_uri_template("").is_ok());
2673    }
2674}