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