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_secs(1)).await;
1428 Ok(ReadResourceResult {
1429 contents: vec![ResourceContent {
1430 uri: "file:///slow.txt".to_string(),
1431 mime_type: Some("text/plain".to_string()),
1432 text: Some("content".to_string()),
1433 blob: None,
1434 }],
1435 })
1436 })
1437 .layer(TimeoutLayer::new(Duration::from_millis(50)))
1438 .build();
1439
1440 let result = resource.read().await;
1441 assert!(
1443 result.contents[0]
1444 .text
1445 .as_ref()
1446 .unwrap()
1447 .contains("Error reading resource")
1448 );
1449 }
1450
1451 #[tokio::test]
1452 async fn test_context_aware_handler() {
1453 let resource = ResourceBuilder::new("file:///ctx.txt")
1454 .name("Context Resource")
1455 .handler_with_context(|_ctx: RequestContext| async {
1456 Ok(ReadResourceResult {
1457 contents: vec![ResourceContent {
1458 uri: "file:///ctx.txt".to_string(),
1459 mime_type: Some("text/plain".to_string()),
1460 text: Some("context aware".to_string()),
1461 blob: None,
1462 }],
1463 })
1464 })
1465 .build();
1466
1467 let result = resource.read().await;
1468 assert_eq!(result.contents[0].text.as_deref(), Some("context aware"));
1469 }
1470
1471 #[tokio::test]
1472 async fn test_context_aware_handler_with_layer() {
1473 let resource = ResourceBuilder::new("file:///ctx-layer.txt")
1474 .name("Context Resource with Layer")
1475 .handler_with_context(|_ctx: RequestContext| async {
1476 Ok(ReadResourceResult {
1477 contents: vec![ResourceContent {
1478 uri: "file:///ctx-layer.txt".to_string(),
1479 mime_type: Some("text/plain".to_string()),
1480 text: Some("context with layer".to_string()),
1481 blob: None,
1482 }],
1483 })
1484 })
1485 .layer(TimeoutLayer::new(Duration::from_secs(30)))
1486 .build();
1487
1488 let result = resource.read().await;
1489 assert_eq!(
1490 result.contents[0].text.as_deref(),
1491 Some("context with layer")
1492 );
1493 }
1494
1495 #[tokio::test]
1496 async fn test_trait_resource() {
1497 struct TestResource;
1498
1499 impl McpResource for TestResource {
1500 const URI: &'static str = "test://resource";
1501 const NAME: &'static str = "Test";
1502 const DESCRIPTION: Option<&'static str> = Some("A test resource");
1503 const MIME_TYPE: Option<&'static str> = Some("text/plain");
1504
1505 async fn read(&self) -> Result<ReadResourceResult> {
1506 Ok(ReadResourceResult {
1507 contents: vec![ResourceContent {
1508 uri: Self::URI.to_string(),
1509 mime_type: Self::MIME_TYPE.map(|s| s.to_string()),
1510 text: Some("test content".to_string()),
1511 blob: None,
1512 }],
1513 })
1514 }
1515 }
1516
1517 let resource = TestResource.into_resource();
1518 assert_eq!(resource.uri, "test://resource");
1519 assert_eq!(resource.name, "Test");
1520
1521 let result = resource.read().await;
1522 assert_eq!(result.contents[0].text.as_deref(), Some("test content"));
1523 }
1524
1525 #[test]
1526 fn test_resource_definition() {
1527 let resource = ResourceBuilder::new("file:///test.txt")
1528 .name("Test")
1529 .description("Description")
1530 .mime_type("text/plain")
1531 .text("content");
1532
1533 let def = resource.definition();
1534 assert_eq!(def.uri, "file:///test.txt");
1535 assert_eq!(def.name, "Test");
1536 assert_eq!(def.description.as_deref(), Some("Description"));
1537 assert_eq!(def.mime_type.as_deref(), Some("text/plain"));
1538 }
1539
1540 #[test]
1541 fn test_resource_request_new() {
1542 let ctx = RequestContext::new(crate::protocol::RequestId::Number(1));
1543 let req = ResourceRequest::new(ctx, "file:///test.txt".to_string());
1544 assert_eq!(req.uri, "file:///test.txt");
1545 }
1546
1547 #[test]
1548 fn test_resource_catch_error_clone() {
1549 let handler = FnHandler {
1550 handler: || async { Ok::<_, Error>(ReadResourceResult { contents: vec![] }) },
1551 };
1552 let service = ResourceHandlerService::new(handler);
1553 let catch_error = ResourceCatchError::new(service);
1554 let _clone = catch_error.clone();
1555 }
1556
1557 #[test]
1558 fn test_resource_catch_error_debug() {
1559 let handler = FnHandler {
1560 handler: || async { Ok::<_, Error>(ReadResourceResult { contents: vec![] }) },
1561 };
1562 let service = ResourceHandlerService::new(handler);
1563 let catch_error = ResourceCatchError::new(service);
1564 let debug = format!("{:?}", catch_error);
1565 assert!(debug.contains("ResourceCatchError"));
1566 }
1567
1568 #[test]
1573 fn test_compile_uri_template_simple() {
1574 let (regex, vars) = compile_uri_template("file:///{path}");
1575 assert_eq!(vars, vec!["path"]);
1576 assert!(regex.is_match("file:///README.md"));
1577 assert!(!regex.is_match("file:///foo/bar")); }
1579
1580 #[test]
1581 fn test_compile_uri_template_multiple_vars() {
1582 let (regex, vars) = compile_uri_template("api://v1/{resource}/{id}");
1583 assert_eq!(vars, vec!["resource", "id"]);
1584 assert!(regex.is_match("api://v1/users/123"));
1585 assert!(regex.is_match("api://v1/posts/abc"));
1586 assert!(!regex.is_match("api://v1/users")); }
1588
1589 #[test]
1590 fn test_compile_uri_template_reserved_expansion() {
1591 let (regex, vars) = compile_uri_template("file:///{+path}");
1592 assert_eq!(vars, vec!["path"]);
1593 assert!(regex.is_match("file:///README.md"));
1594 assert!(regex.is_match("file:///foo/bar/baz.txt")); }
1596
1597 #[test]
1598 fn test_compile_uri_template_special_chars() {
1599 let (regex, vars) = compile_uri_template("http://example.com/api?query={q}");
1600 assert_eq!(vars, vec!["q"]);
1601 assert!(regex.is_match("http://example.com/api?query=hello"));
1602 }
1603
1604 #[test]
1605 fn test_resource_template_match_uri() {
1606 let template = ResourceTemplateBuilder::new("db://users/{id}")
1607 .name("User Records")
1608 .handler(|uri: String, vars: HashMap<String, String>| async move {
1609 Ok(ReadResourceResult {
1610 contents: vec![ResourceContent {
1611 uri,
1612 mime_type: None,
1613 text: Some(format!("User {}", vars.get("id").unwrap())),
1614 blob: None,
1615 }],
1616 })
1617 });
1618
1619 let vars = template.match_uri("db://users/123").unwrap();
1621 assert_eq!(vars.get("id"), Some(&"123".to_string()));
1622
1623 assert!(template.match_uri("db://posts/123").is_none());
1625 assert!(template.match_uri("db://users").is_none());
1626 }
1627
1628 #[test]
1629 fn test_resource_template_match_multiple_vars() {
1630 let template = ResourceTemplateBuilder::new("api://{version}/{resource}/{id}")
1631 .name("API Resources")
1632 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1633 Ok(ReadResourceResult {
1634 contents: vec![ResourceContent {
1635 uri,
1636 mime_type: None,
1637 text: None,
1638 blob: None,
1639 }],
1640 })
1641 });
1642
1643 let vars = template.match_uri("api://v2/users/abc-123").unwrap();
1644 assert_eq!(vars.get("version"), Some(&"v2".to_string()));
1645 assert_eq!(vars.get("resource"), Some(&"users".to_string()));
1646 assert_eq!(vars.get("id"), Some(&"abc-123".to_string()));
1647 }
1648
1649 #[tokio::test]
1650 async fn test_resource_template_read() {
1651 let template = ResourceTemplateBuilder::new("file:///{path}")
1652 .name("Files")
1653 .mime_type("text/plain")
1654 .handler(|uri: String, vars: HashMap<String, String>| async move {
1655 let path = vars.get("path").unwrap().clone();
1656 Ok(ReadResourceResult {
1657 contents: vec![ResourceContent {
1658 uri,
1659 mime_type: Some("text/plain".to_string()),
1660 text: Some(format!("Contents of {}", path)),
1661 blob: None,
1662 }],
1663 })
1664 });
1665
1666 let vars = template.match_uri("file:///README.md").unwrap();
1667 let result = template.read("file:///README.md", vars).await.unwrap();
1668
1669 assert_eq!(result.contents.len(), 1);
1670 assert_eq!(result.contents[0].uri, "file:///README.md");
1671 assert_eq!(
1672 result.contents[0].text.as_deref(),
1673 Some("Contents of README.md")
1674 );
1675 }
1676
1677 #[test]
1678 fn test_resource_template_definition() {
1679 let template = ResourceTemplateBuilder::new("db://records/{id}")
1680 .name("Database Records")
1681 .description("Access database records by ID")
1682 .mime_type("application/json")
1683 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1684 Ok(ReadResourceResult {
1685 contents: vec![ResourceContent {
1686 uri,
1687 mime_type: None,
1688 text: None,
1689 blob: None,
1690 }],
1691 })
1692 });
1693
1694 let def = template.definition();
1695 assert_eq!(def.uri_template, "db://records/{id}");
1696 assert_eq!(def.name, "Database Records");
1697 assert_eq!(
1698 def.description.as_deref(),
1699 Some("Access database records by ID")
1700 );
1701 assert_eq!(def.mime_type.as_deref(), Some("application/json"));
1702 }
1703
1704 #[test]
1705 fn test_resource_template_reserved_path() {
1706 let template = ResourceTemplateBuilder::new("file:///{+path}")
1707 .name("Files with subpaths")
1708 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1709 Ok(ReadResourceResult {
1710 contents: vec![ResourceContent {
1711 uri,
1712 mime_type: None,
1713 text: None,
1714 blob: None,
1715 }],
1716 })
1717 });
1718
1719 let vars = template.match_uri("file:///src/lib/utils.rs").unwrap();
1721 assert_eq!(vars.get("path"), Some(&"src/lib/utils.rs".to_string()));
1722 }
1723}