1use std::collections::HashMap;
72use std::convert::Infallible;
73use std::fmt;
74use std::future::Future;
75use std::pin::Pin;
76use std::sync::Arc;
77use std::task::{Context, Poll};
78
79use pin_project_lite::pin_project;
80use serde_json::Value;
81
82#[cfg(feature = "stateless")]
83use tower::ServiceExt;
84use tower::util::BoxCloneService;
85use tower_service::Service;
86
87#[cfg(feature = "stateless")]
88use tokio::sync::Mutex;
89
90use crate::context::RequestContext;
91use crate::error::{Error, Result};
92use crate::protocol::{
93 ContentAnnotations, ReadResourceResult, RequestOutcome, ResourceContent, ResourceDefinition,
94 ResourceTemplateDefinition, ToolIcon,
95};
96
97#[derive(Debug, Clone)]
106pub struct ResourceRequest {
107 pub ctx: RequestContext,
109 pub uri: String,
111}
112
113impl ResourceRequest {
114 pub fn new(ctx: RequestContext, uri: String) -> Self {
116 Self { ctx, uri }
117 }
118}
119
120pub type BoxResourceService = BoxCloneService<ResourceRequest, ReadResourceResult, Infallible>;
125
126#[cfg(feature = "stateless")]
127type BoxMrtrResourceService =
128 BoxCloneService<ResourceRequest, RequestOutcome<ReadResourceResult>, Infallible>;
129
130#[doc(hidden)]
136pub struct ResourceCatchError<S> {
137 inner: S,
138}
139
140impl<S> ResourceCatchError<S> {
141 pub fn new(inner: S) -> Self {
143 Self { inner }
144 }
145}
146
147impl<S: Clone> Clone for ResourceCatchError<S> {
148 fn clone(&self) -> Self {
149 Self {
150 inner: self.inner.clone(),
151 }
152 }
153}
154
155impl<S: fmt::Debug> fmt::Debug for ResourceCatchError<S> {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 f.debug_struct("ResourceCatchError")
158 .field("inner", &self.inner)
159 .finish()
160 }
161}
162
163pin_project! {
164 #[doc(hidden)]
166 pub struct ResourceCatchErrorFuture<F> {
167 #[pin]
168 inner: F,
169 uri: Option<String>,
170 }
171}
172
173impl<F, E> Future for ResourceCatchErrorFuture<F>
174where
175 F: Future<Output = std::result::Result<ReadResourceResult, E>>,
176 E: fmt::Display,
177{
178 type Output = std::result::Result<ReadResourceResult, Infallible>;
179
180 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
181 let this = self.project();
182 match this.inner.poll(cx) {
183 Poll::Pending => Poll::Pending,
184 Poll::Ready(Ok(result)) => Poll::Ready(Ok(result)),
185 Poll::Ready(Err(err)) => {
186 let uri = this.uri.take().unwrap_or_default();
187 Poll::Ready(Ok(ReadResourceResult {
188 contents: vec![ResourceContent {
189 uri,
190 mime_type: Some("text/plain".to_string()),
191 text: Some(format!("Error reading resource: {}", err)),
192 blob: None,
193 meta: None,
194 }],
195 meta: None,
196 ..Default::default()
197 }))
198 }
199 }
200 }
201}
202
203impl<S> Service<ResourceRequest> for ResourceCatchError<S>
204where
205 S: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
206 S::Error: fmt::Display + Send,
207 S::Future: Send,
208{
209 type Response = ReadResourceResult;
210 type Error = Infallible;
211 type Future = ResourceCatchErrorFuture<S::Future>;
212
213 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
214 match self.inner.poll_ready(cx) {
216 Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
217 Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
218 Poll::Pending => Poll::Pending,
219 }
220 }
221
222 fn call(&mut self, req: ResourceRequest) -> Self::Future {
223 let uri = req.uri.clone();
224 let fut = self.inner.call(req);
225
226 ResourceCatchErrorFuture {
227 inner: fut,
228 uri: Some(uri),
229 }
230 }
231}
232
233#[cfg(feature = "stateless")]
234#[derive(Clone)]
235struct MrtrResourceCatchError<S> {
236 inner: S,
237}
238
239#[cfg(feature = "stateless")]
240impl<S> MrtrResourceCatchError<S> {
241 fn new(inner: S) -> Self {
242 Self { inner }
243 }
244}
245
246#[cfg(feature = "stateless")]
247impl<S> Service<ResourceRequest> for MrtrResourceCatchError<S>
248where
249 S: Service<ResourceRequest, Response = RequestOutcome<ReadResourceResult>>
250 + Clone
251 + Send
252 + 'static,
253 S::Error: fmt::Display + Send + 'static,
254 S::Future: Send + 'static,
255{
256 type Response = RequestOutcome<ReadResourceResult>;
257 type Error = Infallible;
258 type Future =
259 Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
260
261 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
262 match self.inner.poll_ready(cx) {
263 Poll::Ready(Ok(())) | Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
264 Poll::Pending => Poll::Pending,
265 }
266 }
267
268 fn call(&mut self, req: ResourceRequest) -> Self::Future {
269 let uri = req.uri.clone();
270 let future = self.inner.call(req);
271 Box::pin(async move {
272 Ok(match future.await {
273 Ok(outcome) => outcome,
274 Err(error) => RequestOutcome::Complete(ReadResourceResult {
275 contents: vec![ResourceContent {
276 uri,
277 mime_type: Some("text/plain".to_string()),
278 text: Some(format!("Error reading resource: {error}")),
279 blob: None,
280 meta: None,
281 }],
282 meta: None,
283 ..Default::default()
284 }),
285 })
286 })
287 }
288}
289
290pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
292
293pub trait ResourceHandler: Send + Sync {
295 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>>;
297
298 fn read_with_context(&self, _ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
303 self.read()
304 }
305
306 fn uses_context(&self) -> bool {
308 false
309 }
310}
311
312#[cfg(feature = "stateless")]
314pub trait MrtrResourceHandler: Send + Sync {
315 fn read(
317 &self,
318 ctx: RequestContext,
319 ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>>;
320}
321
322#[cfg(feature = "stateless")]
323struct MrtrResourceHandlerService<H> {
324 handler: Arc<H>,
325}
326
327#[cfg(feature = "stateless")]
328impl<H> MrtrResourceHandlerService<H> {
329 fn new(handler: H) -> Self {
330 Self {
331 handler: Arc::new(handler),
332 }
333 }
334}
335
336#[cfg(feature = "stateless")]
337impl<H> Clone for MrtrResourceHandlerService<H> {
338 fn clone(&self) -> Self {
339 Self {
340 handler: self.handler.clone(),
341 }
342 }
343}
344
345#[cfg(feature = "stateless")]
346impl<H> Service<ResourceRequest> for MrtrResourceHandlerService<H>
347where
348 H: MrtrResourceHandler + 'static,
349{
350 type Response = RequestOutcome<ReadResourceResult>;
351 type Error = Error;
352 type Future =
353 Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
354
355 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
356 Poll::Ready(Ok(()))
357 }
358
359 fn call(&mut self, req: ResourceRequest) -> Self::Future {
360 let handler = self.handler.clone();
361 Box::pin(async move { handler.read(req.ctx).await })
362 }
363}
364
365#[cfg(feature = "stateless")]
366struct ServiceMrtrResourceHandler {
367 service: Mutex<BoxMrtrResourceService>,
368 uri: String,
369}
370
371#[cfg(feature = "stateless")]
372impl MrtrResourceHandler for ServiceMrtrResourceHandler {
373 fn read(
374 &self,
375 ctx: RequestContext,
376 ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>> {
377 Box::pin(async move {
378 let request = ResourceRequest::new(ctx, self.uri.clone());
379 let mut service = self.service.lock().await.clone();
380 let outcome = service
381 .ready()
382 .await
383 .expect("MRTR resource service is infallible")
384 .call(request)
385 .await
386 .expect("MRTR resource service is infallible");
387 Ok(outcome)
388 })
389 }
390}
391
392struct ResourceHandlerService<H> {
397 handler: Arc<H>,
398}
399
400impl<H> ResourceHandlerService<H> {
401 fn new(handler: H) -> Self {
402 Self {
403 handler: Arc::new(handler),
404 }
405 }
406}
407
408impl<H> Clone for ResourceHandlerService<H> {
409 fn clone(&self) -> Self {
410 Self {
411 handler: self.handler.clone(),
412 }
413 }
414}
415
416impl<H> fmt::Debug for ResourceHandlerService<H> {
417 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418 f.debug_struct("ResourceHandlerService")
419 .finish_non_exhaustive()
420 }
421}
422
423impl<H> Service<ResourceRequest> for ResourceHandlerService<H>
424where
425 H: ResourceHandler + 'static,
426{
427 type Response = ReadResourceResult;
428 type Error = Error;
429 type Future =
430 Pin<Box<dyn Future<Output = std::result::Result<ReadResourceResult, Error>> + Send>>;
431
432 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
433 Poll::Ready(Ok(()))
434 }
435
436 fn call(&mut self, req: ResourceRequest) -> Self::Future {
437 let handler = self.handler.clone();
438 Box::pin(async move { handler.read_with_context(req.ctx).await })
439 }
440}
441
442pub struct Resource {
449 pub uri: String,
451 pub name: String,
453 pub title: Option<String>,
455 pub description: Option<String>,
457 pub mime_type: Option<String>,
459 pub icons: Option<Vec<ToolIcon>>,
461 pub size: Option<u64>,
463 pub annotations: Option<ContentAnnotations>,
465 pub meta: Option<Value>,
467 service: Option<BoxResourceService>,
469 #[cfg(feature = "stateless")]
470 mrtr_handler: Option<Arc<dyn MrtrResourceHandler>>,
471}
472
473impl Clone for Resource {
474 fn clone(&self) -> Self {
475 Self {
476 uri: self.uri.clone(),
477 name: self.name.clone(),
478 title: self.title.clone(),
479 description: self.description.clone(),
480 mime_type: self.mime_type.clone(),
481 icons: self.icons.clone(),
482 size: self.size,
483 annotations: self.annotations.clone(),
484 meta: self.meta.clone(),
485 service: self.service.clone(),
486 #[cfg(feature = "stateless")]
487 mrtr_handler: self.mrtr_handler.clone(),
488 }
489 }
490}
491
492impl std::fmt::Debug for Resource {
493 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
494 f.debug_struct("Resource")
495 .field("uri", &self.uri)
496 .field("name", &self.name)
497 .field("title", &self.title)
498 .field("description", &self.description)
499 .field("mime_type", &self.mime_type)
500 .field("icons", &self.icons)
501 .field("size", &self.size)
502 .field("annotations", &self.annotations)
503 .field("meta", &self.meta)
504 .finish_non_exhaustive()
505 }
506}
507
508unsafe impl Send for Resource {}
511unsafe impl Sync for Resource {}
512
513impl Resource {
514 pub fn builder(uri: impl Into<String>) -> ResourceBuilder {
516 ResourceBuilder::new(uri)
517 }
518
519 pub fn definition(&self) -> ResourceDefinition {
521 ResourceDefinition {
522 uri: self.uri.clone(),
523 name: self.name.clone(),
524 title: self.title.clone(),
525 description: self.description.clone(),
526 mime_type: self.mime_type.clone(),
527 icons: self.icons.clone(),
528 size: self.size,
529 annotations: self.annotations.clone(),
530 meta: self.meta.clone(),
531 }
532 }
533
534 pub fn with_meta(
536 mut self,
537 meta: Value,
538 ) -> std::result::Result<Self, crate::protocol::MetaValidationError> {
539 crate::protocol::validate_meta_object(&meta)?;
540 self.meta = Some(meta);
541 Ok(self)
542 }
543
544 pub fn read(&self) -> BoxFuture<'static, ReadResourceResult> {
549 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
550 self.read_with_context(ctx)
551 }
552
553 pub fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'static, ReadResourceResult> {
564 let resource = self.clone();
565 let uri = self.uri.clone();
566 Box::pin(async move {
567 match resource.read_outcome_with_context(ctx).await {
568 Ok(RequestOutcome::Complete(result)) => result,
569 Ok(RequestOutcome::InputRequired(_)) => ReadResourceResult {
570 contents: vec![ResourceContent {
571 uri,
572 mime_type: Some("text/plain".into()),
573 text: Some(
574 "resource requires additional client input; use read_outcome_with_context"
575 .into(),
576 ),
577 blob: None,
578 meta: None,
579 }],
580 ..ReadResourceResult::default()
581 },
582 Err(error) => ReadResourceResult {
583 contents: vec![ResourceContent {
584 uri,
585 mime_type: Some("text/plain".into()),
586 text: Some(error.to_string()),
587 blob: None,
588 meta: None,
589 }],
590 ..ReadResourceResult::default()
591 },
592 }
593 })
594 }
595
596 pub fn read_outcome_with_context(
598 &self,
599 ctx: RequestContext,
600 ) -> BoxFuture<'static, Result<RequestOutcome<ReadResourceResult>>> {
601 use tower::ServiceExt;
602 #[cfg(feature = "stateless")]
603 if let Some(handler) = self.mrtr_handler.clone() {
604 return Box::pin(async move { handler.read(ctx).await });
605 }
606 let service = self
607 .service
608 .clone()
609 .expect("resource must have a complete or MRTR handler");
610 let uri = self.uri.clone();
611 Box::pin(async move {
612 let result = service
613 .oneshot(ResourceRequest::new(ctx, uri))
614 .await
615 .unwrap();
616 Ok(RequestOutcome::Complete(result))
617 })
618 }
619
620 #[allow(clippy::too_many_arguments)]
622 fn from_handler<H: ResourceHandler + 'static>(
623 uri: String,
624 name: String,
625 title: Option<String>,
626 description: Option<String>,
627 mime_type: Option<String>,
628 icons: Option<Vec<ToolIcon>>,
629 size: Option<u64>,
630 annotations: Option<ContentAnnotations>,
631 handler: H,
632 ) -> Self {
633 let handler_service = ResourceHandlerService::new(handler);
634 let catch_error = ResourceCatchError::new(handler_service);
635 let service = BoxCloneService::new(catch_error);
636
637 Self {
638 uri,
639 name,
640 title,
641 description,
642 mime_type,
643 icons,
644 size,
645 annotations,
646 meta: None,
647 service: Some(service),
648 #[cfg(feature = "stateless")]
649 mrtr_handler: None,
650 }
651 }
652
653 #[cfg(feature = "stateless")]
654 #[allow(clippy::too_many_arguments)]
655 fn from_mrtr_handler<H: MrtrResourceHandler + 'static>(
656 uri: String,
657 name: String,
658 title: Option<String>,
659 description: Option<String>,
660 mime_type: Option<String>,
661 icons: Option<Vec<ToolIcon>>,
662 size: Option<u64>,
663 annotations: Option<ContentAnnotations>,
664 handler: H,
665 ) -> Self {
666 Self {
667 uri,
668 name,
669 title,
670 description,
671 mime_type,
672 icons,
673 size,
674 annotations,
675 meta: None,
676 service: None,
677 mrtr_handler: Some(Arc::new(handler)),
678 }
679 }
680}
681
682pub struct ResourceBuilder {
716 uri: String,
717 name: Option<String>,
718 title: Option<String>,
719 description: Option<String>,
720 mime_type: Option<String>,
721 icons: Option<Vec<ToolIcon>>,
722 size: Option<u64>,
723 annotations: Option<ContentAnnotations>,
724}
725
726impl ResourceBuilder {
727 pub fn new(uri: impl Into<String>) -> Self {
729 Self {
730 uri: uri.into(),
731 name: None,
732 title: None,
733 description: None,
734 mime_type: None,
735 icons: None,
736 size: None,
737 annotations: None,
738 }
739 }
740
741 pub fn name(mut self, name: impl Into<String>) -> Self {
743 self.name = Some(name.into());
744 self
745 }
746
747 pub fn title(mut self, title: impl Into<String>) -> Self {
749 self.title = Some(title.into());
750 self
751 }
752
753 pub fn description(mut self, description: impl Into<String>) -> Self {
755 self.description = Some(description.into());
756 self
757 }
758
759 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
761 self.mime_type = Some(mime_type.into());
762 self
763 }
764
765 pub fn icon(mut self, src: impl Into<String>) -> Self {
767 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
768 src: src.into(),
769 mime_type: None,
770 sizes: None,
771 theme: None,
772 });
773 self
774 }
775
776 pub fn icon_with_meta(
778 mut self,
779 src: impl Into<String>,
780 mime_type: Option<String>,
781 sizes: Option<Vec<String>>,
782 ) -> Self {
783 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
784 src: src.into(),
785 mime_type,
786 sizes,
787 theme: None,
788 });
789 self
790 }
791
792 pub fn size(mut self, size: u64) -> Self {
794 self.size = Some(size);
795 self
796 }
797
798 pub fn annotations(mut self, annotations: ContentAnnotations) -> Self {
800 self.annotations = Some(annotations);
801 self
802 }
803
804 pub fn handler<F, Fut>(self, handler: F) -> ResourceBuilderWithHandler<F>
847 where
848 F: Fn() -> Fut + Send + Sync + 'static,
849 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
850 {
851 ResourceBuilderWithHandler {
852 uri: self.uri,
853 name: self.name,
854 title: self.title,
855 description: self.description,
856 mime_type: self.mime_type,
857 icons: self.icons,
858 size: self.size,
859 annotations: self.annotations,
860 handler,
861 }
862 }
863
864 pub fn handler_with_context<F, Fut>(self, handler: F) -> ResourceBuilderWithContextHandler<F>
872 where
873 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
874 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
875 {
876 ResourceBuilderWithContextHandler {
877 uri: self.uri,
878 name: self.name,
879 title: self.title,
880 description: self.description,
881 mime_type: self.mime_type,
882 icons: self.icons,
883 size: self.size,
884 annotations: self.annotations,
885 handler,
886 }
887 }
888
889 #[cfg(feature = "stateless")]
891 pub fn mrtr_handler<F, Fut>(self, handler: F) -> ResourceBuilderWithMrtrHandler<F>
892 where
893 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
894 Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
895 {
896 ResourceBuilderWithMrtrHandler {
897 uri: self.uri,
898 name: self.name,
899 title: self.title,
900 description: self.description,
901 mime_type: self.mime_type,
902 icons: self.icons,
903 size: self.size,
904 annotations: self.annotations,
905 handler,
906 }
907 }
908
909 pub fn text(self, content: impl Into<String>) -> Resource {
911 let uri = self.uri.clone();
912 let content = content.into();
913 let mime_type = self.mime_type.clone();
914
915 self.handler(move || {
916 let uri = uri.clone();
917 let content = content.clone();
918 let mime_type = mime_type.clone();
919 async move {
920 Ok(ReadResourceResult {
921 contents: vec![ResourceContent {
922 uri,
923 mime_type,
924 text: Some(content),
925 blob: None,
926 meta: None,
927 }],
928 meta: None,
929 ..Default::default()
930 })
931 }
932 })
933 .build()
934 }
935
936 pub fn json(mut self, value: serde_json::Value) -> Resource {
938 let uri = self.uri.clone();
939 self.mime_type = Some("application/json".to_string());
940 let text = serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{}".to_string());
941
942 self.handler(move || {
943 let uri = uri.clone();
944 let text = text.clone();
945 async move {
946 Ok(ReadResourceResult {
947 contents: vec![ResourceContent {
948 uri,
949 mime_type: Some("application/json".to_string()),
950 text: Some(text),
951 blob: None,
952 meta: None,
953 }],
954 meta: None,
955 ..Default::default()
956 })
957 }
958 })
959 .build()
960 }
961}
962
963#[doc(hidden)]
968pub struct ResourceBuilderWithHandler<F> {
969 uri: String,
970 name: Option<String>,
971 title: Option<String>,
972 description: Option<String>,
973 mime_type: Option<String>,
974 icons: Option<Vec<ToolIcon>>,
975 size: Option<u64>,
976 annotations: Option<ContentAnnotations>,
977 handler: F,
978}
979
980#[cfg(feature = "stateless")]
981#[doc(hidden)]
982pub struct ResourceBuilderWithMrtrHandler<F> {
983 uri: String,
984 name: Option<String>,
985 title: Option<String>,
986 description: Option<String>,
987 mime_type: Option<String>,
988 icons: Option<Vec<ToolIcon>>,
989 size: Option<u64>,
990 annotations: Option<ContentAnnotations>,
991 handler: F,
992}
993
994#[cfg(feature = "stateless")]
995#[doc(hidden)]
996pub struct ResourceBuilderWithMrtrLayer<F, L> {
997 uri: String,
998 name: Option<String>,
999 title: Option<String>,
1000 description: Option<String>,
1001 mime_type: Option<String>,
1002 icons: Option<Vec<ToolIcon>>,
1003 size: Option<u64>,
1004 annotations: Option<ContentAnnotations>,
1005 handler: F,
1006 layer: L,
1007}
1008
1009#[cfg(feature = "stateless")]
1010impl<F, Fut> ResourceBuilderWithMrtrHandler<F>
1011where
1012 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1013 Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
1014{
1015 pub fn build(self) -> Resource {
1017 let name = self.name.unwrap_or_else(|| self.uri.clone());
1018
1019 Resource::from_mrtr_handler(
1020 self.uri,
1021 name,
1022 self.title,
1023 self.description,
1024 self.mime_type,
1025 self.icons,
1026 self.size,
1027 self.annotations,
1028 MrtrContextHandler {
1029 handler: self.handler,
1030 },
1031 )
1032 }
1033
1034 pub fn layer<L>(self, layer: L) -> ResourceBuilderWithMrtrLayer<F, L> {
1040 ResourceBuilderWithMrtrLayer {
1041 uri: self.uri,
1042 name: self.name,
1043 title: self.title,
1044 description: self.description,
1045 mime_type: self.mime_type,
1046 icons: self.icons,
1047 size: self.size,
1048 annotations: self.annotations,
1049 handler: self.handler,
1050 layer,
1051 }
1052 }
1053}
1054
1055#[cfg(feature = "stateless")]
1056#[allow(private_bounds)]
1057impl<F, Fut, L> ResourceBuilderWithMrtrLayer<F, L>
1058where
1059 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1060 Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
1061 L: tower::Layer<MrtrResourceHandlerService<MrtrContextHandler<F>>>
1062 + Clone
1063 + Send
1064 + Sync
1065 + 'static,
1066 L::Service: Service<ResourceRequest, Response = RequestOutcome<ReadResourceResult>>
1067 + Clone
1068 + Send
1069 + 'static,
1070 <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send + 'static,
1071 <L::Service as Service<ResourceRequest>>::Future: Send + 'static,
1072{
1073 pub fn build(self) -> Resource {
1075 let name = self.name.unwrap_or_else(|| self.uri.clone());
1076 let handler = MrtrContextHandler {
1077 handler: self.handler,
1078 };
1079 let service = MrtrResourceHandlerService::new(handler);
1080 let service = self.layer.layer(service);
1081 let service = BoxCloneService::new(MrtrResourceCatchError::new(service));
1082
1083 Resource {
1084 uri: self.uri.clone(),
1085 name,
1086 title: self.title,
1087 description: self.description,
1088 mime_type: self.mime_type,
1089 icons: self.icons,
1090 size: self.size,
1091 annotations: self.annotations,
1092 meta: None,
1093 service: None,
1094 mrtr_handler: Some(Arc::new(ServiceMrtrResourceHandler {
1095 service: Mutex::new(service),
1096 uri: self.uri,
1097 })),
1098 }
1099 }
1100
1101 pub fn layer<L2>(
1103 self,
1104 layer: L2,
1105 ) -> ResourceBuilderWithMrtrLayer<F, tower::layer::util::Stack<L2, L>> {
1106 ResourceBuilderWithMrtrLayer {
1107 uri: self.uri,
1108 name: self.name,
1109 title: self.title,
1110 description: self.description,
1111 mime_type: self.mime_type,
1112 icons: self.icons,
1113 size: self.size,
1114 annotations: self.annotations,
1115 handler: self.handler,
1116 layer: tower::layer::util::Stack::new(layer, self.layer),
1117 }
1118 }
1119}
1120
1121impl<F, Fut> ResourceBuilderWithHandler<F>
1122where
1123 F: Fn() -> Fut + Send + Sync + 'static,
1124 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1125{
1126 pub fn build(self) -> Resource {
1128 let name = self.name.unwrap_or_else(|| self.uri.clone());
1129
1130 Resource::from_handler(
1131 self.uri,
1132 name,
1133 self.title,
1134 self.description,
1135 self.mime_type,
1136 self.icons,
1137 self.size,
1138 self.annotations,
1139 FnHandler {
1140 handler: self.handler,
1141 },
1142 )
1143 }
1144
1145 pub fn layer<L>(self, layer: L) -> ResourceBuilderWithLayer<F, L> {
1177 ResourceBuilderWithLayer {
1178 uri: self.uri,
1179 name: self.name,
1180 title: self.title,
1181 description: self.description,
1182 mime_type: self.mime_type,
1183 icons: self.icons,
1184 size: self.size,
1185 annotations: self.annotations,
1186 handler: self.handler,
1187 layer,
1188 }
1189 }
1190}
1191
1192#[doc(hidden)]
1196pub struct ResourceBuilderWithLayer<F, L> {
1197 uri: String,
1198 name: Option<String>,
1199 title: Option<String>,
1200 description: Option<String>,
1201 mime_type: Option<String>,
1202 icons: Option<Vec<ToolIcon>>,
1203 size: Option<u64>,
1204 annotations: Option<ContentAnnotations>,
1205 handler: F,
1206 layer: L,
1207}
1208
1209#[allow(private_bounds)]
1212impl<F, Fut, L> ResourceBuilderWithLayer<F, L>
1213where
1214 F: Fn() -> Fut + Send + Sync + 'static,
1215 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1216 L: tower::Layer<ResourceHandlerService<FnHandler<F>>> + Clone + Send + Sync + 'static,
1217 L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
1218 <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
1219 <L::Service as Service<ResourceRequest>>::Future: Send,
1220{
1221 pub fn build(self) -> Resource {
1223 let name = self.name.unwrap_or_else(|| self.uri.clone());
1224
1225 let handler_service = ResourceHandlerService::new(FnHandler {
1226 handler: self.handler,
1227 });
1228 let layered = self.layer.layer(handler_service);
1229 let catch_error = ResourceCatchError::new(layered);
1230 let service = BoxCloneService::new(catch_error);
1231
1232 Resource {
1233 uri: self.uri,
1234 name,
1235 title: self.title,
1236 description: self.description,
1237 mime_type: self.mime_type,
1238 icons: self.icons,
1239 size: self.size,
1240 annotations: self.annotations,
1241 meta: None,
1242 service: Some(service),
1243 #[cfg(feature = "stateless")]
1244 mrtr_handler: None,
1245 }
1246 }
1247
1248 pub fn layer<L2>(
1253 self,
1254 layer: L2,
1255 ) -> ResourceBuilderWithLayer<F, tower::layer::util::Stack<L2, L>> {
1256 ResourceBuilderWithLayer {
1257 uri: self.uri,
1258 name: self.name,
1259 title: self.title,
1260 description: self.description,
1261 mime_type: self.mime_type,
1262 icons: self.icons,
1263 size: self.size,
1264 annotations: self.annotations,
1265 handler: self.handler,
1266 layer: tower::layer::util::Stack::new(layer, self.layer),
1267 }
1268 }
1269}
1270
1271#[doc(hidden)]
1273pub struct ResourceBuilderWithContextHandler<F> {
1274 uri: String,
1275 name: Option<String>,
1276 title: Option<String>,
1277 description: Option<String>,
1278 mime_type: Option<String>,
1279 icons: Option<Vec<ToolIcon>>,
1280 size: Option<u64>,
1281 annotations: Option<ContentAnnotations>,
1282 handler: F,
1283}
1284
1285impl<F, Fut> ResourceBuilderWithContextHandler<F>
1286where
1287 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1288 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1289{
1290 pub fn build(self) -> Resource {
1292 let name = self.name.unwrap_or_else(|| self.uri.clone());
1293
1294 Resource::from_handler(
1295 self.uri,
1296 name,
1297 self.title,
1298 self.description,
1299 self.mime_type,
1300 self.icons,
1301 self.size,
1302 self.annotations,
1303 ContextAwareHandler {
1304 handler: self.handler,
1305 },
1306 )
1307 }
1308
1309 pub fn layer<L>(self, layer: L) -> ResourceBuilderWithContextLayer<F, L> {
1313 ResourceBuilderWithContextLayer {
1314 uri: self.uri,
1315 name: self.name,
1316 title: self.title,
1317 description: self.description,
1318 mime_type: self.mime_type,
1319 icons: self.icons,
1320 size: self.size,
1321 annotations: self.annotations,
1322 handler: self.handler,
1323 layer,
1324 }
1325 }
1326}
1327
1328#[doc(hidden)]
1330pub struct ResourceBuilderWithContextLayer<F, L> {
1331 uri: String,
1332 name: Option<String>,
1333 title: Option<String>,
1334 description: Option<String>,
1335 mime_type: Option<String>,
1336 icons: Option<Vec<ToolIcon>>,
1337 size: Option<u64>,
1338 annotations: Option<ContentAnnotations>,
1339 handler: F,
1340 layer: L,
1341}
1342
1343#[allow(private_bounds)]
1345impl<F, Fut, L> ResourceBuilderWithContextLayer<F, L>
1346where
1347 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1348 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1349 L: tower::Layer<ResourceHandlerService<ContextAwareHandler<F>>> + Clone + Send + Sync + 'static,
1350 L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
1351 <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
1352 <L::Service as Service<ResourceRequest>>::Future: Send,
1353{
1354 pub fn build(self) -> Resource {
1356 let name = self.name.unwrap_or_else(|| self.uri.clone());
1357
1358 let handler_service = ResourceHandlerService::new(ContextAwareHandler {
1359 handler: self.handler,
1360 });
1361 let layered = self.layer.layer(handler_service);
1362 let catch_error = ResourceCatchError::new(layered);
1363 let service = BoxCloneService::new(catch_error);
1364
1365 Resource {
1366 uri: self.uri,
1367 name,
1368 title: self.title,
1369 description: self.description,
1370 mime_type: self.mime_type,
1371 icons: self.icons,
1372 size: self.size,
1373 annotations: self.annotations,
1374 meta: None,
1375 service: Some(service),
1376 #[cfg(feature = "stateless")]
1377 mrtr_handler: None,
1378 }
1379 }
1380
1381 pub fn layer<L2>(
1383 self,
1384 layer: L2,
1385 ) -> ResourceBuilderWithContextLayer<F, tower::layer::util::Stack<L2, L>> {
1386 ResourceBuilderWithContextLayer {
1387 uri: self.uri,
1388 name: self.name,
1389 title: self.title,
1390 description: self.description,
1391 mime_type: self.mime_type,
1392 icons: self.icons,
1393 size: self.size,
1394 annotations: self.annotations,
1395 handler: self.handler,
1396 layer: tower::layer::util::Stack::new(layer, self.layer),
1397 }
1398 }
1399}
1400
1401struct FnHandler<F> {
1407 handler: F,
1408}
1409
1410impl<F, Fut> ResourceHandler for FnHandler<F>
1411where
1412 F: Fn() -> Fut + Send + Sync + 'static,
1413 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1414{
1415 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1416 Box::pin((self.handler)())
1417 }
1418}
1419
1420struct ContextAwareHandler<F> {
1422 handler: F,
1423}
1424
1425impl<F, Fut> ResourceHandler for ContextAwareHandler<F>
1426where
1427 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1428 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1429{
1430 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1431 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
1432 self.read_with_context(ctx)
1433 }
1434
1435 fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
1436 Box::pin((self.handler)(ctx))
1437 }
1438
1439 fn uses_context(&self) -> bool {
1440 true
1441 }
1442}
1443
1444#[cfg(feature = "stateless")]
1445struct MrtrContextHandler<F> {
1446 handler: F,
1447}
1448
1449#[cfg(feature = "stateless")]
1450impl<F, Fut> MrtrResourceHandler for MrtrContextHandler<F>
1451where
1452 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1453 Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
1454{
1455 fn read(
1456 &self,
1457 ctx: RequestContext,
1458 ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>> {
1459 Box::pin((self.handler)(ctx))
1460 }
1461}
1462
1463pub trait McpResource: Send + Sync + 'static {
1508 const URI: &'static str;
1510 const NAME: &'static str;
1512 const DESCRIPTION: Option<&'static str> = None;
1514 const MIME_TYPE: Option<&'static str> = None;
1516
1517 fn read(&self) -> impl Future<Output = Result<ReadResourceResult>> + Send;
1519
1520 fn into_resource(self) -> Resource
1522 where
1523 Self: Sized,
1524 {
1525 let resource = Arc::new(self);
1526 Resource::from_handler(
1527 Self::URI.to_string(),
1528 Self::NAME.to_string(),
1529 None,
1530 Self::DESCRIPTION.map(|s| s.to_string()),
1531 Self::MIME_TYPE.map(|s| s.to_string()),
1532 None,
1533 None,
1534 None,
1535 McpResourceHandler { resource },
1536 )
1537 }
1538}
1539
1540struct McpResourceHandler<T: McpResource> {
1542 resource: Arc<T>,
1543}
1544
1545impl<T: McpResource> ResourceHandler for McpResourceHandler<T> {
1546 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1547 let resource = self.resource.clone();
1548 Box::pin(async move { resource.read().await })
1549 }
1550}
1551
1552pub trait ResourceTemplateHandler: Send + Sync {
1561 fn read(
1563 &self,
1564 uri: &str,
1565 variables: HashMap<String, String>,
1566 ) -> BoxFuture<'_, Result<ReadResourceResult>>;
1567}
1568
1569#[cfg(feature = "stateless")]
1571pub trait MrtrResourceTemplateHandler: Send + Sync {
1572 fn read(
1574 &self,
1575 ctx: RequestContext,
1576 uri: &str,
1577 variables: HashMap<String, String>,
1578 ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>>;
1579}
1580
1581pub struct ResourceTemplate {
1612 pub uri_template: String,
1614 pub name: String,
1616 pub title: Option<String>,
1618 pub description: Option<String>,
1620 pub mime_type: Option<String>,
1622 pub icons: Option<Vec<ToolIcon>>,
1624 pub annotations: Option<ContentAnnotations>,
1626 pattern: regex::Regex,
1628 variables: Vec<String>,
1630 handler: Option<Arc<dyn ResourceTemplateHandler>>,
1632 #[cfg(feature = "stateless")]
1633 mrtr_handler: Option<Arc<dyn MrtrResourceTemplateHandler>>,
1634}
1635
1636impl Clone for ResourceTemplate {
1637 fn clone(&self) -> Self {
1638 Self {
1639 uri_template: self.uri_template.clone(),
1640 name: self.name.clone(),
1641 title: self.title.clone(),
1642 description: self.description.clone(),
1643 mime_type: self.mime_type.clone(),
1644 icons: self.icons.clone(),
1645 annotations: self.annotations.clone(),
1646 pattern: self.pattern.clone(),
1647 variables: self.variables.clone(),
1648 handler: self.handler.clone(),
1649 #[cfg(feature = "stateless")]
1650 mrtr_handler: self.mrtr_handler.clone(),
1651 }
1652 }
1653}
1654
1655impl std::fmt::Debug for ResourceTemplate {
1656 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1657 f.debug_struct("ResourceTemplate")
1658 .field("uri_template", &self.uri_template)
1659 .field("name", &self.name)
1660 .field("title", &self.title)
1661 .field("description", &self.description)
1662 .field("mime_type", &self.mime_type)
1663 .field("icons", &self.icons)
1664 .field("variables", &self.variables)
1665 .finish_non_exhaustive()
1666 }
1667}
1668
1669impl ResourceTemplate {
1670 pub fn builder(uri_template: impl Into<String>) -> ResourceTemplateBuilder {
1672 ResourceTemplateBuilder::new(uri_template)
1673 }
1674
1675 pub fn definition(&self) -> ResourceTemplateDefinition {
1677 ResourceTemplateDefinition {
1678 uri_template: self.uri_template.clone(),
1679 name: self.name.clone(),
1680 title: self.title.clone(),
1681 description: self.description.clone(),
1682 mime_type: self.mime_type.clone(),
1683 icons: self.icons.clone(),
1684 annotations: self.annotations.clone(),
1685 arguments: Vec::new(),
1686 meta: None,
1687 }
1688 }
1689
1690 pub fn match_uri(&self, uri: &str) -> Option<HashMap<String, String>> {
1695 self.pattern.captures(uri).map(|caps| {
1696 self.variables
1697 .iter()
1698 .enumerate()
1699 .filter_map(|(i, name)| {
1700 caps.get(i + 1)
1701 .map(|m| (name.clone(), m.as_str().to_string()))
1702 })
1703 .collect()
1704 })
1705 }
1706
1707 pub fn read(
1714 &self,
1715 uri: &str,
1716 variables: HashMap<String, String>,
1717 ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1718 match &self.handler {
1719 Some(handler) => handler.read(uri, variables),
1720 None => Box::pin(async {
1721 Err(Error::invalid_params(
1722 "MRTR resource template requires read_outcome_with_context",
1723 ))
1724 }),
1725 }
1726 }
1727
1728 pub fn read_outcome_with_context(
1730 &self,
1731 ctx: RequestContext,
1732 uri: &str,
1733 variables: HashMap<String, String>,
1734 ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>> {
1735 let _ = &ctx;
1736 #[cfg(feature = "stateless")]
1737 if let Some(handler) = &self.mrtr_handler {
1738 return handler.read(ctx, uri, variables);
1739 }
1740 match &self.handler {
1741 Some(handler) => {
1742 let handler = handler.clone();
1743 let uri = uri.to_string();
1744 Box::pin(async move {
1745 handler
1746 .read(&uri, variables)
1747 .await
1748 .map(RequestOutcome::Complete)
1749 })
1750 }
1751 None => Box::pin(async {
1752 Err(Error::invalid_params(
1753 "resource template has neither a complete nor MRTR handler",
1754 ))
1755 }),
1756 }
1757 }
1758}
1759
1760pub struct ResourceTemplateBuilder {
1788 uri_template: String,
1789 name: Option<String>,
1790 title: Option<String>,
1791 description: Option<String>,
1792 mime_type: Option<String>,
1793 icons: Option<Vec<ToolIcon>>,
1794 annotations: Option<ContentAnnotations>,
1795}
1796
1797impl ResourceTemplateBuilder {
1798 pub fn new(uri_template: impl Into<String>) -> Self {
1811 Self {
1812 uri_template: uri_template.into(),
1813 name: None,
1814 title: None,
1815 description: None,
1816 mime_type: None,
1817 icons: None,
1818 annotations: None,
1819 }
1820 }
1821
1822 pub fn name(mut self, name: impl Into<String>) -> Self {
1824 self.name = Some(name.into());
1825 self
1826 }
1827
1828 pub fn title(mut self, title: impl Into<String>) -> Self {
1830 self.title = Some(title.into());
1831 self
1832 }
1833
1834 pub fn description(mut self, description: impl Into<String>) -> Self {
1836 self.description = Some(description.into());
1837 self
1838 }
1839
1840 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
1842 self.mime_type = Some(mime_type.into());
1843 self
1844 }
1845
1846 pub fn icon(mut self, src: impl Into<String>) -> Self {
1848 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1849 src: src.into(),
1850 mime_type: None,
1851 sizes: None,
1852 theme: None,
1853 });
1854 self
1855 }
1856
1857 pub fn icon_with_meta(
1859 mut self,
1860 src: impl Into<String>,
1861 mime_type: Option<String>,
1862 sizes: Option<Vec<String>>,
1863 ) -> Self {
1864 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1865 src: src.into(),
1866 mime_type,
1867 sizes,
1868 theme: None,
1869 });
1870 self
1871 }
1872
1873 pub fn annotations(mut self, annotations: ContentAnnotations) -> Self {
1875 self.annotations = Some(annotations);
1876 self
1877 }
1878
1879 pub fn handler<F, Fut>(self, handler: F) -> ResourceTemplate
1891 where
1892 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1893 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1894 {
1895 self.try_handler(handler).unwrap_or_else(|e| {
1896 panic!("Invalid URI template: {e}");
1897 })
1898 }
1899
1900 pub fn try_handler<F, Fut>(self, handler: F) -> std::result::Result<ResourceTemplate, Error>
1911 where
1912 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1913 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1914 {
1915 let (pattern, variables) = compile_uri_template(&self.uri_template)?;
1916 let name = self.name.unwrap_or_else(|| self.uri_template.clone());
1917
1918 Ok(ResourceTemplate {
1919 uri_template: self.uri_template,
1920 name,
1921 title: self.title,
1922 description: self.description,
1923 mime_type: self.mime_type,
1924 icons: self.icons,
1925 annotations: self.annotations,
1926 pattern,
1927 variables,
1928 handler: Some(Arc::new(FnTemplateHandler { handler })),
1929 #[cfg(feature = "stateless")]
1930 mrtr_handler: None,
1931 })
1932 }
1933
1934 #[cfg(feature = "stateless")]
1941 pub fn mrtr_handler<F, Fut>(self, handler: F) -> ResourceTemplate
1942 where
1943 F: Fn(RequestContext, String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1944 Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
1945 {
1946 self.try_mrtr_handler(handler)
1947 .unwrap_or_else(|error| panic!("Invalid URI template: {error}"))
1948 }
1949
1950 #[cfg(feature = "stateless")]
1952 pub fn try_mrtr_handler<F, Fut>(
1953 self,
1954 handler: F,
1955 ) -> std::result::Result<ResourceTemplate, Error>
1956 where
1957 F: Fn(RequestContext, String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1958 Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
1959 {
1960 let (pattern, variables) = compile_uri_template(&self.uri_template)?;
1961 let name = self.name.unwrap_or_else(|| self.uri_template.clone());
1962
1963 Ok(ResourceTemplate {
1964 uri_template: self.uri_template,
1965 name,
1966 title: self.title,
1967 description: self.description,
1968 mime_type: self.mime_type,
1969 icons: self.icons,
1970 annotations: self.annotations,
1971 pattern,
1972 variables,
1973 handler: None,
1974 mrtr_handler: Some(Arc::new(MrtrFnTemplateHandler { handler })),
1975 })
1976 }
1977}
1978
1979struct FnTemplateHandler<F> {
1981 handler: F,
1982}
1983
1984impl<F, Fut> ResourceTemplateHandler for FnTemplateHandler<F>
1985where
1986 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1987 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1988{
1989 fn read(
1990 &self,
1991 uri: &str,
1992 variables: HashMap<String, String>,
1993 ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1994 let uri = uri.to_string();
1995 Box::pin((self.handler)(uri, variables))
1996 }
1997}
1998
1999#[cfg(feature = "stateless")]
2000struct MrtrFnTemplateHandler<F> {
2001 handler: F,
2002}
2003
2004#[cfg(feature = "stateless")]
2005impl<F, Fut> MrtrResourceTemplateHandler for MrtrFnTemplateHandler<F>
2006where
2007 F: Fn(RequestContext, String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
2008 Fut: Future<Output = Result<RequestOutcome<ReadResourceResult>>> + Send + 'static,
2009{
2010 fn read(
2011 &self,
2012 ctx: RequestContext,
2013 uri: &str,
2014 variables: HashMap<String, String>,
2015 ) -> BoxFuture<'_, Result<RequestOutcome<ReadResourceResult>>> {
2016 Box::pin((self.handler)(ctx, uri.to_string(), variables))
2017 }
2018}
2019
2020fn compile_uri_template(template: &str) -> std::result::Result<(regex::Regex, Vec<String>), Error> {
2029 let mut pattern = String::from("^");
2030 let mut variables = Vec::new();
2031
2032 let mut chars = template.chars().peekable();
2033 while let Some(c) = chars.next() {
2034 if c == '{' {
2035 let is_reserved = chars.peek() == Some(&'+');
2037 if is_reserved {
2038 chars.next();
2039 }
2040
2041 let var_name: String = chars.by_ref().take_while(|&c| c != '}').collect();
2043 variables.push(var_name);
2044
2045 if is_reserved {
2047 pattern.push_str("(.+)");
2049 } else {
2050 pattern.push_str("([^/]+)");
2052 }
2053 } else {
2054 match c {
2056 '.' | '+' | '*' | '?' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|'
2057 | '\\' => {
2058 pattern.push('\\');
2059 pattern.push(c);
2060 }
2061 _ => pattern.push(c),
2062 }
2063 }
2064 }
2065
2066 pattern.push('$');
2067
2068 let regex = regex::Regex::new(&pattern)
2069 .map_err(|e| Error::Internal(format!("Invalid URI template '{}': {}", template, e)))?;
2070
2071 Ok((regex, variables))
2072}
2073
2074#[cfg(test)]
2075mod tests {
2076 use super::*;
2077 use std::time::Duration;
2078 use tower::timeout::TimeoutLayer;
2079
2080 #[tokio::test]
2081 async fn test_builder_resource() {
2082 let resource = ResourceBuilder::new("file:///test.txt")
2083 .name("Test File")
2084 .description("A test file")
2085 .text("Hello, World!");
2086
2087 assert_eq!(resource.uri, "file:///test.txt");
2088 assert_eq!(resource.name, "Test File");
2089 assert_eq!(resource.description.as_deref(), Some("A test file"));
2090
2091 let result = resource.read().await;
2092 assert_eq!(result.contents.len(), 1);
2093 assert_eq!(result.contents[0].text.as_deref(), Some("Hello, World!"));
2094 }
2095
2096 #[cfg(feature = "stateless")]
2097 #[tokio::test]
2098 async fn test_mrtr_builder_preserves_input_required_outcome() {
2099 let resource = ResourceBuilder::new("test://continue")
2100 .mrtr_handler(|_ctx| async move {
2101 Ok(RequestOutcome::input_required(
2102 crate::protocol::InputRequiredResult::new().with_request_state("signed-state"),
2103 ))
2104 })
2105 .build();
2106
2107 let outcome = resource
2108 .read_outcome_with_context(RequestContext::new(crate::protocol::RequestId::Number(1)))
2109 .await
2110 .unwrap();
2111 assert_eq!(
2112 outcome
2113 .as_input_required()
2114 .and_then(|result| result.request_state.as_deref()),
2115 Some("signed-state")
2116 );
2117 }
2118
2119 #[cfg(feature = "stateless")]
2120 #[tokio::test]
2121 async fn mrtr_resource_composes_middleware() {
2122 let resource = ResourceBuilder::new("test://layered-continue")
2123 .mrtr_handler(|_ctx| async move {
2124 Ok(RequestOutcome::input_required(
2125 crate::protocol::InputRequiredResult::new().with_request_state("layered-state"),
2126 ))
2127 })
2128 .layer(TimeoutLayer::new(Duration::from_secs(1)))
2129 .build();
2130
2131 let outcome = resource
2132 .read_outcome_with_context(RequestContext::new(crate::protocol::RequestId::Number(2)))
2133 .await
2134 .unwrap();
2135 assert_eq!(
2136 outcome
2137 .as_input_required()
2138 .and_then(|result| result.request_state.as_deref()),
2139 Some("layered-state")
2140 );
2141 }
2142
2143 #[cfg(feature = "stateless")]
2144 #[tokio::test]
2145 async fn test_mrtr_template_preserves_context_and_input_required_outcome() {
2146 let template = ResourceTemplateBuilder::new("test://items/{id}").mrtr_handler(
2147 |ctx, uri, variables| async move {
2148 assert_eq!(ctx.request_state(), Some("prior-state"));
2149 assert_eq!(uri, "test://items/42");
2150 assert_eq!(variables.get("id").map(String::as_str), Some("42"));
2151 Ok(RequestOutcome::input_required(
2152 crate::protocol::InputRequiredResult::new().with_request_state("next-state"),
2153 ))
2154 },
2155 );
2156 let variables = template.match_uri("test://items/42").unwrap();
2157 let mut ctx = RequestContext::new(crate::protocol::RequestId::Number(1));
2158 ctx.extensions_mut().insert(crate::mrtr::MrtrRequest::new(
2159 None,
2160 Some("prior-state".into()),
2161 ));
2162
2163 let outcome = template
2164 .read_outcome_with_context(ctx, "test://items/42", variables)
2165 .await
2166 .unwrap();
2167 assert_eq!(
2168 outcome
2169 .as_input_required()
2170 .and_then(|result| result.request_state.as_deref()),
2171 Some("next-state")
2172 );
2173 }
2174
2175 #[tokio::test]
2176 async fn test_json_resource() {
2177 let resource = ResourceBuilder::new("file:///config.json")
2178 .name("Config")
2179 .json(serde_json::json!({"key": "value"}));
2180
2181 assert_eq!(resource.mime_type.as_deref(), Some("application/json"));
2182
2183 let result = resource.read().await;
2184 assert!(result.contents[0].text.as_ref().unwrap().contains("key"));
2185 }
2186
2187 #[tokio::test]
2188 async fn test_handler_resource() {
2189 let resource = ResourceBuilder::new("memory://counter")
2190 .name("Counter")
2191 .handler(|| async {
2192 Ok(ReadResourceResult {
2193 contents: vec![ResourceContent {
2194 uri: "memory://counter".to_string(),
2195 mime_type: Some("text/plain".to_string()),
2196 text: Some("42".to_string()),
2197 blob: None,
2198 meta: None,
2199 }],
2200 meta: None,
2201 ..Default::default()
2202 })
2203 })
2204 .build();
2205
2206 let result = resource.read().await;
2207 assert_eq!(result.contents[0].text.as_deref(), Some("42"));
2208 }
2209
2210 #[tokio::test]
2211 async fn test_handler_resource_with_layer() {
2212 let resource = ResourceBuilder::new("file:///with-timeout.txt")
2213 .name("Resource with Timeout")
2214 .handler(|| async {
2215 Ok(ReadResourceResult {
2216 contents: vec![ResourceContent {
2217 uri: "file:///with-timeout.txt".to_string(),
2218 mime_type: Some("text/plain".to_string()),
2219 text: Some("content".to_string()),
2220 blob: None,
2221 meta: None,
2222 }],
2223 meta: None,
2224 ..Default::default()
2225 })
2226 })
2227 .layer(TimeoutLayer::new(Duration::from_secs(30)))
2228 .build();
2229
2230 let result = resource.read().await;
2231 assert_eq!(result.contents[0].text.as_deref(), Some("content"));
2232 }
2233
2234 #[tokio::test]
2235 async fn test_handler_resource_with_timeout_error() {
2236 let resource = ResourceBuilder::new("file:///slow.txt")
2237 .name("Slow Resource")
2238 .handler(|| async {
2239 tokio::time::sleep(Duration::from_secs(1)).await;
2241 Ok(ReadResourceResult {
2242 contents: vec![ResourceContent {
2243 uri: "file:///slow.txt".to_string(),
2244 mime_type: Some("text/plain".to_string()),
2245 text: Some("content".to_string()),
2246 blob: None,
2247 meta: None,
2248 }],
2249 meta: None,
2250 ..Default::default()
2251 })
2252 })
2253 .layer(TimeoutLayer::new(Duration::from_millis(50)))
2254 .build();
2255
2256 let result = resource.read().await;
2257 assert!(
2259 result.contents[0]
2260 .text
2261 .as_ref()
2262 .unwrap()
2263 .contains("Error reading resource")
2264 );
2265 }
2266
2267 #[tokio::test]
2268 async fn test_context_aware_handler() {
2269 let resource = ResourceBuilder::new("file:///ctx.txt")
2270 .name("Context Resource")
2271 .handler_with_context(|_ctx: RequestContext| async {
2272 Ok(ReadResourceResult {
2273 contents: vec![ResourceContent {
2274 uri: "file:///ctx.txt".to_string(),
2275 mime_type: Some("text/plain".to_string()),
2276 text: Some("context aware".to_string()),
2277 blob: None,
2278 meta: None,
2279 }],
2280 meta: None,
2281 ..Default::default()
2282 })
2283 })
2284 .build();
2285
2286 let result = resource.read().await;
2287 assert_eq!(result.contents[0].text.as_deref(), Some("context aware"));
2288 }
2289
2290 #[tokio::test]
2291 async fn test_context_aware_handler_with_layer() {
2292 let resource = ResourceBuilder::new("file:///ctx-layer.txt")
2293 .name("Context Resource with Layer")
2294 .handler_with_context(|_ctx: RequestContext| async {
2295 Ok(ReadResourceResult {
2296 contents: vec![ResourceContent {
2297 uri: "file:///ctx-layer.txt".to_string(),
2298 mime_type: Some("text/plain".to_string()),
2299 text: Some("context with layer".to_string()),
2300 blob: None,
2301 meta: None,
2302 }],
2303 meta: None,
2304 ..Default::default()
2305 })
2306 })
2307 .layer(TimeoutLayer::new(Duration::from_secs(30)))
2308 .build();
2309
2310 let result = resource.read().await;
2311 assert_eq!(
2312 result.contents[0].text.as_deref(),
2313 Some("context with layer")
2314 );
2315 }
2316
2317 #[tokio::test]
2318 async fn test_trait_resource() {
2319 struct TestResource;
2320
2321 impl McpResource for TestResource {
2322 const URI: &'static str = "test://resource";
2323 const NAME: &'static str = "Test";
2324 const DESCRIPTION: Option<&'static str> = Some("A test resource");
2325 const MIME_TYPE: Option<&'static str> = Some("text/plain");
2326
2327 async fn read(&self) -> Result<ReadResourceResult> {
2328 Ok(ReadResourceResult {
2329 contents: vec![ResourceContent {
2330 uri: Self::URI.to_string(),
2331 mime_type: Self::MIME_TYPE.map(|s| s.to_string()),
2332 text: Some("test content".to_string()),
2333 blob: None,
2334 meta: None,
2335 }],
2336 meta: None,
2337 ..Default::default()
2338 })
2339 }
2340 }
2341
2342 let resource = TestResource.into_resource();
2343 assert_eq!(resource.uri, "test://resource");
2344 assert_eq!(resource.name, "Test");
2345
2346 let result = resource.read().await;
2347 assert_eq!(result.contents[0].text.as_deref(), Some("test content"));
2348 }
2349
2350 #[test]
2351 fn test_resource_definition() {
2352 let resource = ResourceBuilder::new("file:///test.txt")
2353 .name("Test")
2354 .description("Description")
2355 .mime_type("text/plain")
2356 .text("content");
2357
2358 let def = resource.definition();
2359 assert_eq!(def.uri, "file:///test.txt");
2360 assert_eq!(def.name, "Test");
2361 assert_eq!(def.description.as_deref(), Some("Description"));
2362 assert_eq!(def.mime_type.as_deref(), Some("text/plain"));
2363 }
2364
2365 #[test]
2366 fn test_resource_request_new() {
2367 let ctx = RequestContext::new(crate::protocol::RequestId::Number(1));
2368 let req = ResourceRequest::new(ctx, "file:///test.txt".to_string());
2369 assert_eq!(req.uri, "file:///test.txt");
2370 }
2371
2372 #[test]
2373 fn test_resource_catch_error_clone() {
2374 let handler = FnHandler {
2375 handler: || async {
2376 Ok::<_, Error>(ReadResourceResult {
2377 contents: vec![],
2378 meta: None,
2379 ..Default::default()
2380 })
2381 },
2382 };
2383 let service = ResourceHandlerService::new(handler);
2384 let catch_error = ResourceCatchError::new(service);
2385 let _clone = catch_error.clone();
2386 }
2387
2388 #[test]
2389 fn test_resource_catch_error_debug() {
2390 let handler = FnHandler {
2391 handler: || async {
2392 Ok::<_, Error>(ReadResourceResult {
2393 contents: vec![],
2394 meta: None,
2395 ..Default::default()
2396 })
2397 },
2398 };
2399 let service = ResourceHandlerService::new(handler);
2400 let catch_error = ResourceCatchError::new(service);
2401 let debug = format!("{:?}", catch_error);
2402 assert!(debug.contains("ResourceCatchError"));
2403 }
2404
2405 #[test]
2410 fn test_compile_uri_template_simple() {
2411 let (regex, vars) = compile_uri_template("file:///{path}").unwrap();
2412 assert_eq!(vars, vec!["path"]);
2413 assert!(regex.is_match("file:///README.md"));
2414 assert!(!regex.is_match("file:///foo/bar")); }
2416
2417 #[test]
2418 fn test_compile_uri_template_multiple_vars() {
2419 let (regex, vars) = compile_uri_template("api://v1/{resource}/{id}").unwrap();
2420 assert_eq!(vars, vec!["resource", "id"]);
2421 assert!(regex.is_match("api://v1/users/123"));
2422 assert!(regex.is_match("api://v1/posts/abc"));
2423 assert!(!regex.is_match("api://v1/users")); }
2425
2426 #[test]
2427 fn test_compile_uri_template_reserved_expansion() {
2428 let (regex, vars) = compile_uri_template("file:///{+path}").unwrap();
2429 assert_eq!(vars, vec!["path"]);
2430 assert!(regex.is_match("file:///README.md"));
2431 assert!(regex.is_match("file:///foo/bar/baz.txt")); }
2433
2434 #[test]
2435 fn test_compile_uri_template_special_chars() {
2436 let (regex, vars) = compile_uri_template("http://example.com/api?query={q}").unwrap();
2437 assert_eq!(vars, vec!["q"]);
2438 assert!(regex.is_match("http://example.com/api?query=hello"));
2439 }
2440
2441 #[test]
2442 fn test_resource_template_match_uri() {
2443 let template = ResourceTemplateBuilder::new("db://users/{id}")
2444 .name("User Records")
2445 .handler(|uri: String, vars: HashMap<String, String>| async move {
2446 Ok(ReadResourceResult {
2447 contents: vec![ResourceContent {
2448 uri,
2449 mime_type: None,
2450 text: Some(format!("User {}", vars.get("id").unwrap())),
2451 blob: None,
2452 meta: None,
2453 }],
2454 meta: None,
2455 ..Default::default()
2456 })
2457 });
2458
2459 let vars = template.match_uri("db://users/123").unwrap();
2461 assert_eq!(vars.get("id"), Some(&"123".to_string()));
2462
2463 assert!(template.match_uri("db://posts/123").is_none());
2465 assert!(template.match_uri("db://users").is_none());
2466 }
2467
2468 #[test]
2469 fn test_resource_template_match_multiple_vars() {
2470 let template = ResourceTemplateBuilder::new("api://{version}/{resource}/{id}")
2471 .name("API Resources")
2472 .handler(|uri: String, _vars: HashMap<String, String>| async move {
2473 Ok(ReadResourceResult {
2474 contents: vec![ResourceContent {
2475 uri,
2476 mime_type: None,
2477 text: None,
2478 blob: None,
2479 meta: None,
2480 }],
2481 meta: None,
2482 ..Default::default()
2483 })
2484 });
2485
2486 let vars = template.match_uri("api://v2/users/abc-123").unwrap();
2487 assert_eq!(vars.get("version"), Some(&"v2".to_string()));
2488 assert_eq!(vars.get("resource"), Some(&"users".to_string()));
2489 assert_eq!(vars.get("id"), Some(&"abc-123".to_string()));
2490 }
2491
2492 #[tokio::test]
2493 async fn test_resource_template_read() {
2494 let template = ResourceTemplateBuilder::new("file:///{path}")
2495 .name("Files")
2496 .mime_type("text/plain")
2497 .handler(|uri: String, vars: HashMap<String, String>| async move {
2498 let path = vars.get("path").unwrap().clone();
2499 Ok(ReadResourceResult {
2500 contents: vec![ResourceContent {
2501 uri,
2502 mime_type: Some("text/plain".to_string()),
2503 text: Some(format!("Contents of {}", path)),
2504 blob: None,
2505 meta: None,
2506 }],
2507 meta: None,
2508 ..Default::default()
2509 })
2510 });
2511
2512 let vars = template.match_uri("file:///README.md").unwrap();
2513 let result = template.read("file:///README.md", vars).await.unwrap();
2514
2515 assert_eq!(result.contents.len(), 1);
2516 assert_eq!(result.contents[0].uri, "file:///README.md");
2517 assert_eq!(
2518 result.contents[0].text.as_deref(),
2519 Some("Contents of README.md")
2520 );
2521 }
2522
2523 #[test]
2524 fn test_resource_template_definition() {
2525 let template = ResourceTemplateBuilder::new("db://records/{id}")
2526 .name("Database Records")
2527 .description("Access database records by ID")
2528 .mime_type("application/json")
2529 .handler(|uri: String, _vars: HashMap<String, String>| async move {
2530 Ok(ReadResourceResult {
2531 contents: vec![ResourceContent {
2532 uri,
2533 mime_type: None,
2534 text: None,
2535 blob: None,
2536 meta: None,
2537 }],
2538 meta: None,
2539 ..Default::default()
2540 })
2541 });
2542
2543 let def = template.definition();
2544 assert_eq!(def.uri_template, "db://records/{id}");
2545 assert_eq!(def.name, "Database Records");
2546 assert_eq!(
2547 def.description.as_deref(),
2548 Some("Access database records by ID")
2549 );
2550 assert_eq!(def.mime_type.as_deref(), Some("application/json"));
2551 }
2552
2553 #[test]
2554 fn test_resource_template_reserved_path() {
2555 let template = ResourceTemplateBuilder::new("file:///{+path}")
2556 .name("Files with subpaths")
2557 .handler(|uri: String, _vars: HashMap<String, String>| async move {
2558 Ok(ReadResourceResult {
2559 contents: vec![ResourceContent {
2560 uri,
2561 mime_type: None,
2562 text: None,
2563 blob: None,
2564 meta: None,
2565 }],
2566 meta: None,
2567 ..Default::default()
2568 })
2569 });
2570
2571 let vars = template.match_uri("file:///src/lib/utils.rs").unwrap();
2573 assert_eq!(vars.get("path"), Some(&"src/lib/utils.rs".to_string()));
2574 }
2575
2576 #[test]
2577 fn test_resource_annotations() {
2578 use crate::protocol::{ContentAnnotations, ContentRole};
2579
2580 let annotations = ContentAnnotations {
2581 audience: Some(vec![ContentRole::User]),
2582 priority: Some(0.8),
2583 last_modified: None,
2584 };
2585
2586 let resource = ResourceBuilder::new("file:///important.txt")
2587 .name("Important File")
2588 .annotations(annotations.clone())
2589 .text("content");
2590
2591 let def = resource.definition();
2592 assert!(def.annotations.is_some());
2593 let ann = def.annotations.unwrap();
2594 assert_eq!(ann.priority, Some(0.8));
2595 assert_eq!(ann.audience.unwrap(), vec![ContentRole::User]);
2596 }
2597
2598 #[test]
2599 fn test_resource_template_annotations() {
2600 use crate::protocol::{ContentAnnotations, ContentRole};
2601
2602 let annotations = ContentAnnotations {
2603 audience: Some(vec![ContentRole::Assistant]),
2604 priority: Some(0.5),
2605 last_modified: None,
2606 };
2607
2608 let template = ResourceTemplateBuilder::new("db://users/{id}")
2609 .name("Users")
2610 .annotations(annotations)
2611 .handler(|uri: String, _vars: HashMap<String, String>| async move {
2612 Ok(ReadResourceResult {
2613 contents: vec![ResourceContent {
2614 uri,
2615 mime_type: None,
2616 text: Some("data".to_string()),
2617 blob: None,
2618 meta: None,
2619 }],
2620 meta: None,
2621 ..Default::default()
2622 })
2623 });
2624
2625 let def = template.definition();
2626 assert!(def.annotations.is_some());
2627 let ann = def.annotations.unwrap();
2628 assert_eq!(ann.priority, Some(0.5));
2629 assert_eq!(ann.audience.unwrap(), vec![ContentRole::Assistant]);
2630 }
2631
2632 #[test]
2633 fn test_resource_no_annotations_by_default() {
2634 let resource = ResourceBuilder::new("file:///test.txt")
2635 .name("Test")
2636 .text("content");
2637
2638 let def = resource.definition();
2639 assert!(def.annotations.is_none());
2640 }
2641
2642 #[test]
2643 fn test_try_handler_success() {
2644 let result = ResourceTemplateBuilder::new("db://users/{id}")
2645 .name("Users")
2646 .try_handler(|uri: String, _vars: HashMap<String, String>| async move {
2647 Ok(ReadResourceResult {
2648 contents: vec![ResourceContent {
2649 uri,
2650 mime_type: None,
2651 text: Some("ok".to_string()),
2652 blob: None,
2653 meta: None,
2654 }],
2655 meta: None,
2656 ..Default::default()
2657 })
2658 });
2659
2660 assert!(result.is_ok());
2661 let template = result.unwrap();
2662 assert_eq!(template.uri_template, "db://users/{id}");
2663 }
2664
2665 #[test]
2666 fn test_compile_uri_template_returns_result() {
2667 assert!(compile_uri_template("file:///{path}").is_ok());
2669 assert!(compile_uri_template("api://v1/{resource}/{id}").is_ok());
2670 assert!(compile_uri_template("file:///{+path}").is_ok());
2671 assert!(compile_uri_template("no-vars").is_ok());
2672 assert!(compile_uri_template("").is_ok());
2673 }
2674}