1use std::collections::HashMap;
70use std::convert::Infallible;
71use std::fmt;
72use std::future::Future;
73use std::pin::Pin;
74use std::sync::Arc;
75use std::task::{Context, Poll};
76
77use pin_project_lite::pin_project;
78
79use tower::util::BoxCloneService;
80use tower_service::Service;
81
82use crate::context::RequestContext;
83use crate::error::{Error, Result};
84use crate::protocol::{
85 ContentAnnotations, ReadResourceResult, ResourceContent, ResourceDefinition,
86 ResourceTemplateDefinition, ToolIcon,
87};
88
89#[derive(Debug, Clone)]
98pub struct ResourceRequest {
99 pub ctx: RequestContext,
101 pub uri: String,
103}
104
105impl ResourceRequest {
106 pub fn new(ctx: RequestContext, uri: String) -> Self {
108 Self { ctx, uri }
109 }
110}
111
112pub type BoxResourceService = BoxCloneService<ResourceRequest, ReadResourceResult, Infallible>;
117
118#[doc(hidden)]
124pub struct ResourceCatchError<S> {
125 inner: S,
126}
127
128impl<S> ResourceCatchError<S> {
129 pub fn new(inner: S) -> Self {
131 Self { inner }
132 }
133}
134
135impl<S: Clone> Clone for ResourceCatchError<S> {
136 fn clone(&self) -> Self {
137 Self {
138 inner: self.inner.clone(),
139 }
140 }
141}
142
143impl<S: fmt::Debug> fmt::Debug for ResourceCatchError<S> {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 f.debug_struct("ResourceCatchError")
146 .field("inner", &self.inner)
147 .finish()
148 }
149}
150
151pin_project! {
152 #[doc(hidden)]
154 pub struct ResourceCatchErrorFuture<F> {
155 #[pin]
156 inner: F,
157 uri: Option<String>,
158 }
159}
160
161impl<F, E> Future for ResourceCatchErrorFuture<F>
162where
163 F: Future<Output = std::result::Result<ReadResourceResult, E>>,
164 E: fmt::Display,
165{
166 type Output = std::result::Result<ReadResourceResult, Infallible>;
167
168 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
169 let this = self.project();
170 match this.inner.poll(cx) {
171 Poll::Pending => Poll::Pending,
172 Poll::Ready(Ok(result)) => Poll::Ready(Ok(result)),
173 Poll::Ready(Err(err)) => {
174 let uri = this.uri.take().unwrap_or_default();
175 Poll::Ready(Ok(ReadResourceResult {
176 contents: vec![ResourceContent {
177 uri,
178 mime_type: Some("text/plain".to_string()),
179 text: Some(format!("Error reading resource: {}", err)),
180 blob: None,
181 meta: None,
182 }],
183 meta: None,
184 }))
185 }
186 }
187 }
188}
189
190impl<S> Service<ResourceRequest> for ResourceCatchError<S>
191where
192 S: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
193 S::Error: fmt::Display + Send,
194 S::Future: Send,
195{
196 type Response = ReadResourceResult;
197 type Error = Infallible;
198 type Future = ResourceCatchErrorFuture<S::Future>;
199
200 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
201 match self.inner.poll_ready(cx) {
203 Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
204 Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
205 Poll::Pending => Poll::Pending,
206 }
207 }
208
209 fn call(&mut self, req: ResourceRequest) -> Self::Future {
210 let uri = req.uri.clone();
211 let fut = self.inner.call(req);
212
213 ResourceCatchErrorFuture {
214 inner: fut,
215 uri: Some(uri),
216 }
217 }
218}
219
220pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
222
223pub trait ResourceHandler: Send + Sync {
225 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>>;
227
228 fn read_with_context(&self, _ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
233 self.read()
234 }
235
236 fn uses_context(&self) -> bool {
238 false
239 }
240}
241
242struct ResourceHandlerService<H> {
247 handler: Arc<H>,
248}
249
250impl<H> ResourceHandlerService<H> {
251 fn new(handler: H) -> Self {
252 Self {
253 handler: Arc::new(handler),
254 }
255 }
256}
257
258impl<H> Clone for ResourceHandlerService<H> {
259 fn clone(&self) -> Self {
260 Self {
261 handler: self.handler.clone(),
262 }
263 }
264}
265
266impl<H> fmt::Debug for ResourceHandlerService<H> {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 f.debug_struct("ResourceHandlerService")
269 .finish_non_exhaustive()
270 }
271}
272
273impl<H> Service<ResourceRequest> for ResourceHandlerService<H>
274where
275 H: ResourceHandler + 'static,
276{
277 type Response = ReadResourceResult;
278 type Error = Error;
279 type Future =
280 Pin<Box<dyn Future<Output = std::result::Result<ReadResourceResult, Error>> + Send>>;
281
282 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
283 Poll::Ready(Ok(()))
284 }
285
286 fn call(&mut self, req: ResourceRequest) -> Self::Future {
287 let handler = self.handler.clone();
288 Box::pin(async move { handler.read_with_context(req.ctx).await })
289 }
290}
291
292pub struct Resource {
299 pub uri: String,
301 pub name: String,
303 pub title: Option<String>,
305 pub description: Option<String>,
307 pub mime_type: Option<String>,
309 pub icons: Option<Vec<ToolIcon>>,
311 pub size: Option<u64>,
313 pub annotations: Option<ContentAnnotations>,
315 service: BoxResourceService,
317}
318
319impl Clone for Resource {
320 fn clone(&self) -> Self {
321 Self {
322 uri: self.uri.clone(),
323 name: self.name.clone(),
324 title: self.title.clone(),
325 description: self.description.clone(),
326 mime_type: self.mime_type.clone(),
327 icons: self.icons.clone(),
328 size: self.size,
329 annotations: self.annotations.clone(),
330 service: self.service.clone(),
331 }
332 }
333}
334
335impl std::fmt::Debug for Resource {
336 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337 f.debug_struct("Resource")
338 .field("uri", &self.uri)
339 .field("name", &self.name)
340 .field("title", &self.title)
341 .field("description", &self.description)
342 .field("mime_type", &self.mime_type)
343 .field("icons", &self.icons)
344 .field("size", &self.size)
345 .field("annotations", &self.annotations)
346 .finish_non_exhaustive()
347 }
348}
349
350unsafe impl Send for Resource {}
353unsafe impl Sync for Resource {}
354
355impl Resource {
356 pub fn builder(uri: impl Into<String>) -> ResourceBuilder {
358 ResourceBuilder::new(uri)
359 }
360
361 pub fn definition(&self) -> ResourceDefinition {
363 ResourceDefinition {
364 uri: self.uri.clone(),
365 name: self.name.clone(),
366 title: self.title.clone(),
367 description: self.description.clone(),
368 mime_type: self.mime_type.clone(),
369 icons: self.icons.clone(),
370 size: self.size,
371 annotations: self.annotations.clone(),
372 meta: None,
373 }
374 }
375
376 pub fn read(&self) -> BoxFuture<'static, ReadResourceResult> {
381 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
382 self.read_with_context(ctx)
383 }
384
385 pub fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'static, ReadResourceResult> {
396 use tower::ServiceExt;
397 let service = self.service.clone();
398 let uri = self.uri.clone();
399 Box::pin(async move {
400 service
403 .oneshot(ResourceRequest::new(ctx, uri))
404 .await
405 .unwrap()
406 })
407 }
408
409 #[allow(clippy::too_many_arguments)]
411 fn from_handler<H: ResourceHandler + 'static>(
412 uri: String,
413 name: String,
414 title: Option<String>,
415 description: Option<String>,
416 mime_type: Option<String>,
417 icons: Option<Vec<ToolIcon>>,
418 size: Option<u64>,
419 annotations: Option<ContentAnnotations>,
420 handler: H,
421 ) -> Self {
422 let handler_service = ResourceHandlerService::new(handler);
423 let catch_error = ResourceCatchError::new(handler_service);
424 let service = BoxCloneService::new(catch_error);
425
426 Self {
427 uri,
428 name,
429 title,
430 description,
431 mime_type,
432 icons,
433 size,
434 annotations,
435 service,
436 }
437 }
438}
439
440pub struct ResourceBuilder {
473 uri: String,
474 name: Option<String>,
475 title: Option<String>,
476 description: Option<String>,
477 mime_type: Option<String>,
478 icons: Option<Vec<ToolIcon>>,
479 size: Option<u64>,
480 annotations: Option<ContentAnnotations>,
481}
482
483impl ResourceBuilder {
484 pub fn new(uri: impl Into<String>) -> Self {
486 Self {
487 uri: uri.into(),
488 name: None,
489 title: None,
490 description: None,
491 mime_type: None,
492 icons: None,
493 size: None,
494 annotations: None,
495 }
496 }
497
498 pub fn name(mut self, name: impl Into<String>) -> Self {
500 self.name = Some(name.into());
501 self
502 }
503
504 pub fn title(mut self, title: impl Into<String>) -> Self {
506 self.title = Some(title.into());
507 self
508 }
509
510 pub fn description(mut self, description: impl Into<String>) -> Self {
512 self.description = Some(description.into());
513 self
514 }
515
516 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
518 self.mime_type = Some(mime_type.into());
519 self
520 }
521
522 pub fn icon(mut self, src: impl Into<String>) -> Self {
524 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
525 src: src.into(),
526 mime_type: None,
527 sizes: None,
528 theme: None,
529 });
530 self
531 }
532
533 pub fn icon_with_meta(
535 mut self,
536 src: impl Into<String>,
537 mime_type: Option<String>,
538 sizes: Option<Vec<String>>,
539 ) -> Self {
540 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
541 src: src.into(),
542 mime_type,
543 sizes,
544 theme: None,
545 });
546 self
547 }
548
549 pub fn size(mut self, size: u64) -> Self {
551 self.size = Some(size);
552 self
553 }
554
555 pub fn annotations(mut self, annotations: ContentAnnotations) -> Self {
557 self.annotations = Some(annotations);
558 self
559 }
560
561 pub fn handler<F, Fut>(self, handler: F) -> ResourceBuilderWithHandler<F>
603 where
604 F: Fn() -> Fut + Send + Sync + 'static,
605 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
606 {
607 ResourceBuilderWithHandler {
608 uri: self.uri,
609 name: self.name,
610 title: self.title,
611 description: self.description,
612 mime_type: self.mime_type,
613 icons: self.icons,
614 size: self.size,
615 annotations: self.annotations,
616 handler,
617 }
618 }
619
620 pub fn handler_with_context<F, Fut>(self, handler: F) -> ResourceBuilderWithContextHandler<F>
628 where
629 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
630 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
631 {
632 ResourceBuilderWithContextHandler {
633 uri: self.uri,
634 name: self.name,
635 title: self.title,
636 description: self.description,
637 mime_type: self.mime_type,
638 icons: self.icons,
639 size: self.size,
640 annotations: self.annotations,
641 handler,
642 }
643 }
644
645 pub fn text(self, content: impl Into<String>) -> Resource {
647 let uri = self.uri.clone();
648 let content = content.into();
649 let mime_type = self.mime_type.clone();
650
651 self.handler(move || {
652 let uri = uri.clone();
653 let content = content.clone();
654 let mime_type = mime_type.clone();
655 async move {
656 Ok(ReadResourceResult {
657 contents: vec![ResourceContent {
658 uri,
659 mime_type,
660 text: Some(content),
661 blob: None,
662 meta: None,
663 }],
664 meta: None,
665 })
666 }
667 })
668 .build()
669 }
670
671 pub fn json(mut self, value: serde_json::Value) -> Resource {
673 let uri = self.uri.clone();
674 self.mime_type = Some("application/json".to_string());
675 let text = serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{}".to_string());
676
677 self.handler(move || {
678 let uri = uri.clone();
679 let text = text.clone();
680 async move {
681 Ok(ReadResourceResult {
682 contents: vec![ResourceContent {
683 uri,
684 mime_type: Some("application/json".to_string()),
685 text: Some(text),
686 blob: None,
687 meta: None,
688 }],
689 meta: None,
690 })
691 }
692 })
693 .build()
694 }
695}
696
697#[doc(hidden)]
702pub struct ResourceBuilderWithHandler<F> {
703 uri: String,
704 name: Option<String>,
705 title: Option<String>,
706 description: Option<String>,
707 mime_type: Option<String>,
708 icons: Option<Vec<ToolIcon>>,
709 size: Option<u64>,
710 annotations: Option<ContentAnnotations>,
711 handler: F,
712}
713
714impl<F, Fut> ResourceBuilderWithHandler<F>
715where
716 F: Fn() -> Fut + Send + Sync + 'static,
717 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
718{
719 pub fn build(self) -> Resource {
721 let name = self.name.unwrap_or_else(|| self.uri.clone());
722
723 Resource::from_handler(
724 self.uri,
725 name,
726 self.title,
727 self.description,
728 self.mime_type,
729 self.icons,
730 self.size,
731 self.annotations,
732 FnHandler {
733 handler: self.handler,
734 },
735 )
736 }
737
738 pub fn layer<L>(self, layer: L) -> ResourceBuilderWithLayer<F, L> {
769 ResourceBuilderWithLayer {
770 uri: self.uri,
771 name: self.name,
772 title: self.title,
773 description: self.description,
774 mime_type: self.mime_type,
775 icons: self.icons,
776 size: self.size,
777 annotations: self.annotations,
778 handler: self.handler,
779 layer,
780 }
781 }
782}
783
784#[doc(hidden)]
788pub struct ResourceBuilderWithLayer<F, L> {
789 uri: String,
790 name: Option<String>,
791 title: Option<String>,
792 description: Option<String>,
793 mime_type: Option<String>,
794 icons: Option<Vec<ToolIcon>>,
795 size: Option<u64>,
796 annotations: Option<ContentAnnotations>,
797 handler: F,
798 layer: L,
799}
800
801#[allow(private_bounds)]
804impl<F, Fut, L> ResourceBuilderWithLayer<F, L>
805where
806 F: Fn() -> Fut + Send + Sync + 'static,
807 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
808 L: tower::Layer<ResourceHandlerService<FnHandler<F>>> + Clone + Send + Sync + 'static,
809 L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
810 <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
811 <L::Service as Service<ResourceRequest>>::Future: Send,
812{
813 pub fn build(self) -> Resource {
815 let name = self.name.unwrap_or_else(|| self.uri.clone());
816
817 let handler_service = ResourceHandlerService::new(FnHandler {
818 handler: self.handler,
819 });
820 let layered = self.layer.layer(handler_service);
821 let catch_error = ResourceCatchError::new(layered);
822 let service = BoxCloneService::new(catch_error);
823
824 Resource {
825 uri: self.uri,
826 name,
827 title: self.title,
828 description: self.description,
829 mime_type: self.mime_type,
830 icons: self.icons,
831 size: self.size,
832 annotations: self.annotations,
833 service,
834 }
835 }
836
837 pub fn layer<L2>(
842 self,
843 layer: L2,
844 ) -> ResourceBuilderWithLayer<F, tower::layer::util::Stack<L2, L>> {
845 ResourceBuilderWithLayer {
846 uri: self.uri,
847 name: self.name,
848 title: self.title,
849 description: self.description,
850 mime_type: self.mime_type,
851 icons: self.icons,
852 size: self.size,
853 annotations: self.annotations,
854 handler: self.handler,
855 layer: tower::layer::util::Stack::new(layer, self.layer),
856 }
857 }
858}
859
860#[doc(hidden)]
862pub struct ResourceBuilderWithContextHandler<F> {
863 uri: String,
864 name: Option<String>,
865 title: Option<String>,
866 description: Option<String>,
867 mime_type: Option<String>,
868 icons: Option<Vec<ToolIcon>>,
869 size: Option<u64>,
870 annotations: Option<ContentAnnotations>,
871 handler: F,
872}
873
874impl<F, Fut> ResourceBuilderWithContextHandler<F>
875where
876 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
877 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
878{
879 pub fn build(self) -> Resource {
881 let name = self.name.unwrap_or_else(|| self.uri.clone());
882
883 Resource::from_handler(
884 self.uri,
885 name,
886 self.title,
887 self.description,
888 self.mime_type,
889 self.icons,
890 self.size,
891 self.annotations,
892 ContextAwareHandler {
893 handler: self.handler,
894 },
895 )
896 }
897
898 pub fn layer<L>(self, layer: L) -> ResourceBuilderWithContextLayer<F, L> {
902 ResourceBuilderWithContextLayer {
903 uri: self.uri,
904 name: self.name,
905 title: self.title,
906 description: self.description,
907 mime_type: self.mime_type,
908 icons: self.icons,
909 size: self.size,
910 annotations: self.annotations,
911 handler: self.handler,
912 layer,
913 }
914 }
915}
916
917#[doc(hidden)]
919pub struct ResourceBuilderWithContextLayer<F, L> {
920 uri: String,
921 name: Option<String>,
922 title: Option<String>,
923 description: Option<String>,
924 mime_type: Option<String>,
925 icons: Option<Vec<ToolIcon>>,
926 size: Option<u64>,
927 annotations: Option<ContentAnnotations>,
928 handler: F,
929 layer: L,
930}
931
932#[allow(private_bounds)]
934impl<F, Fut, L> ResourceBuilderWithContextLayer<F, L>
935where
936 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
937 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
938 L: tower::Layer<ResourceHandlerService<ContextAwareHandler<F>>> + Clone + Send + Sync + 'static,
939 L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
940 <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
941 <L::Service as Service<ResourceRequest>>::Future: Send,
942{
943 pub fn build(self) -> Resource {
945 let name = self.name.unwrap_or_else(|| self.uri.clone());
946
947 let handler_service = ResourceHandlerService::new(ContextAwareHandler {
948 handler: self.handler,
949 });
950 let layered = self.layer.layer(handler_service);
951 let catch_error = ResourceCatchError::new(layered);
952 let service = BoxCloneService::new(catch_error);
953
954 Resource {
955 uri: self.uri,
956 name,
957 title: self.title,
958 description: self.description,
959 mime_type: self.mime_type,
960 icons: self.icons,
961 size: self.size,
962 annotations: self.annotations,
963 service,
964 }
965 }
966
967 pub fn layer<L2>(
969 self,
970 layer: L2,
971 ) -> ResourceBuilderWithContextLayer<F, tower::layer::util::Stack<L2, L>> {
972 ResourceBuilderWithContextLayer {
973 uri: self.uri,
974 name: self.name,
975 title: self.title,
976 description: self.description,
977 mime_type: self.mime_type,
978 icons: self.icons,
979 size: self.size,
980 annotations: self.annotations,
981 handler: self.handler,
982 layer: tower::layer::util::Stack::new(layer, self.layer),
983 }
984 }
985}
986
987struct FnHandler<F> {
993 handler: F,
994}
995
996impl<F, Fut> ResourceHandler for FnHandler<F>
997where
998 F: Fn() -> Fut + Send + Sync + 'static,
999 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1000{
1001 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1002 Box::pin((self.handler)())
1003 }
1004}
1005
1006struct ContextAwareHandler<F> {
1008 handler: F,
1009}
1010
1011impl<F, Fut> ResourceHandler for ContextAwareHandler<F>
1012where
1013 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
1014 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1015{
1016 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1017 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
1018 self.read_with_context(ctx)
1019 }
1020
1021 fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
1022 Box::pin((self.handler)(ctx))
1023 }
1024
1025 fn uses_context(&self) -> bool {
1026 true
1027 }
1028}
1029
1030pub trait McpResource: Send + Sync + 'static {
1074 const URI: &'static str;
1076 const NAME: &'static str;
1078 const DESCRIPTION: Option<&'static str> = None;
1080 const MIME_TYPE: Option<&'static str> = None;
1082
1083 fn read(&self) -> impl Future<Output = Result<ReadResourceResult>> + Send;
1085
1086 fn into_resource(self) -> Resource
1088 where
1089 Self: Sized,
1090 {
1091 let resource = Arc::new(self);
1092 Resource::from_handler(
1093 Self::URI.to_string(),
1094 Self::NAME.to_string(),
1095 None,
1096 Self::DESCRIPTION.map(|s| s.to_string()),
1097 Self::MIME_TYPE.map(|s| s.to_string()),
1098 None,
1099 None,
1100 None,
1101 McpResourceHandler { resource },
1102 )
1103 }
1104}
1105
1106struct McpResourceHandler<T: McpResource> {
1108 resource: Arc<T>,
1109}
1110
1111impl<T: McpResource> ResourceHandler for McpResourceHandler<T> {
1112 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1113 let resource = self.resource.clone();
1114 Box::pin(async move { resource.read().await })
1115 }
1116}
1117
1118pub trait ResourceTemplateHandler: Send + Sync {
1127 fn read(
1129 &self,
1130 uri: &str,
1131 variables: HashMap<String, String>,
1132 ) -> BoxFuture<'_, Result<ReadResourceResult>>;
1133}
1134
1135pub struct ResourceTemplate {
1165 pub uri_template: String,
1167 pub name: String,
1169 pub title: Option<String>,
1171 pub description: Option<String>,
1173 pub mime_type: Option<String>,
1175 pub icons: Option<Vec<ToolIcon>>,
1177 pub annotations: Option<ContentAnnotations>,
1179 pattern: regex::Regex,
1181 variables: Vec<String>,
1183 handler: Arc<dyn ResourceTemplateHandler>,
1185}
1186
1187impl Clone for ResourceTemplate {
1188 fn clone(&self) -> Self {
1189 Self {
1190 uri_template: self.uri_template.clone(),
1191 name: self.name.clone(),
1192 title: self.title.clone(),
1193 description: self.description.clone(),
1194 mime_type: self.mime_type.clone(),
1195 icons: self.icons.clone(),
1196 annotations: self.annotations.clone(),
1197 pattern: self.pattern.clone(),
1198 variables: self.variables.clone(),
1199 handler: self.handler.clone(),
1200 }
1201 }
1202}
1203
1204impl std::fmt::Debug for ResourceTemplate {
1205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1206 f.debug_struct("ResourceTemplate")
1207 .field("uri_template", &self.uri_template)
1208 .field("name", &self.name)
1209 .field("title", &self.title)
1210 .field("description", &self.description)
1211 .field("mime_type", &self.mime_type)
1212 .field("icons", &self.icons)
1213 .field("variables", &self.variables)
1214 .finish_non_exhaustive()
1215 }
1216}
1217
1218impl ResourceTemplate {
1219 pub fn builder(uri_template: impl Into<String>) -> ResourceTemplateBuilder {
1221 ResourceTemplateBuilder::new(uri_template)
1222 }
1223
1224 pub fn definition(&self) -> ResourceTemplateDefinition {
1226 ResourceTemplateDefinition {
1227 uri_template: self.uri_template.clone(),
1228 name: self.name.clone(),
1229 title: self.title.clone(),
1230 description: self.description.clone(),
1231 mime_type: self.mime_type.clone(),
1232 icons: self.icons.clone(),
1233 annotations: self.annotations.clone(),
1234 arguments: Vec::new(),
1235 meta: None,
1236 }
1237 }
1238
1239 pub fn match_uri(&self, uri: &str) -> Option<HashMap<String, String>> {
1244 self.pattern.captures(uri).map(|caps| {
1245 self.variables
1246 .iter()
1247 .enumerate()
1248 .filter_map(|(i, name)| {
1249 caps.get(i + 1)
1250 .map(|m| (name.clone(), m.as_str().to_string()))
1251 })
1252 .collect()
1253 })
1254 }
1255
1256 pub fn read(
1263 &self,
1264 uri: &str,
1265 variables: HashMap<String, String>,
1266 ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1267 self.handler.read(uri, variables)
1268 }
1269}
1270
1271pub struct ResourceTemplateBuilder {
1298 uri_template: String,
1299 name: Option<String>,
1300 title: Option<String>,
1301 description: Option<String>,
1302 mime_type: Option<String>,
1303 icons: Option<Vec<ToolIcon>>,
1304 annotations: Option<ContentAnnotations>,
1305}
1306
1307impl ResourceTemplateBuilder {
1308 pub fn new(uri_template: impl Into<String>) -> Self {
1321 Self {
1322 uri_template: uri_template.into(),
1323 name: None,
1324 title: None,
1325 description: None,
1326 mime_type: None,
1327 icons: None,
1328 annotations: None,
1329 }
1330 }
1331
1332 pub fn name(mut self, name: impl Into<String>) -> Self {
1334 self.name = Some(name.into());
1335 self
1336 }
1337
1338 pub fn title(mut self, title: impl Into<String>) -> Self {
1340 self.title = Some(title.into());
1341 self
1342 }
1343
1344 pub fn description(mut self, description: impl Into<String>) -> Self {
1346 self.description = Some(description.into());
1347 self
1348 }
1349
1350 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
1352 self.mime_type = Some(mime_type.into());
1353 self
1354 }
1355
1356 pub fn icon(mut self, src: impl Into<String>) -> Self {
1358 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1359 src: src.into(),
1360 mime_type: None,
1361 sizes: None,
1362 theme: None,
1363 });
1364 self
1365 }
1366
1367 pub fn icon_with_meta(
1369 mut self,
1370 src: impl Into<String>,
1371 mime_type: Option<String>,
1372 sizes: Option<Vec<String>>,
1373 ) -> Self {
1374 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1375 src: src.into(),
1376 mime_type,
1377 sizes,
1378 theme: None,
1379 });
1380 self
1381 }
1382
1383 pub fn annotations(mut self, annotations: ContentAnnotations) -> Self {
1385 self.annotations = Some(annotations);
1386 self
1387 }
1388
1389 pub fn handler<F, Fut>(self, handler: F) -> ResourceTemplate
1401 where
1402 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1403 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1404 {
1405 self.try_handler(handler).unwrap_or_else(|e| {
1406 panic!("Invalid URI template: {e}");
1407 })
1408 }
1409
1410 pub fn try_handler<F, Fut>(self, handler: F) -> std::result::Result<ResourceTemplate, Error>
1421 where
1422 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1423 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1424 {
1425 let (pattern, variables) = compile_uri_template(&self.uri_template)?;
1426 let name = self.name.unwrap_or_else(|| self.uri_template.clone());
1427
1428 Ok(ResourceTemplate {
1429 uri_template: self.uri_template,
1430 name,
1431 title: self.title,
1432 description: self.description,
1433 mime_type: self.mime_type,
1434 icons: self.icons,
1435 annotations: self.annotations,
1436 pattern,
1437 variables,
1438 handler: Arc::new(FnTemplateHandler { handler }),
1439 })
1440 }
1441}
1442
1443struct FnTemplateHandler<F> {
1445 handler: F,
1446}
1447
1448impl<F, Fut> ResourceTemplateHandler for FnTemplateHandler<F>
1449where
1450 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1451 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1452{
1453 fn read(
1454 &self,
1455 uri: &str,
1456 variables: HashMap<String, String>,
1457 ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1458 let uri = uri.to_string();
1459 Box::pin((self.handler)(uri, variables))
1460 }
1461}
1462
1463fn compile_uri_template(template: &str) -> std::result::Result<(regex::Regex, Vec<String>), Error> {
1472 let mut pattern = String::from("^");
1473 let mut variables = Vec::new();
1474
1475 let mut chars = template.chars().peekable();
1476 while let Some(c) = chars.next() {
1477 if c == '{' {
1478 let is_reserved = chars.peek() == Some(&'+');
1480 if is_reserved {
1481 chars.next();
1482 }
1483
1484 let var_name: String = chars.by_ref().take_while(|&c| c != '}').collect();
1486 variables.push(var_name);
1487
1488 if is_reserved {
1490 pattern.push_str("(.+)");
1492 } else {
1493 pattern.push_str("([^/]+)");
1495 }
1496 } else {
1497 match c {
1499 '.' | '+' | '*' | '?' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|'
1500 | '\\' => {
1501 pattern.push('\\');
1502 pattern.push(c);
1503 }
1504 _ => pattern.push(c),
1505 }
1506 }
1507 }
1508
1509 pattern.push('$');
1510
1511 let regex = regex::Regex::new(&pattern)
1512 .map_err(|e| Error::Internal(format!("Invalid URI template '{}': {}", template, e)))?;
1513
1514 Ok((regex, variables))
1515}
1516
1517#[cfg(test)]
1518mod tests {
1519 use super::*;
1520 use std::time::Duration;
1521 use tower::timeout::TimeoutLayer;
1522
1523 #[tokio::test]
1524 async fn test_builder_resource() {
1525 let resource = ResourceBuilder::new("file:///test.txt")
1526 .name("Test File")
1527 .description("A test file")
1528 .text("Hello, World!");
1529
1530 assert_eq!(resource.uri, "file:///test.txt");
1531 assert_eq!(resource.name, "Test File");
1532 assert_eq!(resource.description.as_deref(), Some("A test file"));
1533
1534 let result = resource.read().await;
1535 assert_eq!(result.contents.len(), 1);
1536 assert_eq!(result.contents[0].text.as_deref(), Some("Hello, World!"));
1537 }
1538
1539 #[tokio::test]
1540 async fn test_json_resource() {
1541 let resource = ResourceBuilder::new("file:///config.json")
1542 .name("Config")
1543 .json(serde_json::json!({"key": "value"}));
1544
1545 assert_eq!(resource.mime_type.as_deref(), Some("application/json"));
1546
1547 let result = resource.read().await;
1548 assert!(result.contents[0].text.as_ref().unwrap().contains("key"));
1549 }
1550
1551 #[tokio::test]
1552 async fn test_handler_resource() {
1553 let resource = ResourceBuilder::new("memory://counter")
1554 .name("Counter")
1555 .handler(|| async {
1556 Ok(ReadResourceResult {
1557 contents: vec![ResourceContent {
1558 uri: "memory://counter".to_string(),
1559 mime_type: Some("text/plain".to_string()),
1560 text: Some("42".to_string()),
1561 blob: None,
1562 meta: None,
1563 }],
1564 meta: None,
1565 })
1566 })
1567 .build();
1568
1569 let result = resource.read().await;
1570 assert_eq!(result.contents[0].text.as_deref(), Some("42"));
1571 }
1572
1573 #[tokio::test]
1574 async fn test_handler_resource_with_layer() {
1575 let resource = ResourceBuilder::new("file:///with-timeout.txt")
1576 .name("Resource with Timeout")
1577 .handler(|| async {
1578 Ok(ReadResourceResult {
1579 contents: vec![ResourceContent {
1580 uri: "file:///with-timeout.txt".to_string(),
1581 mime_type: Some("text/plain".to_string()),
1582 text: Some("content".to_string()),
1583 blob: None,
1584 meta: None,
1585 }],
1586 meta: None,
1587 })
1588 })
1589 .layer(TimeoutLayer::new(Duration::from_secs(30)))
1590 .build();
1591
1592 let result = resource.read().await;
1593 assert_eq!(result.contents[0].text.as_deref(), Some("content"));
1594 }
1595
1596 #[tokio::test]
1597 async fn test_handler_resource_with_timeout_error() {
1598 let resource = ResourceBuilder::new("file:///slow.txt")
1599 .name("Slow Resource")
1600 .handler(|| async {
1601 tokio::time::sleep(Duration::from_secs(1)).await;
1603 Ok(ReadResourceResult {
1604 contents: vec![ResourceContent {
1605 uri: "file:///slow.txt".to_string(),
1606 mime_type: Some("text/plain".to_string()),
1607 text: Some("content".to_string()),
1608 blob: None,
1609 meta: None,
1610 }],
1611 meta: None,
1612 })
1613 })
1614 .layer(TimeoutLayer::new(Duration::from_millis(50)))
1615 .build();
1616
1617 let result = resource.read().await;
1618 assert!(
1620 result.contents[0]
1621 .text
1622 .as_ref()
1623 .unwrap()
1624 .contains("Error reading resource")
1625 );
1626 }
1627
1628 #[tokio::test]
1629 async fn test_context_aware_handler() {
1630 let resource = ResourceBuilder::new("file:///ctx.txt")
1631 .name("Context Resource")
1632 .handler_with_context(|_ctx: RequestContext| async {
1633 Ok(ReadResourceResult {
1634 contents: vec![ResourceContent {
1635 uri: "file:///ctx.txt".to_string(),
1636 mime_type: Some("text/plain".to_string()),
1637 text: Some("context aware".to_string()),
1638 blob: None,
1639 meta: None,
1640 }],
1641 meta: None,
1642 })
1643 })
1644 .build();
1645
1646 let result = resource.read().await;
1647 assert_eq!(result.contents[0].text.as_deref(), Some("context aware"));
1648 }
1649
1650 #[tokio::test]
1651 async fn test_context_aware_handler_with_layer() {
1652 let resource = ResourceBuilder::new("file:///ctx-layer.txt")
1653 .name("Context Resource with Layer")
1654 .handler_with_context(|_ctx: RequestContext| async {
1655 Ok(ReadResourceResult {
1656 contents: vec![ResourceContent {
1657 uri: "file:///ctx-layer.txt".to_string(),
1658 mime_type: Some("text/plain".to_string()),
1659 text: Some("context with layer".to_string()),
1660 blob: None,
1661 meta: None,
1662 }],
1663 meta: None,
1664 })
1665 })
1666 .layer(TimeoutLayer::new(Duration::from_secs(30)))
1667 .build();
1668
1669 let result = resource.read().await;
1670 assert_eq!(
1671 result.contents[0].text.as_deref(),
1672 Some("context with layer")
1673 );
1674 }
1675
1676 #[tokio::test]
1677 async fn test_trait_resource() {
1678 struct TestResource;
1679
1680 impl McpResource for TestResource {
1681 const URI: &'static str = "test://resource";
1682 const NAME: &'static str = "Test";
1683 const DESCRIPTION: Option<&'static str> = Some("A test resource");
1684 const MIME_TYPE: Option<&'static str> = Some("text/plain");
1685
1686 async fn read(&self) -> Result<ReadResourceResult> {
1687 Ok(ReadResourceResult {
1688 contents: vec![ResourceContent {
1689 uri: Self::URI.to_string(),
1690 mime_type: Self::MIME_TYPE.map(|s| s.to_string()),
1691 text: Some("test content".to_string()),
1692 blob: None,
1693 meta: None,
1694 }],
1695 meta: None,
1696 })
1697 }
1698 }
1699
1700 let resource = TestResource.into_resource();
1701 assert_eq!(resource.uri, "test://resource");
1702 assert_eq!(resource.name, "Test");
1703
1704 let result = resource.read().await;
1705 assert_eq!(result.contents[0].text.as_deref(), Some("test content"));
1706 }
1707
1708 #[test]
1709 fn test_resource_definition() {
1710 let resource = ResourceBuilder::new("file:///test.txt")
1711 .name("Test")
1712 .description("Description")
1713 .mime_type("text/plain")
1714 .text("content");
1715
1716 let def = resource.definition();
1717 assert_eq!(def.uri, "file:///test.txt");
1718 assert_eq!(def.name, "Test");
1719 assert_eq!(def.description.as_deref(), Some("Description"));
1720 assert_eq!(def.mime_type.as_deref(), Some("text/plain"));
1721 }
1722
1723 #[test]
1724 fn test_resource_request_new() {
1725 let ctx = RequestContext::new(crate::protocol::RequestId::Number(1));
1726 let req = ResourceRequest::new(ctx, "file:///test.txt".to_string());
1727 assert_eq!(req.uri, "file:///test.txt");
1728 }
1729
1730 #[test]
1731 fn test_resource_catch_error_clone() {
1732 let handler = FnHandler {
1733 handler: || async {
1734 Ok::<_, Error>(ReadResourceResult {
1735 contents: vec![],
1736 meta: None,
1737 })
1738 },
1739 };
1740 let service = ResourceHandlerService::new(handler);
1741 let catch_error = ResourceCatchError::new(service);
1742 let _clone = catch_error.clone();
1743 }
1744
1745 #[test]
1746 fn test_resource_catch_error_debug() {
1747 let handler = FnHandler {
1748 handler: || async {
1749 Ok::<_, Error>(ReadResourceResult {
1750 contents: vec![],
1751 meta: None,
1752 })
1753 },
1754 };
1755 let service = ResourceHandlerService::new(handler);
1756 let catch_error = ResourceCatchError::new(service);
1757 let debug = format!("{:?}", catch_error);
1758 assert!(debug.contains("ResourceCatchError"));
1759 }
1760
1761 #[test]
1766 fn test_compile_uri_template_simple() {
1767 let (regex, vars) = compile_uri_template("file:///{path}").unwrap();
1768 assert_eq!(vars, vec!["path"]);
1769 assert!(regex.is_match("file:///README.md"));
1770 assert!(!regex.is_match("file:///foo/bar")); }
1772
1773 #[test]
1774 fn test_compile_uri_template_multiple_vars() {
1775 let (regex, vars) = compile_uri_template("api://v1/{resource}/{id}").unwrap();
1776 assert_eq!(vars, vec!["resource", "id"]);
1777 assert!(regex.is_match("api://v1/users/123"));
1778 assert!(regex.is_match("api://v1/posts/abc"));
1779 assert!(!regex.is_match("api://v1/users")); }
1781
1782 #[test]
1783 fn test_compile_uri_template_reserved_expansion() {
1784 let (regex, vars) = compile_uri_template("file:///{+path}").unwrap();
1785 assert_eq!(vars, vec!["path"]);
1786 assert!(regex.is_match("file:///README.md"));
1787 assert!(regex.is_match("file:///foo/bar/baz.txt")); }
1789
1790 #[test]
1791 fn test_compile_uri_template_special_chars() {
1792 let (regex, vars) = compile_uri_template("http://example.com/api?query={q}").unwrap();
1793 assert_eq!(vars, vec!["q"]);
1794 assert!(regex.is_match("http://example.com/api?query=hello"));
1795 }
1796
1797 #[test]
1798 fn test_resource_template_match_uri() {
1799 let template = ResourceTemplateBuilder::new("db://users/{id}")
1800 .name("User Records")
1801 .handler(|uri: String, vars: HashMap<String, String>| async move {
1802 Ok(ReadResourceResult {
1803 contents: vec![ResourceContent {
1804 uri,
1805 mime_type: None,
1806 text: Some(format!("User {}", vars.get("id").unwrap())),
1807 blob: None,
1808 meta: None,
1809 }],
1810 meta: None,
1811 })
1812 });
1813
1814 let vars = template.match_uri("db://users/123").unwrap();
1816 assert_eq!(vars.get("id"), Some(&"123".to_string()));
1817
1818 assert!(template.match_uri("db://posts/123").is_none());
1820 assert!(template.match_uri("db://users").is_none());
1821 }
1822
1823 #[test]
1824 fn test_resource_template_match_multiple_vars() {
1825 let template = ResourceTemplateBuilder::new("api://{version}/{resource}/{id}")
1826 .name("API Resources")
1827 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1828 Ok(ReadResourceResult {
1829 contents: vec![ResourceContent {
1830 uri,
1831 mime_type: None,
1832 text: None,
1833 blob: None,
1834 meta: None,
1835 }],
1836 meta: None,
1837 })
1838 });
1839
1840 let vars = template.match_uri("api://v2/users/abc-123").unwrap();
1841 assert_eq!(vars.get("version"), Some(&"v2".to_string()));
1842 assert_eq!(vars.get("resource"), Some(&"users".to_string()));
1843 assert_eq!(vars.get("id"), Some(&"abc-123".to_string()));
1844 }
1845
1846 #[tokio::test]
1847 async fn test_resource_template_read() {
1848 let template = ResourceTemplateBuilder::new("file:///{path}")
1849 .name("Files")
1850 .mime_type("text/plain")
1851 .handler(|uri: String, vars: HashMap<String, String>| async move {
1852 let path = vars.get("path").unwrap().clone();
1853 Ok(ReadResourceResult {
1854 contents: vec![ResourceContent {
1855 uri,
1856 mime_type: Some("text/plain".to_string()),
1857 text: Some(format!("Contents of {}", path)),
1858 blob: None,
1859 meta: None,
1860 }],
1861 meta: None,
1862 })
1863 });
1864
1865 let vars = template.match_uri("file:///README.md").unwrap();
1866 let result = template.read("file:///README.md", vars).await.unwrap();
1867
1868 assert_eq!(result.contents.len(), 1);
1869 assert_eq!(result.contents[0].uri, "file:///README.md");
1870 assert_eq!(
1871 result.contents[0].text.as_deref(),
1872 Some("Contents of README.md")
1873 );
1874 }
1875
1876 #[test]
1877 fn test_resource_template_definition() {
1878 let template = ResourceTemplateBuilder::new("db://records/{id}")
1879 .name("Database Records")
1880 .description("Access database records by ID")
1881 .mime_type("application/json")
1882 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1883 Ok(ReadResourceResult {
1884 contents: vec![ResourceContent {
1885 uri,
1886 mime_type: None,
1887 text: None,
1888 blob: None,
1889 meta: None,
1890 }],
1891 meta: None,
1892 })
1893 });
1894
1895 let def = template.definition();
1896 assert_eq!(def.uri_template, "db://records/{id}");
1897 assert_eq!(def.name, "Database Records");
1898 assert_eq!(
1899 def.description.as_deref(),
1900 Some("Access database records by ID")
1901 );
1902 assert_eq!(def.mime_type.as_deref(), Some("application/json"));
1903 }
1904
1905 #[test]
1906 fn test_resource_template_reserved_path() {
1907 let template = ResourceTemplateBuilder::new("file:///{+path}")
1908 .name("Files with subpaths")
1909 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1910 Ok(ReadResourceResult {
1911 contents: vec![ResourceContent {
1912 uri,
1913 mime_type: None,
1914 text: None,
1915 blob: None,
1916 meta: None,
1917 }],
1918 meta: None,
1919 })
1920 });
1921
1922 let vars = template.match_uri("file:///src/lib/utils.rs").unwrap();
1924 assert_eq!(vars.get("path"), Some(&"src/lib/utils.rs".to_string()));
1925 }
1926
1927 #[test]
1928 fn test_resource_annotations() {
1929 use crate::protocol::{ContentAnnotations, ContentRole};
1930
1931 let annotations = ContentAnnotations {
1932 audience: Some(vec![ContentRole::User]),
1933 priority: Some(0.8),
1934 last_modified: None,
1935 };
1936
1937 let resource = ResourceBuilder::new("file:///important.txt")
1938 .name("Important File")
1939 .annotations(annotations.clone())
1940 .text("content");
1941
1942 let def = resource.definition();
1943 assert!(def.annotations.is_some());
1944 let ann = def.annotations.unwrap();
1945 assert_eq!(ann.priority, Some(0.8));
1946 assert_eq!(ann.audience.unwrap(), vec![ContentRole::User]);
1947 }
1948
1949 #[test]
1950 fn test_resource_template_annotations() {
1951 use crate::protocol::{ContentAnnotations, ContentRole};
1952
1953 let annotations = ContentAnnotations {
1954 audience: Some(vec![ContentRole::Assistant]),
1955 priority: Some(0.5),
1956 last_modified: None,
1957 };
1958
1959 let template = ResourceTemplateBuilder::new("db://users/{id}")
1960 .name("Users")
1961 .annotations(annotations)
1962 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1963 Ok(ReadResourceResult {
1964 contents: vec![ResourceContent {
1965 uri,
1966 mime_type: None,
1967 text: Some("data".to_string()),
1968 blob: None,
1969 meta: None,
1970 }],
1971 meta: None,
1972 })
1973 });
1974
1975 let def = template.definition();
1976 assert!(def.annotations.is_some());
1977 let ann = def.annotations.unwrap();
1978 assert_eq!(ann.priority, Some(0.5));
1979 assert_eq!(ann.audience.unwrap(), vec![ContentRole::Assistant]);
1980 }
1981
1982 #[test]
1983 fn test_resource_no_annotations_by_default() {
1984 let resource = ResourceBuilder::new("file:///test.txt")
1985 .name("Test")
1986 .text("content");
1987
1988 let def = resource.definition();
1989 assert!(def.annotations.is_none());
1990 }
1991
1992 #[test]
1993 fn test_try_handler_success() {
1994 let result = ResourceTemplateBuilder::new("db://users/{id}")
1995 .name("Users")
1996 .try_handler(|uri: String, _vars: HashMap<String, String>| async move {
1997 Ok(ReadResourceResult {
1998 contents: vec![ResourceContent {
1999 uri,
2000 mime_type: None,
2001 text: Some("ok".to_string()),
2002 blob: None,
2003 meta: None,
2004 }],
2005 meta: None,
2006 })
2007 });
2008
2009 assert!(result.is_ok());
2010 let template = result.unwrap();
2011 assert_eq!(template.uri_template, "db://users/{id}");
2012 }
2013
2014 #[test]
2015 fn test_compile_uri_template_returns_result() {
2016 assert!(compile_uri_template("file:///{path}").is_ok());
2018 assert!(compile_uri_template("api://v1/{resource}/{id}").is_ok());
2019 assert!(compile_uri_template("file:///{+path}").is_ok());
2020 assert!(compile_uri_template("no-vars").is_ok());
2021 assert!(compile_uri_template("").is_ok());
2022 }
2023}