1use std::collections::HashMap;
70use std::convert::Infallible;
71use std::fmt;
72use std::future::Future;
73use std::pin::Pin;
74use std::sync::Arc;
75use std::task::{Context, Poll};
76
77use tower::util::BoxCloneService;
78use tower_service::Service;
79
80use crate::context::RequestContext;
81use crate::error::{Error, Result};
82use crate::protocol::{
83 ContentAnnotations, ReadResourceResult, ResourceContent, ResourceDefinition,
84 ResourceTemplateDefinition, ToolIcon,
85};
86
87#[derive(Debug, Clone)]
96pub struct ResourceRequest {
97 pub ctx: RequestContext,
99 pub uri: String,
101}
102
103impl ResourceRequest {
104 pub fn new(ctx: RequestContext, uri: String) -> Self {
106 Self { ctx, uri }
107 }
108}
109
110pub type BoxResourceService = BoxCloneService<ResourceRequest, ReadResourceResult, Infallible>;
115
116pub struct ResourceCatchError<S> {
122 inner: S,
123}
124
125impl<S> ResourceCatchError<S> {
126 pub fn new(inner: S) -> Self {
128 Self { inner }
129 }
130}
131
132impl<S: Clone> Clone for ResourceCatchError<S> {
133 fn clone(&self) -> Self {
134 Self {
135 inner: self.inner.clone(),
136 }
137 }
138}
139
140impl<S: fmt::Debug> fmt::Debug for ResourceCatchError<S> {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 f.debug_struct("ResourceCatchError")
143 .field("inner", &self.inner)
144 .finish()
145 }
146}
147
148impl<S> Service<ResourceRequest> for ResourceCatchError<S>
149where
150 S: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
151 S::Error: fmt::Display + Send,
152 S::Future: Send,
153{
154 type Response = ReadResourceResult;
155 type Error = Infallible;
156 type Future =
157 Pin<Box<dyn Future<Output = std::result::Result<ReadResourceResult, Infallible>> + Send>>;
158
159 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
160 match self.inner.poll_ready(cx) {
162 Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
163 Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
164 Poll::Pending => Poll::Pending,
165 }
166 }
167
168 fn call(&mut self, req: ResourceRequest) -> Self::Future {
169 let uri = req.uri.clone();
170 let fut = self.inner.call(req);
171
172 Box::pin(async move {
173 match fut.await {
174 Ok(result) => Ok(result),
175 Err(err) => {
176 Ok(ReadResourceResult {
178 contents: vec![ResourceContent {
179 uri,
180 mime_type: Some("text/plain".to_string()),
181 text: Some(format!("Error reading resource: {}", err)),
182 blob: None,
183 meta: None,
184 }],
185 meta: None,
186 })
187 }
188 }
189 })
190 }
191}
192
193pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
195
196pub trait ResourceHandler: Send + Sync {
198 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>>;
200
201 fn read_with_context(&self, _ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
206 self.read()
207 }
208
209 fn uses_context(&self) -> bool {
211 false
212 }
213}
214
215struct ResourceHandlerService<H> {
220 handler: Arc<H>,
221}
222
223impl<H> ResourceHandlerService<H> {
224 fn new(handler: H) -> Self {
225 Self {
226 handler: Arc::new(handler),
227 }
228 }
229}
230
231impl<H> Clone for ResourceHandlerService<H> {
232 fn clone(&self) -> Self {
233 Self {
234 handler: self.handler.clone(),
235 }
236 }
237}
238
239impl<H> fmt::Debug for ResourceHandlerService<H> {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 f.debug_struct("ResourceHandlerService")
242 .finish_non_exhaustive()
243 }
244}
245
246impl<H> Service<ResourceRequest> for ResourceHandlerService<H>
247where
248 H: ResourceHandler + 'static,
249{
250 type Response = ReadResourceResult;
251 type Error = Error;
252 type Future =
253 Pin<Box<dyn Future<Output = std::result::Result<ReadResourceResult, Error>> + Send>>;
254
255 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
256 Poll::Ready(Ok(()))
257 }
258
259 fn call(&mut self, req: ResourceRequest) -> Self::Future {
260 let handler = self.handler.clone();
261 Box::pin(async move { handler.read_with_context(req.ctx).await })
262 }
263}
264
265pub struct Resource {
272 pub uri: String,
274 pub name: String,
276 pub title: Option<String>,
278 pub description: Option<String>,
280 pub mime_type: Option<String>,
282 pub icons: Option<Vec<ToolIcon>>,
284 pub size: Option<u64>,
286 pub annotations: Option<ContentAnnotations>,
288 service: BoxResourceService,
290}
291
292impl Clone for Resource {
293 fn clone(&self) -> Self {
294 Self {
295 uri: self.uri.clone(),
296 name: self.name.clone(),
297 title: self.title.clone(),
298 description: self.description.clone(),
299 mime_type: self.mime_type.clone(),
300 icons: self.icons.clone(),
301 size: self.size,
302 annotations: self.annotations.clone(),
303 service: self.service.clone(),
304 }
305 }
306}
307
308impl std::fmt::Debug for Resource {
309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310 f.debug_struct("Resource")
311 .field("uri", &self.uri)
312 .field("name", &self.name)
313 .field("title", &self.title)
314 .field("description", &self.description)
315 .field("mime_type", &self.mime_type)
316 .field("icons", &self.icons)
317 .field("size", &self.size)
318 .field("annotations", &self.annotations)
319 .finish_non_exhaustive()
320 }
321}
322
323unsafe impl Send for Resource {}
326unsafe impl Sync for Resource {}
327
328impl Resource {
329 pub fn builder(uri: impl Into<String>) -> ResourceBuilder {
331 ResourceBuilder::new(uri)
332 }
333
334 pub fn definition(&self) -> ResourceDefinition {
336 ResourceDefinition {
337 uri: self.uri.clone(),
338 name: self.name.clone(),
339 title: self.title.clone(),
340 description: self.description.clone(),
341 mime_type: self.mime_type.clone(),
342 icons: self.icons.clone(),
343 size: self.size,
344 annotations: self.annotations.clone(),
345 meta: None,
346 }
347 }
348
349 pub fn read(&self) -> BoxFuture<'static, ReadResourceResult> {
354 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
355 self.read_with_context(ctx)
356 }
357
358 pub fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'static, ReadResourceResult> {
369 use tower::ServiceExt;
370 let service = self.service.clone();
371 let uri = self.uri.clone();
372 Box::pin(async move {
373 service
376 .oneshot(ResourceRequest::new(ctx, uri))
377 .await
378 .unwrap()
379 })
380 }
381
382 #[allow(clippy::too_many_arguments)]
384 fn from_handler<H: ResourceHandler + 'static>(
385 uri: String,
386 name: String,
387 title: Option<String>,
388 description: Option<String>,
389 mime_type: Option<String>,
390 icons: Option<Vec<ToolIcon>>,
391 size: Option<u64>,
392 annotations: Option<ContentAnnotations>,
393 handler: H,
394 ) -> Self {
395 let handler_service = ResourceHandlerService::new(handler);
396 let catch_error = ResourceCatchError::new(handler_service);
397 let service = BoxCloneService::new(catch_error);
398
399 Self {
400 uri,
401 name,
402 title,
403 description,
404 mime_type,
405 icons,
406 size,
407 annotations,
408 service,
409 }
410 }
411}
412
413pub struct ResourceBuilder {
446 uri: String,
447 name: Option<String>,
448 title: Option<String>,
449 description: Option<String>,
450 mime_type: Option<String>,
451 icons: Option<Vec<ToolIcon>>,
452 size: Option<u64>,
453 annotations: Option<ContentAnnotations>,
454}
455
456impl ResourceBuilder {
457 pub fn new(uri: impl Into<String>) -> Self {
458 Self {
459 uri: uri.into(),
460 name: None,
461 title: None,
462 description: None,
463 mime_type: None,
464 icons: None,
465 size: None,
466 annotations: None,
467 }
468 }
469
470 pub fn name(mut self, name: impl Into<String>) -> Self {
472 self.name = Some(name.into());
473 self
474 }
475
476 pub fn title(mut self, title: impl Into<String>) -> Self {
478 self.title = Some(title.into());
479 self
480 }
481
482 pub fn description(mut self, description: impl Into<String>) -> Self {
484 self.description = Some(description.into());
485 self
486 }
487
488 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
490 self.mime_type = Some(mime_type.into());
491 self
492 }
493
494 pub fn icon(mut self, src: impl Into<String>) -> Self {
496 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
497 src: src.into(),
498 mime_type: None,
499 sizes: None,
500 theme: None,
501 });
502 self
503 }
504
505 pub fn icon_with_meta(
507 mut self,
508 src: impl Into<String>,
509 mime_type: Option<String>,
510 sizes: Option<Vec<String>>,
511 ) -> Self {
512 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
513 src: src.into(),
514 mime_type,
515 sizes,
516 theme: None,
517 });
518 self
519 }
520
521 pub fn size(mut self, size: u64) -> Self {
523 self.size = Some(size);
524 self
525 }
526
527 pub fn annotations(mut self, annotations: ContentAnnotations) -> Self {
529 self.annotations = Some(annotations);
530 self
531 }
532
533 pub fn handler<F, Fut>(self, handler: F) -> ResourceBuilderWithHandler<F>
575 where
576 F: Fn() -> Fut + Send + Sync + 'static,
577 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
578 {
579 ResourceBuilderWithHandler {
580 uri: self.uri,
581 name: self.name,
582 title: self.title,
583 description: self.description,
584 mime_type: self.mime_type,
585 icons: self.icons,
586 size: self.size,
587 annotations: self.annotations,
588 handler,
589 }
590 }
591
592 pub fn handler_with_context<F, Fut>(self, handler: F) -> ResourceBuilderWithContextHandler<F>
600 where
601 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
602 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
603 {
604 ResourceBuilderWithContextHandler {
605 uri: self.uri,
606 name: self.name,
607 title: self.title,
608 description: self.description,
609 mime_type: self.mime_type,
610 icons: self.icons,
611 size: self.size,
612 annotations: self.annotations,
613 handler,
614 }
615 }
616
617 pub fn text(self, content: impl Into<String>) -> Resource {
619 let uri = self.uri.clone();
620 let content = content.into();
621 let mime_type = self.mime_type.clone();
622
623 self.handler(move || {
624 let uri = uri.clone();
625 let content = content.clone();
626 let mime_type = mime_type.clone();
627 async move {
628 Ok(ReadResourceResult {
629 contents: vec![ResourceContent {
630 uri,
631 mime_type,
632 text: Some(content),
633 blob: None,
634 meta: None,
635 }],
636 meta: None,
637 })
638 }
639 })
640 .build()
641 }
642
643 pub fn json(mut self, value: serde_json::Value) -> Resource {
645 let uri = self.uri.clone();
646 self.mime_type = Some("application/json".to_string());
647 let text = serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{}".to_string());
648
649 self.handler(move || {
650 let uri = uri.clone();
651 let text = text.clone();
652 async move {
653 Ok(ReadResourceResult {
654 contents: vec![ResourceContent {
655 uri,
656 mime_type: Some("application/json".to_string()),
657 text: Some(text),
658 blob: None,
659 meta: None,
660 }],
661 meta: None,
662 })
663 }
664 })
665 .build()
666 }
667}
668
669pub struct ResourceBuilderWithHandler<F> {
674 uri: String,
675 name: Option<String>,
676 title: Option<String>,
677 description: Option<String>,
678 mime_type: Option<String>,
679 icons: Option<Vec<ToolIcon>>,
680 size: Option<u64>,
681 annotations: Option<ContentAnnotations>,
682 handler: F,
683}
684
685impl<F, Fut> ResourceBuilderWithHandler<F>
686where
687 F: Fn() -> Fut + Send + Sync + 'static,
688 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
689{
690 pub fn build(self) -> Resource {
692 let name = self.name.unwrap_or_else(|| self.uri.clone());
693
694 Resource::from_handler(
695 self.uri,
696 name,
697 self.title,
698 self.description,
699 self.mime_type,
700 self.icons,
701 self.size,
702 self.annotations,
703 FnHandler {
704 handler: self.handler,
705 },
706 )
707 }
708
709 pub fn layer<L>(self, layer: L) -> ResourceBuilderWithLayer<F, L> {
740 ResourceBuilderWithLayer {
741 uri: self.uri,
742 name: self.name,
743 title: self.title,
744 description: self.description,
745 mime_type: self.mime_type,
746 icons: self.icons,
747 size: self.size,
748 annotations: self.annotations,
749 handler: self.handler,
750 layer,
751 }
752 }
753}
754
755pub struct ResourceBuilderWithLayer<F, L> {
759 uri: String,
760 name: Option<String>,
761 title: Option<String>,
762 description: Option<String>,
763 mime_type: Option<String>,
764 icons: Option<Vec<ToolIcon>>,
765 size: Option<u64>,
766 annotations: Option<ContentAnnotations>,
767 handler: F,
768 layer: L,
769}
770
771#[allow(private_bounds)]
774impl<F, Fut, L> ResourceBuilderWithLayer<F, L>
775where
776 F: Fn() -> Fut + Send + Sync + 'static,
777 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
778 L: tower::Layer<ResourceHandlerService<FnHandler<F>>> + Clone + Send + Sync + 'static,
779 L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
780 <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
781 <L::Service as Service<ResourceRequest>>::Future: Send,
782{
783 pub fn build(self) -> Resource {
785 let name = self.name.unwrap_or_else(|| self.uri.clone());
786
787 let handler_service = ResourceHandlerService::new(FnHandler {
788 handler: self.handler,
789 });
790 let layered = self.layer.layer(handler_service);
791 let catch_error = ResourceCatchError::new(layered);
792 let service = BoxCloneService::new(catch_error);
793
794 Resource {
795 uri: self.uri,
796 name,
797 title: self.title,
798 description: self.description,
799 mime_type: self.mime_type,
800 icons: self.icons,
801 size: self.size,
802 annotations: self.annotations,
803 service,
804 }
805 }
806
807 pub fn layer<L2>(
812 self,
813 layer: L2,
814 ) -> ResourceBuilderWithLayer<F, tower::layer::util::Stack<L2, L>> {
815 ResourceBuilderWithLayer {
816 uri: self.uri,
817 name: self.name,
818 title: self.title,
819 description: self.description,
820 mime_type: self.mime_type,
821 icons: self.icons,
822 size: self.size,
823 annotations: self.annotations,
824 handler: self.handler,
825 layer: tower::layer::util::Stack::new(layer, self.layer),
826 }
827 }
828}
829
830pub struct ResourceBuilderWithContextHandler<F> {
832 uri: String,
833 name: Option<String>,
834 title: Option<String>,
835 description: Option<String>,
836 mime_type: Option<String>,
837 icons: Option<Vec<ToolIcon>>,
838 size: Option<u64>,
839 annotations: Option<ContentAnnotations>,
840 handler: F,
841}
842
843impl<F, Fut> ResourceBuilderWithContextHandler<F>
844where
845 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
846 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
847{
848 pub fn build(self) -> Resource {
850 let name = self.name.unwrap_or_else(|| self.uri.clone());
851
852 Resource::from_handler(
853 self.uri,
854 name,
855 self.title,
856 self.description,
857 self.mime_type,
858 self.icons,
859 self.size,
860 self.annotations,
861 ContextAwareHandler {
862 handler: self.handler,
863 },
864 )
865 }
866
867 pub fn layer<L>(self, layer: L) -> ResourceBuilderWithContextLayer<F, L> {
871 ResourceBuilderWithContextLayer {
872 uri: self.uri,
873 name: self.name,
874 title: self.title,
875 description: self.description,
876 mime_type: self.mime_type,
877 icons: self.icons,
878 size: self.size,
879 annotations: self.annotations,
880 handler: self.handler,
881 layer,
882 }
883 }
884}
885
886pub struct ResourceBuilderWithContextLayer<F, L> {
888 uri: String,
889 name: Option<String>,
890 title: Option<String>,
891 description: Option<String>,
892 mime_type: Option<String>,
893 icons: Option<Vec<ToolIcon>>,
894 size: Option<u64>,
895 annotations: Option<ContentAnnotations>,
896 handler: F,
897 layer: L,
898}
899
900#[allow(private_bounds)]
902impl<F, Fut, L> ResourceBuilderWithContextLayer<F, L>
903where
904 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
905 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
906 L: tower::Layer<ResourceHandlerService<ContextAwareHandler<F>>> + Clone + Send + Sync + 'static,
907 L::Service: Service<ResourceRequest, Response = ReadResourceResult> + Clone + Send + 'static,
908 <L::Service as Service<ResourceRequest>>::Error: fmt::Display + Send,
909 <L::Service as Service<ResourceRequest>>::Future: Send,
910{
911 pub fn build(self) -> Resource {
913 let name = self.name.unwrap_or_else(|| self.uri.clone());
914
915 let handler_service = ResourceHandlerService::new(ContextAwareHandler {
916 handler: self.handler,
917 });
918 let layered = self.layer.layer(handler_service);
919 let catch_error = ResourceCatchError::new(layered);
920 let service = BoxCloneService::new(catch_error);
921
922 Resource {
923 uri: self.uri,
924 name,
925 title: self.title,
926 description: self.description,
927 mime_type: self.mime_type,
928 icons: self.icons,
929 size: self.size,
930 annotations: self.annotations,
931 service,
932 }
933 }
934
935 pub fn layer<L2>(
937 self,
938 layer: L2,
939 ) -> ResourceBuilderWithContextLayer<F, tower::layer::util::Stack<L2, L>> {
940 ResourceBuilderWithContextLayer {
941 uri: self.uri,
942 name: self.name,
943 title: self.title,
944 description: self.description,
945 mime_type: self.mime_type,
946 icons: self.icons,
947 size: self.size,
948 annotations: self.annotations,
949 handler: self.handler,
950 layer: tower::layer::util::Stack::new(layer, self.layer),
951 }
952 }
953}
954
955struct FnHandler<F> {
961 handler: F,
962}
963
964impl<F, Fut> ResourceHandler for FnHandler<F>
965where
966 F: Fn() -> Fut + Send + Sync + 'static,
967 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
968{
969 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
970 Box::pin((self.handler)())
971 }
972}
973
974struct ContextAwareHandler<F> {
976 handler: F,
977}
978
979impl<F, Fut> ResourceHandler for ContextAwareHandler<F>
980where
981 F: Fn(RequestContext) -> Fut + Send + Sync + 'static,
982 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
983{
984 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
985 let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
986 self.read_with_context(ctx)
987 }
988
989 fn read_with_context(&self, ctx: RequestContext) -> BoxFuture<'_, Result<ReadResourceResult>> {
990 Box::pin((self.handler)(ctx))
991 }
992
993 fn uses_context(&self) -> bool {
994 true
995 }
996}
997
998pub trait McpResource: Send + Sync + 'static {
1042 const URI: &'static str;
1043 const NAME: &'static str;
1044 const DESCRIPTION: Option<&'static str> = None;
1045 const MIME_TYPE: Option<&'static str> = None;
1046
1047 fn read(&self) -> impl Future<Output = Result<ReadResourceResult>> + Send;
1048
1049 fn into_resource(self) -> Resource
1051 where
1052 Self: Sized,
1053 {
1054 let resource = Arc::new(self);
1055 Resource::from_handler(
1056 Self::URI.to_string(),
1057 Self::NAME.to_string(),
1058 None,
1059 Self::DESCRIPTION.map(|s| s.to_string()),
1060 Self::MIME_TYPE.map(|s| s.to_string()),
1061 None,
1062 None,
1063 None,
1064 McpResourceHandler { resource },
1065 )
1066 }
1067}
1068
1069struct McpResourceHandler<T: McpResource> {
1071 resource: Arc<T>,
1072}
1073
1074impl<T: McpResource> ResourceHandler for McpResourceHandler<T> {
1075 fn read(&self) -> BoxFuture<'_, Result<ReadResourceResult>> {
1076 let resource = self.resource.clone();
1077 Box::pin(async move { resource.read().await })
1078 }
1079}
1080
1081pub trait ResourceTemplateHandler: Send + Sync {
1090 fn read(
1092 &self,
1093 uri: &str,
1094 variables: HashMap<String, String>,
1095 ) -> BoxFuture<'_, Result<ReadResourceResult>>;
1096}
1097
1098pub struct ResourceTemplate {
1128 pub uri_template: String,
1130 pub name: String,
1132 pub title: Option<String>,
1134 pub description: Option<String>,
1136 pub mime_type: Option<String>,
1138 pub icons: Option<Vec<ToolIcon>>,
1140 pub annotations: Option<ContentAnnotations>,
1142 pattern: regex::Regex,
1144 variables: Vec<String>,
1146 handler: Arc<dyn ResourceTemplateHandler>,
1148}
1149
1150impl Clone for ResourceTemplate {
1151 fn clone(&self) -> Self {
1152 Self {
1153 uri_template: self.uri_template.clone(),
1154 name: self.name.clone(),
1155 title: self.title.clone(),
1156 description: self.description.clone(),
1157 mime_type: self.mime_type.clone(),
1158 icons: self.icons.clone(),
1159 annotations: self.annotations.clone(),
1160 pattern: self.pattern.clone(),
1161 variables: self.variables.clone(),
1162 handler: self.handler.clone(),
1163 }
1164 }
1165}
1166
1167impl std::fmt::Debug for ResourceTemplate {
1168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1169 f.debug_struct("ResourceTemplate")
1170 .field("uri_template", &self.uri_template)
1171 .field("name", &self.name)
1172 .field("title", &self.title)
1173 .field("description", &self.description)
1174 .field("mime_type", &self.mime_type)
1175 .field("icons", &self.icons)
1176 .field("variables", &self.variables)
1177 .finish_non_exhaustive()
1178 }
1179}
1180
1181impl ResourceTemplate {
1182 pub fn builder(uri_template: impl Into<String>) -> ResourceTemplateBuilder {
1184 ResourceTemplateBuilder::new(uri_template)
1185 }
1186
1187 pub fn definition(&self) -> ResourceTemplateDefinition {
1189 ResourceTemplateDefinition {
1190 uri_template: self.uri_template.clone(),
1191 name: self.name.clone(),
1192 title: self.title.clone(),
1193 description: self.description.clone(),
1194 mime_type: self.mime_type.clone(),
1195 icons: self.icons.clone(),
1196 annotations: self.annotations.clone(),
1197 arguments: Vec::new(),
1198 meta: None,
1199 }
1200 }
1201
1202 pub fn match_uri(&self, uri: &str) -> Option<HashMap<String, String>> {
1207 self.pattern.captures(uri).map(|caps| {
1208 self.variables
1209 .iter()
1210 .enumerate()
1211 .filter_map(|(i, name)| {
1212 caps.get(i + 1)
1213 .map(|m| (name.clone(), m.as_str().to_string()))
1214 })
1215 .collect()
1216 })
1217 }
1218
1219 pub fn read(
1226 &self,
1227 uri: &str,
1228 variables: HashMap<String, String>,
1229 ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1230 self.handler.read(uri, variables)
1231 }
1232}
1233
1234pub struct ResourceTemplateBuilder {
1261 uri_template: String,
1262 name: Option<String>,
1263 title: Option<String>,
1264 description: Option<String>,
1265 mime_type: Option<String>,
1266 icons: Option<Vec<ToolIcon>>,
1267 annotations: Option<ContentAnnotations>,
1268}
1269
1270impl ResourceTemplateBuilder {
1271 pub fn new(uri_template: impl Into<String>) -> Self {
1284 Self {
1285 uri_template: uri_template.into(),
1286 name: None,
1287 title: None,
1288 description: None,
1289 mime_type: None,
1290 icons: None,
1291 annotations: None,
1292 }
1293 }
1294
1295 pub fn name(mut self, name: impl Into<String>) -> Self {
1297 self.name = Some(name.into());
1298 self
1299 }
1300
1301 pub fn title(mut self, title: impl Into<String>) -> Self {
1303 self.title = Some(title.into());
1304 self
1305 }
1306
1307 pub fn description(mut self, description: impl Into<String>) -> Self {
1309 self.description = Some(description.into());
1310 self
1311 }
1312
1313 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
1315 self.mime_type = Some(mime_type.into());
1316 self
1317 }
1318
1319 pub fn icon(mut self, src: impl Into<String>) -> Self {
1321 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1322 src: src.into(),
1323 mime_type: None,
1324 sizes: None,
1325 theme: None,
1326 });
1327 self
1328 }
1329
1330 pub fn icon_with_meta(
1332 mut self,
1333 src: impl Into<String>,
1334 mime_type: Option<String>,
1335 sizes: Option<Vec<String>>,
1336 ) -> Self {
1337 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
1338 src: src.into(),
1339 mime_type,
1340 sizes,
1341 theme: None,
1342 });
1343 self
1344 }
1345
1346 pub fn annotations(mut self, annotations: ContentAnnotations) -> Self {
1348 self.annotations = Some(annotations);
1349 self
1350 }
1351
1352 pub fn handler<F, Fut>(self, handler: F) -> ResourceTemplate
1358 where
1359 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1360 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1361 {
1362 let (pattern, variables) = compile_uri_template(&self.uri_template);
1363 let name = self.name.unwrap_or_else(|| self.uri_template.clone());
1364
1365 ResourceTemplate {
1366 uri_template: self.uri_template,
1367 name,
1368 title: self.title,
1369 description: self.description,
1370 mime_type: self.mime_type,
1371 icons: self.icons,
1372 annotations: self.annotations,
1373 pattern,
1374 variables,
1375 handler: Arc::new(FnTemplateHandler { handler }),
1376 }
1377 }
1378}
1379
1380struct FnTemplateHandler<F> {
1382 handler: F,
1383}
1384
1385impl<F, Fut> ResourceTemplateHandler for FnTemplateHandler<F>
1386where
1387 F: Fn(String, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1388 Fut: Future<Output = Result<ReadResourceResult>> + Send + 'static,
1389{
1390 fn read(
1391 &self,
1392 uri: &str,
1393 variables: HashMap<String, String>,
1394 ) -> BoxFuture<'_, Result<ReadResourceResult>> {
1395 let uri = uri.to_string();
1396 Box::pin((self.handler)(uri, variables))
1397 }
1398}
1399
1400fn compile_uri_template(template: &str) -> (regex::Regex, Vec<String>) {
1408 let mut pattern = String::from("^");
1409 let mut variables = Vec::new();
1410
1411 let mut chars = template.chars().peekable();
1412 while let Some(c) = chars.next() {
1413 if c == '{' {
1414 let is_reserved = chars.peek() == Some(&'+');
1416 if is_reserved {
1417 chars.next();
1418 }
1419
1420 let var_name: String = chars.by_ref().take_while(|&c| c != '}').collect();
1422 variables.push(var_name);
1423
1424 if is_reserved {
1426 pattern.push_str("(.+)");
1428 } else {
1429 pattern.push_str("([^/]+)");
1431 }
1432 } else {
1433 match c {
1435 '.' | '+' | '*' | '?' | '^' | '$' | '(' | ')' | '[' | ']' | '{' | '}' | '|'
1436 | '\\' => {
1437 pattern.push('\\');
1438 pattern.push(c);
1439 }
1440 _ => pattern.push(c),
1441 }
1442 }
1443 }
1444
1445 pattern.push('$');
1446
1447 let regex = regex::Regex::new(&pattern)
1449 .unwrap_or_else(|e| panic!("Invalid URI template '{}': {}", template, e));
1450
1451 (regex, variables)
1452}
1453
1454#[cfg(test)]
1455mod tests {
1456 use super::*;
1457 use std::time::Duration;
1458 use tower::timeout::TimeoutLayer;
1459
1460 #[tokio::test]
1461 async fn test_builder_resource() {
1462 let resource = ResourceBuilder::new("file:///test.txt")
1463 .name("Test File")
1464 .description("A test file")
1465 .text("Hello, World!");
1466
1467 assert_eq!(resource.uri, "file:///test.txt");
1468 assert_eq!(resource.name, "Test File");
1469 assert_eq!(resource.description.as_deref(), Some("A test file"));
1470
1471 let result = resource.read().await;
1472 assert_eq!(result.contents.len(), 1);
1473 assert_eq!(result.contents[0].text.as_deref(), Some("Hello, World!"));
1474 }
1475
1476 #[tokio::test]
1477 async fn test_json_resource() {
1478 let resource = ResourceBuilder::new("file:///config.json")
1479 .name("Config")
1480 .json(serde_json::json!({"key": "value"}));
1481
1482 assert_eq!(resource.mime_type.as_deref(), Some("application/json"));
1483
1484 let result = resource.read().await;
1485 assert!(result.contents[0].text.as_ref().unwrap().contains("key"));
1486 }
1487
1488 #[tokio::test]
1489 async fn test_handler_resource() {
1490 let resource = ResourceBuilder::new("memory://counter")
1491 .name("Counter")
1492 .handler(|| async {
1493 Ok(ReadResourceResult {
1494 contents: vec![ResourceContent {
1495 uri: "memory://counter".to_string(),
1496 mime_type: Some("text/plain".to_string()),
1497 text: Some("42".to_string()),
1498 blob: None,
1499 meta: None,
1500 }],
1501 meta: None,
1502 })
1503 })
1504 .build();
1505
1506 let result = resource.read().await;
1507 assert_eq!(result.contents[0].text.as_deref(), Some("42"));
1508 }
1509
1510 #[tokio::test]
1511 async fn test_handler_resource_with_layer() {
1512 let resource = ResourceBuilder::new("file:///with-timeout.txt")
1513 .name("Resource with Timeout")
1514 .handler(|| async {
1515 Ok(ReadResourceResult {
1516 contents: vec![ResourceContent {
1517 uri: "file:///with-timeout.txt".to_string(),
1518 mime_type: Some("text/plain".to_string()),
1519 text: Some("content".to_string()),
1520 blob: None,
1521 meta: None,
1522 }],
1523 meta: None,
1524 })
1525 })
1526 .layer(TimeoutLayer::new(Duration::from_secs(30)))
1527 .build();
1528
1529 let result = resource.read().await;
1530 assert_eq!(result.contents[0].text.as_deref(), Some("content"));
1531 }
1532
1533 #[tokio::test]
1534 async fn test_handler_resource_with_timeout_error() {
1535 let resource = ResourceBuilder::new("file:///slow.txt")
1536 .name("Slow Resource")
1537 .handler(|| async {
1538 tokio::time::sleep(Duration::from_secs(1)).await;
1540 Ok(ReadResourceResult {
1541 contents: vec![ResourceContent {
1542 uri: "file:///slow.txt".to_string(),
1543 mime_type: Some("text/plain".to_string()),
1544 text: Some("content".to_string()),
1545 blob: None,
1546 meta: None,
1547 }],
1548 meta: None,
1549 })
1550 })
1551 .layer(TimeoutLayer::new(Duration::from_millis(50)))
1552 .build();
1553
1554 let result = resource.read().await;
1555 assert!(
1557 result.contents[0]
1558 .text
1559 .as_ref()
1560 .unwrap()
1561 .contains("Error reading resource")
1562 );
1563 }
1564
1565 #[tokio::test]
1566 async fn test_context_aware_handler() {
1567 let resource = ResourceBuilder::new("file:///ctx.txt")
1568 .name("Context Resource")
1569 .handler_with_context(|_ctx: RequestContext| async {
1570 Ok(ReadResourceResult {
1571 contents: vec![ResourceContent {
1572 uri: "file:///ctx.txt".to_string(),
1573 mime_type: Some("text/plain".to_string()),
1574 text: Some("context aware".to_string()),
1575 blob: None,
1576 meta: None,
1577 }],
1578 meta: None,
1579 })
1580 })
1581 .build();
1582
1583 let result = resource.read().await;
1584 assert_eq!(result.contents[0].text.as_deref(), Some("context aware"));
1585 }
1586
1587 #[tokio::test]
1588 async fn test_context_aware_handler_with_layer() {
1589 let resource = ResourceBuilder::new("file:///ctx-layer.txt")
1590 .name("Context Resource with Layer")
1591 .handler_with_context(|_ctx: RequestContext| async {
1592 Ok(ReadResourceResult {
1593 contents: vec![ResourceContent {
1594 uri: "file:///ctx-layer.txt".to_string(),
1595 mime_type: Some("text/plain".to_string()),
1596 text: Some("context with layer".to_string()),
1597 blob: None,
1598 meta: None,
1599 }],
1600 meta: None,
1601 })
1602 })
1603 .layer(TimeoutLayer::new(Duration::from_secs(30)))
1604 .build();
1605
1606 let result = resource.read().await;
1607 assert_eq!(
1608 result.contents[0].text.as_deref(),
1609 Some("context with layer")
1610 );
1611 }
1612
1613 #[tokio::test]
1614 async fn test_trait_resource() {
1615 struct TestResource;
1616
1617 impl McpResource for TestResource {
1618 const URI: &'static str = "test://resource";
1619 const NAME: &'static str = "Test";
1620 const DESCRIPTION: Option<&'static str> = Some("A test resource");
1621 const MIME_TYPE: Option<&'static str> = Some("text/plain");
1622
1623 async fn read(&self) -> Result<ReadResourceResult> {
1624 Ok(ReadResourceResult {
1625 contents: vec![ResourceContent {
1626 uri: Self::URI.to_string(),
1627 mime_type: Self::MIME_TYPE.map(|s| s.to_string()),
1628 text: Some("test content".to_string()),
1629 blob: None,
1630 meta: None,
1631 }],
1632 meta: None,
1633 })
1634 }
1635 }
1636
1637 let resource = TestResource.into_resource();
1638 assert_eq!(resource.uri, "test://resource");
1639 assert_eq!(resource.name, "Test");
1640
1641 let result = resource.read().await;
1642 assert_eq!(result.contents[0].text.as_deref(), Some("test content"));
1643 }
1644
1645 #[test]
1646 fn test_resource_definition() {
1647 let resource = ResourceBuilder::new("file:///test.txt")
1648 .name("Test")
1649 .description("Description")
1650 .mime_type("text/plain")
1651 .text("content");
1652
1653 let def = resource.definition();
1654 assert_eq!(def.uri, "file:///test.txt");
1655 assert_eq!(def.name, "Test");
1656 assert_eq!(def.description.as_deref(), Some("Description"));
1657 assert_eq!(def.mime_type.as_deref(), Some("text/plain"));
1658 }
1659
1660 #[test]
1661 fn test_resource_request_new() {
1662 let ctx = RequestContext::new(crate::protocol::RequestId::Number(1));
1663 let req = ResourceRequest::new(ctx, "file:///test.txt".to_string());
1664 assert_eq!(req.uri, "file:///test.txt");
1665 }
1666
1667 #[test]
1668 fn test_resource_catch_error_clone() {
1669 let handler = FnHandler {
1670 handler: || async {
1671 Ok::<_, Error>(ReadResourceResult {
1672 contents: vec![],
1673 meta: None,
1674 })
1675 },
1676 };
1677 let service = ResourceHandlerService::new(handler);
1678 let catch_error = ResourceCatchError::new(service);
1679 let _clone = catch_error.clone();
1680 }
1681
1682 #[test]
1683 fn test_resource_catch_error_debug() {
1684 let handler = FnHandler {
1685 handler: || async {
1686 Ok::<_, Error>(ReadResourceResult {
1687 contents: vec![],
1688 meta: None,
1689 })
1690 },
1691 };
1692 let service = ResourceHandlerService::new(handler);
1693 let catch_error = ResourceCatchError::new(service);
1694 let debug = format!("{:?}", catch_error);
1695 assert!(debug.contains("ResourceCatchError"));
1696 }
1697
1698 #[test]
1703 fn test_compile_uri_template_simple() {
1704 let (regex, vars) = compile_uri_template("file:///{path}");
1705 assert_eq!(vars, vec!["path"]);
1706 assert!(regex.is_match("file:///README.md"));
1707 assert!(!regex.is_match("file:///foo/bar")); }
1709
1710 #[test]
1711 fn test_compile_uri_template_multiple_vars() {
1712 let (regex, vars) = compile_uri_template("api://v1/{resource}/{id}");
1713 assert_eq!(vars, vec!["resource", "id"]);
1714 assert!(regex.is_match("api://v1/users/123"));
1715 assert!(regex.is_match("api://v1/posts/abc"));
1716 assert!(!regex.is_match("api://v1/users")); }
1718
1719 #[test]
1720 fn test_compile_uri_template_reserved_expansion() {
1721 let (regex, vars) = compile_uri_template("file:///{+path}");
1722 assert_eq!(vars, vec!["path"]);
1723 assert!(regex.is_match("file:///README.md"));
1724 assert!(regex.is_match("file:///foo/bar/baz.txt")); }
1726
1727 #[test]
1728 fn test_compile_uri_template_special_chars() {
1729 let (regex, vars) = compile_uri_template("http://example.com/api?query={q}");
1730 assert_eq!(vars, vec!["q"]);
1731 assert!(regex.is_match("http://example.com/api?query=hello"));
1732 }
1733
1734 #[test]
1735 fn test_resource_template_match_uri() {
1736 let template = ResourceTemplateBuilder::new("db://users/{id}")
1737 .name("User Records")
1738 .handler(|uri: String, vars: HashMap<String, String>| async move {
1739 Ok(ReadResourceResult {
1740 contents: vec![ResourceContent {
1741 uri,
1742 mime_type: None,
1743 text: Some(format!("User {}", vars.get("id").unwrap())),
1744 blob: None,
1745 meta: None,
1746 }],
1747 meta: None,
1748 })
1749 });
1750
1751 let vars = template.match_uri("db://users/123").unwrap();
1753 assert_eq!(vars.get("id"), Some(&"123".to_string()));
1754
1755 assert!(template.match_uri("db://posts/123").is_none());
1757 assert!(template.match_uri("db://users").is_none());
1758 }
1759
1760 #[test]
1761 fn test_resource_template_match_multiple_vars() {
1762 let template = ResourceTemplateBuilder::new("api://{version}/{resource}/{id}")
1763 .name("API Resources")
1764 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1765 Ok(ReadResourceResult {
1766 contents: vec![ResourceContent {
1767 uri,
1768 mime_type: None,
1769 text: None,
1770 blob: None,
1771 meta: None,
1772 }],
1773 meta: None,
1774 })
1775 });
1776
1777 let vars = template.match_uri("api://v2/users/abc-123").unwrap();
1778 assert_eq!(vars.get("version"), Some(&"v2".to_string()));
1779 assert_eq!(vars.get("resource"), Some(&"users".to_string()));
1780 assert_eq!(vars.get("id"), Some(&"abc-123".to_string()));
1781 }
1782
1783 #[tokio::test]
1784 async fn test_resource_template_read() {
1785 let template = ResourceTemplateBuilder::new("file:///{path}")
1786 .name("Files")
1787 .mime_type("text/plain")
1788 .handler(|uri: String, vars: HashMap<String, String>| async move {
1789 let path = vars.get("path").unwrap().clone();
1790 Ok(ReadResourceResult {
1791 contents: vec![ResourceContent {
1792 uri,
1793 mime_type: Some("text/plain".to_string()),
1794 text: Some(format!("Contents of {}", path)),
1795 blob: None,
1796 meta: None,
1797 }],
1798 meta: None,
1799 })
1800 });
1801
1802 let vars = template.match_uri("file:///README.md").unwrap();
1803 let result = template.read("file:///README.md", vars).await.unwrap();
1804
1805 assert_eq!(result.contents.len(), 1);
1806 assert_eq!(result.contents[0].uri, "file:///README.md");
1807 assert_eq!(
1808 result.contents[0].text.as_deref(),
1809 Some("Contents of README.md")
1810 );
1811 }
1812
1813 #[test]
1814 fn test_resource_template_definition() {
1815 let template = ResourceTemplateBuilder::new("db://records/{id}")
1816 .name("Database Records")
1817 .description("Access database records by ID")
1818 .mime_type("application/json")
1819 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1820 Ok(ReadResourceResult {
1821 contents: vec![ResourceContent {
1822 uri,
1823 mime_type: None,
1824 text: None,
1825 blob: None,
1826 meta: None,
1827 }],
1828 meta: None,
1829 })
1830 });
1831
1832 let def = template.definition();
1833 assert_eq!(def.uri_template, "db://records/{id}");
1834 assert_eq!(def.name, "Database Records");
1835 assert_eq!(
1836 def.description.as_deref(),
1837 Some("Access database records by ID")
1838 );
1839 assert_eq!(def.mime_type.as_deref(), Some("application/json"));
1840 }
1841
1842 #[test]
1843 fn test_resource_template_reserved_path() {
1844 let template = ResourceTemplateBuilder::new("file:///{+path}")
1845 .name("Files with subpaths")
1846 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1847 Ok(ReadResourceResult {
1848 contents: vec![ResourceContent {
1849 uri,
1850 mime_type: None,
1851 text: None,
1852 blob: None,
1853 meta: None,
1854 }],
1855 meta: None,
1856 })
1857 });
1858
1859 let vars = template.match_uri("file:///src/lib/utils.rs").unwrap();
1861 assert_eq!(vars.get("path"), Some(&"src/lib/utils.rs".to_string()));
1862 }
1863
1864 #[test]
1865 fn test_resource_annotations() {
1866 use crate::protocol::{ContentAnnotations, ContentRole};
1867
1868 let annotations = ContentAnnotations {
1869 audience: Some(vec![ContentRole::User]),
1870 priority: Some(0.8),
1871 last_modified: None,
1872 };
1873
1874 let resource = ResourceBuilder::new("file:///important.txt")
1875 .name("Important File")
1876 .annotations(annotations.clone())
1877 .text("content");
1878
1879 let def = resource.definition();
1880 assert!(def.annotations.is_some());
1881 let ann = def.annotations.unwrap();
1882 assert_eq!(ann.priority, Some(0.8));
1883 assert_eq!(ann.audience.unwrap(), vec![ContentRole::User]);
1884 }
1885
1886 #[test]
1887 fn test_resource_template_annotations() {
1888 use crate::protocol::{ContentAnnotations, ContentRole};
1889
1890 let annotations = ContentAnnotations {
1891 audience: Some(vec![ContentRole::Assistant]),
1892 priority: Some(0.5),
1893 last_modified: None,
1894 };
1895
1896 let template = ResourceTemplateBuilder::new("db://users/{id}")
1897 .name("Users")
1898 .annotations(annotations)
1899 .handler(|uri: String, _vars: HashMap<String, String>| async move {
1900 Ok(ReadResourceResult {
1901 contents: vec![ResourceContent {
1902 uri,
1903 mime_type: None,
1904 text: Some("data".to_string()),
1905 blob: None,
1906 meta: None,
1907 }],
1908 meta: None,
1909 })
1910 });
1911
1912 let def = template.definition();
1913 assert!(def.annotations.is_some());
1914 let ann = def.annotations.unwrap();
1915 assert_eq!(ann.priority, Some(0.5));
1916 assert_eq!(ann.audience.unwrap(), vec![ContentRole::Assistant]);
1917 }
1918
1919 #[test]
1920 fn test_resource_no_annotations_by_default() {
1921 let resource = ResourceBuilder::new("file:///test.txt")
1922 .name("Test")
1923 .text("content");
1924
1925 let def = resource.definition();
1926 assert!(def.annotations.is_none());
1927 }
1928}