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//!         })
35//!     })
36//!     .layer(TimeoutLayer::new(Duration::from_secs(30)))
37//!     .build();
38//! ```
39//!
40//! # Resource Templates
41//!
42//! Resource templates allow servers to expose parameterized resources using URI templates.
43//! When a client requests `resources/read` with a URI matching a template, the server
44//! extracts the variables and passes them to the handler.
45//!
46//! ```rust
47//! use tower_mcp::resource::ResourceTemplateBuilder;
48//! use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
49//! use std::collections::HashMap;
50//!
51//! let template = ResourceTemplateBuilder::new("file:///{path}")
52//!     .name("Project Files")
53//!     .description("Access files in the project directory")
54//!     .handler(|uri: String, vars: HashMap<String, String>| async move {
55//!         let path = vars.get("path").unwrap_or(&String::new()).clone();
56//!         Ok(ReadResourceResult {
57//!             contents: vec![ResourceContent {
58//!                 uri,
59//!                 mime_type: Some("text/plain".to_string()),
60//!                 text: Some(format!("Contents of {}", path)),
61//!                 blob: None,
62//!                 meta: None,
63//!             }],
64//!             meta: None,
65//!         })
66//!     });
67//! ```
68
69use std::collections::HashMap;
70use std::convert::Infallible;
71use std::fmt;
72use std::future::Future;
73use std::pin::Pin;
74use std::sync::Arc;
75use std::task::{Context, Poll};
76
77use pin_project_lite::pin_project;
78
79use tower::util::BoxCloneService;
80use tower_service::Service;
81
82use crate::context::RequestContext;
83use crate::error::{Error, Result};
84use crate::protocol::{
85    ContentAnnotations, ReadResourceResult, ResourceContent, ResourceDefinition,
86    ResourceTemplateDefinition, ToolIcon,
87};
88
89// =============================================================================
90// Service Types for Per-Resource Middleware
91// =============================================================================
92
93/// Request type for resource services.
94///
95/// Contains the request context (for progress reporting, cancellation, etc.)
96/// and the resource URI being read.
97#[derive(Debug, Clone)]
98pub struct ResourceRequest {
99    /// Request context for progress reporting, cancellation, and client requests
100    pub ctx: RequestContext,
101    /// The URI of the resource being read
102    pub uri: String,
103}
104
105impl ResourceRequest {
106    /// Create a new resource request
107    pub fn new(ctx: RequestContext, uri: String) -> Self {
108        Self { ctx, uri }
109    }
110}
111
112/// A boxed, cloneable resource service with `Error = Infallible`.
113///
114/// This is the internal service type that resources use. Middleware errors are
115/// caught and converted to error results, so the service never fails at the Tower level.
116pub type BoxResourceService = BoxCloneService<ResourceRequest, ReadResourceResult, Infallible>;
117
118/// Catches errors from the inner service and converts them to error results.
119///
120/// This wrapper ensures that middleware errors (e.g., timeouts, rate limits)
121/// and handler errors are converted to `Err(Error)` responses wrapped in
122/// `Ok`, rather than propagating as Tower service errors.
123#[doc(hidden)]
124pub struct ResourceCatchError<S> {
125    inner: S,
126}
127
128impl<S> ResourceCatchError<S> {
129    /// Create a new `ResourceCatchError` wrapping the given service.
130    pub fn new(inner: S) -> Self {
131        Self { inner }
132    }
133}
134
135impl<S: Clone> Clone for ResourceCatchError<S> {
136    fn clone(&self) -> Self {
137        Self {
138            inner: self.inner.clone(),
139        }
140    }
141}
142
143impl<S: fmt::Debug> fmt::Debug for ResourceCatchError<S> {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        f.debug_struct("ResourceCatchError")
146            .field("inner", &self.inner)
147            .finish()
148    }
149}
150
151pin_project! {
152    /// Future for [`ResourceCatchError`].
153    #[doc(hidden)]
154    pub struct ResourceCatchErrorFuture<F> {
155        #[pin]
156        inner: F,
157        uri: Option<String>,
158    }
159}
160
161impl<F, E> Future for ResourceCatchErrorFuture<F>
162where
163    F: Future<Output = std::result::Result<ReadResourceResult, E>>,
164    E: fmt::Display,
165{
166    type Output = std::result::Result<ReadResourceResult, Infallible>;
167
168    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
169        let this = self.project();
170        match this.inner.poll(cx) {
171            Poll::Pending => Poll::Pending,
172            Poll::Ready(Ok(result)) => Poll::Ready(Ok(result)),
173            Poll::Ready(Err(err)) => {
174                let uri = this.uri.take().unwrap_or_default();
175                Poll::Ready(Ok(ReadResourceResult {
176                    contents: vec![ResourceContent {
177                        uri,
178                        mime_type: Some("text/plain".to_string()),
179                        text: Some(format!("Error reading resource: {}", err)),
180                        blob: None,
181                        meta: None,
182                    }],
183                    meta: None,
184                }))
185            }
186        }
187    }
188}
189
190impl<S> Service<ResourceRequest> for ResourceCatchError<S>
191where
192    S: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
193    S::Error: fmt::Display + Send,
194    S::Future: Send,
195{
196    type Response = ReadResourceResult;
197    type Error = Infallible;
198    type Future = ResourceCatchErrorFuture<S::Future>;
199
200    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
201        // Map any readiness error to Infallible (we catch it on call)
202        match self.inner.poll_ready(cx) {
203            Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
204            Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
205            Poll::Pending => Poll::Pending,
206        }
207    }
208
209    fn call(&mut self, req: ResourceRequest) -> Self::Future {
210        let uri = req.uri.clone();
211        let fut = self.inner.call(req);
212
213        ResourceCatchErrorFuture {
214            inner: fut,
215            uri: Some(uri),
216        }
217    }
218}
219
220/// A boxed future for resource handlers
221pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
222
223/// Resource handler trait - the core abstraction for resource reading
224pub trait ResourceHandler: Send + Sync {
225    /// Read the resource contents
226    fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>>;
227
228    /// Read the resource with request context for progress/cancellation support
229    ///
230    /// The default implementation ignores the context and calls `read`.
231    /// Override this to receive progress/cancellation context.
232    fn read_with_context(&self, _ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
233        self.read()
234    }
235
236    /// Returns true if this handler uses context (for optimization)
237    fn uses_context(&self) -> bool {
238        false
239    }
240}
241
242/// Adapts a `ResourceHandler` to a Tower `Service<ResourceRequest>`.
243///
244/// This is an internal adapter that bridges the handler abstraction to the
245/// service abstraction, enabling middleware composition.
246struct ResourceHandlerService<H> {
247    handler: Arc<H>,
248}
249
250impl<H> ResourceHandlerService<H> {
251    fn new(handler: H) -> Self {
252        Self {
253            handler: Arc::new(handler),
254        }
255    }
256}
257
258impl<H> Clone for ResourceHandlerService<H> {
259    fn clone(&self) -> Self {
260        Self {
261            handler: self.handler.clone(),
262        }
263    }
264}
265
266impl<H> fmt::Debug for ResourceHandlerService<H> {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        f.debug_struct("ResourceHandlerService")
269            .finish_non_exhaustive()
270    }
271}
272
273impl<H> Service<ResourceRequest> for ResourceHandlerService<H>
274where
275    H: ResourceHandler + 'static,
276{
277    type Response = ReadResourceResult;
278    type Error = Error;
279    type Future =
280        Pin<Box<dyn Future<Output = std::result::Result<ReadResourceResult, Error>> + Send>>;
281
282    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
283        Poll::Ready(Ok(()))
284    }
285
286    fn call(&mut self, req: ResourceRequest) -> Self::Future {
287        let handler = self.handler.clone();
288        Box::pin(async move { handler.read_with_context(req.ctx).await })
289    }
290}
291
292/// A complete resource definition with service-based execution.
293///
294/// Resources are implemented as Tower services internally, enabling middleware
295/// composition via the builder's `.layer()` method. The service is wrapped
296/// in [`ResourceCatchError`] to convert any errors (from handlers or middleware)
297/// into error result responses.
298pub struct Resource {
299    /// Resource URI
300    pub uri: String,
301    /// Human-readable name
302    pub name: String,
303    /// Human-readable title for display purposes
304    pub title: Option<String>,
305    /// Optional description
306    pub description: Option<String>,
307    /// Optional MIME type
308    pub mime_type: Option<String>,
309    /// Optional icons for display in user interfaces
310    pub icons: Option<Vec<ToolIcon>>,
311    /// Optional size in bytes
312    pub size: Option<u64>,
313    /// Optional annotations (audience, priority hints)
314    pub annotations: Option<ContentAnnotations>,
315    /// The boxed service that reads the resource
316    service: BoxResourceService,
317}
318
319impl Clone for Resource {
320    fn clone(&self) -> Self {
321        Self {
322            uri: self.uri.clone(),
323            name: self.name.clone(),
324            title: self.title.clone(),
325            description: self.description.clone(),
326            mime_type: self.mime_type.clone(),
327            icons: self.icons.clone(),
328            size: self.size,
329            annotations: self.annotations.clone(),
330            service: self.service.clone(),
331        }
332    }
333}
334
335impl std::fmt::Debug for Resource {
336    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337        f.debug_struct("Resource")
338            .field("uri", &self.uri)
339            .field("name", &self.name)
340            .field("title", &self.title)
341            .field("description", &self.description)
342            .field("mime_type", &self.mime_type)
343            .field("icons", &self.icons)
344            .field("size", &self.size)
345            .field("annotations", &self.annotations)
346            .finish_non_exhaustive()
347    }
348}
349
350// SAFETY: BoxCloneService is Send + Sync (tower provides unsafe impl Sync),
351// and all other fields in Resource are Send + Sync.
352unsafe impl Send for Resource {}
353unsafe impl Sync for Resource {}
354
355impl Resource {
356    /// Create a new resource builder
357    pub fn builder(uri: impl Into<String>) -> ResourceBuilder {
358        ResourceBuilder::new(uri)
359    }
360
361    /// Get the resource definition for resources/list
362    pub fn definition(&self) -> ResourceDefinition {
363        ResourceDefinition {
364            uri: self.uri.clone(),
365            name: self.name.clone(),
366            title: self.title.clone(),
367            description: self.description.clone(),
368            mime_type: self.mime_type.clone(),
369            icons: self.icons.clone(),
370            size: self.size,
371            annotations: self.annotations.clone(),
372            meta: None,
373        }
374    }
375
376    /// Read the resource without context
377    ///
378    /// Creates a dummy request context. For full context support, use
379    /// [`read_with_context`](Self::read_with_context).
380    pub fn read(&self) -> BoxFuture<'static, ReadResourceResult> {
381        let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
382        self.read_with_context(ctx)
383    }
384
385    /// Read the resource with request context
386    ///
387    /// The context provides progress reporting, cancellation support, and
388    /// access to client requests (for sampling, etc.).
389    ///
390    /// # Note
391    ///
392    /// This method returns `ReadResourceResult` directly (not `Result<ReadResourceResult>`).
393    /// Any errors from the handler or middleware are converted to error responses
394    /// in the result contents.
395    pub fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'static, ReadResourceResult> {
396        use tower::ServiceExt;
397        let service = self.service.clone();
398        let uri = self.uri.clone();
399        Box::pin(async move {
400            // ServiceExt::oneshot properly handles poll_ready before call
401            // Service is Infallible, so unwrap is safe
402            service
403                .oneshot(ResourceRequest::new(ctx, uri))
404                .await
405                .unwrap()
406        })
407    }
408
409    /// Create a resource from a handler (internal helper)
410    #[allow(clippy::too_many_arguments)]
411    fn from_handler<H: ResourceHandler + 'static>(
412        uri: String,
413        name: String,
414        title: Option<String>,
415        description: Option<String>,
416        mime_type: Option<String>,
417        icons: Option<Vec<ToolIcon>>,
418        size: Option<u64>,
419        annotations: Option<ContentAnnotations>,
420        handler: H,
421    ) -> Self {
422        let handler_service = ResourceHandlerService::new(handler);
423        let catch_error = ResourceCatchError::new(handler_service);
424        let service = BoxCloneService::new(catch_error);
425
426        Self {
427            uri,
428            name,
429            title,
430            description,
431            mime_type,
432            icons,
433            size,
434            annotations,
435            service,
436        }
437    }
438}
439
440// =============================================================================
441// Builder API
442// =============================================================================
443
444/// Builder for creating resources with a fluent API
445///
446/// # Example
447///
448/// ```rust
449/// use tower_mcp::resource::ResourceBuilder;
450/// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
451///
452/// let resource = ResourceBuilder::new("file:///config.json")
453///     .name("Configuration")
454///     .description("Application configuration file")
455///     .mime_type("application/json")
456///     .handler(|| async {
457///         Ok(ReadResourceResult {
458///             contents: vec![ResourceContent {
459///                 uri: "file:///config.json".to_string(),
460///                 mime_type: Some("application/json".to_string()),
461///                 text: Some(r#"{"setting": "value"}"#.to_string()),
462///                 blob: None,
463///                 meta: None,
464///             }],
465///             meta: None,
466///         })
467///     })
468///     .build();
469///
470/// assert_eq!(resource.uri, "file:///config.json");
471/// ```
472pub struct ResourceBuilder {
473    uri: String,
474    name: Option<String>,
475    title: Option<String>,
476    description: Option<String>,
477    mime_type: Option<String>,
478    icons: Option<Vec<ToolIcon>>,
479    size: Option<u64>,
480    annotations: Option<ContentAnnotations>,
481}
482
483impl ResourceBuilder {
484    /// Create a new resource builder with the given URI.
485    pub fn new(uri: impl Into<String>) -> Self {
486        Self {
487            uri: uri.into(),
488            name: None,
489            title: None,
490            description: None,
491            mime_type: None,
492            icons: None,
493            size: None,
494            annotations: None,
495        }
496    }
497
498    /// Set the resource name (human-readable)
499    pub fn name(mut self, name: impl Into<String>) -> Self {
500        self.name = Some(name.into());
501        self
502    }
503
504    /// Set a human-readable title for the resource
505    pub fn title(mut self, title: impl Into<String>) -> Self {
506        self.title = Some(title.into());
507        self
508    }
509
510    /// Set the resource description
511    pub fn description(mut self, description: impl Into<String>) -> Self {
512        self.description = Some(description.into());
513        self
514    }
515
516    /// Set the MIME type of the resource
517    pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
518        self.mime_type = Some(mime_type.into());
519        self
520    }
521
522    /// Add an icon for the resource
523    pub fn icon(mut self, src: impl Into<String>) -> Self {
524        self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
525            src: src.into(),
526            mime_type: None,
527            sizes: None,
528            theme: None,
529        });
530        self
531    }
532
533    /// Add an icon with metadata
534    pub fn icon_with_meta(
535        mut self,
536        src: impl Into<String>,
537        mime_type: Option<String>,
538        sizes: Option<Vec<String>>,
539    ) -> Self {
540        self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
541            src: src.into(),
542            mime_type,
543            sizes,
544            theme: None,
545        });
546        self
547    }
548
549    /// Set the size of the resource in bytes
550    pub fn size(mut self, size: u64) -> Self {
551        self.size = Some(size);
552        self
553    }
554
555    /// Set annotations (audience, priority hints) for this resource
556    pub fn annotations(mut self, annotations: ContentAnnotations) -> Self {
557        self.annotations = Some(annotations);
558        self
559    }
560
561    /// Set the handler function for reading the resource.
562    ///
563    /// Returns a [`ResourceBuilderWithHandler`] that can be used to apply
564    /// middleware layers via `.layer()` or build the resource directly via `.build()`.
565    ///
566    /// # Sharing State
567    ///
568    /// Capture an [`Arc`] in the closure to share state across handler
569    /// invocations or with other parts of your application:
570    ///
571    /// ```rust
572    /// use std::sync::Arc;
573    /// use tokio::sync::RwLock;
574    /// use tower_mcp::resource::ResourceBuilder;
575    /// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
576    ///
577    /// let db = Arc::new(RwLock::new(vec!["initial".to_string()]));
578    ///
579    /// let db_clone = Arc::clone(&db);
580    /// let resource = ResourceBuilder::new("app://entries")
581    ///     .name("Entries")
582    ///     .handler(move || {
583    ///         let db = Arc::clone(&db_clone);
584    ///         async move {
585    ///             let entries = db.read().await;
586    ///             Ok(ReadResourceResult {
587    ///                 contents: vec![ResourceContent {
588    ///                     uri: "app://entries".to_string(),
589    ///                     mime_type: Some("text/plain".to_string()),
590    ///                     text: Some(entries.join("\n")),
591    ///                     blob: None,
592    ///                     meta: None,
593    ///                 }],
594    ///                 meta: None,
595    ///             })
596    ///         }
597    ///     })
598    ///     .build();
599    /// ```
600    ///
601    /// [`Arc`]: std::sync::Arc
602    pub fn handler<F, Fut>(self, handler: F) -> ResourceBuilderWithHandler<F>
603    where
604        F: Fn() -> Fut + Send + Sync + 'static,
605        Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
606    {
607        ResourceBuilderWithHandler {
608            uri: self.uri,
609            name: self.name,
610            title: self.title,
611            description: self.description,
612            mime_type: self.mime_type,
613            icons: self.icons,
614            size: self.size,
615            annotations: self.annotations,
616            handler,
617        }
618    }
619
620    /// Set a context-aware handler for reading the resource.
621    ///
622    /// The handler receives a `RequestContext` for progress reporting and
623    /// cancellation checking.
624    ///
625    /// Returns a [`ResourceBuilderWithContextHandler`] that can be used to apply
626    /// middleware layers via `.layer()` or build the resource directly via `.build()`.
627    pub fn handler_with_context<F, Fut>(self, handler: F) -> ResourceBuilderWithContextHandler<F>
628    where
629        F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
630        Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
631    {
632        ResourceBuilderWithContextHandler {
633            uri: self.uri,
634            name: self.name,
635            title: self.title,
636            description: self.description,
637            mime_type: self.mime_type,
638            icons: self.icons,
639            size: self.size,
640            annotations: self.annotations,
641            handler,
642        }
643    }
644
645    /// Create a static text resource (convenience method)
646    pub fn text(self, content: impl Into<String>) -> Resource {
647        let uri = self.uri.clone();
648        let content = content.into();
649        let mime_type = self.mime_type.clone();
650
651        self.handler(move || {
652            let uri = uri.clone();
653            let content = content.clone();
654            let mime_type = mime_type.clone();
655            async move {
656                Ok(ReadResourceResult {
657                    contents: vec![ResourceContent {
658                        uri,
659                        mime_type,
660                        text: Some(content),
661                        blob: None,
662                        meta: None,
663                    }],
664                    meta: None,
665                })
666            }
667        })
668        .build()
669    }
670
671    /// Create a static JSON resource (convenience method)
672    pub fn json(mut self, value: serde_json::Value) -> Resource {
673        let uri = self.uri.clone();
674        self.mime_type = Some("application/json".to_string());
675        let text = serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{}".to_string());
676
677        self.handler(move || {
678            let uri = uri.clone();
679            let text = text.clone();
680            async move {
681                Ok(ReadResourceResult {
682                    contents: vec![ResourceContent {
683                        uri,
684                        mime_type: Some("application/json".to_string()),
685                        text: Some(text),
686                        blob: None,
687                        meta: None,
688                    }],
689                    meta: None,
690                })
691            }
692        })
693        .build()
694    }
695}
696
697/// Builder state after handler is specified.
698///
699/// This builder allows applying middleware layers via `.layer()` or building
700/// the resource directly via `.build()`.
701#[doc(hidden)]
702pub struct ResourceBuilderWithHandler<F> {
703    uri: String,
704    name: Option<String>,
705    title: Option<String>,
706    description: Option<String>,
707    mime_type: Option<String>,
708    icons: Option<Vec<ToolIcon>>,
709    size: Option<u64>,
710    annotations: Option<ContentAnnotations>,
711    handler: F,
712}
713
714impl<F, Fut> ResourceBuilderWithHandler<F>
715where
716    F: Fn() -> Fut + Send + Sync + 'static,
717    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
718{
719    /// Build the resource without any middleware layers.
720    pub fn build(self) -> Resource {
721        let name = self.name.unwrap_or_else(|| self.uri.clone());
722
723        Resource::from_handler(
724            self.uri,
725            name,
726            self.title,
727            self.description,
728            self.mime_type,
729            self.icons,
730            self.size,
731            self.annotations,
732            FnHandler {
733                handler: self.handler,
734            },
735        )
736    }
737
738    /// Apply a Tower layer (middleware) to this resource.
739    ///
740    /// The layer wraps the resource's handler service, enabling functionality like
741    /// timeouts, rate limiting, and metrics collection at the per-resource level.
742    ///
743    /// # Example
744    ///
745    /// ```rust
746    /// use std::time::Duration;
747    /// use tower::timeout::TimeoutLayer;
748    /// use tower_mcp::resource::ResourceBuilder;
749    /// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
750    ///
751    /// let resource = ResourceBuilder::new("file:///slow.txt")
752    ///     .name("Slow Resource")
753    ///     .handler(|| async {
754    ///         Ok(ReadResourceResult {
755    ///             contents: vec![ResourceContent {
756    ///                 uri: "file:///slow.txt".to_string(),
757    ///                 mime_type: Some("text/plain".to_string()),
758    ///                 text: Some("content".to_string()),
759    ///                 blob: None,
760    ///                 meta: None,
761    ///             }],
762    ///             meta: None,
763    ///         })
764    ///     })
765    ///     .layer(TimeoutLayer::new(Duration::from_secs(30)))
766    ///     .build();
767    /// ```
768    pub fn layer<L>(self, layer: L) -> ResourceBuilderWithLayer<F, L> {
769        ResourceBuilderWithLayer {
770            uri: self.uri,
771            name: self.name,
772            title: self.title,
773            description: self.description,
774            mime_type: self.mime_type,
775            icons: self.icons,
776            size: self.size,
777            annotations: self.annotations,
778            handler: self.handler,
779            layer,
780        }
781    }
782}
783
784/// Builder state after a layer has been applied to the handler.
785///
786/// This builder allows chaining additional layers and building the final resource.
787#[doc(hidden)]
788pub struct ResourceBuilderWithLayer<F, L> {
789    uri: String,
790    name: Option<String>,
791    title: Option<String>,
792    description: Option<String>,
793    mime_type: Option<String>,
794    icons: Option<Vec<ToolIcon>>,
795    size: Option<u64>,
796    annotations: Option<ContentAnnotations>,
797    handler: F,
798    layer: L,
799}
800
801// Allow private_bounds because these internal types (ResourceHandlerService, FnHandler, etc.)
802// are implementation details that users don't interact with directly.
803#[allow(private_bounds)]
804impl<F, Fut, L> ResourceBuilderWithLayer<F, L>
805where
806    F: Fn() -> Fut + Send + Sync + 'static,
807    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
808    L: tower::Layer<ResourceHandlerService<FnHandler<F>>> + Clone + Send + Sync + 'static,
809    L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
810    <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
811    <L::Service as Service<ResourceRequest>>::Future: Send,
812{
813    /// Build the resource with the applied layer(s).
814    pub fn build(self) -> Resource {
815        let name = self.name.unwrap_or_else(|| self.uri.clone());
816
817        let handler_service = ResourceHandlerService::new(FnHandler {
818            handler: self.handler,
819        });
820        let layered = self.layer.layer(handler_service);
821        let catch_error = ResourceCatchError::new(layered);
822        let service = BoxCloneService::new(catch_error);
823
824        Resource {
825            uri: self.uri,
826            name,
827            title: self.title,
828            description: self.description,
829            mime_type: self.mime_type,
830            icons: self.icons,
831            size: self.size,
832            annotations: self.annotations,
833            service,
834        }
835    }
836
837    /// Apply an additional Tower layer (middleware).
838    ///
839    /// Layers are applied in order, with earlier layers wrapping later ones.
840    /// This means the first layer added is the outermost middleware.
841    pub fn layer<L2>(
842        self,
843        layer: L2,
844    ) -> ResourceBuilderWithLayer<F, tower::layer::util::Stack<L2, L>> {
845        ResourceBuilderWithLayer {
846            uri: self.uri,
847            name: self.name,
848            title: self.title,
849            description: self.description,
850            mime_type: self.mime_type,
851            icons: self.icons,
852            size: self.size,
853            annotations: self.annotations,
854            handler: self.handler,
855            layer: tower::layer::util::Stack::new(layer, self.layer),
856        }
857    }
858}
859
860/// Builder state after context-aware handler is specified.
861#[doc(hidden)]
862pub struct ResourceBuilderWithContextHandler<F> {
863    uri: String,
864    name: Option<String>,
865    title: Option<String>,
866    description: Option<String>,
867    mime_type: Option<String>,
868    icons: Option<Vec<ToolIcon>>,
869    size: Option<u64>,
870    annotations: Option<ContentAnnotations>,
871    handler: F,
872}
873
874impl<F, Fut> ResourceBuilderWithContextHandler<F>
875where
876    F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
877    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
878{
879    /// Build the resource without any middleware layers.
880    pub fn build(self) -> Resource {
881        let name = self.name.unwrap_or_else(|| self.uri.clone());
882
883        Resource::from_handler(
884            self.uri,
885            name,
886            self.title,
887            self.description,
888            self.mime_type,
889            self.icons,
890            self.size,
891            self.annotations,
892            ContextAwareHandler {
893                handler: self.handler,
894            },
895        )
896    }
897
898    /// Apply a Tower layer (middleware) to this resource.
899    ///
900    /// Works the same as [`ResourceBuilderWithHandler::layer`].
901    pub fn layer<L>(self, layer: L) -> ResourceBuilderWithContextLayer<F, L> {
902        ResourceBuilderWithContextLayer {
903            uri: self.uri,
904            name: self.name,
905            title: self.title,
906            description: self.description,
907            mime_type: self.mime_type,
908            icons: self.icons,
909            size: self.size,
910            annotations: self.annotations,
911            handler: self.handler,
912            layer,
913        }
914    }
915}
916
917/// Builder state after a layer has been applied to a context-aware handler.
918#[doc(hidden)]
919pub struct ResourceBuilderWithContextLayer<F, L> {
920    uri: String,
921    name: Option<String>,
922    title: Option<String>,
923    description: Option<String>,
924    mime_type: Option<String>,
925    icons: Option<Vec<ToolIcon>>,
926    size: Option<u64>,
927    annotations: Option<ContentAnnotations>,
928    handler: F,
929    layer: L,
930}
931
932// Allow private_bounds because these internal types are implementation details.
933#[allow(private_bounds)]
934impl<F, Fut, L> ResourceBuilderWithContextLayer<F, L>
935where
936    F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
937    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
938    L: tower::Layer<ResourceHandlerService<ContextAwareHandler<F>>> + Clone + Send + Sync + 'static,
939    L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
940    <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
941    <L::Service as Service<ResourceRequest>>::Future: Send,
942{
943    /// Build the resource with the applied layer(s).
944    pub fn build(self) -> Resource {
945        let name = self.name.unwrap_or_else(|| self.uri.clone());
946
947        let handler_service = ResourceHandlerService::new(ContextAwareHandler {
948            handler: self.handler,
949        });
950        let layered = self.layer.layer(handler_service);
951        let catch_error = ResourceCatchError::new(layered);
952        let service = BoxCloneService::new(catch_error);
953
954        Resource {
955            uri: self.uri,
956            name,
957            title: self.title,
958            description: self.description,
959            mime_type: self.mime_type,
960            icons: self.icons,
961            size: self.size,
962            annotations: self.annotations,
963            service,
964        }
965    }
966
967    /// Apply an additional Tower layer (middleware).
968    pub fn layer<L2>(
969        self,
970        layer: L2,
971    ) -> ResourceBuilderWithContextLayer<F, tower::layer::util::Stack<L2, L>> {
972        ResourceBuilderWithContextLayer {
973            uri: self.uri,
974            name: self.name,
975            title: self.title,
976            description: self.description,
977            mime_type: self.mime_type,
978            icons: self.icons,
979            size: self.size,
980            annotations: self.annotations,
981            handler: self.handler,
982            layer: tower::layer::util::Stack::new(layer, self.layer),
983        }
984    }
985}
986
987// =============================================================================
988// Handler implementations
989// =============================================================================
990
991/// Handler wrapping a function
992struct FnHandler<F> {
993    handler: F,
994}
995
996impl<F, Fut> ResourceHandler for FnHandler<F>
997where
998    F: Fn() -> Fut + Send + Sync + 'static,
999    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1000{
1001    fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1002        Box::pin((self.handler)())
1003    }
1004}
1005
1006/// Handler that receives request context
1007struct ContextAwareHandler<F> {
1008    handler: F,
1009}
1010
1011impl<F, Fut> ResourceHandler for ContextAwareHandler<F>
1012where
1013    F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1014    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1015{
1016    fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1017        let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
1018        self.read_with_context(ctx)
1019    }
1020
1021    fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
1022        Box::pin((self.handler)(ctx))
1023    }
1024
1025    fn uses_context(&self) -> bool {
1026        true
1027    }
1028}
1029
1030// =============================================================================
1031// Trait-based resource definition
1032// =============================================================================
1033
1034/// Trait for defining resources with full control
1035///
1036/// Implement this trait when you need more control than the builder provides,
1037/// or when you want to define resources as standalone types.
1038///
1039/// # Example
1040///
1041/// ```rust
1042/// use tower_mcp::resource::McpResource;
1043/// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
1044/// use tower_mcp::error::Result;
1045///
1046/// struct ConfigResource {
1047///     config: String,
1048/// }
1049///
1050/// impl McpResource for ConfigResource {
1051///     const URI: &'static str = "file:///config.json";
1052///     const NAME: &'static str = "Configuration";
1053///     const DESCRIPTION: Option<&'static str> = Some("Application configuration");
1054///     const MIME_TYPE: Option<&'static str> = Some("application/json");
1055///
1056///     async fn read(&self) -> Result<ReadResourceResult> {
1057///         Ok(ReadResourceResult {
1058///             contents: vec![ResourceContent {
1059///                 uri: Self::URI.to_string(),
1060///                 mime_type: Self::MIME_TYPE.map(|s| s.to_string()),
1061///                 text: Some(self.config.clone()),
1062///                 blob: None,
1063///                 meta: None,
1064///             }],
1065///             meta: None,
1066///         })
1067///     }
1068/// }
1069///
1070/// let resource = ConfigResource { config: "{}".to_string() }.into_resource();
1071/// assert_eq!(resource.uri, "file:///config.json");
1072/// ```
1073pub trait McpResource: Send + Sync + 'static {
1074    /// The resource URI.
1075    const URI: &'static str;
1076    /// The resource name.
1077    const NAME: &'static str;
1078    /// Optional human-readable description.
1079    const DESCRIPTION: Option<&'static str> = None;
1080    /// Optional MIME type for the resource content.
1081    const MIME_TYPE: Option<&'static str> = None;
1082
1083    /// Read the resource content.
1084    fn read(&self) -> impl Future<Output = Result<ReadResourceResult>> + Send;
1085
1086    /// Convert to a Resource instance
1087    fn into_resource(self) -> Resource
1088    where
1089        Self: Sized,
1090    {
1091        let resource = Arc::new(self);
1092        Resource::from_handler(
1093            Self::URI.to_string(),
1094            Self::NAME.to_string(),
1095            None,
1096            Self::DESCRIPTION.map(|s| s.to_string()),
1097            Self::MIME_TYPE.map(|s| s.to_string()),
1098            None,
1099            None,
1100            None,
1101            McpResourceHandler { resource },
1102        )
1103    }
1104}
1105
1106/// Wrapper to make McpResource implement ResourceHandler
1107struct McpResourceHandler<T: McpResource> {
1108    resource: Arc<T>,
1109}
1110
1111impl<T: McpResource> ResourceHandler for McpResourceHandler<T> {
1112    fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1113        let resource = self.resource.clone();
1114        Box::pin(async move { resource.read().await })
1115    }
1116}
1117
1118// =============================================================================
1119// Resource Templates
1120// =============================================================================
1121
1122/// Handler trait for resource templates
1123///
1124/// Unlike [`ResourceHandler`], template handlers receive the extracted
1125/// URI variables as a parameter.
1126pub trait ResourceTemplateHandler: Send + Sync {
1127    /// Read a resource with the given URI variables extracted from the template
1128    fn read(
1129        &self,
1130        uri: &str,
1131        variables: HashMap<String, String>,
1132    ) -> BoxFuture<'_, Result<ReadResourceResult>>;
1133}
1134
1135/// A parameterized resource template
1136///
1137/// Resource templates use URI template syntax (RFC 6570) to match multiple URIs
1138/// and extract variable values. This allows servers to expose dynamic resources
1139/// like file systems or database records.
1140///
1141/// # Example
1142///
1143/// ```rust
1144/// use tower_mcp::resource::ResourceTemplateBuilder;
1145/// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
1146/// use std::collections::HashMap;
1147///
1148/// let template = ResourceTemplateBuilder::new("file:///{path}")
1149///     .name("Project Files")
1150///     .handler(|uri: String, vars: HashMap<String, String>| async move {
1151///         let path = vars.get("path").unwrap_or(&String::new()).clone();
1152///         Ok(ReadResourceResult {
1153///             contents: vec![ResourceContent {
1154///                 uri,
1155///                 mime_type: Some("text/plain".to_string()),
1156///                 text: Some(format!("Contents of {}", path)),
1157///                 blob: None,
1158///                 meta: None,
1159///             }],
1160///             meta: None,
1161///         })
1162///     });
1163/// ```
1164pub struct ResourceTemplate {
1165    /// The URI template pattern (e.g., `file:///{path}`)
1166    pub uri_template: String,
1167    /// Human-readable name
1168    pub name: String,
1169    /// Human-readable title for display purposes
1170    pub title: Option<String>,
1171    /// Optional description
1172    pub description: Option<String>,
1173    /// Optional MIME type hint
1174    pub mime_type: Option<String>,
1175    /// Optional icons for display in user interfaces
1176    pub icons: Option<Vec<ToolIcon>>,
1177    /// Optional annotations (audience, priority hints)
1178    pub annotations: Option<ContentAnnotations>,
1179    /// Compiled regex for matching URIs
1180    pattern: regex::Regex,
1181    /// Variable names in order of appearance
1182    variables: Vec<String>,
1183    /// Handler for reading matched resources
1184    handler: Arc<dyn ResourceTemplateHandler>,
1185}
1186
1187impl Clone for ResourceTemplate {
1188    fn clone(&self) -> Self {
1189        Self {
1190            uri_template: self.uri_template.clone(),
1191            name: self.name.clone(),
1192            title: self.title.clone(),
1193            description: self.description.clone(),
1194            mime_type: self.mime_type.clone(),
1195            icons: self.icons.clone(),
1196            annotations: self.annotations.clone(),
1197            pattern: self.pattern.clone(),
1198            variables: self.variables.clone(),
1199            handler: self.handler.clone(),
1200        }
1201    }
1202}
1203
1204impl std::fmt::Debug for ResourceTemplate {
1205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1206        f.debug_struct("ResourceTemplate")
1207            .field("uri_template", &self.uri_template)
1208            .field("name", &self.name)
1209            .field("title", &self.title)
1210            .field("description", &self.description)
1211            .field("mime_type", &self.mime_type)
1212            .field("icons", &self.icons)
1213            .field("variables", &self.variables)
1214            .finish_non_exhaustive()
1215    }
1216}
1217
1218impl ResourceTemplate {
1219    /// Create a new resource template builder
1220    pub fn builder(uri_template: impl Into<String>) -> ResourceTemplateBuilder {
1221        ResourceTemplateBuilder::new(uri_template)
1222    }
1223
1224    /// Get the template definition for resources/templates/list
1225    pub fn definition(&self) -> ResourceTemplateDefinition {
1226        ResourceTemplateDefinition {
1227            uri_template: self.uri_template.clone(),
1228            name: self.name.clone(),
1229            title: self.title.clone(),
1230            description: self.description.clone(),
1231            mime_type: self.mime_type.clone(),
1232            icons: self.icons.clone(),
1233            annotations: self.annotations.clone(),
1234            arguments: Vec::new(),
1235            meta: None,
1236        }
1237    }
1238
1239    /// Check if a URI matches this template and extract variables
1240    ///
1241    /// Returns `Some(HashMap)` with extracted variables if the URI matches,
1242    /// `None` if it doesn't match.
1243    pub fn match_uri(&self, uri: &str) -> Option<HashMap<String, String>> {
1244        self.pattern.captures(uri).map(|caps| {
1245            self.variables
1246                .iter()
1247                .enumerate()
1248                .filter_map(|(i, name)| {
1249                    caps.get(i + 1)
1250                        .map(|m| (name.clone(), m.as_str().to_string()))
1251                })
1252                .collect()
1253        })
1254    }
1255
1256    /// Read a resource at the given URI using this template's handler
1257    ///
1258    /// # Arguments
1259    ///
1260    /// * `uri` - The actual URI being read
1261    /// * `variables` - Variables extracted from matching the URI against the template
1262    pub fn read(
1263        &self,
1264        uri: &str,
1265        variables: HashMap<String, String>,
1266    ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1267        self.handler.read(uri, variables)
1268    }
1269}
1270
1271/// Builder for creating resource templates
1272///
1273/// # Example
1274///
1275/// ```rust
1276/// use tower_mcp::resource::ResourceTemplateBuilder;
1277/// use tower_mcp::protocol::{ReadResourceResult, ResourceContent};
1278/// use std::collections::HashMap;
1279///
1280/// let template = ResourceTemplateBuilder::new("db://users/{id}")
1281///     .name("User Records")
1282///     .description("Access user records by ID")
1283///     .handler(|uri: String, vars: HashMap<String, String>| async move {
1284///         let id = vars.get("id").unwrap();
1285///         Ok(ReadResourceResult {
1286///             contents: vec![ResourceContent {
1287///                 uri,
1288///                 mime_type: Some("application/json".to_string()),
1289///                 text: Some(format!(r#"{{"id": "{}"}}"#, id)),
1290///                 blob: None,
1291///                 meta: None,
1292///             }],
1293///             meta: None,
1294///         })
1295///     });
1296/// ```
1297pub struct ResourceTemplateBuilder {
1298    uri_template: String,
1299    name: Option<String>,
1300    title: Option<String>,
1301    description: Option<String>,
1302    mime_type: Option<String>,
1303    icons: Option<Vec<ToolIcon>>,
1304    annotations: Option<ContentAnnotations>,
1305}
1306
1307impl ResourceTemplateBuilder {
1308    /// Create a new builder with the given URI template
1309    ///
1310    /// # URI Template Syntax
1311    ///
1312    /// Templates use RFC 6570 Level 1 syntax with simple variable expansion:
1313    /// - `{varname}` - Matches any non-slash characters
1314    ///
1315    /// # Examples
1316    ///
1317    /// - `file:///{path}` - Matches `file:///README.md`
1318    /// - `db://users/{id}` - Matches `db://users/123`
1319    /// - `api://v1/{resource}/{id}` - Matches `api://v1/posts/456`
1320    pub fn new(uri_template: impl Into<String>) -> Self {
1321        Self {
1322            uri_template: uri_template.into(),
1323            name: None,
1324            title: None,
1325            description: None,
1326            mime_type: None,
1327            icons: None,
1328            annotations: None,
1329        }
1330    }
1331
1332    /// Set the human-readable name for this template
1333    pub fn name(mut self, name: impl Into<String>) -> Self {
1334        self.name = Some(name.into());
1335        self
1336    }
1337
1338    /// Set a human-readable title for the template
1339    pub fn title(mut self, title: impl Into<String>) -> Self {
1340        self.title = Some(title.into());
1341        self
1342    }
1343
1344    /// Set the description for this template
1345    pub fn description(mut self, description: impl Into<String>) -> Self {
1346        self.description = Some(description.into());
1347        self
1348    }
1349
1350    /// Set the MIME type hint for resources from this template
1351    pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
1352        self.mime_type = Some(mime_type.into());
1353        self
1354    }
1355
1356    /// Add an icon for the template
1357    pub fn icon(mut self, src: impl Into<String>) -> Self {
1358        self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1359            src: src.into(),
1360            mime_type: None,
1361            sizes: None,
1362            theme: None,
1363        });
1364        self
1365    }
1366
1367    /// Add an icon with metadata
1368    pub fn icon_with_meta(
1369        mut self,
1370        src: impl Into<String>,
1371        mime_type: Option<String>,
1372        sizes: Option<Vec<String>>,
1373    ) -> Self {
1374        self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1375            src: src.into(),
1376            mime_type,
1377            sizes,
1378            theme: None,
1379        });
1380        self
1381    }
1382
1383    /// Set annotations (audience, priority hints) for this resource template
1384    pub fn annotations(mut self, annotations: ContentAnnotations) -> Self {
1385        self.annotations = Some(annotations);
1386        self
1387    }
1388
1389    /// Set the handler function for reading template resources.
1390    ///
1391    /// The handler receives:
1392    /// - `uri`: The full URI being read
1393    /// - `variables`: A map of variable names to their values extracted from the URI
1394    ///
1395    /// # Panics
1396    ///
1397    /// Panics if the URI template produces an invalid regex pattern. For a
1398    /// non-panicking alternative (useful with dynamic/user-supplied templates),
1399    /// use [`try_handler`](Self::try_handler).
1400    pub fn handler<F, Fut>(self, handler: F) -> ResourceTemplate
1401    where
1402        F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1403        Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1404    {
1405        self.try_handler(handler).unwrap_or_else(|e| {
1406            panic!("Invalid URI template: {e}");
1407        })
1408    }
1409
1410    /// Set the handler function for reading template resources, returning an
1411    /// error if the URI template is invalid.
1412    ///
1413    /// This is the fallible version of [`handler`](Self::handler), suitable for
1414    /// use with dynamically created templates where the URI pattern may come
1415    /// from user input.
1416    ///
1417    /// The handler receives:
1418    /// - `uri`: The full URI being read
1419    /// - `variables`: A map of variable names to their values extracted from the URI
1420    pub fn try_handler<F, Fut>(self, handler: F) -> std::result::Result<ResourceTemplate, Error>
1421    where
1422        F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1423        Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1424    {
1425        let (pattern, variables) = compile_uri_template(&self.uri_template)?;
1426        let name = self.name.unwrap_or_else(|| self.uri_template.clone());
1427
1428        Ok(ResourceTemplate {
1429            uri_template: self.uri_template,
1430            name,
1431            title: self.title,
1432            description: self.description,
1433            mime_type: self.mime_type,
1434            icons: self.icons,
1435            annotations: self.annotations,
1436            pattern,
1437            variables,
1438            handler: Arc::new(FnTemplateHandler { handler }),
1439        })
1440    }
1441}
1442
1443/// Handler wrapping a function for templates
1444struct FnTemplateHandler<F> {
1445    handler: F,
1446}
1447
1448impl<F, Fut> ResourceTemplateHandler for FnTemplateHandler<F>
1449where
1450    F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1451    Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1452{
1453    fn read(
1454        &self,
1455        uri: &str,
1456        variables: HashMap<String, String>,
1457    ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1458        let uri = uri.to_string();
1459        Box::pin((self.handler)(uri, variables))
1460    }
1461}
1462
1463/// Compile a URI template into a regex pattern and extract variable names.
1464///
1465/// Supports RFC 6570 Level 1 (simple expansion):
1466/// - `{var}` matches any characters except `/`
1467/// - `{+var}` matches any characters including `/` (reserved expansion)
1468///
1469/// Returns the compiled regex and a list of variable names in order,
1470/// or an error if the template produces an invalid regex pattern.
1471fn compile_uri_template(template: &str) -> std::result::Result<(regex::Regex, Vec<String>), Error> {
1472    let mut pattern = String::from("^");
1473    let mut variables = Vec::new();
1474
1475    let mut chars = template.chars().peekable();
1476    while let Some(c) = chars.next() {
1477        if c == '{' {
1478            // Check for + prefix (reserved expansion)
1479            let is_reserved = chars.peek() == Some(&'+');
1480            if is_reserved {
1481                chars.next();
1482            }
1483
1484            // Collect variable name
1485            let var_name: String = chars.by_ref().take_while(|&c| c != '}').collect();
1486            variables.push(var_name);
1487
1488            // Choose pattern based on expansion type
1489            if is_reserved {
1490                // Reserved expansion - match anything
1491                pattern.push_str("(.+)");
1492            } else {
1493                // Simple expansion - match non-slash characters
1494                pattern.push_str("([^/]+)");
1495            }
1496        } else {
1497            // Escape regex special characters
1498            match c {
1499                '.' | '+' | '*' | '?' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|'
1500                | '\\' => {
1501                    pattern.push('\\');
1502                    pattern.push(c);
1503                }
1504                _ => pattern.push(c),
1505            }
1506        }
1507    }
1508
1509    pattern.push('$');
1510
1511    let regex = regex::Regex::new(&pattern)
1512        .map_err(|e| Error::Internal(format!("Invalid URI template '{}': {}", template, e)))?;
1513
1514    Ok((regex, variables))
1515}
1516
1517#[cfg(test)]
1518mod tests {
1519    use super::*;
1520    use std::time::Duration;
1521    use tower::timeout::TimeoutLayer;
1522
1523    #[tokio::test]
1524    async fn test_builder_resource() {
1525        let resource = ResourceBuilder::new("file:///test.txt")
1526            .name("Test File")
1527            .description("A test file")
1528            .text("Hello, World!");
1529
1530        assert_eq!(resource.uri, "file:///test.txt");
1531        assert_eq!(resource.name, "Test File");
1532        assert_eq!(resource.description.as_deref(), Some("A test file"));
1533
1534        let result = resource.read().await;
1535        assert_eq!(result.contents.len(), 1);
1536        assert_eq!(result.contents[0].text.as_deref(), Some("Hello, World!"));
1537    }
1538
1539    #[tokio::test]
1540    async fn test_json_resource() {
1541        let resource = ResourceBuilder::new("file:///config.json")
1542            .name("Config")
1543            .json(serde_json::json!({"key": "value"}));
1544
1545        assert_eq!(resource.mime_type.as_deref(), Some("application/json"));
1546
1547        let result = resource.read().await;
1548        assert!(result.contents[0].text.as_ref().unwrap().contains("key"));
1549    }
1550
1551    #[tokio::test]
1552    async fn test_handler_resource() {
1553        let resource = ResourceBuilder::new("memory://counter")
1554            .name("Counter")
1555            .handler(|| async {
1556                Ok(ReadResourceResult {
1557                    contents: vec![ResourceContent {
1558                        uri: "memory://counter".to_string(),
1559                        mime_type: Some("text/plain".to_string()),
1560                        text: Some("42".to_string()),
1561                        blob: None,
1562                        meta: None,
1563                    }],
1564                    meta: None,
1565                })
1566            })
1567            .build();
1568
1569        let result = resource.read().await;
1570        assert_eq!(result.contents[0].text.as_deref(), Some("42"));
1571    }
1572
1573    #[tokio::test]
1574    async fn test_handler_resource_with_layer() {
1575        let resource = ResourceBuilder::new("file:///with-timeout.txt")
1576            .name("Resource with Timeout")
1577            .handler(|| async {
1578                Ok(ReadResourceResult {
1579                    contents: vec![ResourceContent {
1580                        uri: "file:///with-timeout.txt".to_string(),
1581                        mime_type: Some("text/plain".to_string()),
1582                        text: Some("content".to_string()),
1583                        blob: None,
1584                        meta: None,
1585                    }],
1586                    meta: None,
1587                })
1588            })
1589            .layer(TimeoutLayer::new(Duration::from_secs(30)))
1590            .build();
1591
1592        let result = resource.read().await;
1593        assert_eq!(result.contents[0].text.as_deref(), Some("content"));
1594    }
1595
1596    #[tokio::test]
1597    async fn test_handler_resource_with_timeout_error() {
1598        let resource = ResourceBuilder::new("file:///slow.txt")
1599            .name("Slow Resource")
1600            .handler(|| async {
1601                // Sleep much longer than timeout to ensure timeout fires reliably in CI
1602                tokio::time::sleep(Duration::from_secs(1)).await;
1603                Ok(ReadResourceResult {
1604                    contents: vec![ResourceContent {
1605                        uri: "file:///slow.txt".to_string(),
1606                        mime_type: Some("text/plain".to_string()),
1607                        text: Some("content".to_string()),
1608                        blob: None,
1609                        meta: None,
1610                    }],
1611                    meta: None,
1612                })
1613            })
1614            .layer(TimeoutLayer::new(Duration::from_millis(50)))
1615            .build();
1616
1617        let result = resource.read().await;
1618        // Timeout error should be caught and converted to error content
1619        assert!(
1620            result.contents[0]
1621                .text
1622                .as_ref()
1623                .unwrap()
1624                .contains("Error reading resource")
1625        );
1626    }
1627
1628    #[tokio::test]
1629    async fn test_context_aware_handler() {
1630        let resource = ResourceBuilder::new("file:///ctx.txt")
1631            .name("Context Resource")
1632            .handler_with_context(|_ctx: RequestContext| async {
1633                Ok(ReadResourceResult {
1634                    contents: vec![ResourceContent {
1635                        uri: "file:///ctx.txt".to_string(),
1636                        mime_type: Some("text/plain".to_string()),
1637                        text: Some("context aware".to_string()),
1638                        blob: None,
1639                        meta: None,
1640                    }],
1641                    meta: None,
1642                })
1643            })
1644            .build();
1645
1646        let result = resource.read().await;
1647        assert_eq!(result.contents[0].text.as_deref(), Some("context aware"));
1648    }
1649
1650    #[tokio::test]
1651    async fn test_context_aware_handler_with_layer() {
1652        let resource = ResourceBuilder::new("file:///ctx-layer.txt")
1653            .name("Context Resource with Layer")
1654            .handler_with_context(|_ctx: RequestContext| async {
1655                Ok(ReadResourceResult {
1656                    contents: vec![ResourceContent {
1657                        uri: "file:///ctx-layer.txt".to_string(),
1658                        mime_type: Some("text/plain".to_string()),
1659                        text: Some("context with layer".to_string()),
1660                        blob: None,
1661                        meta: None,
1662                    }],
1663                    meta: None,
1664                })
1665            })
1666            .layer(TimeoutLayer::new(Duration::from_secs(30)))
1667            .build();
1668
1669        let result = resource.read().await;
1670        assert_eq!(
1671            result.contents[0].text.as_deref(),
1672            Some("context with layer")
1673        );
1674    }
1675
1676    #[tokio::test]
1677    async fn test_trait_resource() {
1678        struct TestResource;
1679
1680        impl McpResource for TestResource {
1681            const URI: &'static str = "test://resource";
1682            const NAME: &'static str = "Test";
1683            const DESCRIPTION: Option<&'static str> = Some("A test resource");
1684            const MIME_TYPE: Option<&'static str> = Some("text/plain");
1685
1686            async fn read(&self) -> Result<ReadResourceResult> {
1687                Ok(ReadResourceResult {
1688                    contents: vec![ResourceContent {
1689                        uri: Self::URI.to_string(),
1690                        mime_type: Self::MIME_TYPE.map(|s| s.to_string()),
1691                        text: Some("test content".to_string()),
1692                        blob: None,
1693                        meta: None,
1694                    }],
1695                    meta: None,
1696                })
1697            }
1698        }
1699
1700        let resource = TestResource.into_resource();
1701        assert_eq!(resource.uri, "test://resource");
1702        assert_eq!(resource.name, "Test");
1703
1704        let result = resource.read().await;
1705        assert_eq!(result.contents[0].text.as_deref(), Some("test content"));
1706    }
1707
1708    #[test]
1709    fn test_resource_definition() {
1710        let resource = ResourceBuilder::new("file:///test.txt")
1711            .name("Test")
1712            .description("Description")
1713            .mime_type("text/plain")
1714            .text("content");
1715
1716        let def = resource.definition();
1717        assert_eq!(def.uri, "file:///test.txt");
1718        assert_eq!(def.name, "Test");
1719        assert_eq!(def.description.as_deref(), Some("Description"));
1720        assert_eq!(def.mime_type.as_deref(), Some("text/plain"));
1721    }
1722
1723    #[test]
1724    fn test_resource_request_new() {
1725        let ctx = RequestContext::new(crate::protocol::RequestId::Number(1));
1726        let req = ResourceRequest::new(ctx, "file:///test.txt".to_string());
1727        assert_eq!(req.uri, "file:///test.txt");
1728    }
1729
1730    #[test]
1731    fn test_resource_catch_error_clone() {
1732        let handler = FnHandler {
1733            handler: || async {
1734                Ok::<_, Error>(ReadResourceResult {
1735                    contents: vec![],
1736                    meta: None,
1737                })
1738            },
1739        };
1740        let service = ResourceHandlerService::new(handler);
1741        let catch_error = ResourceCatchError::new(service);
1742        let _clone = catch_error.clone();
1743    }
1744
1745    #[test]
1746    fn test_resource_catch_error_debug() {
1747        let handler = FnHandler {
1748            handler: || async {
1749                Ok::<_, Error>(ReadResourceResult {
1750                    contents: vec![],
1751                    meta: None,
1752                })
1753            },
1754        };
1755        let service = ResourceHandlerService::new(handler);
1756        let catch_error = ResourceCatchError::new(service);
1757        let debug = format!("{:?}", catch_error);
1758        assert!(debug.contains("ResourceCatchError"));
1759    }
1760
1761    // =========================================================================
1762    // Resource Template Tests
1763    // =========================================================================
1764
1765    #[test]
1766    fn test_compile_uri_template_simple() {
1767        let (regex, vars) = compile_uri_template("file:///{path}").unwrap();
1768        assert_eq!(vars, vec!["path"]);
1769        assert!(regex.is_match("file:///README.md"));
1770        assert!(!regex.is_match("file:///foo/bar")); // no slashes in simple expansion
1771    }
1772
1773    #[test]
1774    fn test_compile_uri_template_multiple_vars() {
1775        let (regex, vars) = compile_uri_template("api://v1/{resource}/{id}").unwrap();
1776        assert_eq!(vars, vec!["resource", "id"]);
1777        assert!(regex.is_match("api://v1/users/123"));
1778        assert!(regex.is_match("api://v1/posts/abc"));
1779        assert!(!regex.is_match("api://v1/users")); // missing id
1780    }
1781
1782    #[test]
1783    fn test_compile_uri_template_reserved_expansion() {
1784        let (regex, vars) = compile_uri_template("file:///{+path}").unwrap();
1785        assert_eq!(vars, vec!["path"]);
1786        assert!(regex.is_match("file:///README.md"));
1787        assert!(regex.is_match("file:///foo/bar/baz.txt")); // slashes allowed
1788    }
1789
1790    #[test]
1791    fn test_compile_uri_template_special_chars() {
1792        let (regex, vars) = compile_uri_template("http://example.com/api?query={q}").unwrap();
1793        assert_eq!(vars, vec!["q"]);
1794        assert!(regex.is_match("http://example.com/api?query=hello"));
1795    }
1796
1797    #[test]
1798    fn test_resource_template_match_uri() {
1799        let template = ResourceTemplateBuilder::new("db://users/{id}")
1800            .name("User Records")
1801            .handler(|uri: String, vars: HashMap<String, String>| async move {
1802                Ok(ReadResourceResult {
1803                    contents: vec![ResourceContent {
1804                        uri,
1805                        mime_type: None,
1806                        text: Some(format!("User {}", vars.get("id").unwrap())),
1807                        blob: None,
1808                        meta: None,
1809                    }],
1810                    meta: None,
1811                })
1812            });
1813
1814        // Test matching
1815        let vars = template.match_uri("db://users/123").unwrap();
1816        assert_eq!(vars.get("id"), Some(&"123".to_string()));
1817
1818        // Test non-matching
1819        assert!(template.match_uri("db://posts/123").is_none());
1820        assert!(template.match_uri("db://users").is_none());
1821    }
1822
1823    #[test]
1824    fn test_resource_template_match_multiple_vars() {
1825        let template = ResourceTemplateBuilder::new("api://{version}/{resource}/{id}")
1826            .name("API Resources")
1827            .handler(|uri: String, _vars: HashMap<String, String>| async move {
1828                Ok(ReadResourceResult {
1829                    contents: vec![ResourceContent {
1830                        uri,
1831                        mime_type: None,
1832                        text: None,
1833                        blob: None,
1834                        meta: None,
1835                    }],
1836                    meta: None,
1837                })
1838            });
1839
1840        let vars = template.match_uri("api://v2/users/abc-123").unwrap();
1841        assert_eq!(vars.get("version"), Some(&"v2".to_string()));
1842        assert_eq!(vars.get("resource"), Some(&"users".to_string()));
1843        assert_eq!(vars.get("id"), Some(&"abc-123".to_string()));
1844    }
1845
1846    #[tokio::test]
1847    async fn test_resource_template_read() {
1848        let template = ResourceTemplateBuilder::new("file:///{path}")
1849            .name("Files")
1850            .mime_type("text/plain")
1851            .handler(|uri: String, vars: HashMap<String, String>| async move {
1852                let path = vars.get("path").unwrap().clone();
1853                Ok(ReadResourceResult {
1854                    contents: vec![ResourceContent {
1855                        uri,
1856                        mime_type: Some("text/plain".to_string()),
1857                        text: Some(format!("Contents of {}", path)),
1858                        blob: None,
1859                        meta: None,
1860                    }],
1861                    meta: None,
1862                })
1863            });
1864
1865        let vars = template.match_uri("file:///README.md").unwrap();
1866        let result = template.read("file:///README.md", vars).await.unwrap();
1867
1868        assert_eq!(result.contents.len(), 1);
1869        assert_eq!(result.contents[0].uri, "file:///README.md");
1870        assert_eq!(
1871            result.contents[0].text.as_deref(),
1872            Some("Contents of README.md")
1873        );
1874    }
1875
1876    #[test]
1877    fn test_resource_template_definition() {
1878        let template = ResourceTemplateBuilder::new("db://records/{id}")
1879            .name("Database Records")
1880            .description("Access database records by ID")
1881            .mime_type("application/json")
1882            .handler(|uri: String, _vars: HashMap<String, String>| async move {
1883                Ok(ReadResourceResult {
1884                    contents: vec![ResourceContent {
1885                        uri,
1886                        mime_type: None,
1887                        text: None,
1888                        blob: None,
1889                        meta: None,
1890                    }],
1891                    meta: None,
1892                })
1893            });
1894
1895        let def = template.definition();
1896        assert_eq!(def.uri_template, "db://records/{id}");
1897        assert_eq!(def.name, "Database Records");
1898        assert_eq!(
1899            def.description.as_deref(),
1900            Some("Access database records by ID")
1901        );
1902        assert_eq!(def.mime_type.as_deref(), Some("application/json"));
1903    }
1904
1905    #[test]
1906    fn test_resource_template_reserved_path() {
1907        let template = ResourceTemplateBuilder::new("file:///{+path}")
1908            .name("Files with subpaths")
1909            .handler(|uri: String, _vars: HashMap<String, String>| async move {
1910                Ok(ReadResourceResult {
1911                    contents: vec![ResourceContent {
1912                        uri,
1913                        mime_type: None,
1914                        text: None,
1915                        blob: None,
1916                        meta: None,
1917                    }],
1918                    meta: None,
1919                })
1920            });
1921
1922        // Reserved expansion should match slashes
1923        let vars = template.match_uri("file:///src/lib/utils.rs").unwrap();
1924        assert_eq!(vars.get("path"), Some(&"src/lib/utils.rs".to_string()));
1925    }
1926
1927    #[test]
1928    fn test_resource_annotations() {
1929        use crate::protocol::{ContentAnnotations, ContentRole};
1930
1931        let annotations = ContentAnnotations {
1932            audience: Some(vec![ContentRole::User]),
1933            priority: Some(0.8),
1934            last_modified: None,
1935        };
1936
1937        let resource = ResourceBuilder::new("file:///important.txt")
1938            .name("Important File")
1939            .annotations(annotations.clone())
1940            .text("content");
1941
1942        let def = resource.definition();
1943        assert!(def.annotations.is_some());
1944        let ann = def.annotations.unwrap();
1945        assert_eq!(ann.priority, Some(0.8));
1946        assert_eq!(ann.audience.unwrap(), vec![ContentRole::User]);
1947    }
1948
1949    #[test]
1950    fn test_resource_template_annotations() {
1951        use crate::protocol::{ContentAnnotations, ContentRole};
1952
1953        let annotations = ContentAnnotations {
1954            audience: Some(vec![ContentRole::Assistant]),
1955            priority: Some(0.5),
1956            last_modified: None,
1957        };
1958
1959        let template = ResourceTemplateBuilder::new("db://users/{id}")
1960            .name("Users")
1961            .annotations(annotations)
1962            .handler(|uri: String, _vars: HashMap<String, String>| async move {
1963                Ok(ReadResourceResult {
1964                    contents: vec![ResourceContent {
1965                        uri,
1966                        mime_type: None,
1967                        text: Some("data".to_string()),
1968                        blob: None,
1969                        meta: None,
1970                    }],
1971                    meta: None,
1972                })
1973            });
1974
1975        let def = template.definition();
1976        assert!(def.annotations.is_some());
1977        let ann = def.annotations.unwrap();
1978        assert_eq!(ann.priority, Some(0.5));
1979        assert_eq!(ann.audience.unwrap(), vec![ContentRole::Assistant]);
1980    }
1981
1982    #[test]
1983    fn test_resource_no_annotations_by_default() {
1984        let resource = ResourceBuilder::new("file:///test.txt")
1985            .name("Test")
1986            .text("content");
1987
1988        let def = resource.definition();
1989        assert!(def.annotations.is_none());
1990    }
1991
1992    #[test]
1993    fn test_try_handler_success() {
1994        let result = ResourceTemplateBuilder::new("db://users/{id}")
1995            .name("Users")
1996            .try_handler(|uri: String, _vars: HashMap<String, String>| async move {
1997                Ok(ReadResourceResult {
1998                    contents: vec![ResourceContent {
1999                        uri,
2000                        mime_type: None,
2001                        text: Some("ok".to_string()),
2002                        blob: None,
2003                        meta: None,
2004                    }],
2005                    meta: None,
2006                })
2007            });
2008
2009        assert!(result.is_ok());
2010        let template = result.unwrap();
2011        assert_eq!(template.uri_template, "db://users/{id}");
2012    }
2013
2014    #[test]
2015    fn test_compile_uri_template_returns_result() {
2016        // Valid templates should succeed
2017        assert!(compile_uri_template("file:///{path}").is_ok());
2018        assert!(compile_uri_template("api://v1/{resource}/{id}").is_ok());
2019        assert!(compile_uri_template("file:///{+path}").is_ok());
2020        assert!(compile_uri_template("no-vars").is_ok());
2021        assert!(compile_uri_template("").is_ok());
2022    }
2023}