1use std::collections::HashMap;
31use std::sync::{Arc, RwLock};
32
33use crate::context::{NotificationSender, ServerNotification};
34use crate::tool::Tool;
35
36pub(crate) struct DynamicToolsInner {
38 tools: RwLock<HashMap<String, Arc<Tool>>>,
39 notification_senders: RwLock<Vec<NotificationSender>>,
40}
41
42impl DynamicToolsInner {
43 pub(crate) fn new() -> Self {
44 Self {
45 tools: RwLock::new(HashMap::new()),
46 notification_senders: RwLock::new(Vec::new()),
47 }
48 }
49
50 pub(crate) fn add_notification_sender(&self, sender: NotificationSender) {
52 let mut senders = self.notification_senders.write().unwrap();
53 senders.push(sender);
54 }
55
56 fn broadcast_tools_changed(&self) {
58 let mut senders = self.notification_senders.write().unwrap();
59 senders.retain(|tx| !tx.is_closed());
60 for tx in senders.iter() {
61 let _ = tx.try_send(ServerNotification::ToolsListChanged);
62 }
63 }
64
65 pub(crate) fn list(&self) -> Vec<Arc<Tool>> {
67 let tools = self.tools.read().unwrap();
68 tools.values().cloned().collect()
69 }
70
71 pub(crate) fn get(&self, name: &str) -> Option<Arc<Tool>> {
73 let tools = self.tools.read().unwrap();
74 tools.get(name).cloned()
75 }
76
77 pub(crate) fn contains(&self, name: &str) -> bool {
79 let tools = self.tools.read().unwrap();
80 tools.contains_key(name)
81 }
82}
83
84#[derive(Clone)]
93pub struct DynamicToolRegistry {
94 inner: Arc<DynamicToolsInner>,
95}
96
97impl DynamicToolRegistry {
98 pub(crate) fn new(inner: Arc<DynamicToolsInner>) -> Self {
99 Self { inner }
100 }
101
102 pub fn register(&self, tool: Tool) {
106 {
107 let mut tools = self.inner.tools.write().unwrap();
108 tools.insert(tool.name.clone(), Arc::new(tool));
109 }
110 self.inner.broadcast_tools_changed();
111 }
112
113 pub fn unregister(&self, name: &str) -> bool {
118 let removed = {
119 let mut tools = self.inner.tools.write().unwrap();
120 tools.remove(name).is_some()
121 };
122 if removed {
123 self.inner.broadcast_tools_changed();
124 }
125 removed
126 }
127
128 pub fn list(&self) -> Vec<Arc<Tool>> {
130 self.inner.list()
131 }
132
133 pub fn contains(&self, name: &str) -> bool {
135 self.inner.contains(name)
136 }
137}
138
139pub(crate) struct DynamicPromptsInner {
145 prompts: RwLock<HashMap<String, Arc<crate::prompt::Prompt>>>,
146 notification_senders: RwLock<Vec<NotificationSender>>,
147}
148
149impl DynamicPromptsInner {
150 pub(crate) fn new() -> Self {
151 Self {
152 prompts: RwLock::new(HashMap::new()),
153 notification_senders: RwLock::new(Vec::new()),
154 }
155 }
156
157 pub(crate) fn add_notification_sender(&self, sender: NotificationSender) {
159 let mut senders = self.notification_senders.write().unwrap();
160 senders.push(sender);
161 }
162
163 fn broadcast_prompts_changed(&self) {
165 let mut senders = self.notification_senders.write().unwrap();
166 senders.retain(|tx| !tx.is_closed());
167 for tx in senders.iter() {
168 let _ = tx.try_send(ServerNotification::PromptsListChanged);
169 }
170 }
171
172 pub(crate) fn list(&self) -> Vec<Arc<crate::prompt::Prompt>> {
174 let prompts = self.prompts.read().unwrap();
175 prompts.values().cloned().collect()
176 }
177
178 pub(crate) fn get(&self, name: &str) -> Option<Arc<crate::prompt::Prompt>> {
180 let prompts = self.prompts.read().unwrap();
181 prompts.get(name).cloned()
182 }
183
184 pub(crate) fn contains(&self, name: &str) -> bool {
186 let prompts = self.prompts.read().unwrap();
187 prompts.contains_key(name)
188 }
189}
190
191#[derive(Clone)]
217pub struct DynamicPromptRegistry {
218 inner: Arc<DynamicPromptsInner>,
219}
220
221impl DynamicPromptRegistry {
222 pub(crate) fn new(inner: Arc<DynamicPromptsInner>) -> Self {
223 Self { inner }
224 }
225
226 pub fn register(&self, prompt: crate::prompt::Prompt) {
230 {
231 let mut prompts = self.inner.prompts.write().unwrap();
232 prompts.insert(prompt.name.clone(), Arc::new(prompt));
233 }
234 self.inner.broadcast_prompts_changed();
235 }
236
237 pub fn unregister(&self, name: &str) -> bool {
242 let removed = {
243 let mut prompts = self.inner.prompts.write().unwrap();
244 prompts.remove(name).is_some()
245 };
246 if removed {
247 self.inner.broadcast_prompts_changed();
248 }
249 removed
250 }
251
252 pub fn list(&self) -> Vec<Arc<crate::prompt::Prompt>> {
254 self.inner.list()
255 }
256
257 pub fn contains(&self, name: &str) -> bool {
259 self.inner.contains(name)
260 }
261}
262
263pub(crate) struct DynamicResourcesInner {
269 resources: RwLock<HashMap<String, Arc<crate::resource::Resource>>>,
270 notification_senders: RwLock<Vec<NotificationSender>>,
271}
272
273impl DynamicResourcesInner {
274 pub(crate) fn new() -> Self {
275 Self {
276 resources: RwLock::new(HashMap::new()),
277 notification_senders: RwLock::new(Vec::new()),
278 }
279 }
280
281 pub(crate) fn add_notification_sender(&self, sender: NotificationSender) {
282 let mut senders = self.notification_senders.write().unwrap();
283 senders.push(sender);
284 }
285
286 fn broadcast_resources_changed(&self) {
287 let mut senders = self.notification_senders.write().unwrap();
288 senders.retain(|tx| !tx.is_closed());
289 for tx in senders.iter() {
290 let _ = tx.try_send(ServerNotification::ResourcesListChanged);
291 }
292 }
293
294 pub(crate) fn list(&self) -> Vec<Arc<crate::resource::Resource>> {
295 let resources = self.resources.read().unwrap();
296 resources.values().cloned().collect()
297 }
298
299 pub(crate) fn get(&self, uri: &str) -> Option<Arc<crate::resource::Resource>> {
300 let resources = self.resources.read().unwrap();
301 resources.get(uri).cloned()
302 }
303}
304
305#[derive(Clone)]
331pub struct DynamicResourceRegistry {
332 inner: Arc<DynamicResourcesInner>,
333}
334
335impl DynamicResourceRegistry {
336 pub(crate) fn new(inner: Arc<DynamicResourcesInner>) -> Self {
337 Self { inner }
338 }
339
340 pub fn register(&self, resource: crate::resource::Resource) {
344 {
345 let mut resources = self.inner.resources.write().unwrap();
346 resources.insert(resource.uri.clone(), Arc::new(resource));
347 }
348 self.inner.broadcast_resources_changed();
349 }
350
351 pub fn unregister(&self, uri: &str) -> bool {
356 let removed = {
357 let mut resources = self.inner.resources.write().unwrap();
358 resources.remove(uri).is_some()
359 };
360 if removed {
361 self.inner.broadcast_resources_changed();
362 }
363 removed
364 }
365
366 pub fn list(&self) -> Vec<Arc<crate::resource::Resource>> {
368 self.inner.list()
369 }
370
371 pub fn contains(&self, uri: &str) -> bool {
373 let resources = self.inner.resources.read().unwrap();
374 resources.contains_key(uri)
375 }
376}
377
378pub(crate) struct DynamicResourceTemplatesInner {
384 templates: RwLock<Vec<Arc<crate::resource::ResourceTemplate>>>,
385 notification_senders: RwLock<Vec<NotificationSender>>,
386}
387
388impl DynamicResourceTemplatesInner {
389 pub(crate) fn new() -> Self {
390 Self {
391 templates: RwLock::new(Vec::new()),
392 notification_senders: RwLock::new(Vec::new()),
393 }
394 }
395
396 pub(crate) fn add_notification_sender(&self, sender: NotificationSender) {
397 let mut senders = self.notification_senders.write().unwrap();
398 senders.push(sender);
399 }
400
401 fn broadcast_resources_changed(&self) {
402 let mut senders = self.notification_senders.write().unwrap();
403 senders.retain(|tx| !tx.is_closed());
404 for tx in senders.iter() {
405 let _ = tx.try_send(ServerNotification::ResourcesListChanged);
406 }
407 }
408
409 pub(crate) fn list(&self) -> Vec<Arc<crate::resource::ResourceTemplate>> {
410 let templates = self.templates.read().unwrap();
411 templates.clone()
412 }
413
414 pub(crate) fn match_uri(
415 &self,
416 uri: &str,
417 ) -> Option<(
418 Arc<crate::resource::ResourceTemplate>,
419 std::collections::HashMap<String, String>,
420 )> {
421 let templates = self.templates.read().unwrap();
422 for template in templates.iter() {
423 if let Some(variables) = template.match_uri(uri) {
424 return Some((Arc::clone(template), variables));
425 }
426 }
427 None
428 }
429}
430
431#[derive(Clone)]
457pub struct DynamicResourceTemplateRegistry {
458 inner: Arc<DynamicResourceTemplatesInner>,
459}
460
461impl DynamicResourceTemplateRegistry {
462 pub(crate) fn new(inner: Arc<DynamicResourceTemplatesInner>) -> Self {
463 Self { inner }
464 }
465
466 pub fn register(&self, template: crate::resource::ResourceTemplate) {
470 {
471 let mut templates = self.inner.templates.write().unwrap();
472 templates.retain(|t| t.uri_template != template.uri_template);
474 templates.push(Arc::new(template));
475 }
476 self.inner.broadcast_resources_changed();
477 }
478
479 pub fn unregister(&self, uri_template: &str) -> bool {
484 let removed = {
485 let mut templates = self.inner.templates.write().unwrap();
486 let before = templates.len();
487 templates.retain(|t| t.uri_template != uri_template);
488 templates.len() < before
489 };
490 if removed {
491 self.inner.broadcast_resources_changed();
492 }
493 removed
494 }
495
496 pub fn list(&self) -> Vec<Arc<crate::resource::ResourceTemplate>> {
498 self.inner.list()
499 }
500
501 pub fn contains(&self, uri_template: &str) -> bool {
503 let templates = self.inner.templates.read().unwrap();
504 templates.iter().any(|t| t.uri_template == uri_template)
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511 use crate::CallToolResult;
512 use crate::tool::ToolBuilder;
513 use tokio::sync::mpsc;
514
515 fn make_tool(name: &str) -> Tool {
516 ToolBuilder::new(name)
517 .description(format!("Test tool: {name}"))
518 .no_params_handler(|| async { Ok(CallToolResult::text("ok")) })
519 .build()
520 }
521
522 fn make_registry() -> (DynamicToolRegistry, Arc<DynamicToolsInner>) {
523 let inner = Arc::new(DynamicToolsInner::new());
524 let registry = DynamicToolRegistry::new(inner.clone());
525 (registry, inner)
526 }
527
528 #[test]
529 fn test_register_and_list() {
530 let (registry, _) = make_registry();
531
532 assert!(registry.list().is_empty());
533
534 registry.register(make_tool("tool_a"));
535 assert_eq!(registry.list().len(), 1);
536 assert!(registry.contains("tool_a"));
537
538 registry.register(make_tool("tool_b"));
539 assert_eq!(registry.list().len(), 2);
540 assert!(registry.contains("tool_b"));
541 }
542
543 #[test]
544 fn test_unregister() {
545 let (registry, _) = make_registry();
546
547 registry.register(make_tool("tool_a"));
548 registry.register(make_tool("tool_b"));
549 assert_eq!(registry.list().len(), 2);
550
551 assert!(registry.unregister("tool_a"));
552 assert_eq!(registry.list().len(), 1);
553 assert!(!registry.contains("tool_a"));
554 assert!(registry.contains("tool_b"));
555 }
556
557 #[test]
558 fn test_unregister_nonexistent() {
559 let (registry, _) = make_registry();
560 assert!(!registry.unregister("no_such_tool"));
561 }
562
563 #[test]
564 fn test_register_replaces_existing() {
565 let (registry, _) = make_registry();
566
567 registry.register(make_tool("tool_a"));
568 registry.register(make_tool("tool_a"));
569 assert_eq!(registry.list().len(), 1);
570 }
571
572 #[test]
573 fn test_contains() {
574 let (registry, _) = make_registry();
575
576 assert!(!registry.contains("tool_a"));
577 registry.register(make_tool("tool_a"));
578 assert!(registry.contains("tool_a"));
579 registry.unregister("tool_a");
580 assert!(!registry.contains("tool_a"));
581 }
582
583 #[test]
584 fn test_inner_get() {
585 let (registry, inner) = make_registry();
586
587 assert!(inner.get("tool_a").is_none());
588 registry.register(make_tool("tool_a"));
589 let tool = inner.get("tool_a").unwrap();
590 assert_eq!(tool.name, "tool_a");
591 }
592
593 #[tokio::test]
594 async fn test_broadcast_on_register() {
595 let (registry, inner) = make_registry();
596
597 let (tx, mut rx) = mpsc::channel(16);
598 inner.add_notification_sender(tx);
599
600 registry.register(make_tool("tool_a"));
601
602 let notification = rx.try_recv().unwrap();
603 assert!(matches!(notification, ServerNotification::ToolsListChanged));
604 }
605
606 #[tokio::test]
607 async fn test_broadcast_on_unregister() {
608 let (registry, inner) = make_registry();
609
610 registry.register(make_tool("tool_a"));
611
612 let (tx, mut rx) = mpsc::channel(16);
613 inner.add_notification_sender(tx);
614
615 registry.unregister("tool_a");
616
617 let notification = rx.try_recv().unwrap();
618 assert!(matches!(notification, ServerNotification::ToolsListChanged));
619 }
620
621 #[tokio::test]
622 async fn test_no_broadcast_on_unregister_nonexistent() {
623 let (registry, inner) = make_registry();
624
625 let (tx, mut rx) = mpsc::channel(16);
626 inner.add_notification_sender(tx);
627
628 registry.unregister("no_such_tool");
629
630 assert!(rx.try_recv().is_err());
631 }
632
633 #[tokio::test]
634 async fn test_closed_senders_are_cleaned_up() {
635 let (registry, inner) = make_registry();
636
637 let (tx, rx) = mpsc::channel(16);
638 inner.add_notification_sender(tx);
639 drop(rx);
641
642 registry.register(make_tool("tool_a"));
644
645 let (tx2, mut rx2) = mpsc::channel(16);
647 inner.add_notification_sender(tx2);
648
649 registry.register(make_tool("tool_b"));
650 let notification = rx2.try_recv().unwrap();
651 assert!(matches!(notification, ServerNotification::ToolsListChanged));
652 }
653
654 fn make_prompt(name: &str) -> crate::prompt::Prompt {
659 crate::prompt::PromptBuilder::new(name)
660 .description(format!("Test prompt: {name}"))
661 .user_message("ok")
662 }
663
664 fn make_prompt_registry() -> (DynamicPromptRegistry, Arc<DynamicPromptsInner>) {
665 let inner = Arc::new(DynamicPromptsInner::new());
666 let registry = DynamicPromptRegistry::new(inner.clone());
667 (registry, inner)
668 }
669
670 #[test]
671 fn test_prompt_register_and_list() {
672 let (registry, _) = make_prompt_registry();
673
674 assert!(registry.list().is_empty());
675
676 registry.register(make_prompt("prompt_a"));
677 assert_eq!(registry.list().len(), 1);
678 assert!(registry.contains("prompt_a"));
679
680 registry.register(make_prompt("prompt_b"));
681 assert_eq!(registry.list().len(), 2);
682 assert!(registry.contains("prompt_b"));
683 }
684
685 #[test]
686 fn test_prompt_unregister() {
687 let (registry, _) = make_prompt_registry();
688
689 registry.register(make_prompt("prompt_a"));
690 registry.register(make_prompt("prompt_b"));
691
692 assert!(registry.unregister("prompt_a"));
693 assert_eq!(registry.list().len(), 1);
694 assert!(!registry.contains("prompt_a"));
695 assert!(registry.contains("prompt_b"));
696 }
697
698 #[test]
699 fn test_prompt_unregister_nonexistent() {
700 let (registry, _) = make_prompt_registry();
701 assert!(!registry.unregister("no_such_prompt"));
702 }
703
704 #[tokio::test]
705 async fn test_prompt_broadcast_on_register() {
706 let (registry, inner) = make_prompt_registry();
707
708 let (tx, mut rx) = mpsc::channel(16);
709 inner.add_notification_sender(tx);
710
711 registry.register(make_prompt("prompt_a"));
712
713 let notification = rx.try_recv().unwrap();
714 assert!(matches!(
715 notification,
716 ServerNotification::PromptsListChanged
717 ));
718 }
719
720 #[tokio::test]
721 async fn test_prompt_broadcast_on_unregister() {
722 let (registry, inner) = make_prompt_registry();
723
724 registry.register(make_prompt("prompt_a"));
725
726 let (tx, mut rx) = mpsc::channel(16);
727 inner.add_notification_sender(tx);
728
729 registry.unregister("prompt_a");
730
731 let notification = rx.try_recv().unwrap();
732 assert!(matches!(
733 notification,
734 ServerNotification::PromptsListChanged
735 ));
736 }
737
738 fn make_resource(uri: &str) -> crate::resource::Resource {
743 crate::resource::ResourceBuilder::new(uri)
744 .name(uri)
745 .text("content")
746 }
747
748 fn make_resource_registry() -> (DynamicResourceRegistry, Arc<DynamicResourcesInner>) {
749 let inner = Arc::new(DynamicResourcesInner::new());
750 let registry = DynamicResourceRegistry::new(inner.clone());
751 (registry, inner)
752 }
753
754 #[test]
755 fn test_resource_register_and_list() {
756 let (registry, _) = make_resource_registry();
757
758 assert!(registry.list().is_empty());
759
760 registry.register(make_resource("file:///a.txt"));
761 assert_eq!(registry.list().len(), 1);
762 assert!(registry.contains("file:///a.txt"));
763
764 registry.register(make_resource("file:///b.txt"));
765 assert_eq!(registry.list().len(), 2);
766 }
767
768 #[test]
769 fn test_resource_unregister() {
770 let (registry, _) = make_resource_registry();
771
772 registry.register(make_resource("file:///a.txt"));
773 registry.register(make_resource("file:///b.txt"));
774
775 assert!(registry.unregister("file:///a.txt"));
776 assert_eq!(registry.list().len(), 1);
777 assert!(!registry.contains("file:///a.txt"));
778 assert!(registry.contains("file:///b.txt"));
779 }
780
781 #[test]
782 fn test_resource_unregister_nonexistent() {
783 let (registry, _) = make_resource_registry();
784 assert!(!registry.unregister("file:///nope"));
785 }
786
787 #[tokio::test]
788 async fn test_resource_broadcast_on_register() {
789 let (registry, inner) = make_resource_registry();
790
791 let (tx, mut rx) = mpsc::channel(16);
792 inner.add_notification_sender(tx);
793
794 registry.register(make_resource("file:///a.txt"));
795
796 let notification = rx.try_recv().unwrap();
797 assert!(matches!(
798 notification,
799 ServerNotification::ResourcesListChanged
800 ));
801 }
802
803 fn make_template_registry() -> (
808 DynamicResourceTemplateRegistry,
809 Arc<DynamicResourceTemplatesInner>,
810 ) {
811 let inner = Arc::new(DynamicResourceTemplatesInner::new());
812 let registry = DynamicResourceTemplateRegistry::new(inner.clone());
813 (registry, inner)
814 }
815
816 #[test]
817 fn test_template_register_and_list() {
818 use crate::resource::ResourceTemplateBuilder;
819
820 let (registry, _) = make_template_registry();
821 assert!(registry.list().is_empty());
822
823 let template = ResourceTemplateBuilder::new("db://tables/{table}")
824 .name("Tables")
825 .handler(
826 |uri: String, _vars: std::collections::HashMap<String, String>| async move {
827 Ok(crate::protocol::ReadResourceResult {
828 contents: vec![crate::protocol::ResourceContent {
829 uri,
830 mime_type: None,
831 text: Some("data".to_string()),
832 blob: None,
833 meta: None,
834 }],
835 meta: None,
836 ..Default::default()
837 })
838 },
839 );
840
841 registry.register(template);
842 assert_eq!(registry.list().len(), 1);
843 assert!(registry.contains("db://tables/{table}"));
844 }
845
846 #[test]
847 fn test_template_unregister() {
848 use crate::resource::ResourceTemplateBuilder;
849
850 let (registry, _) = make_template_registry();
851
852 let template = ResourceTemplateBuilder::new("db://tables/{table}")
853 .name("Tables")
854 .handler(
855 |uri: String, _vars: std::collections::HashMap<String, String>| async move {
856 Ok(crate::protocol::ReadResourceResult {
857 contents: vec![crate::protocol::ResourceContent {
858 uri,
859 mime_type: None,
860 text: Some("data".to_string()),
861 blob: None,
862 meta: None,
863 }],
864 meta: None,
865 ..Default::default()
866 })
867 },
868 );
869
870 registry.register(template);
871 assert!(registry.unregister("db://tables/{table}"));
872 assert!(registry.list().is_empty());
873 assert!(!registry.unregister("db://tables/{table}"));
874 }
875
876 #[tokio::test]
877 async fn test_template_broadcast_on_register() {
878 use crate::resource::ResourceTemplateBuilder;
879
880 let (registry, inner) = make_template_registry();
881
882 let (tx, mut rx) = mpsc::channel(16);
883 inner.add_notification_sender(tx);
884
885 let template = ResourceTemplateBuilder::new("db://tables/{table}")
886 .name("Tables")
887 .handler(
888 |uri: String, _vars: std::collections::HashMap<String, String>| async move {
889 Ok(crate::protocol::ReadResourceResult {
890 contents: vec![crate::protocol::ResourceContent {
891 uri,
892 mime_type: None,
893 text: Some("data".to_string()),
894 blob: None,
895 meta: None,
896 }],
897 meta: None,
898 ..Default::default()
899 })
900 },
901 );
902
903 registry.register(template);
904
905 let notification = rx.try_recv().unwrap();
906 assert!(matches!(
907 notification,
908 ServerNotification::ResourcesListChanged
909 ));
910 }
911
912 #[tokio::test]
913 async fn test_template_match_uri() {
914 use crate::resource::ResourceTemplateBuilder;
915
916 let (_, inner) = make_template_registry();
917
918 let template = ResourceTemplateBuilder::new("db://tables/{table}")
919 .name("Tables")
920 .handler(
921 |uri: String, _vars: std::collections::HashMap<String, String>| async move {
922 Ok(crate::protocol::ReadResourceResult {
923 contents: vec![crate::protocol::ResourceContent {
924 uri,
925 mime_type: None,
926 text: Some("data".to_string()),
927 blob: None,
928 meta: None,
929 }],
930 meta: None,
931 ..Default::default()
932 })
933 },
934 );
935
936 {
937 let mut templates = inner.templates.write().unwrap();
938 templates.push(Arc::new(template));
939 }
940
941 let result = inner.match_uri("db://tables/users");
942 assert!(result.is_some());
943 let (_, vars) = result.unwrap();
944 assert_eq!(vars.get("table").unwrap(), "users");
945
946 assert!(inner.match_uri("db://other/path").is_none());
947 }
948}