1use std::collections::HashMap;
66use std::convert::Infallible;
67use std::fmt;
68use std::future::Future;
69use std::pin::Pin;
70use std::sync::Arc;
71use std::task::{Context, Poll};
72
73use tower::util::BoxCloneService;
74use tower_service::Service;
75
76use crate::context::RequestContext;
77use crate::error::{Error, Result};
78use crate::protocol::{
79 ReadResourceResult, ResourceContent, ResourceDefinition, ResourceTemplateDefinition, ToolIcon,
80};
81
82#[derive(Debug, Clone)]
91pub struct ResourceRequest {
92 pub ctx: RequestContext,
94 pub uri: String,
96}
97
98impl ResourceRequest {
99 pub fn new(ctx: RequestContext, uri: String) -> Self {
101 Self { ctx, uri }
102 }
103}
104
105pub type BoxResourceService = BoxCloneService<ResourceRequest, ReadResourceResult, Infallible>;
110
111pub struct ResourceCatchError<S> {
117 inner: S,
118}
119
120impl<S> ResourceCatchError<S> {
121 pub fn new(inner: S) -> Self {
123 Self { inner }
124 }
125}
126
127impl<S: Clone> Clone for ResourceCatchError<S> {
128 fn clone(&self) -> Self {
129 Self {
130 inner: self.inner.clone(),
131 }
132 }
133}
134
135impl<S: fmt::Debug> fmt::Debug for ResourceCatchError<S> {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 f.debug_struct("ResourceCatchError")
138 .field("inner", &self.inner)
139 .finish()
140 }
141}
142
143impl<S> Service<ResourceRequest> for ResourceCatchError<S>
144where
145 S: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
146 S::Error: fmt::Display + Send,
147 S::Future: Send,
148{
149 type Response = ReadResourceResult;
150 type Error = Infallible;
151 type Future =
152 Pin<Box<dyn Future<Output = std::result::Result<ReadResourceResult, Infallible>> + Send>>;
153
154 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
155 match self.inner.poll_ready(cx) {
157 Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
158 Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
159 Poll::Pending => Poll::Pending,
160 }
161 }
162
163 fn call(&mut self, req: ResourceRequest) -> Self::Future {
164 let uri = req.uri.clone();
165 let fut = self.inner.call(req);
166
167 Box::pin(async move {
168 match fut.await {
169 Ok(result) => Ok(result),
170 Err(err) => {
171 Ok(ReadResourceResult {
173 contents: vec![ResourceContent {
174 uri,
175 mime_type: Some("text/plain".to_string()),
176 text: Some(format!("Error reading resource: {}", err)),
177 blob: None,
178 }],
179 })
180 }
181 }
182 })
183 }
184}
185
186pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
188
189pub trait ResourceHandler: Send + Sync {
191 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>>;
193
194 fn read_with_context(&self, _ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
199 self.read()
200 }
201
202 fn uses_context(&self) -> bool {
204 false
205 }
206}
207
208struct ResourceHandlerService<H> {
213 handler: Arc<H>,
214}
215
216impl<H> ResourceHandlerService<H> {
217 fn new(handler: H) -> Self {
218 Self {
219 handler: Arc::new(handler),
220 }
221 }
222}
223
224impl<H> Clone for ResourceHandlerService<H> {
225 fn clone(&self) -> Self {
226 Self {
227 handler: self.handler.clone(),
228 }
229 }
230}
231
232impl<H> fmt::Debug for ResourceHandlerService<H> {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 f.debug_struct("ResourceHandlerService")
235 .finish_non_exhaustive()
236 }
237}
238
239impl<H> Service<ResourceRequest> for ResourceHandlerService<H>
240where
241 H: ResourceHandler + 'static,
242{
243 type Response = ReadResourceResult;
244 type Error = Error;
245 type Future =
246 Pin<Box<dyn Future<Output = std::result::Result<ReadResourceResult, Error>> + Send>>;
247
248 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
249 Poll::Ready(Ok(()))
250 }
251
252 fn call(&mut self, req: ResourceRequest) -> Self::Future {
253 let handler = self.handler.clone();
254 Box::pin(async move { handler.read_with_context(req.ctx).await })
255 }
256}
257
258pub struct Resource {
265 pub uri: String,
267 pub name: String,
269 pub title: Option<String>,
271 pub description: Option<String>,
273 pub mime_type: Option<String>,
275 pub icons: Option<Vec<ToolIcon>>,
277 pub size: Option<u64>,
279 service: BoxResourceService,
281}
282
283impl Clone for Resource {
284 fn clone(&self) -> Self {
285 Self {
286 uri: self.uri.clone(),
287 name: self.name.clone(),
288 title: self.title.clone(),
289 description: self.description.clone(),
290 mime_type: self.mime_type.clone(),
291 icons: self.icons.clone(),
292 size: self.size,
293 service: self.service.clone(),
294 }
295 }
296}
297
298impl std::fmt::Debug for Resource {
299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300 f.debug_struct("Resource")
301 .field("uri", &self.uri)
302 .field("name", &self.name)
303 .field("title", &self.title)
304 .field("description", &self.description)
305 .field("mime_type", &self.mime_type)
306 .field("icons", &self.icons)
307 .field("size", &self.size)
308 .finish_non_exhaustive()
309 }
310}
311
312unsafe impl Send for Resource {}
315unsafe impl Sync for Resource {}
316
317impl Resource {
318 pub fn builder(uri: impl Into<String>) -> ResourceBuilder {
320 ResourceBuilder::new(uri)
321 }
322
323 pub fn definition(&self) -> ResourceDefinition {
325 ResourceDefinition {
326 uri: self.uri.clone(),
327 name: self.name.clone(),
328 title: self.title.clone(),
329 description: self.description.clone(),
330 mime_type: self.mime_type.clone(),
331 icons: self.icons.clone(),
332 size: self.size,
333 }
334 }
335
336 pub fn read(&self) -> BoxFuture<'static, ReadResourceResult> {
341 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
342 self.read_with_context(ctx)
343 }
344
345 pub fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'static, ReadResourceResult> {
356 use tower::ServiceExt;
357 let service = self.service.clone();
358 let uri = self.uri.clone();
359 Box::pin(async move {
360 service
363 .oneshot(ResourceRequest::new(ctx, uri))
364 .await
365 .unwrap()
366 })
367 }
368
369 #[allow(clippy::too_many_arguments)]
371 fn from_handler<H: ResourceHandler + 'static>(
372 uri: String,
373 name: String,
374 title: Option<String>,
375 description: Option<String>,
376 mime_type: Option<String>,
377 icons: Option<Vec<ToolIcon>>,
378 size: Option<u64>,
379 handler: H,
380 ) -> Self {
381 let handler_service = ResourceHandlerService::new(handler);
382 let catch_error = ResourceCatchError::new(handler_service);
383 let service = BoxCloneService::new(catch_error);
384
385 Self {
386 uri,
387 name,
388 title,
389 description,
390 mime_type,
391 icons,
392 size,
393 service,
394 }
395 }
396}
397
398pub struct ResourceBuilder {
429 uri: String,
430 name: Option<String>,
431 title: Option<String>,
432 description: Option<String>,
433 mime_type: Option<String>,
434 icons: Option<Vec<ToolIcon>>,
435 size: Option<u64>,
436}
437
438impl ResourceBuilder {
439 pub fn new(uri: impl Into<String>) -> Self {
440 Self {
441 uri: uri.into(),
442 name: None,
443 title: None,
444 description: None,
445 mime_type: None,
446 icons: None,
447 size: None,
448 }
449 }
450
451 pub fn name(mut self, name: impl Into<String>) -> Self {
453 self.name = Some(name.into());
454 self
455 }
456
457 pub fn title(mut self, title: impl Into<String>) -> Self {
459 self.title = Some(title.into());
460 self
461 }
462
463 pub fn description(mut self, description: impl Into<String>) -> Self {
465 self.description = Some(description.into());
466 self
467 }
468
469 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
471 self.mime_type = Some(mime_type.into());
472 self
473 }
474
475 pub fn icon(mut self, src: impl Into<String>) -> Self {
477 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
478 src: src.into(),
479 mime_type: None,
480 sizes: None,
481 });
482 self
483 }
484
485 pub fn icon_with_meta(
487 mut self,
488 src: impl Into<String>,
489 mime_type: Option<String>,
490 sizes: Option<Vec<String>>,
491 ) -> Self {
492 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
493 src: src.into(),
494 mime_type,
495 sizes,
496 });
497 self
498 }
499
500 pub fn size(mut self, size: u64) -> Self {
502 self.size = Some(size);
503 self
504 }
505
506 pub fn handler<F, Fut>(self, handler: F) -> ResourceBuilderWithHandler<F>
511 where
512 F: Fn() -> Fut + Send + Sync + 'static,
513 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
514 {
515 ResourceBuilderWithHandler {
516 uri: self.uri,
517 name: self.name,
518 title: self.title,
519 description: self.description,
520 mime_type: self.mime_type,
521 icons: self.icons,
522 size: self.size,
523 handler,
524 }
525 }
526
527 pub fn handler_with_context<F, Fut>(self, handler: F) -> ResourceBuilderWithContextHandler<F>
535 where
536 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
537 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
538 {
539 ResourceBuilderWithContextHandler {
540 uri: self.uri,
541 name: self.name,
542 title: self.title,
543 description: self.description,
544 mime_type: self.mime_type,
545 icons: self.icons,
546 size: self.size,
547 handler,
548 }
549 }
550
551 pub fn text(self, content: impl Into<String>) -> Resource {
553 let uri = self.uri.clone();
554 let content = content.into();
555 let mime_type = self.mime_type.clone();
556
557 self.handler(move || {
558 let uri = uri.clone();
559 let content = content.clone();
560 let mime_type = mime_type.clone();
561 async move {
562 Ok(ReadResourceResult {
563 contents: vec![ResourceContent {
564 uri,
565 mime_type,
566 text: Some(content),
567 blob: None,
568 }],
569 })
570 }
571 })
572 .build()
573 }
574
575 pub fn json(mut self, value: serde_json::Value) -> Resource {
577 let uri = self.uri.clone();
578 self.mime_type = Some("application/json".to_string());
579 let text = serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{}".to_string());
580
581 self.handler(move || {
582 let uri = uri.clone();
583 let text = text.clone();
584 async move {
585 Ok(ReadResourceResult {
586 contents: vec![ResourceContent {
587 uri,
588 mime_type: Some("application/json".to_string()),
589 text: Some(text),
590 blob: None,
591 }],
592 })
593 }
594 })
595 .build()
596 }
597}
598
599pub struct ResourceBuilderWithHandler<F> {
604 uri: String,
605 name: Option<String>,
606 title: Option<String>,
607 description: Option<String>,
608 mime_type: Option<String>,
609 icons: Option<Vec<ToolIcon>>,
610 size: Option<u64>,
611 handler: F,
612}
613
614impl<F, Fut> ResourceBuilderWithHandler<F>
615where
616 F: Fn() -> Fut + Send + Sync + 'static,
617 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
618{
619 pub fn build(self) -> Resource {
621 let name = self.name.unwrap_or_else(|| self.uri.clone());
622
623 Resource::from_handler(
624 self.uri,
625 name,
626 self.title,
627 self.description,
628 self.mime_type,
629 self.icons,
630 self.size,
631 FnHandler {
632 handler: self.handler,
633 },
634 )
635 }
636
637 pub fn layer<L>(self, layer: L) -> ResourceBuilderWithLayer<F, L> {
666 ResourceBuilderWithLayer {
667 uri: self.uri,
668 name: self.name,
669 title: self.title,
670 description: self.description,
671 mime_type: self.mime_type,
672 icons: self.icons,
673 size: self.size,
674 handler: self.handler,
675 layer,
676 }
677 }
678}
679
680pub struct ResourceBuilderWithLayer<F, L> {
684 uri: String,
685 name: Option<String>,
686 title: Option<String>,
687 description: Option<String>,
688 mime_type: Option<String>,
689 icons: Option<Vec<ToolIcon>>,
690 size: Option<u64>,
691 handler: F,
692 layer: L,
693}
694
695#[allow(private_bounds)]
698impl<F, Fut, L> ResourceBuilderWithLayer<F, L>
699where
700 F: Fn() -> Fut + Send + Sync + 'static,
701 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
702 L: tower::Layer<ResourceHandlerService<FnHandler<F>>> + Clone + Send + Sync + 'static,
703 L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
704 <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
705 <L::Service as Service<ResourceRequest>>::Future: Send,
706{
707 pub fn build(self) -> Resource {
709 let name = self.name.unwrap_or_else(|| self.uri.clone());
710
711 let handler_service = ResourceHandlerService::new(FnHandler {
712 handler: self.handler,
713 });
714 let layered = self.layer.layer(handler_service);
715 let catch_error = ResourceCatchError::new(layered);
716 let service = BoxCloneService::new(catch_error);
717
718 Resource {
719 uri: self.uri,
720 name,
721 title: self.title,
722 description: self.description,
723 mime_type: self.mime_type,
724 icons: self.icons,
725 size: self.size,
726 service,
727 }
728 }
729
730 pub fn layer<L2>(
735 self,
736 layer: L2,
737 ) -> ResourceBuilderWithLayer<F, tower::layer::util::Stack<L2, L>> {
738 ResourceBuilderWithLayer {
739 uri: self.uri,
740 name: self.name,
741 title: self.title,
742 description: self.description,
743 mime_type: self.mime_type,
744 icons: self.icons,
745 size: self.size,
746 handler: self.handler,
747 layer: tower::layer::util::Stack::new(layer, self.layer),
748 }
749 }
750}
751
752pub struct ResourceBuilderWithContextHandler<F> {
754 uri: String,
755 name: Option<String>,
756 title: Option<String>,
757 description: Option<String>,
758 mime_type: Option<String>,
759 icons: Option<Vec<ToolIcon>>,
760 size: Option<u64>,
761 handler: F,
762}
763
764impl<F, Fut> ResourceBuilderWithContextHandler<F>
765where
766 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
767 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
768{
769 pub fn build(self) -> Resource {
771 let name = self.name.unwrap_or_else(|| self.uri.clone());
772
773 Resource::from_handler(
774 self.uri,
775 name,
776 self.title,
777 self.description,
778 self.mime_type,
779 self.icons,
780 self.size,
781 ContextAwareHandler {
782 handler: self.handler,
783 },
784 )
785 }
786
787 pub fn layer<L>(self, layer: L) -> ResourceBuilderWithContextLayer<F, L> {
791 ResourceBuilderWithContextLayer {
792 uri: self.uri,
793 name: self.name,
794 title: self.title,
795 description: self.description,
796 mime_type: self.mime_type,
797 icons: self.icons,
798 size: self.size,
799 handler: self.handler,
800 layer,
801 }
802 }
803}
804
805pub struct ResourceBuilderWithContextLayer<F, L> {
807 uri: String,
808 name: Option<String>,
809 title: Option<String>,
810 description: Option<String>,
811 mime_type: Option<String>,
812 icons: Option<Vec<ToolIcon>>,
813 size: Option<u64>,
814 handler: F,
815 layer: L,
816}
817
818#[allow(private_bounds)]
820impl<F, Fut, L> ResourceBuilderWithContextLayer<F, L>
821where
822 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
823 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
824 L: tower::Layer<ResourceHandlerService<ContextAwareHandler<F>>> + Clone + Send + Sync + 'static,
825 L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
826 <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
827 <L::Service as Service<ResourceRequest>>::Future: Send,
828{
829 pub fn build(self) -> Resource {
831 let name = self.name.unwrap_or_else(|| self.uri.clone());
832
833 let handler_service = ResourceHandlerService::new(ContextAwareHandler {
834 handler: self.handler,
835 });
836 let layered = self.layer.layer(handler_service);
837 let catch_error = ResourceCatchError::new(layered);
838 let service = BoxCloneService::new(catch_error);
839
840 Resource {
841 uri: self.uri,
842 name,
843 title: self.title,
844 description: self.description,
845 mime_type: self.mime_type,
846 icons: self.icons,
847 size: self.size,
848 service,
849 }
850 }
851
852 pub fn layer<L2>(
854 self,
855 layer: L2,
856 ) -> ResourceBuilderWithContextLayer<F, tower::layer::util::Stack<L2, L>> {
857 ResourceBuilderWithContextLayer {
858 uri: self.uri,
859 name: self.name,
860 title: self.title,
861 description: self.description,
862 mime_type: self.mime_type,
863 icons: self.icons,
864 size: self.size,
865 handler: self.handler,
866 layer: tower::layer::util::Stack::new(layer, self.layer),
867 }
868 }
869}
870
871struct FnHandler<F> {
877 handler: F,
878}
879
880impl<F, Fut> ResourceHandler for FnHandler<F>
881where
882 F: Fn() -> Fut + Send + Sync + 'static,
883 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
884{
885 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
886 Box::pin((self.handler)())
887 }
888}
889
890struct ContextAwareHandler<F> {
892 handler: F,
893}
894
895impl<F, Fut> ResourceHandler for ContextAwareHandler<F>
896where
897 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
898 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
899{
900 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
901 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
902 self.read_with_context(ctx)
903 }
904
905 fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
906 Box::pin((self.handler)(ctx))
907 }
908
909 fn uses_context(&self) -> bool {
910 true
911 }
912}
913
914pub trait McpResource: Send + Sync + 'static {
956 const URI: &'static str;
957 const NAME: &'static str;
958 const DESCRIPTION: Option<&'static str> = None;
959 const MIME_TYPE: Option<&'static str> = None;
960
961 fn read(&self) -> impl Future<Output = Result<ReadResourceResult>> + Send;
962
963 fn into_resource(self) -> Resource
965 where
966 Self: Sized,
967 {
968 let resource = Arc::new(self);
969 Resource::from_handler(
970 Self::URI.to_string(),
971 Self::NAME.to_string(),
972 None,
973 Self::DESCRIPTION.map(|s| s.to_string()),
974 Self::MIME_TYPE.map(|s| s.to_string()),
975 None,
976 None,
977 McpResourceHandler { resource },
978 )
979 }
980}
981
982struct McpResourceHandler<T: McpResource> {
984 resource: Arc<T>,
985}
986
987impl<T: McpResource> ResourceHandler for McpResourceHandler<T> {
988 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
989 let resource = self.resource.clone();
990 Box::pin(async move { resource.read().await })
991 }
992}
993
994pub trait ResourceTemplateHandler: Send + Sync {
1003 fn read(
1005 &self,
1006 uri: &str,
1007 variables: HashMap<String, String>,
1008 ) -> BoxFuture<'_, Result<ReadResourceResult>>;
1009}
1010
1011pub struct ResourceTemplate {
1039 pub uri_template: String,
1041 pub name: String,
1043 pub title: Option<String>,
1045 pub description: Option<String>,
1047 pub mime_type: Option<String>,
1049 pub icons: Option<Vec<ToolIcon>>,
1051 pattern: regex::Regex,
1053 variables: Vec<String>,
1055 handler: Arc<dyn ResourceTemplateHandler>,
1057}
1058
1059impl Clone for ResourceTemplate {
1060 fn clone(&self) -> Self {
1061 Self {
1062 uri_template: self.uri_template.clone(),
1063 name: self.name.clone(),
1064 title: self.title.clone(),
1065 description: self.description.clone(),
1066 mime_type: self.mime_type.clone(),
1067 icons: self.icons.clone(),
1068 pattern: self.pattern.clone(),
1069 variables: self.variables.clone(),
1070 handler: self.handler.clone(),
1071 }
1072 }
1073}
1074
1075impl std::fmt::Debug for ResourceTemplate {
1076 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1077 f.debug_struct("ResourceTemplate")
1078 .field("uri_template", &self.uri_template)
1079 .field("name", &self.name)
1080 .field("title", &self.title)
1081 .field("description", &self.description)
1082 .field("mime_type", &self.mime_type)
1083 .field("icons", &self.icons)
1084 .field("variables", &self.variables)
1085 .finish_non_exhaustive()
1086 }
1087}
1088
1089impl ResourceTemplate {
1090 pub fn builder(uri_template: impl Into<String>) -> ResourceTemplateBuilder {
1092 ResourceTemplateBuilder::new(uri_template)
1093 }
1094
1095 pub fn definition(&self) -> ResourceTemplateDefinition {
1097 ResourceTemplateDefinition {
1098 uri_template: self.uri_template.clone(),
1099 name: self.name.clone(),
1100 title: self.title.clone(),
1101 description: self.description.clone(),
1102 mime_type: self.mime_type.clone(),
1103 icons: self.icons.clone(),
1104 }
1105 }
1106
1107 pub fn match_uri(&self, uri: &str) -> Option<HashMap<String, String>> {
1112 self.pattern.captures(uri).map(|caps| {
1113 self.variables
1114 .iter()
1115 .enumerate()
1116 .filter_map(|(i, name)| {
1117 caps.get(i + 1)
1118 .map(|m| (name.clone(), m.as_str().to_string()))
1119 })
1120 .collect()
1121 })
1122 }
1123
1124 pub fn read(
1131 &self,
1132 uri: &str,
1133 variables: HashMap<String, String>,
1134 ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1135 self.handler.read(uri, variables)
1136 }
1137}
1138
1139pub struct ResourceTemplateBuilder {
1164 uri_template: String,
1165 name: Option<String>,
1166 title: Option<String>,
1167 description: Option<String>,
1168 mime_type: Option<String>,
1169 icons: Option<Vec<ToolIcon>>,
1170}
1171
1172impl ResourceTemplateBuilder {
1173 pub fn new(uri_template: impl Into<String>) -> Self {
1186 Self {
1187 uri_template: uri_template.into(),
1188 name: None,
1189 title: None,
1190 description: None,
1191 mime_type: None,
1192 icons: None,
1193 }
1194 }
1195
1196 pub fn name(mut self, name: impl Into<String>) -> Self {
1198 self.name = Some(name.into());
1199 self
1200 }
1201
1202 pub fn title(mut self, title: impl Into<String>) -> Self {
1204 self.title = Some(title.into());
1205 self
1206 }
1207
1208 pub fn description(mut self, description: impl Into<String>) -> Self {
1210 self.description = Some(description.into());
1211 self
1212 }
1213
1214 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
1216 self.mime_type = Some(mime_type.into());
1217 self
1218 }
1219
1220 pub fn icon(mut self, src: impl Into<String>) -> Self {
1222 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1223 src: src.into(),
1224 mime_type: None,
1225 sizes: None,
1226 });
1227 self
1228 }
1229
1230 pub fn icon_with_meta(
1232 mut self,
1233 src: impl Into<String>,
1234 mime_type: Option<String>,
1235 sizes: Option<Vec<String>>,
1236 ) -> Self {
1237 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1238 src: src.into(),
1239 mime_type,
1240 sizes,
1241 });
1242 self
1243 }
1244
1245 pub fn handler<F, Fut>(self, handler: F) -> ResourceTemplate
1251 where
1252 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1253 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1254 {
1255 let (pattern, variables) = compile_uri_template(&self.uri_template);
1256 let name = self.name.unwrap_or_else(|| self.uri_template.clone());
1257
1258 ResourceTemplate {
1259 uri_template: self.uri_template,
1260 name,
1261 title: self.title,
1262 description: self.description,
1263 mime_type: self.mime_type,
1264 icons: self.icons,
1265 pattern,
1266 variables,
1267 handler: Arc::new(FnTemplateHandler { handler }),
1268 }
1269 }
1270}
1271
1272struct FnTemplateHandler<F> {
1274 handler: F,
1275}
1276
1277impl<F, Fut> ResourceTemplateHandler for FnTemplateHandler<F>
1278where
1279 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1280 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1281{
1282 fn read(
1283 &self,
1284 uri: &str,
1285 variables: HashMap<String, String>,
1286 ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1287 let uri = uri.to_string();
1288 Box::pin((self.handler)(uri, variables))
1289 }
1290}
1291
1292fn compile_uri_template(template: &str) -> (regex::Regex, Vec<String>) {
1300 let mut pattern = String::from("^");
1301 let mut variables = Vec::new();
1302
1303 let mut chars = template.chars().peekable();
1304 while let Some(c) = chars.next() {
1305 if c == '{' {
1306 let is_reserved = chars.peek() == Some(&'+');
1308 if is_reserved {
1309 chars.next();
1310 }
1311
1312 let var_name: String = chars.by_ref().take_while(|&c| c != '}').collect();
1314 variables.push(var_name);
1315
1316 if is_reserved {
1318 pattern.push_str("(.+)");
1320 } else {
1321 pattern.push_str("([^/]+)");
1323 }
1324 } else {
1325 match c {
1327 '.' | '+' | '*' | '?' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|'
1328 | '\\' => {
1329 pattern.push('\\');
1330 pattern.push(c);
1331 }
1332 _ => pattern.push(c),
1333 }
1334 }
1335 }
1336
1337 pattern.push('$');
1338
1339 let regex = regex::Regex::new(&pattern)
1341 .unwrap_or_else(|e| panic!("Invalid URI template '{}': {}", template, e));
1342
1343 (regex, variables)
1344}
1345
1346#[cfg(test)]
1347mod tests {
1348 use super::*;
1349 use std::time::Duration;
1350 use tower::timeout::TimeoutLayer;
1351
1352 #[tokio::test]
1353 async fn test_builder_resource() {
1354 let resource = ResourceBuilder::new("file:///test.txt")
1355 .name("Test File")
1356 .description("A test file")
1357 .text("Hello, World!");
1358
1359 assert_eq!(resource.uri, "file:///test.txt");
1360 assert_eq!(resource.name, "Test File");
1361 assert_eq!(resource.description.as_deref(), Some("A test file"));
1362
1363 let result = resource.read().await;
1364 assert_eq!(result.contents.len(), 1);
1365 assert_eq!(result.contents[0].text.as_deref(), Some("Hello, World!"));
1366 }
1367
1368 #[tokio::test]
1369 async fn test_json_resource() {
1370 let resource = ResourceBuilder::new("file:///config.json")
1371 .name("Config")
1372 .json(serde_json::json!({"key": "value"}));
1373
1374 assert_eq!(resource.mime_type.as_deref(), Some("application/json"));
1375
1376 let result = resource.read().await;
1377 assert!(result.contents[0].text.as_ref().unwrap().contains("key"));
1378 }
1379
1380 #[tokio::test]
1381 async fn test_handler_resource() {
1382 let resource = ResourceBuilder::new("memory://counter")
1383 .name("Counter")
1384 .handler(|| async {
1385 Ok(ReadResourceResult {
1386 contents: vec![ResourceContent {
1387 uri: "memory://counter".to_string(),
1388 mime_type: Some("text/plain".to_string()),
1389 text: Some("42".to_string()),
1390 blob: None,
1391 }],
1392 })
1393 })
1394 .build();
1395
1396 let result = resource.read().await;
1397 assert_eq!(result.contents[0].text.as_deref(), Some("42"));
1398 }
1399
1400 #[tokio::test]
1401 async fn test_handler_resource_with_layer() {
1402 let resource = ResourceBuilder::new("file:///with-timeout.txt")
1403 .name("Resource with Timeout")
1404 .handler(|| async {
1405 Ok(ReadResourceResult {
1406 contents: vec![ResourceContent {
1407 uri: "file:///with-timeout.txt".to_string(),
1408 mime_type: Some("text/plain".to_string()),
1409 text: Some("content".to_string()),
1410 blob: None,
1411 }],
1412 })
1413 })
1414 .layer(TimeoutLayer::new(Duration::from_secs(30)))
1415 .build();
1416
1417 let result = resource.read().await;
1418 assert_eq!(result.contents[0].text.as_deref(), Some("content"));
1419 }
1420
1421 #[tokio::test]
1422 async fn test_handler_resource_with_timeout_error() {
1423 let resource = ResourceBuilder::new("file:///slow.txt")
1424 .name("Slow Resource")
1425 .handler(|| async {
1426 tokio::time::sleep(Duration::from_millis(100)).await;
1427 Ok(ReadResourceResult {
1428 contents: vec![ResourceContent {
1429 uri: "file:///slow.txt".to_string(),
1430 mime_type: Some("text/plain".to_string()),
1431 text: Some("content".to_string()),
1432 blob: None,
1433 }],
1434 })
1435 })
1436 .layer(TimeoutLayer::new(Duration::from_millis(10)))
1437 .build();
1438
1439 let result = resource.read().await;
1440 assert!(
1442 result.contents[0]
1443 .text
1444 .as_ref()
1445 .unwrap()
1446 .contains("Error reading resource")
1447 );
1448 }
1449
1450 #[tokio::test]
1451 async fn test_context_aware_handler() {
1452 let resource = ResourceBuilder::new("file:///ctx.txt")
1453 .name("Context Resource")
1454 .handler_with_context(|_ctx: RequestContext| async {
1455 Ok(ReadResourceResult {
1456 contents: vec![ResourceContent {
1457 uri: "file:///ctx.txt".to_string(),
1458 mime_type: Some("text/plain".to_string()),
1459 text: Some("context aware".to_string()),
1460 blob: None,
1461 }],
1462 })
1463 })
1464 .build();
1465
1466 let result = resource.read().await;
1467 assert_eq!(result.contents[0].text.as_deref(), Some("context aware"));
1468 }
1469
1470 #[tokio::test]
1471 async fn test_context_aware_handler_with_layer() {
1472 let resource = ResourceBuilder::new("file:///ctx-layer.txt")
1473 .name("Context Resource with Layer")
1474 .handler_with_context(|_ctx: RequestContext| async {
1475 Ok(ReadResourceResult {
1476 contents: vec![ResourceContent {
1477 uri: "file:///ctx-layer.txt".to_string(),
1478 mime_type: Some("text/plain".to_string()),
1479 text: Some("context with layer".to_string()),
1480 blob: None,
1481 }],
1482 })
1483 })
1484 .layer(TimeoutLayer::new(Duration::from_secs(30)))
1485 .build();
1486
1487 let result = resource.read().await;
1488 assert_eq!(
1489 result.contents[0].text.as_deref(),
1490 Some("context with layer")
1491 );
1492 }
1493
1494 #[tokio::test]
1495 async fn test_trait_resource() {
1496 struct TestResource;
1497
1498 impl McpResource for TestResource {
1499 const URI: &'static str = "test://resource";
1500 const NAME: &'static str = "Test";
1501 const DESCRIPTION: Option<&'static str> = Some("A test resource");
1502 const MIME_TYPE: Option<&'static str> = Some("text/plain");
1503
1504 async fn read(&self) -> Result<ReadResourceResult> {
1505 Ok(ReadResourceResult {
1506 contents: vec![ResourceContent {
1507 uri: Self::URI.to_string(),
1508 mime_type: Self::MIME_TYPE.map(|s| s.to_string()),
1509 text: Some("test content".to_string()),
1510 blob: None,
1511 }],
1512 })
1513 }
1514 }
1515
1516 let resource = TestResource.into_resource();
1517 assert_eq!(resource.uri, "test://resource");
1518 assert_eq!(resource.name, "Test");
1519
1520 let result = resource.read().await;
1521 assert_eq!(result.contents[0].text.as_deref(), Some("test content"));
1522 }
1523
1524 #[test]
1525 fn test_resource_definition() {
1526 let resource = ResourceBuilder::new("file:///test.txt")
1527 .name("Test")
1528 .description("Description")
1529 .mime_type("text/plain")
1530 .text("content");
1531
1532 let def = resource.definition();
1533 assert_eq!(def.uri, "file:///test.txt");
1534 assert_eq!(def.name, "Test");
1535 assert_eq!(def.description.as_deref(), Some("Description"));
1536 assert_eq!(def.mime_type.as_deref(), Some("text/plain"));
1537 }
1538
1539 #[test]
1540 fn test_resource_request_new() {
1541 let ctx = RequestContext::new(crate::protocol::RequestId::Number(1));
1542 let req = ResourceRequest::new(ctx, "file:///test.txt".to_string());
1543 assert_eq!(req.uri, "file:///test.txt");
1544 }
1545
1546 #[test]
1547 fn test_resource_catch_error_clone() {
1548 let handler = FnHandler {
1549 handler: || async { Ok::<_, Error>(ReadResourceResult { contents: vec![] }) },
1550 };
1551 let service = ResourceHandlerService::new(handler);
1552 let catch_error = ResourceCatchError::new(service);
1553 let _clone = catch_error.clone();
1554 }
1555
1556 #[test]
1557 fn test_resource_catch_error_debug() {
1558 let handler = FnHandler {
1559 handler: || async { Ok::<_, Error>(ReadResourceResult { contents: vec![] }) },
1560 };
1561 let service = ResourceHandlerService::new(handler);
1562 let catch_error = ResourceCatchError::new(service);
1563 let debug = format!("{:?}", catch_error);
1564 assert!(debug.contains("ResourceCatchError"));
1565 }
1566
1567 #[test]
1572 fn test_compile_uri_template_simple() {
1573 let (regex, vars) = compile_uri_template("file:///{path}");
1574 assert_eq!(vars, vec!["path"]);
1575 assert!(regex.is_match("file:///README.md"));
1576 assert!(!regex.is_match("file:///foo/bar")); }
1578
1579 #[test]
1580 fn test_compile_uri_template_multiple_vars() {
1581 let (regex, vars) = compile_uri_template("api://v1/{resource}/{id}");
1582 assert_eq!(vars, vec!["resource", "id"]);
1583 assert!(regex.is_match("api://v1/users/123"));
1584 assert!(regex.is_match("api://v1/posts/abc"));
1585 assert!(!regex.is_match("api://v1/users")); }
1587
1588 #[test]
1589 fn test_compile_uri_template_reserved_expansion() {
1590 let (regex, vars) = compile_uri_template("file:///{+path}");
1591 assert_eq!(vars, vec!["path"]);
1592 assert!(regex.is_match("file:///README.md"));
1593 assert!(regex.is_match("file:///foo/bar/baz.txt")); }
1595
1596 #[test]
1597 fn test_compile_uri_template_special_chars() {
1598 let (regex, vars) = compile_uri_template("http://example.com/api?query={q}");
1599 assert_eq!(vars, vec!["q"]);
1600 assert!(regex.is_match("http://example.com/api?query=hello"));
1601 }
1602
1603 #[test]
1604 fn test_resource_template_match_uri() {
1605 let template = ResourceTemplateBuilder::new("db://users/{id}")
1606 .name("User Records")
1607 .handler(|uri: String, vars: HashMap<String, String>| async move {
1608 Ok(ReadResourceResult {
1609 contents: vec![ResourceContent {
1610 uri,
1611 mime_type: None,
1612 text: Some(format!("User {}", vars.get("id").unwrap())),
1613 blob: None,
1614 }],
1615 })
1616 });
1617
1618 let vars = template.match_uri("db://users/123").unwrap();
1620 assert_eq!(vars.get("id"), Some(&"123".to_string()));
1621
1622 assert!(template.match_uri("db://posts/123").is_none());
1624 assert!(template.match_uri("db://users").is_none());
1625 }
1626
1627 #[test]
1628 fn test_resource_template_match_multiple_vars() {
1629 let template = ResourceTemplateBuilder::new("api://{version}/{resource}/{id}")
1630 .name("API Resources")
1631 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1632 Ok(ReadResourceResult {
1633 contents: vec![ResourceContent {
1634 uri,
1635 mime_type: None,
1636 text: None,
1637 blob: None,
1638 }],
1639 })
1640 });
1641
1642 let vars = template.match_uri("api://v2/users/abc-123").unwrap();
1643 assert_eq!(vars.get("version"), Some(&"v2".to_string()));
1644 assert_eq!(vars.get("resource"), Some(&"users".to_string()));
1645 assert_eq!(vars.get("id"), Some(&"abc-123".to_string()));
1646 }
1647
1648 #[tokio::test]
1649 async fn test_resource_template_read() {
1650 let template = ResourceTemplateBuilder::new("file:///{path}")
1651 .name("Files")
1652 .mime_type("text/plain")
1653 .handler(|uri: String, vars: HashMap<String, String>| async move {
1654 let path = vars.get("path").unwrap().clone();
1655 Ok(ReadResourceResult {
1656 contents: vec![ResourceContent {
1657 uri,
1658 mime_type: Some("text/plain".to_string()),
1659 text: Some(format!("Contents of {}", path)),
1660 blob: None,
1661 }],
1662 })
1663 });
1664
1665 let vars = template.match_uri("file:///README.md").unwrap();
1666 let result = template.read("file:///README.md", vars).await.unwrap();
1667
1668 assert_eq!(result.contents.len(), 1);
1669 assert_eq!(result.contents[0].uri, "file:///README.md");
1670 assert_eq!(
1671 result.contents[0].text.as_deref(),
1672 Some("Contents of README.md")
1673 );
1674 }
1675
1676 #[test]
1677 fn test_resource_template_definition() {
1678 let template = ResourceTemplateBuilder::new("db://records/{id}")
1679 .name("Database Records")
1680 .description("Access database records by ID")
1681 .mime_type("application/json")
1682 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1683 Ok(ReadResourceResult {
1684 contents: vec![ResourceContent {
1685 uri,
1686 mime_type: None,
1687 text: None,
1688 blob: None,
1689 }],
1690 })
1691 });
1692
1693 let def = template.definition();
1694 assert_eq!(def.uri_template, "db://records/{id}");
1695 assert_eq!(def.name, "Database Records");
1696 assert_eq!(
1697 def.description.as_deref(),
1698 Some("Access database records by ID")
1699 );
1700 assert_eq!(def.mime_type.as_deref(), Some("application/json"));
1701 }
1702
1703 #[test]
1704 fn test_resource_template_reserved_path() {
1705 let template = ResourceTemplateBuilder::new("file:///{+path}")
1706 .name("Files with subpaths")
1707 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1708 Ok(ReadResourceResult {
1709 contents: vec![ResourceContent {
1710 uri,
1711 mime_type: None,
1712 text: None,
1713 blob: None,
1714 }],
1715 })
1716 });
1717
1718 let vars = template.match_uri("file:///src/lib/utils.rs").unwrap();
1720 assert_eq!(vars.get("path"), Some(&"src/lib/utils.rs".to_string()));
1721 }
1722}