turbomcp_server/
registry.rs

1//! Server-side handler registry for MCP protocol implementation.
2//!
3//! This is a **server-specific registry** distinct from the protocol-level registries.
4//! It manages actual MCP protocol handler instances with dynamic dispatch for
5//! tools, prompts, resources, sampling, and logging.
6//!
7//! ## Key Differences from Protocol Registries
8//!
9//! | Registry | Purpose | Handlers |
10//! |----------|---------|----------|
11//! | `turbomcp_protocol::Registry` | Generic component storage | Any `Send + Sync` trait objects |
12//! | `turbomcp_protocol::EnhancedRegistry` | Protocol handlers + stats | Elicitation, completion, templates, ping |
13//! | **This registry** (`HandlerRegistry`) | **Server implementation** | **Tools, prompts, resources, sampling, logging** |
14//!
15//! ## When to Use This Registry
16//!
17//! Use `HandlerRegistry` when building an MCP **server** implementation. This registry:
18//! - Stores actual tool/prompt/resource handler implementations
19//! - Provides the DashMap-based concurrent access needed for server workloads
20//! - Integrates with server middleware and request routing
21//! - Tracks metrics and validation specific to server operations
22//!
23//! For **protocol-level** abstractions, use the registries in `turbomcp_protocol` instead.
24//!
25//! # Examples
26//!
27//! ## Creating a registry
28//!
29//! ```
30//! use turbomcp_server::registry::HandlerRegistry;
31//!
32//! let registry = HandlerRegistry::new();
33//! assert_eq!(registry.tools.len(), 0);
34//! assert_eq!(registry.prompts.len(), 0);
35//! ```
36//!
37//! ## Working with registry configuration
38//!
39//! ```
40//! use turbomcp_server::registry::RegistryConfig;
41//!
42//! let config = RegistryConfig::default();
43//! assert_eq!(config.max_handlers_per_type, 1000);
44//! assert!(config.enable_metrics);
45//! assert!(config.enable_validation);
46//!
47//! let custom_config = RegistryConfig {
48//!     max_handlers_per_type: 100,
49//!     enable_metrics: false,
50//!     enable_validation: true,
51//!     handler_timeout_ms: 15_000,
52//!     enable_hot_reload: false,
53//!     event_listeners: vec!["audit".to_string()],
54//! };
55//! assert_eq!(custom_config.max_handlers_per_type, 100);
56//! ```
57
58use dashmap::DashMap;
59use parking_lot::RwLock;
60use std::collections::HashMap;
61use std::sync::Arc;
62use turbomcp_protocol::types::{Prompt, Resource, Root, Tool};
63
64use crate::handlers::{
65    HandlerMetadata, LoggingHandler, PromptHandler, ResourceHandler, SamplingHandler, ToolHandler,
66};
67use crate::{ServerError, ServerResult};
68
69/// Handler registry for managing all server handlers
70pub struct HandlerRegistry {
71    /// Tool handlers
72    pub tools: DashMap<String, Arc<dyn ToolHandler + 'static>>,
73    /// Prompt handlers  
74    pub prompts: DashMap<String, Arc<dyn PromptHandler + 'static>>,
75    /// Resource handlers
76    pub resources: DashMap<String, Arc<dyn ResourceHandler + 'static>>,
77    /// Sampling handlers
78    pub sampling: DashMap<String, Arc<dyn SamplingHandler + 'static>>,
79    /// Logging handlers
80    pub logging: DashMap<String, Arc<dyn LoggingHandler + 'static>>,
81    /// Filesystem roots
82    pub roots: Arc<RwLock<Vec<Root>>>,
83    /// Handler metadata
84    metadata: DashMap<String, HandlerMetadata>,
85    /// Registry configuration
86    config: Arc<RwLock<RegistryConfig>>,
87}
88
89impl std::fmt::Debug for HandlerRegistry {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        f.debug_struct("HandlerRegistry")
92            .field("tools_count", &self.tools.len())
93            .field("prompts_count", &self.prompts.len())
94            .field("resources_count", &self.resources.len())
95            .field("sampling_count", &self.sampling.len())
96            .field("logging_count", &self.logging.len())
97            .field("roots_count", &self.roots.read().len())
98            .finish()
99    }
100}
101
102/// Registry configuration
103#[derive(Debug, Clone)]
104pub struct RegistryConfig {
105    /// Maximum number of handlers per type
106    pub max_handlers_per_type: usize,
107    /// Enable handler metrics
108    pub enable_metrics: bool,
109    /// Enable handler validation
110    pub enable_validation: bool,
111    /// Handler timeout in milliseconds
112    pub handler_timeout_ms: u64,
113    /// Enable hot reloading
114    pub enable_hot_reload: bool,
115    /// Registry event listeners
116    pub event_listeners: Vec<String>,
117}
118
119impl Default for RegistryConfig {
120    fn default() -> Self {
121        Self {
122            max_handlers_per_type: 1000,
123            enable_metrics: true,
124            enable_validation: true,
125            handler_timeout_ms: 30_000,
126            enable_hot_reload: false,
127            event_listeners: Vec::new(),
128        }
129    }
130}
131
132/// Registry events
133#[derive(Debug, Clone)]
134pub enum RegistryEvent {
135    /// Handler registered
136    HandlerRegistered {
137        /// Handler type
138        handler_type: String,
139        /// Handler name
140        name: String,
141        /// Registration timestamp
142        timestamp: chrono::DateTime<chrono::Utc>,
143    },
144    /// Handler unregistered
145    HandlerUnregistered {
146        /// Handler type
147        handler_type: String,
148        /// Handler name
149        name: String,
150        /// Unregistration timestamp
151        timestamp: chrono::DateTime<chrono::Utc>,
152    },
153    /// Handler updated
154    HandlerUpdated {
155        /// Handler type
156        handler_type: String,
157        /// Handler name
158        name: String,
159        /// Update timestamp
160        timestamp: chrono::DateTime<chrono::Utc>,
161    },
162    /// Registry cleared
163    RegistryCleared {
164        /// Clear timestamp
165        timestamp: chrono::DateTime<chrono::Utc>,
166    },
167}
168
169impl HandlerRegistry {
170    /// Create a new handler registry
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use turbomcp_server::registry::HandlerRegistry;
176    ///
177    /// let registry = HandlerRegistry::new();
178    ///
179    /// // All collections start empty
180    /// assert_eq!(registry.tools.len(), 0);
181    /// assert_eq!(registry.prompts.len(), 0);
182    /// assert_eq!(registry.resources.len(), 0);
183    /// assert_eq!(registry.sampling.len(), 0);
184    /// assert_eq!(registry.logging.len(), 0);
185    /// ```
186    #[must_use]
187    pub fn new() -> Self {
188        Self {
189            tools: DashMap::new(),
190            prompts: DashMap::new(),
191            resources: DashMap::new(),
192            sampling: DashMap::new(),
193            logging: DashMap::new(),
194            roots: Arc::new(RwLock::new(Vec::new())),
195            metadata: DashMap::new(),
196            config: Arc::new(RwLock::new(RegistryConfig::default())),
197        }
198    }
199
200    /// Create a registry with configuration
201    #[must_use]
202    pub fn with_config(config: RegistryConfig) -> Self {
203        Self {
204            tools: DashMap::new(),
205            prompts: DashMap::new(),
206            resources: DashMap::new(),
207            sampling: DashMap::new(),
208            logging: DashMap::new(),
209            roots: Arc::new(RwLock::new(Vec::new())),
210            metadata: DashMap::new(),
211            config: Arc::new(RwLock::new(config)),
212        }
213    }
214
215    /// Register a tool handler
216    ///
217    /// # Errors
218    ///
219    /// Returns [`crate::ServerError::Handler`] if:
220    /// - Maximum handler limit is exceeded
221    /// - Tool name is empty or too long
222    /// - A tool with the same name already exists
223    pub fn register_tool<T>(&self, name: impl Into<String>, handler: T) -> ServerResult<()>
224    where
225        T: ToolHandler + 'static,
226    {
227        let name = name.into();
228
229        // Check limits
230        if self.tools.len() >= self.config.read().max_handlers_per_type {
231            return Err(ServerError::handler(format!(
232                "Maximum number of tool handlers ({}) exceeded",
233                self.config.read().max_handlers_per_type
234            )));
235        }
236
237        // Validate handler if enabled
238        if self.config.read().enable_validation {
239            self.validate_tool_handler(&handler)?;
240        }
241
242        // Register the handler
243        self.tools.insert(name.clone(), Arc::new(handler));
244
245        // Store metadata
246        let metadata = HandlerMetadata {
247            name: name.clone(),
248            version: "1.0.0".to_string(),
249            description: None,
250            tags: vec!["tool".to_string()],
251            created_at: chrono::Utc::now(),
252            config: HashMap::new(),
253            metrics_enabled: self.config.read().enable_metrics,
254            rate_limit: None,
255            allowed_roles: None,
256        };
257        self.metadata.insert(format!("tool:{name}"), metadata);
258
259        tracing::info!("Registered tool handler: {}", name);
260        Ok(())
261    }
262
263    /// Register a prompt handler
264    ///
265    /// # Errors
266    ///
267    /// Returns [`crate::ServerError::Handler`] if:
268    /// - Maximum handler limit is exceeded
269    /// - Prompt name is empty or too long
270    /// - A prompt with the same name already exists
271    pub fn register_prompt<P>(&self, name: impl Into<String>, handler: P) -> ServerResult<()>
272    where
273        P: PromptHandler + 'static,
274    {
275        let name = name.into();
276
277        // Check limits
278        if self.prompts.len() >= self.config.read().max_handlers_per_type {
279            return Err(ServerError::handler(format!(
280                "Maximum number of prompt handlers ({}) exceeded",
281                self.config.read().max_handlers_per_type
282            )));
283        }
284
285        // Validate handler if enabled
286        if self.config.read().enable_validation {
287            self.validate_prompt_handler(&handler)?;
288        }
289
290        // Register the handler
291        self.prompts.insert(name.clone(), Arc::new(handler));
292
293        // Store metadata
294        let metadata = HandlerMetadata {
295            name: name.clone(),
296            version: "1.0.0".to_string(),
297            description: None,
298            tags: vec!["prompt".to_string()],
299            created_at: chrono::Utc::now(),
300            config: HashMap::new(),
301            metrics_enabled: self.config.read().enable_metrics,
302            rate_limit: None,
303            allowed_roles: None,
304        };
305        self.metadata.insert(format!("prompt:{name}"), metadata);
306
307        tracing::info!("Registered prompt handler: {}", name);
308        Ok(())
309    }
310
311    /// Register a resource handler
312    ///
313    /// # Errors
314    ///
315    /// Returns [`crate::ServerError::Handler`] if:
316    /// - Maximum handler limit is exceeded
317    /// - Resource name or URI is empty
318    /// - A resource with the same URI already exists
319    pub fn register_resource<R>(&self, name: impl Into<String>, handler: R) -> ServerResult<()>
320    where
321        R: ResourceHandler + 'static,
322    {
323        let name = name.into();
324
325        // Check limits
326        if self.resources.len() >= self.config.read().max_handlers_per_type {
327            return Err(ServerError::handler(format!(
328                "Maximum number of resource handlers ({}) exceeded",
329                self.config.read().max_handlers_per_type
330            )));
331        }
332
333        // Validate handler if enabled
334        if self.config.read().enable_validation {
335            self.validate_resource_handler(&handler)?;
336        }
337
338        // Register the handler
339        self.resources.insert(name.clone(), Arc::new(handler));
340
341        // Store metadata
342        let metadata = HandlerMetadata {
343            name: name.clone(),
344            version: "1.0.0".to_string(),
345            description: None,
346            tags: vec!["resource".to_string()],
347            created_at: chrono::Utc::now(),
348            config: HashMap::new(),
349            metrics_enabled: self.config.read().enable_metrics,
350            rate_limit: None,
351            allowed_roles: None,
352        };
353        self.metadata.insert(format!("resource:{name}"), metadata);
354
355        tracing::info!("Registered resource handler: {}", name);
356        Ok(())
357    }
358
359    /// Register a sampling handler
360    ///
361    /// # Errors
362    ///
363    /// Returns [`crate::ServerError::Handler`] if maximum handler limit is exceeded.
364    pub fn register_sampling<S>(&self, name: impl Into<String>, handler: S) -> ServerResult<()>
365    where
366        S: SamplingHandler + 'static,
367    {
368        let name = name.into();
369
370        // Check limits
371        if self.sampling.len() >= self.config.read().max_handlers_per_type {
372            return Err(ServerError::handler(format!(
373                "Maximum number of sampling handlers ({}) exceeded",
374                self.config.read().max_handlers_per_type
375            )));
376        }
377
378        self.sampling.insert(name.clone(), Arc::new(handler));
379
380        // Store metadata
381        let metadata = HandlerMetadata {
382            name: name.clone(),
383            version: "1.0.0".to_string(),
384            description: None,
385            tags: vec!["sampling".to_string()],
386            created_at: chrono::Utc::now(),
387            config: HashMap::new(),
388            metrics_enabled: self.config.read().enable_metrics,
389            rate_limit: None,
390            allowed_roles: None,
391        };
392        self.metadata.insert(format!("sampling:{name}"), metadata);
393
394        tracing::info!("Registered sampling handler: {}", name);
395        Ok(())
396    }
397
398    /// Register a logging handler
399    ///
400    /// # Errors
401    ///
402    /// Returns [`crate::ServerError::Handler`] if maximum handler limit is exceeded.
403    pub fn register_logging<L>(&self, name: impl Into<String>, handler: L) -> ServerResult<()>
404    where
405        L: LoggingHandler + 'static,
406    {
407        let name = name.into();
408
409        // Check limits
410        if self.logging.len() >= self.config.read().max_handlers_per_type {
411            return Err(ServerError::handler(format!(
412                "Maximum number of logging handlers ({}) exceeded",
413                self.config.read().max_handlers_per_type
414            )));
415        }
416
417        self.logging.insert(name.clone(), Arc::new(handler));
418
419        // Store metadata
420        let metadata = HandlerMetadata {
421            name: name.clone(),
422            version: "1.0.0".to_string(),
423            description: None,
424            tags: vec!["logging".to_string()],
425            created_at: chrono::Utc::now(),
426            config: HashMap::new(),
427            metrics_enabled: self.config.read().enable_metrics,
428            rate_limit: None,
429            allowed_roles: None,
430        };
431        self.metadata.insert(format!("logging:{name}"), metadata);
432
433        tracing::info!("Registered logging handler: {}", name);
434        Ok(())
435    }
436
437    /// Get a tool handler by name
438    #[must_use]
439    pub fn get_tool(&self, name: &str) -> Option<Arc<dyn ToolHandler>> {
440        self.tools.get(name).map(|entry| Arc::clone(entry.value()))
441    }
442
443    /// Get a prompt handler by name
444    #[must_use]
445    pub fn get_prompt(&self, name: &str) -> Option<Arc<dyn PromptHandler>> {
446        self.prompts
447            .get(name)
448            .map(|entry| Arc::clone(entry.value()))
449    }
450
451    /// Get a resource handler by name
452    #[must_use]
453    pub fn get_resource(&self, name: &str) -> Option<Arc<dyn ResourceHandler>> {
454        self.resources
455            .get(name)
456            .map(|entry| Arc::clone(entry.value()))
457    }
458
459    /// Get a sampling handler by name
460    #[must_use]
461    pub fn get_sampling(&self, name: &str) -> Option<Arc<dyn SamplingHandler>> {
462        self.sampling
463            .get(name)
464            .map(|entry| Arc::clone(entry.value()))
465    }
466
467    /// Get a logging handler by name
468    #[must_use]
469    pub fn get_logging(&self, name: &str) -> Option<Arc<dyn LoggingHandler>> {
470        self.logging
471            .get(name)
472            .map(|entry| Arc::clone(entry.value()))
473    }
474
475    /// List all tool names
476    #[must_use]
477    pub fn list_tools(&self) -> Vec<String> {
478        self.tools.iter().map(|entry| entry.key().clone()).collect()
479    }
480
481    /// List all prompt names
482    #[must_use]
483    pub fn list_prompts(&self) -> Vec<String> {
484        self.prompts
485            .iter()
486            .map(|entry| entry.key().clone())
487            .collect()
488    }
489
490    /// List all resource names
491    #[must_use]
492    pub fn list_resources(&self) -> Vec<String> {
493        self.resources
494            .iter()
495            .map(|entry| entry.key().clone())
496            .collect()
497    }
498
499    /// List all sampling names
500    #[must_use]
501    pub fn list_sampling(&self) -> Vec<String> {
502        self.sampling
503            .iter()
504            .map(|entry| entry.key().clone())
505            .collect()
506    }
507
508    /// List all logging names
509    #[must_use]
510    pub fn list_logging(&self) -> Vec<String> {
511        self.logging
512            .iter()
513            .map(|entry| entry.key().clone())
514            .collect()
515    }
516
517    /// Get all tool definitions
518    #[must_use]
519    pub fn get_tool_definitions(&self) -> Vec<Tool> {
520        self.tools
521            .iter()
522            .map(|entry| entry.value().tool_definition())
523            .collect()
524    }
525
526    /// Get all prompt definitions
527    #[must_use]
528    pub fn get_prompt_definitions(&self) -> Vec<Prompt> {
529        self.prompts
530            .iter()
531            .map(|entry| entry.value().prompt_definition())
532            .collect()
533    }
534
535    /// Get all resource definitions
536    #[must_use]
537    pub fn get_resource_definitions(&self) -> Vec<Resource> {
538        self.resources
539            .iter()
540            .map(|entry| entry.value().resource_definition())
541            .collect()
542    }
543
544    /// Add a root to the registry
545    pub fn add_root(&self, root: Root) {
546        self.roots.write().push(root);
547    }
548
549    /// Set multiple roots at once
550    pub fn set_roots(&self, roots: Vec<Root>) {
551        *self.roots.write() = roots;
552    }
553
554    /// Get all registered roots
555    #[must_use]
556    pub fn get_roots(&self) -> Vec<Root> {
557        self.roots.read().clone()
558    }
559
560    /// Clear all roots
561    pub fn clear_roots(&self) {
562        self.roots.write().clear();
563    }
564
565    /// Unregister a tool handler
566    pub fn unregister_tool(&self, name: &str) -> bool {
567        let removed = self.tools.remove(name).is_some();
568        if removed {
569            self.metadata.remove(&format!("tool:{name}"));
570            tracing::info!("Unregistered tool handler: {}", name);
571        }
572        removed
573    }
574
575    /// Unregister a prompt handler
576    pub fn unregister_prompt(&self, name: &str) -> bool {
577        let removed = self.prompts.remove(name).is_some();
578        if removed {
579            self.metadata.remove(&format!("prompt:{name}"));
580            tracing::info!("Unregistered prompt handler: {}", name);
581        }
582        removed
583    }
584
585    /// Unregister a resource handler
586    pub fn unregister_resource(&self, name: &str) -> bool {
587        let removed = self.resources.remove(name).is_some();
588        if removed {
589            self.metadata.remove(&format!("resource:{name}"));
590            tracing::info!("Unregistered resource handler: {}", name);
591        }
592        removed
593    }
594
595    /// Clear all handlers
596    pub fn clear(&self) {
597        self.tools.clear();
598        self.prompts.clear();
599        self.resources.clear();
600        self.sampling.clear();
601        self.logging.clear();
602        self.metadata.clear();
603        tracing::info!("Cleared all handlers from registry");
604    }
605
606    /// Get registry statistics
607    #[must_use]
608    pub fn stats(&self) -> RegistryStats {
609        RegistryStats {
610            tool_count: self.tools.len(),
611            prompt_count: self.prompts.len(),
612            resource_count: self.resources.len(),
613            sampling_count: self.sampling.len(),
614            logging_count: self.logging.len(),
615            total_count: self.tools.len()
616                + self.prompts.len()
617                + self.resources.len()
618                + self.sampling.len()
619                + self.logging.len(),
620        }
621    }
622
623    /// Get handler metadata
624    #[must_use]
625    pub fn get_metadata(&self, key: &str) -> Option<HandlerMetadata> {
626        self.metadata.get(key).map(|entry| entry.value().clone())
627    }
628
629    /// Update registry configuration
630    pub fn update_config<F>(&self, f: F)
631    where
632        F: FnOnce(&mut RegistryConfig),
633    {
634        let mut config = self.config.write();
635        f(&mut config);
636    }
637
638    // Private validation methods
639
640    fn validate_tool_handler(&self, handler: &dyn ToolHandler) -> ServerResult<()> {
641        let tool_def = handler.tool_definition();
642
643        if tool_def.name.is_empty() {
644            return Err(ServerError::handler("Tool name cannot be empty"));
645        }
646
647        if tool_def.name.len() > 100 {
648            return Err(ServerError::handler(
649                "Tool name too long (max 100 characters)",
650            ));
651        }
652
653        // Check for duplicate names
654        if self.tools.contains_key(&tool_def.name) {
655            return Err(ServerError::handler(format!(
656                "Tool with name '{}' already exists",
657                tool_def.name
658            )));
659        }
660
661        Ok(())
662    }
663
664    fn validate_prompt_handler(&self, handler: &dyn PromptHandler) -> ServerResult<()> {
665        let prompt_def = handler.prompt_definition();
666
667        if prompt_def.name.is_empty() {
668            return Err(ServerError::handler("Prompt name cannot be empty"));
669        }
670
671        if prompt_def.name.len() > 100 {
672            return Err(ServerError::handler(
673                "Prompt name too long (max 100 characters)",
674            ));
675        }
676
677        // Check for duplicate names
678        if self.prompts.contains_key(&prompt_def.name) {
679            return Err(ServerError::handler(format!(
680                "Prompt with name '{}' already exists",
681                prompt_def.name
682            )));
683        }
684
685        Ok(())
686    }
687
688    fn validate_resource_handler(&self, handler: &dyn ResourceHandler) -> ServerResult<()> {
689        let resource_def = handler.resource_definition();
690
691        if resource_def.uri.is_empty() {
692            return Err(ServerError::handler("Resource URI cannot be empty"));
693        }
694
695        if resource_def.name.is_empty() {
696            return Err(ServerError::handler("Resource name cannot be empty"));
697        }
698
699        // Check for duplicate URIs
700        for entry in &self.resources {
701            if entry.value().resource_definition().uri == resource_def.uri {
702                return Err(ServerError::handler(format!(
703                    "Resource with URI '{}' already exists",
704                    resource_def.uri
705                )));
706            }
707        }
708
709        Ok(())
710    }
711}
712
713impl Default for HandlerRegistry {
714    fn default() -> Self {
715        Self::new()
716    }
717}
718
719/// Registry statistics
720#[derive(Debug, Clone)]
721pub struct RegistryStats {
722    /// Number of tool handlers
723    pub tool_count: usize,
724    /// Number of prompt handlers
725    pub prompt_count: usize,
726    /// Number of resource handlers
727    pub resource_count: usize,
728    /// Number of sampling handlers
729    pub sampling_count: usize,
730    /// Number of logging handlers
731    pub logging_count: usize,
732    /// Total number of handlers
733    pub total_count: usize,
734}
735
736/// Registry builder for configuring the registry
737#[derive(Debug)]
738pub struct RegistryBuilder {
739    config: RegistryConfig,
740}
741
742impl RegistryBuilder {
743    /// Create a new registry builder
744    #[must_use]
745    pub fn new() -> Self {
746        Self {
747            config: RegistryConfig::default(),
748        }
749    }
750
751    /// Set maximum handlers per type
752    #[must_use]
753    pub const fn max_handlers_per_type(mut self, max: usize) -> Self {
754        self.config.max_handlers_per_type = max;
755        self
756    }
757
758    /// Enable or disable metrics
759    #[must_use]
760    pub const fn enable_metrics(mut self, enable: bool) -> Self {
761        self.config.enable_metrics = enable;
762        self
763    }
764
765    /// Enable or disable validation
766    #[must_use]
767    pub const fn enable_validation(mut self, enable: bool) -> Self {
768        self.config.enable_validation = enable;
769        self
770    }
771
772    /// Set handler timeout
773    #[must_use]
774    pub const fn handler_timeout_ms(mut self, timeout: u64) -> Self {
775        self.config.handler_timeout_ms = timeout;
776        self
777    }
778
779    /// Enable or disable hot reload
780    #[must_use]
781    pub const fn enable_hot_reload(mut self, enable: bool) -> Self {
782        self.config.enable_hot_reload = enable;
783        self
784    }
785
786    /// Build the registry
787    #[must_use]
788    pub fn build(self) -> HandlerRegistry {
789        HandlerRegistry::with_config(self.config)
790    }
791}
792
793impl Default for RegistryBuilder {
794    fn default() -> Self {
795        Self::new()
796    }
797}
798
799/// Main registry interface (alias for `HandlerRegistry`)
800pub type Registry = HandlerRegistry;
801
802// Comprehensive tests in separate file (tokio/axum pattern)
803#[cfg(test)]
804mod tests;