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>
546 where
547 F: Fn() -> Fut + Send + Sync + 'static,
548 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
549 {
550 ResourceBuilderWithHandler {
551 uri: self.uri,
552 name: self.name,
553 title: self.title,
554 description: self.description,
555 mime_type: self.mime_type,
556 icons: self.icons,
557 size: self.size,
558 handler,
559 }
560 }
561
562 pub fn handler_with_context<F, Fut>(self, handler: F) -> ResourceBuilderWithContextHandler<F>
570 where
571 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
572 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
573 {
574 ResourceBuilderWithContextHandler {
575 uri: self.uri,
576 name: self.name,
577 title: self.title,
578 description: self.description,
579 mime_type: self.mime_type,
580 icons: self.icons,
581 size: self.size,
582 handler,
583 }
584 }
585
586 pub fn text(self, content: impl Into<String>) -> Resource {
588 let uri = self.uri.clone();
589 let content = content.into();
590 let mime_type = self.mime_type.clone();
591
592 self.handler(move || {
593 let uri = uri.clone();
594 let content = content.clone();
595 let mime_type = mime_type.clone();
596 async move {
597 Ok(ReadResourceResult {
598 contents: vec![ResourceContent {
599 uri,
600 mime_type,
601 text: Some(content),
602 blob: None,
603 }],
604 })
605 }
606 })
607 .build()
608 }
609
610 pub fn json(mut self, value: serde_json::Value) -> Resource {
612 let uri = self.uri.clone();
613 self.mime_type = Some("application/json".to_string());
614 let text = serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{}".to_string());
615
616 self.handler(move || {
617 let uri = uri.clone();
618 let text = text.clone();
619 async move {
620 Ok(ReadResourceResult {
621 contents: vec![ResourceContent {
622 uri,
623 mime_type: Some("application/json".to_string()),
624 text: Some(text),
625 blob: None,
626 }],
627 })
628 }
629 })
630 .build()
631 }
632}
633
634pub struct ResourceBuilderWithHandler<F> {
639 uri: String,
640 name: Option<String>,
641 title: Option<String>,
642 description: Option<String>,
643 mime_type: Option<String>,
644 icons: Option<Vec<ToolIcon>>,
645 size: Option<u64>,
646 handler: F,
647}
648
649impl<F, Fut> ResourceBuilderWithHandler<F>
650where
651 F: Fn() -> Fut + Send + Sync + 'static,
652 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
653{
654 pub fn build(self) -> Resource {
656 let name = self.name.unwrap_or_else(|| self.uri.clone());
657
658 Resource::from_handler(
659 self.uri,
660 name,
661 self.title,
662 self.description,
663 self.mime_type,
664 self.icons,
665 self.size,
666 FnHandler {
667 handler: self.handler,
668 },
669 )
670 }
671
672 pub fn layer<L>(self, layer: L) -> ResourceBuilderWithLayer<F, L> {
701 ResourceBuilderWithLayer {
702 uri: self.uri,
703 name: self.name,
704 title: self.title,
705 description: self.description,
706 mime_type: self.mime_type,
707 icons: self.icons,
708 size: self.size,
709 handler: self.handler,
710 layer,
711 }
712 }
713}
714
715pub struct ResourceBuilderWithLayer<F, L> {
719 uri: String,
720 name: Option<String>,
721 title: Option<String>,
722 description: Option<String>,
723 mime_type: Option<String>,
724 icons: Option<Vec<ToolIcon>>,
725 size: Option<u64>,
726 handler: F,
727 layer: L,
728}
729
730#[allow(private_bounds)]
733impl<F, Fut, L> ResourceBuilderWithLayer<F, L>
734where
735 F: Fn() -> Fut + Send + Sync + 'static,
736 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
737 L: tower::Layer<ResourceHandlerService<FnHandler<F>>> + Clone + Send + Sync + 'static,
738 L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
739 <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
740 <L::Service as Service<ResourceRequest>>::Future: Send,
741{
742 pub fn build(self) -> Resource {
744 let name = self.name.unwrap_or_else(|| self.uri.clone());
745
746 let handler_service = ResourceHandlerService::new(FnHandler {
747 handler: self.handler,
748 });
749 let layered = self.layer.layer(handler_service);
750 let catch_error = ResourceCatchError::new(layered);
751 let service = BoxCloneService::new(catch_error);
752
753 Resource {
754 uri: self.uri,
755 name,
756 title: self.title,
757 description: self.description,
758 mime_type: self.mime_type,
759 icons: self.icons,
760 size: self.size,
761 service,
762 }
763 }
764
765 pub fn layer<L2>(
770 self,
771 layer: L2,
772 ) -> ResourceBuilderWithLayer<F, tower::layer::util::Stack<L2, L>> {
773 ResourceBuilderWithLayer {
774 uri: self.uri,
775 name: self.name,
776 title: self.title,
777 description: self.description,
778 mime_type: self.mime_type,
779 icons: self.icons,
780 size: self.size,
781 handler: self.handler,
782 layer: tower::layer::util::Stack::new(layer, self.layer),
783 }
784 }
785}
786
787pub struct ResourceBuilderWithContextHandler<F> {
789 uri: String,
790 name: Option<String>,
791 title: Option<String>,
792 description: Option<String>,
793 mime_type: Option<String>,
794 icons: Option<Vec<ToolIcon>>,
795 size: Option<u64>,
796 handler: F,
797}
798
799impl<F, Fut> ResourceBuilderWithContextHandler<F>
800where
801 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
802 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
803{
804 pub fn build(self) -> Resource {
806 let name = self.name.unwrap_or_else(|| self.uri.clone());
807
808 Resource::from_handler(
809 self.uri,
810 name,
811 self.title,
812 self.description,
813 self.mime_type,
814 self.icons,
815 self.size,
816 ContextAwareHandler {
817 handler: self.handler,
818 },
819 )
820 }
821
822 pub fn layer<L>(self, layer: L) -> ResourceBuilderWithContextLayer<F, L> {
826 ResourceBuilderWithContextLayer {
827 uri: self.uri,
828 name: self.name,
829 title: self.title,
830 description: self.description,
831 mime_type: self.mime_type,
832 icons: self.icons,
833 size: self.size,
834 handler: self.handler,
835 layer,
836 }
837 }
838}
839
840pub struct ResourceBuilderWithContextLayer<F, L> {
842 uri: String,
843 name: Option<String>,
844 title: Option<String>,
845 description: Option<String>,
846 mime_type: Option<String>,
847 icons: Option<Vec<ToolIcon>>,
848 size: Option<u64>,
849 handler: F,
850 layer: L,
851}
852
853#[allow(private_bounds)]
855impl<F, Fut, L> ResourceBuilderWithContextLayer<F, L>
856where
857 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
858 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
859 L: tower::Layer<ResourceHandlerService<ContextAwareHandler<F>>> + Clone + Send + Sync + 'static,
860 L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
861 <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
862 <L::Service as Service<ResourceRequest>>::Future: Send,
863{
864 pub fn build(self) -> Resource {
866 let name = self.name.unwrap_or_else(|| self.uri.clone());
867
868 let handler_service = ResourceHandlerService::new(ContextAwareHandler {
869 handler: self.handler,
870 });
871 let layered = self.layer.layer(handler_service);
872 let catch_error = ResourceCatchError::new(layered);
873 let service = BoxCloneService::new(catch_error);
874
875 Resource {
876 uri: self.uri,
877 name,
878 title: self.title,
879 description: self.description,
880 mime_type: self.mime_type,
881 icons: self.icons,
882 size: self.size,
883 service,
884 }
885 }
886
887 pub fn layer<L2>(
889 self,
890 layer: L2,
891 ) -> ResourceBuilderWithContextLayer<F, tower::layer::util::Stack<L2, L>> {
892 ResourceBuilderWithContextLayer {
893 uri: self.uri,
894 name: self.name,
895 title: self.title,
896 description: self.description,
897 mime_type: self.mime_type,
898 icons: self.icons,
899 size: self.size,
900 handler: self.handler,
901 layer: tower::layer::util::Stack::new(layer, self.layer),
902 }
903 }
904}
905
906struct FnHandler<F> {
912 handler: F,
913}
914
915impl<F, Fut> ResourceHandler for FnHandler<F>
916where
917 F: Fn() -> Fut + Send + Sync + 'static,
918 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
919{
920 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
921 Box::pin((self.handler)())
922 }
923}
924
925struct ContextAwareHandler<F> {
927 handler: F,
928}
929
930impl<F, Fut> ResourceHandler for ContextAwareHandler<F>
931where
932 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
933 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
934{
935 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
936 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
937 self.read_with_context(ctx)
938 }
939
940 fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
941 Box::pin((self.handler)(ctx))
942 }
943
944 fn uses_context(&self) -> bool {
945 true
946 }
947}
948
949pub trait McpResource: Send + Sync + 'static {
991 const URI: &'static str;
992 const NAME: &'static str;
993 const DESCRIPTION: Option<&'static str> = None;
994 const MIME_TYPE: Option<&'static str> = None;
995
996 fn read(&self) -> impl Future<Output = Result<ReadResourceResult>> + Send;
997
998 fn into_resource(self) -> Resource
1000 where
1001 Self: Sized,
1002 {
1003 let resource = Arc::new(self);
1004 Resource::from_handler(
1005 Self::URI.to_string(),
1006 Self::NAME.to_string(),
1007 None,
1008 Self::DESCRIPTION.map(|s| s.to_string()),
1009 Self::MIME_TYPE.map(|s| s.to_string()),
1010 None,
1011 None,
1012 McpResourceHandler { resource },
1013 )
1014 }
1015}
1016
1017struct McpResourceHandler<T: McpResource> {
1019 resource: Arc<T>,
1020}
1021
1022impl<T: McpResource> ResourceHandler for McpResourceHandler<T> {
1023 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1024 let resource = self.resource.clone();
1025 Box::pin(async move { resource.read().await })
1026 }
1027}
1028
1029pub trait ResourceTemplateHandler: Send + Sync {
1038 fn read(
1040 &self,
1041 uri: &str,
1042 variables: HashMap<String, String>,
1043 ) -> BoxFuture<'_, Result<ReadResourceResult>>;
1044}
1045
1046pub struct ResourceTemplate {
1074 pub uri_template: String,
1076 pub name: String,
1078 pub title: Option<String>,
1080 pub description: Option<String>,
1082 pub mime_type: Option<String>,
1084 pub icons: Option<Vec<ToolIcon>>,
1086 pattern: regex::Regex,
1088 variables: Vec<String>,
1090 handler: Arc<dyn ResourceTemplateHandler>,
1092}
1093
1094impl Clone for ResourceTemplate {
1095 fn clone(&self) -> Self {
1096 Self {
1097 uri_template: self.uri_template.clone(),
1098 name: self.name.clone(),
1099 title: self.title.clone(),
1100 description: self.description.clone(),
1101 mime_type: self.mime_type.clone(),
1102 icons: self.icons.clone(),
1103 pattern: self.pattern.clone(),
1104 variables: self.variables.clone(),
1105 handler: self.handler.clone(),
1106 }
1107 }
1108}
1109
1110impl std::fmt::Debug for ResourceTemplate {
1111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1112 f.debug_struct("ResourceTemplate")
1113 .field("uri_template", &self.uri_template)
1114 .field("name", &self.name)
1115 .field("title", &self.title)
1116 .field("description", &self.description)
1117 .field("mime_type", &self.mime_type)
1118 .field("icons", &self.icons)
1119 .field("variables", &self.variables)
1120 .finish_non_exhaustive()
1121 }
1122}
1123
1124impl ResourceTemplate {
1125 pub fn builder(uri_template: impl Into<String>) -> ResourceTemplateBuilder {
1127 ResourceTemplateBuilder::new(uri_template)
1128 }
1129
1130 pub fn definition(&self) -> ResourceTemplateDefinition {
1132 ResourceTemplateDefinition {
1133 uri_template: self.uri_template.clone(),
1134 name: self.name.clone(),
1135 title: self.title.clone(),
1136 description: self.description.clone(),
1137 mime_type: self.mime_type.clone(),
1138 icons: self.icons.clone(),
1139 }
1140 }
1141
1142 pub fn match_uri(&self, uri: &str) -> Option<HashMap<String, String>> {
1147 self.pattern.captures(uri).map(|caps| {
1148 self.variables
1149 .iter()
1150 .enumerate()
1151 .filter_map(|(i, name)| {
1152 caps.get(i + 1)
1153 .map(|m| (name.clone(), m.as_str().to_string()))
1154 })
1155 .collect()
1156 })
1157 }
1158
1159 pub fn read(
1166 &self,
1167 uri: &str,
1168 variables: HashMap<String, String>,
1169 ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1170 self.handler.read(uri, variables)
1171 }
1172}
1173
1174pub struct ResourceTemplateBuilder {
1199 uri_template: String,
1200 name: Option<String>,
1201 title: Option<String>,
1202 description: Option<String>,
1203 mime_type: Option<String>,
1204 icons: Option<Vec<ToolIcon>>,
1205}
1206
1207impl ResourceTemplateBuilder {
1208 pub fn new(uri_template: impl Into<String>) -> Self {
1221 Self {
1222 uri_template: uri_template.into(),
1223 name: None,
1224 title: None,
1225 description: None,
1226 mime_type: None,
1227 icons: None,
1228 }
1229 }
1230
1231 pub fn name(mut self, name: impl Into<String>) -> Self {
1233 self.name = Some(name.into());
1234 self
1235 }
1236
1237 pub fn title(mut self, title: impl Into<String>) -> Self {
1239 self.title = Some(title.into());
1240 self
1241 }
1242
1243 pub fn description(mut self, description: impl Into<String>) -> Self {
1245 self.description = Some(description.into());
1246 self
1247 }
1248
1249 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
1251 self.mime_type = Some(mime_type.into());
1252 self
1253 }
1254
1255 pub fn icon(mut self, src: impl Into<String>) -> Self {
1257 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1258 src: src.into(),
1259 mime_type: None,
1260 sizes: None,
1261 });
1262 self
1263 }
1264
1265 pub fn icon_with_meta(
1267 mut self,
1268 src: impl Into<String>,
1269 mime_type: Option<String>,
1270 sizes: Option<Vec<String>>,
1271 ) -> Self {
1272 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1273 src: src.into(),
1274 mime_type,
1275 sizes,
1276 });
1277 self
1278 }
1279
1280 pub fn handler<F, Fut>(self, handler: F) -> ResourceTemplate
1286 where
1287 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1288 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1289 {
1290 let (pattern, variables) = compile_uri_template(&self.uri_template);
1291 let name = self.name.unwrap_or_else(|| self.uri_template.clone());
1292
1293 ResourceTemplate {
1294 uri_template: self.uri_template,
1295 name,
1296 title: self.title,
1297 description: self.description,
1298 mime_type: self.mime_type,
1299 icons: self.icons,
1300 pattern,
1301 variables,
1302 handler: Arc::new(FnTemplateHandler { handler }),
1303 }
1304 }
1305}
1306
1307struct FnTemplateHandler<F> {
1309 handler: F,
1310}
1311
1312impl<F, Fut> ResourceTemplateHandler for FnTemplateHandler<F>
1313where
1314 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1315 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1316{
1317 fn read(
1318 &self,
1319 uri: &str,
1320 variables: HashMap<String, String>,
1321 ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1322 let uri = uri.to_string();
1323 Box::pin((self.handler)(uri, variables))
1324 }
1325}
1326
1327fn compile_uri_template(template: &str) -> (regex::Regex, Vec<String>) {
1335 let mut pattern = String::from("^");
1336 let mut variables = Vec::new();
1337
1338 let mut chars = template.chars().peekable();
1339 while let Some(c) = chars.next() {
1340 if c == '{' {
1341 let is_reserved = chars.peek() == Some(&'+');
1343 if is_reserved {
1344 chars.next();
1345 }
1346
1347 let var_name: String = chars.by_ref().take_while(|&c| c != '}').collect();
1349 variables.push(var_name);
1350
1351 if is_reserved {
1353 pattern.push_str("(.+)");
1355 } else {
1356 pattern.push_str("([^/]+)");
1358 }
1359 } else {
1360 match c {
1362 '.' | '+' | '*' | '?' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|'
1363 | '\\' => {
1364 pattern.push('\\');
1365 pattern.push(c);
1366 }
1367 _ => pattern.push(c),
1368 }
1369 }
1370 }
1371
1372 pattern.push('$');
1373
1374 let regex = regex::Regex::new(&pattern)
1376 .unwrap_or_else(|e| panic!("Invalid URI template '{}': {}", template, e));
1377
1378 (regex, variables)
1379}
1380
1381#[cfg(test)]
1382mod tests {
1383 use super::*;
1384 use std::time::Duration;
1385 use tower::timeout::TimeoutLayer;
1386
1387 #[tokio::test]
1388 async fn test_builder_resource() {
1389 let resource = ResourceBuilder::new("file:///test.txt")
1390 .name("Test File")
1391 .description("A test file")
1392 .text("Hello, World!");
1393
1394 assert_eq!(resource.uri, "file:///test.txt");
1395 assert_eq!(resource.name, "Test File");
1396 assert_eq!(resource.description.as_deref(), Some("A test file"));
1397
1398 let result = resource.read().await;
1399 assert_eq!(result.contents.len(), 1);
1400 assert_eq!(result.contents[0].text.as_deref(), Some("Hello, World!"));
1401 }
1402
1403 #[tokio::test]
1404 async fn test_json_resource() {
1405 let resource = ResourceBuilder::new("file:///config.json")
1406 .name("Config")
1407 .json(serde_json::json!({"key": "value"}));
1408
1409 assert_eq!(resource.mime_type.as_deref(), Some("application/json"));
1410
1411 let result = resource.read().await;
1412 assert!(result.contents[0].text.as_ref().unwrap().contains("key"));
1413 }
1414
1415 #[tokio::test]
1416 async fn test_handler_resource() {
1417 let resource = ResourceBuilder::new("memory://counter")
1418 .name("Counter")
1419 .handler(|| async {
1420 Ok(ReadResourceResult {
1421 contents: vec![ResourceContent {
1422 uri: "memory://counter".to_string(),
1423 mime_type: Some("text/plain".to_string()),
1424 text: Some("42".to_string()),
1425 blob: None,
1426 }],
1427 })
1428 })
1429 .build();
1430
1431 let result = resource.read().await;
1432 assert_eq!(result.contents[0].text.as_deref(), Some("42"));
1433 }
1434
1435 #[tokio::test]
1436 async fn test_handler_resource_with_layer() {
1437 let resource = ResourceBuilder::new("file:///with-timeout.txt")
1438 .name("Resource with Timeout")
1439 .handler(|| async {
1440 Ok(ReadResourceResult {
1441 contents: vec![ResourceContent {
1442 uri: "file:///with-timeout.txt".to_string(),
1443 mime_type: Some("text/plain".to_string()),
1444 text: Some("content".to_string()),
1445 blob: None,
1446 }],
1447 })
1448 })
1449 .layer(TimeoutLayer::new(Duration::from_secs(30)))
1450 .build();
1451
1452 let result = resource.read().await;
1453 assert_eq!(result.contents[0].text.as_deref(), Some("content"));
1454 }
1455
1456 #[tokio::test]
1457 async fn test_handler_resource_with_timeout_error() {
1458 let resource = ResourceBuilder::new("file:///slow.txt")
1459 .name("Slow Resource")
1460 .handler(|| async {
1461 tokio::time::sleep(Duration::from_secs(1)).await;
1463 Ok(ReadResourceResult {
1464 contents: vec![ResourceContent {
1465 uri: "file:///slow.txt".to_string(),
1466 mime_type: Some("text/plain".to_string()),
1467 text: Some("content".to_string()),
1468 blob: None,
1469 }],
1470 })
1471 })
1472 .layer(TimeoutLayer::new(Duration::from_millis(50)))
1473 .build();
1474
1475 let result = resource.read().await;
1476 assert!(
1478 result.contents[0]
1479 .text
1480 .as_ref()
1481 .unwrap()
1482 .contains("Error reading resource")
1483 );
1484 }
1485
1486 #[tokio::test]
1487 async fn test_context_aware_handler() {
1488 let resource = ResourceBuilder::new("file:///ctx.txt")
1489 .name("Context Resource")
1490 .handler_with_context(|_ctx: RequestContext| async {
1491 Ok(ReadResourceResult {
1492 contents: vec![ResourceContent {
1493 uri: "file:///ctx.txt".to_string(),
1494 mime_type: Some("text/plain".to_string()),
1495 text: Some("context aware".to_string()),
1496 blob: None,
1497 }],
1498 })
1499 })
1500 .build();
1501
1502 let result = resource.read().await;
1503 assert_eq!(result.contents[0].text.as_deref(), Some("context aware"));
1504 }
1505
1506 #[tokio::test]
1507 async fn test_context_aware_handler_with_layer() {
1508 let resource = ResourceBuilder::new("file:///ctx-layer.txt")
1509 .name("Context Resource with Layer")
1510 .handler_with_context(|_ctx: RequestContext| async {
1511 Ok(ReadResourceResult {
1512 contents: vec![ResourceContent {
1513 uri: "file:///ctx-layer.txt".to_string(),
1514 mime_type: Some("text/plain".to_string()),
1515 text: Some("context with layer".to_string()),
1516 blob: None,
1517 }],
1518 })
1519 })
1520 .layer(TimeoutLayer::new(Duration::from_secs(30)))
1521 .build();
1522
1523 let result = resource.read().await;
1524 assert_eq!(
1525 result.contents[0].text.as_deref(),
1526 Some("context with layer")
1527 );
1528 }
1529
1530 #[tokio::test]
1531 async fn test_trait_resource() {
1532 struct TestResource;
1533
1534 impl McpResource for TestResource {
1535 const URI: &'static str = "test://resource";
1536 const NAME: &'static str = "Test";
1537 const DESCRIPTION: Option<&'static str> = Some("A test resource");
1538 const MIME_TYPE: Option<&'static str> = Some("text/plain");
1539
1540 async fn read(&self) -> Result<ReadResourceResult> {
1541 Ok(ReadResourceResult {
1542 contents: vec![ResourceContent {
1543 uri: Self::URI.to_string(),
1544 mime_type: Self::MIME_TYPE.map(|s| s.to_string()),
1545 text: Some("test content".to_string()),
1546 blob: None,
1547 }],
1548 })
1549 }
1550 }
1551
1552 let resource = TestResource.into_resource();
1553 assert_eq!(resource.uri, "test://resource");
1554 assert_eq!(resource.name, "Test");
1555
1556 let result = resource.read().await;
1557 assert_eq!(result.contents[0].text.as_deref(), Some("test content"));
1558 }
1559
1560 #[test]
1561 fn test_resource_definition() {
1562 let resource = ResourceBuilder::new("file:///test.txt")
1563 .name("Test")
1564 .description("Description")
1565 .mime_type("text/plain")
1566 .text("content");
1567
1568 let def = resource.definition();
1569 assert_eq!(def.uri, "file:///test.txt");
1570 assert_eq!(def.name, "Test");
1571 assert_eq!(def.description.as_deref(), Some("Description"));
1572 assert_eq!(def.mime_type.as_deref(), Some("text/plain"));
1573 }
1574
1575 #[test]
1576 fn test_resource_request_new() {
1577 let ctx = RequestContext::new(crate::protocol::RequestId::Number(1));
1578 let req = ResourceRequest::new(ctx, "file:///test.txt".to_string());
1579 assert_eq!(req.uri, "file:///test.txt");
1580 }
1581
1582 #[test]
1583 fn test_resource_catch_error_clone() {
1584 let handler = FnHandler {
1585 handler: || async { Ok::<_, Error>(ReadResourceResult { contents: vec![] }) },
1586 };
1587 let service = ResourceHandlerService::new(handler);
1588 let catch_error = ResourceCatchError::new(service);
1589 let _clone = catch_error.clone();
1590 }
1591
1592 #[test]
1593 fn test_resource_catch_error_debug() {
1594 let handler = FnHandler {
1595 handler: || async { Ok::<_, Error>(ReadResourceResult { contents: vec![] }) },
1596 };
1597 let service = ResourceHandlerService::new(handler);
1598 let catch_error = ResourceCatchError::new(service);
1599 let debug = format!("{:?}", catch_error);
1600 assert!(debug.contains("ResourceCatchError"));
1601 }
1602
1603 #[test]
1608 fn test_compile_uri_template_simple() {
1609 let (regex, vars) = compile_uri_template("file:///{path}");
1610 assert_eq!(vars, vec!["path"]);
1611 assert!(regex.is_match("file:///README.md"));
1612 assert!(!regex.is_match("file:///foo/bar")); }
1614
1615 #[test]
1616 fn test_compile_uri_template_multiple_vars() {
1617 let (regex, vars) = compile_uri_template("api://v1/{resource}/{id}");
1618 assert_eq!(vars, vec!["resource", "id"]);
1619 assert!(regex.is_match("api://v1/users/123"));
1620 assert!(regex.is_match("api://v1/posts/abc"));
1621 assert!(!regex.is_match("api://v1/users")); }
1623
1624 #[test]
1625 fn test_compile_uri_template_reserved_expansion() {
1626 let (regex, vars) = compile_uri_template("file:///{+path}");
1627 assert_eq!(vars, vec!["path"]);
1628 assert!(regex.is_match("file:///README.md"));
1629 assert!(regex.is_match("file:///foo/bar/baz.txt")); }
1631
1632 #[test]
1633 fn test_compile_uri_template_special_chars() {
1634 let (regex, vars) = compile_uri_template("http://example.com/api?query={q}");
1635 assert_eq!(vars, vec!["q"]);
1636 assert!(regex.is_match("http://example.com/api?query=hello"));
1637 }
1638
1639 #[test]
1640 fn test_resource_template_match_uri() {
1641 let template = ResourceTemplateBuilder::new("db://users/{id}")
1642 .name("User Records")
1643 .handler(|uri: String, vars: HashMap<String, String>| async move {
1644 Ok(ReadResourceResult {
1645 contents: vec![ResourceContent {
1646 uri,
1647 mime_type: None,
1648 text: Some(format!("User {}", vars.get("id").unwrap())),
1649 blob: None,
1650 }],
1651 })
1652 });
1653
1654 let vars = template.match_uri("db://users/123").unwrap();
1656 assert_eq!(vars.get("id"), Some(&"123".to_string()));
1657
1658 assert!(template.match_uri("db://posts/123").is_none());
1660 assert!(template.match_uri("db://users").is_none());
1661 }
1662
1663 #[test]
1664 fn test_resource_template_match_multiple_vars() {
1665 let template = ResourceTemplateBuilder::new("api://{version}/{resource}/{id}")
1666 .name("API Resources")
1667 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1668 Ok(ReadResourceResult {
1669 contents: vec![ResourceContent {
1670 uri,
1671 mime_type: None,
1672 text: None,
1673 blob: None,
1674 }],
1675 })
1676 });
1677
1678 let vars = template.match_uri("api://v2/users/abc-123").unwrap();
1679 assert_eq!(vars.get("version"), Some(&"v2".to_string()));
1680 assert_eq!(vars.get("resource"), Some(&"users".to_string()));
1681 assert_eq!(vars.get("id"), Some(&"abc-123".to_string()));
1682 }
1683
1684 #[tokio::test]
1685 async fn test_resource_template_read() {
1686 let template = ResourceTemplateBuilder::new("file:///{path}")
1687 .name("Files")
1688 .mime_type("text/plain")
1689 .handler(|uri: String, vars: HashMap<String, String>| async move {
1690 let path = vars.get("path").unwrap().clone();
1691 Ok(ReadResourceResult {
1692 contents: vec![ResourceContent {
1693 uri,
1694 mime_type: Some("text/plain".to_string()),
1695 text: Some(format!("Contents of {}", path)),
1696 blob: None,
1697 }],
1698 })
1699 });
1700
1701 let vars = template.match_uri("file:///README.md").unwrap();
1702 let result = template.read("file:///README.md", vars).await.unwrap();
1703
1704 assert_eq!(result.contents.len(), 1);
1705 assert_eq!(result.contents[0].uri, "file:///README.md");
1706 assert_eq!(
1707 result.contents[0].text.as_deref(),
1708 Some("Contents of README.md")
1709 );
1710 }
1711
1712 #[test]
1713 fn test_resource_template_definition() {
1714 let template = ResourceTemplateBuilder::new("db://records/{id}")
1715 .name("Database Records")
1716 .description("Access database records by ID")
1717 .mime_type("application/json")
1718 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1719 Ok(ReadResourceResult {
1720 contents: vec![ResourceContent {
1721 uri,
1722 mime_type: None,
1723 text: None,
1724 blob: None,
1725 }],
1726 })
1727 });
1728
1729 let def = template.definition();
1730 assert_eq!(def.uri_template, "db://records/{id}");
1731 assert_eq!(def.name, "Database Records");
1732 assert_eq!(
1733 def.description.as_deref(),
1734 Some("Access database records by ID")
1735 );
1736 assert_eq!(def.mime_type.as_deref(), Some("application/json"));
1737 }
1738
1739 #[test]
1740 fn test_resource_template_reserved_path() {
1741 let template = ResourceTemplateBuilder::new("file:///{+path}")
1742 .name("Files with subpaths")
1743 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1744 Ok(ReadResourceResult {
1745 contents: vec![ResourceContent {
1746 uri,
1747 mime_type: None,
1748 text: None,
1749 blob: None,
1750 }],
1751 })
1752 });
1753
1754 let vars = template.match_uri("file:///src/lib/utils.rs").unwrap();
1756 assert_eq!(vars.get("path"), Some(&"src/lib/utils.rs".to_string()));
1757 }
1758}