Skip to main content

mcp/
protocol.rs

1//! Tool protocol abstraction layer.
2//!
3//! This module provides a flexible abstraction for connecting agents to various tool protocols.
4//! It supports multiple standards including MCP (Model Context Protocol), custom function calling,
5//! Memory persistence, and allows users to implement their own tool communication mechanisms.
6//!
7//! # Architecture
8//!
9//! **Single Protocol** (traditional):
10//! ```text
11//! Agent → ToolRegistry → ToolProtocol → Single Tool Source
12//! ```
13//!
14//! **Multi-Protocol** (new in 0.5.0):
15//! ```text
16//! Agent → ToolRegistry → [Protocol1, Protocol2, Protocol3]
17//!         (routing map)     ↓          ↓          ↓
18//!                        Local      YouTube    GitHub
19//!                        Tools      Server     Server
20//! ```
21//!
22//! # Key Components
23//!
24//! - **ToolProtocol trait**: Define how tools are executed, discovered, and described
25//! - **ToolRegistry**: Single or multi-protocol tool aggregation with transparent routing
26//! - **ToolMetadata**: Tool identity, description, parameters
27//! - **ToolParameter**: Type-safe parameter definitions with validation
28//! - **ToolResult**: Structured tool execution results
29//! - **Tool**: Runtime tool instance bound to a protocol
30//!
31//! # Single Protocol Example
32//!
33//! ```rust,ignore
34//! use std::sync::Arc;
35//! use mcp::protocol::{ToolMetadata, ToolProtocol, ToolRegistry, ToolResult};
36//!
37//! struct MyProtocol;
38//!
39//! #[async_trait::async_trait]
40//! impl ToolProtocol for MyProtocol {
41//!     async fn execute(
42//!         &self,
43//!         _tool_name: &str,
44//!         _parameters: serde_json::Value,
45//!     ) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
46//!         Ok(ToolResult::success(serde_json::json!({"ok": true})))
47//!     }
48//!     async fn list_tools(&self) -> Result<Vec<ToolMetadata>, Box<dyn std::error::Error + Send + Sync>> {
49//!         Ok(vec![ToolMetadata::new("demo", "Demo tool")])
50//!     }
51//!     async fn get_tool_metadata(&self, _: &str) -> Result<ToolMetadata, Box<dyn std::error::Error + Send + Sync>> {
52//!         Ok(ToolMetadata::new("demo", "Demo tool"))
53//!     }
54//!     fn protocol_name(&self) -> &str { "demo" }
55//! }
56//!
57//! # #[tokio::main]
58//! # async fn main() {
59//! let protocol = Arc::new(MyProtocol);
60//! let mut registry = ToolRegistry::new(protocol);
61//! let _ = registry.discover_tools_from_primary().await;
62//! # }
63//! ```
64//!
65//! # Multi-Protocol Example
66//!
67//! ```rust,ignore
68//! use mcp::client::McpClientProtocol;
69//! use mcp::protocol::ToolRegistry;
70//!
71//! # #[tokio::main]
72//! # async fn main() {
73//! let mut registry = ToolRegistry::empty();
74//! let _ = registry
75//!     .add_protocol("remote", std::sync::Arc::new(McpClientProtocol::new("http://localhost:8080".to_string())))
76//!     .await;
77//! # }
78//! ```
79
80use crate::resources::ResourceMetadata;
81use async_trait::async_trait;
82use serde::{Deserialize, Serialize};
83use std::collections::HashMap;
84use std::error::Error;
85use std::fmt;
86use std::sync::Arc;
87
88/// Provider-agnostic tool definition suitable for LLM native tool calling.
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct ToolDefinition {
91    /// Tool name as it will appear in the API `tools` array.
92    pub name: String,
93    /// Human-readable description surfaced to the model.
94    pub description: String,
95    /// JSON Schema object describing accepted parameters.
96    pub parameters_schema: serde_json::Value,
97}
98
99/// Represents the result of a tool execution
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ToolResult {
102    /// Whether the tool execution was successful
103    pub success: bool,
104    /// The output data from the tool
105    pub output: serde_json::Value,
106    /// Optional error message if execution failed
107    pub error: Option<String>,
108    /// Metadata about the execution (timing, cost, etc.)
109    pub metadata: HashMap<String, serde_json::Value>,
110}
111
112impl ToolResult {
113    /// Convenience constructor for successful tool execution.
114    pub fn success(output: serde_json::Value) -> Self {
115        Self {
116            success: true,
117            output,
118            error: None,
119            metadata: HashMap::new(),
120        }
121    }
122
123    /// Convenience constructor for failed tool execution.
124    pub fn failure(error: String) -> Self {
125        Self {
126            success: false,
127            output: serde_json::Value::Null,
128            error: Some(error),
129            metadata: HashMap::new(),
130        }
131    }
132
133    /// Attach protocol or application specific metadata to the result.
134    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
135        self.metadata.insert(key.into(), value);
136        self
137    }
138}
139
140/// Defines the type of a tool parameter
141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
142#[serde(rename_all = "lowercase")]
143pub enum ToolParameterType {
144    /// UTF-8 string value.
145    String,
146    /// Floating-point number.
147    Number,
148    /// Integer number.
149    Integer,
150    /// Boolean value.
151    Boolean,
152    /// JSON array.
153    Array,
154    /// JSON object.
155    Object,
156}
157
158/// Defines a parameter for a tool
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ToolParameter {
161    /// Parameter name.
162    pub name: String,
163    /// Parameter value type.
164    #[serde(rename = "type")]
165    pub param_type: ToolParameterType,
166    /// Human-readable description.
167    pub description: Option<String>,
168    /// Whether the caller must provide this parameter.
169    pub required: bool,
170    /// Optional default value.
171    pub default: Option<serde_json::Value>,
172    /// For array types, specifies the type of items
173    pub items: Option<Box<ToolParameterType>>,
174    /// For object types, specifies nested properties
175    pub properties: Option<HashMap<String, ToolParameter>>,
176}
177
178impl ToolParameter {
179    /// Define a new tool parameter with the provided name and type.
180    pub fn new(name: impl Into<String>, param_type: ToolParameterType) -> Self {
181        Self {
182            name: name.into(),
183            param_type,
184            description: None,
185            required: false,
186            default: None,
187            items: None,
188            properties: None,
189        }
190    }
191
192    /// Add a human readable description that will surface in generated schemas.
193    pub fn with_description(mut self, description: impl Into<String>) -> Self {
194        self.description = Some(description.into());
195        self
196    }
197
198    /// Mark the argument as required.
199    pub fn required(mut self) -> Self {
200        self.required = true;
201        self
202    }
203
204    /// Provide a default value that will be used when the LLM omits the parameter.
205    pub fn with_default(mut self, default: serde_json::Value) -> Self {
206        self.default = Some(default);
207        self
208    }
209
210    /// For array parameters, declare the type of the contained items.
211    pub fn with_items(mut self, item_type: ToolParameterType) -> Self {
212        self.items = Some(Box::new(item_type));
213        self
214    }
215
216    /// For object parameters, describe the nested properties.
217    pub fn with_properties(mut self, properties: HashMap<String, ToolParameter>) -> Self {
218        self.properties = Some(properties);
219        self
220    }
221
222    /// Converts this parameter to a JSON Schema snippet suitable for native tool-calling.
223    ///
224    /// The generated schema follows [JSON Schema draft-07] conventions accepted by all major
225    /// providers (OpenAI, Anthropic, Grok, Gemini).
226    ///
227    /// | `ToolParameterType` | Schema produced |
228    /// |---|---|
229    /// | `String` | `{"type":"string"}` |
230    /// | `Number` | `{"type":"number"}` |
231    /// | `Integer` | `{"type":"integer"}` |
232    /// | `Boolean` | `{"type":"boolean"}` |
233    /// | `Array` | `{"type":"array","items":{...}}` |
234    /// | `Object` | `{"type":"object","properties":{...},"required":[...]}` |
235    ///
236    /// # Example
237    ///
238    /// ```rust
239    /// use mcp::{ToolParameter, ToolParameterType};
240    ///
241    /// let param = ToolParameter::new("query", ToolParameterType::String)
242    ///     .with_description("Search query string")
243    ///     .required();
244    ///
245    /// let schema = param.to_json_schema();
246    /// assert_eq!(schema["type"], "string");
247    /// assert_eq!(schema["description"], "Search query string");
248    /// ```
249    pub fn to_json_schema(&self) -> serde_json::Value {
250        let mut schema = match &self.param_type {
251            ToolParameterType::String => serde_json::json!({"type": "string"}),
252            ToolParameterType::Number => serde_json::json!({"type": "number"}),
253            ToolParameterType::Integer => serde_json::json!({"type": "integer"}),
254            ToolParameterType::Boolean => serde_json::json!({"type": "boolean"}),
255            ToolParameterType::Array => {
256                let items_schema = self
257                    .items
258                    .as_ref()
259                    .map(|t| param_type_to_schema(t))
260                    .unwrap_or_else(|| serde_json::json!({}));
261                serde_json::json!({"type": "array", "items": items_schema})
262            }
263            ToolParameterType::Object => {
264                if let Some(props) = &self.properties {
265                    let properties: serde_json::Map<String, serde_json::Value> = props
266                        .iter()
267                        .map(|(k, v)| (k.clone(), v.to_json_schema()))
268                        .collect();
269                    let required: Vec<&str> = props
270                        .values()
271                        .filter(|p| p.required)
272                        .map(|p| p.name.as_str())
273                        .collect();
274                    serde_json::json!({
275                        "type": "object",
276                        "properties": properties,
277                        "required": required
278                    })
279                } else {
280                    serde_json::json!({"type": "object"})
281                }
282            }
283        };
284
285        // Attach description if present
286        if let Some(desc) = &self.description {
287            schema["description"] = serde_json::Value::String(desc.clone());
288        }
289
290        schema
291    }
292}
293
294/// Convert a bare [`ToolParameterType`] to a minimal JSON Schema value (no description).
295///
296/// Used internally when building array `items` schemas.
297fn param_type_to_schema(t: &ToolParameterType) -> serde_json::Value {
298    match t {
299        ToolParameterType::String => serde_json::json!({"type": "string"}),
300        ToolParameterType::Number => serde_json::json!({"type": "number"}),
301        ToolParameterType::Integer => serde_json::json!({"type": "integer"}),
302        ToolParameterType::Boolean => serde_json::json!({"type": "boolean"}),
303        ToolParameterType::Array => serde_json::json!({"type": "array"}),
304        ToolParameterType::Object => serde_json::json!({"type": "object"}),
305    }
306}
307
308/// Metadata about a tool
309#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct ToolMetadata {
311    /// Tool name.
312    pub name: String,
313    /// Human-readable description.
314    pub description: String,
315    /// Parameter schema entries.
316    pub parameters: Vec<ToolParameter>,
317    /// Additional metadata specific to the protocol
318    pub protocol_metadata: HashMap<String, serde_json::Value>,
319}
320
321impl ToolMetadata {
322    /// Create metadata with the supplied identifier and description.
323    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
324        Self {
325            name: name.into(),
326            description: description.into(),
327            parameters: Vec::new(),
328            protocol_metadata: HashMap::new(),
329        }
330    }
331
332    /// Append a parameter definition to the tool metadata.
333    pub fn with_parameter(mut self, param: ToolParameter) -> Self {
334        self.parameters.push(param);
335        self
336    }
337
338    /// Add protocol specific metadata (e.g. MCP capability flags).
339    pub fn with_protocol_metadata(
340        mut self,
341        key: impl Into<String>,
342        value: serde_json::Value,
343    ) -> Self {
344        self.protocol_metadata.insert(key.into(), value);
345        self
346    }
347
348    /// Builds a [`ToolDefinition`] (JSON Schema) from this metadata.
349    ///
350    /// The resulting [`ToolDefinition`] is ready to be passed to an MCP client
351    /// as part of the `tools` slice.  Parameters marked `required` are collected into the
352    /// JSON Schema `"required"` array automatically.
353    ///
354    /// # Example
355    ///
356    /// ```rust
357    /// use mcp::{ToolMetadata, ToolParameter, ToolParameterType};
358    ///
359    /// let meta = ToolMetadata::new("calculator", "Evaluates a math expression")
360    ///     .with_parameter(
361    ///         ToolParameter::new("expression", ToolParameterType::String)
362    ///             .with_description("The expression to evaluate")
363    ///             .required(),
364    ///     );
365    ///
366    /// let def = meta.to_tool_definition();
367    /// assert_eq!(def.name, "calculator");
368    /// let schema = &def.parameters_schema;
369    /// assert_eq!(schema["type"], "object");
370    /// assert!(schema["properties"]["expression"].is_object());
371    /// ```
372    pub fn to_tool_definition(&self) -> ToolDefinition {
373        let mut properties = serde_json::Map::new();
374        let mut required: Vec<String> = Vec::new();
375
376        for param in &self.parameters {
377            properties.insert(param.name.clone(), param.to_json_schema());
378            if param.required {
379                required.push(param.name.clone());
380            }
381        }
382
383        let parameters_schema = serde_json::json!({
384            "type": "object",
385            "properties": properties,
386            "required": required
387        });
388
389        ToolDefinition {
390            name: self.name.clone(),
391            description: self.description.clone(),
392            parameters_schema,
393        }
394    }
395}
396
397/// Trait for implementing tool execution protocols
398#[async_trait]
399pub trait ToolProtocol: Send + Sync {
400    /// Execute a tool with the given parameters
401    async fn execute(
402        &self,
403        tool_name: &str,
404        parameters: serde_json::Value,
405    ) -> Result<ToolResult, Box<dyn Error + Send + Sync>>;
406
407    /// Get metadata about available tools
408    async fn list_tools(&self) -> Result<Vec<ToolMetadata>, Box<dyn Error + Send + Sync>>;
409
410    /// Get metadata about a specific tool
411    async fn get_tool_metadata(
412        &self,
413        tool_name: &str,
414    ) -> Result<ToolMetadata, Box<dyn Error + Send + Sync>>;
415
416    /// Protocol identifier (e.g., "mcp", "custom", "openai-functions")
417    fn protocol_name(&self) -> &str;
418
419    /// Initialize/connect to the tool protocol
420    async fn initialize(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
421        Ok(())
422    }
423
424    /// Cleanup/disconnect from the tool protocol
425    async fn shutdown(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
426        Ok(())
427    }
428
429    /// List available resources (MCP Resource support)
430    ///
431    /// Resources are application-provided contextual data that agents can read.
432    /// This method is optional and defaults to returning an empty list.
433    async fn list_resources(&self) -> Result<Vec<ResourceMetadata>, Box<dyn Error + Send + Sync>> {
434        Ok(Vec::new())
435    }
436
437    /// Read the content of a resource by URI (MCP Resource support)
438    ///
439    /// This method is optional and defaults to returning NotFound.
440    async fn read_resource(&self, uri: &str) -> Result<String, Box<dyn Error + Send + Sync>> {
441        Err(format!("Resource not found: {}", uri).into())
442    }
443
444    /// Check if this protocol supports resources
445    fn supports_resources(&self) -> bool {
446        false
447    }
448}
449
450/// Error types for tool operations
451#[derive(Debug, Clone)]
452pub enum ToolError {
453    /// Requested tool is not registered in the current registry/protocol.
454    NotFound(String),
455    /// Tool execution completed with an application level failure.
456    ExecutionFailed(String),
457    /// The provided JSON parameters failed validation or deserialization.
458    InvalidParameters(String),
459    /// A lower level protocol/transport error occurred.
460    ProtocolError(String),
461}
462
463impl fmt::Display for ToolError {
464    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465        match self {
466            ToolError::NotFound(name) => write!(f, "Tool not found: {}", name),
467            ToolError::ExecutionFailed(msg) => write!(f, "Tool execution failed: {}", msg),
468            ToolError::InvalidParameters(msg) => write!(f, "Invalid parameters: {}", msg),
469            ToolError::ProtocolError(msg) => write!(f, "Protocol error: {}", msg),
470        }
471    }
472}
473
474impl Error for ToolError {}
475
476/// A tool that can be used by agents
477pub struct Tool {
478    /// Metadata describing the tool interface.
479    metadata: ToolMetadata,
480    /// Underlying protocol implementation that actually executes the tool.
481    protocol: Arc<dyn ToolProtocol>,
482}
483
484impl Tool {
485    /// Create a new tool bound to the supplied protocol implementation.
486    pub fn new(
487        name: impl Into<String>,
488        description: impl Into<String>,
489        protocol: Arc<dyn ToolProtocol>,
490    ) -> Self {
491        Self {
492            metadata: ToolMetadata::new(name, description),
493            protocol,
494        }
495    }
496
497    /// Add a parameter definition to the tool builder.
498    pub fn with_parameter(mut self, param: ToolParameter) -> Self {
499        self.metadata.parameters.push(param);
500        self
501    }
502
503    /// Attach protocol specific metadata to the tool builder.
504    pub fn with_protocol_metadata(
505        mut self,
506        key: impl Into<String>,
507        value: serde_json::Value,
508    ) -> Self {
509        self.metadata.protocol_metadata.insert(key.into(), value);
510        self
511    }
512
513    /// Borrow the static metadata for the tool.
514    pub fn metadata(&self) -> &ToolMetadata {
515        &self.metadata
516    }
517
518    /// Execute the tool using the configured protocol.
519    pub async fn execute(
520        &self,
521        parameters: serde_json::Value,
522    ) -> Result<ToolResult, Box<dyn Error + Send + Sync>> {
523        self.protocol.execute(&self.metadata.name, parameters).await
524    }
525}
526
527/// Registry for managing tools available to agents
528///
529/// Supports single or multiple tool protocols, enabling agents to transparently
530/// access tools from multiple sources (local functions, MCP servers, etc.)
531///
532/// # Single Protocol
533///
534/// ```ignore
535/// use async_trait::async_trait;
536/// use mcp::{ToolMetadata, ToolProtocol, ToolRegistry, ToolResult};
537/// use std::sync::Arc;
538///
539/// struct DemoProtocol;
540///
541/// #[async_trait]
542/// impl ToolProtocol for DemoProtocol {
543///     async fn execute(
544///         &self,
545///         _tool_name: &str,
546///         _parameters: serde_json::Value,
547///     ) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
548///         Ok(ToolResult::success(serde_json::json!({"ok": true})))
549///     }
550///
551///     async fn list_tools(
552///         &self,
553///     ) -> Result<Vec<ToolMetadata>, Box<dyn std::error::Error + Send + Sync>> {
554///         Ok(vec![])
555///     }
556/// }
557///
558/// let protocol = Arc::new(DemoProtocol);
559/// let registry = ToolRegistry::new(protocol);
560/// ```
561///
562/// # Multiple Protocols
563///
564/// ```ignore
565/// use async_trait::async_trait;
566/// use mcp::{ToolMetadata, ToolProtocol, ToolRegistry, ToolResult};
567/// use std::sync::Arc;
568///
569/// struct LocalProtocol;
570/// struct RemoteProtocol;
571///
572/// #[async_trait]
573/// impl ToolProtocol for LocalProtocol {
574///     async fn execute(
575///         &self,
576///         _tool_name: &str,
577///         _parameters: serde_json::Value,
578///     ) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
579///         Ok(ToolResult::success(serde_json::json!({"source": "local"})))
580///     }
581///
582///     async fn list_tools(
583///         &self,
584///     ) -> Result<Vec<ToolMetadata>, Box<dyn std::error::Error + Send + Sync>> {
585///         Ok(vec![])
586///     }
587/// }
588///
589/// #[async_trait]
590/// impl ToolProtocol for RemoteProtocol {
591///     async fn execute(
592///         &self,
593///         _tool_name: &str,
594///         _parameters: serde_json::Value,
595///     ) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
596///         Ok(ToolResult::success(serde_json::json!({"source": "remote"})))
597///     }
598///
599///     async fn list_tools(
600///         &self,
601///     ) -> Result<Vec<ToolMetadata>, Box<dyn std::error::Error + Send + Sync>> {
602///         Ok(vec![])
603///     }
604/// }
605///
606/// # async {
607/// let mut registry = ToolRegistry::empty();
608///
609/// // Add local tools
610/// registry.add_protocol(
611///     "local",
612///     Arc::new(LocalProtocol)
613/// ).await.ok();
614///
615/// // Add remote MCP server
616/// registry.add_protocol(
617///     "youtube",
618///     Arc::new(RemoteProtocol)
619/// ).await.ok();
620///
621/// // Agent transparently accesses both
622/// # };
623/// ```
624pub struct ToolRegistry {
625    /// All discovered tools from all protocols
626    tools: HashMap<String, Tool>,
627    /// Mapping of tool_name -> protocol_name for routing
628    tool_to_protocol: HashMap<String, String>,
629    /// All registered protocols
630    protocols: HashMap<String, Arc<dyn ToolProtocol>>,
631    /// Primary protocol (for backwards compatibility with single-protocol code)
632    primary_protocol: Option<Arc<dyn ToolProtocol>>,
633}
634
635impl ToolRegistry {
636    /// Build a registry powered by a single protocol implementation.
637    ///
638    /// This is the traditional single-protocol mode. Use `empty()` and `add_protocol()`
639    /// for multi-protocol support.
640    pub fn new(protocol: Arc<dyn ToolProtocol>) -> Self {
641        Self {
642            tools: HashMap::new(),
643            tool_to_protocol: HashMap::new(),
644            protocols: {
645                let mut m = HashMap::new();
646                m.insert("primary".to_string(), protocol.clone());
647                m
648            },
649            primary_protocol: Some(protocol),
650        }
651    }
652
653    /// Create an empty registry ready to accept multiple protocols.
654    ///
655    /// Use `add_protocol()` to register protocols.
656    pub fn empty() -> Self {
657        Self {
658            tools: HashMap::new(),
659            tool_to_protocol: HashMap::new(),
660            protocols: HashMap::new(),
661            primary_protocol: None,
662        }
663    }
664
665    /// Register a protocol and discover its tools.
666    ///
667    /// # Arguments
668    ///
669    /// * `protocol_name` - Unique identifier for this protocol (e.g., "local", "youtube", "github")
670    /// * `protocol` - The ToolProtocol implementation
671    ///
672    /// # Tool Discovery
673    ///
674    /// This method calls `protocol.list_tools()` to discover available tools
675    /// and automatically registers them in the registry.
676    ///
677    /// # Conflicts
678    ///
679    /// If a tool with the same name already exists, it will be replaced.
680    /// The new protocol's tool takes precedence.
681    ///
682    /// # Example
683    ///
684    /// ```ignore
685    /// use async_trait::async_trait;
686    /// use mcp::{ToolMetadata, ToolProtocol, ToolRegistry, ToolResult};
687    /// use std::sync::Arc;
688    ///
689    /// struct MemoryServerProtocol;
690    ///
691    /// #[async_trait]
692    /// impl ToolProtocol for MemoryServerProtocol {
693    ///     async fn execute(
694    ///         &self,
695    ///         _tool_name: &str,
696    ///         _parameters: serde_json::Value,
697    ///     ) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
698    ///         Ok(ToolResult::success(serde_json::json!({"ok": true})))
699    ///     }
700    ///
701    ///     async fn list_tools(
702    ///         &self,
703    ///     ) -> Result<Vec<ToolMetadata>, Box<dyn std::error::Error + Send + Sync>> {
704    ///         Ok(vec![])
705    ///     }
706    /// }
707    ///
708    /// # async {
709    /// let mut registry = ToolRegistry::empty();
710    /// registry.add_protocol(
711    ///     "memory_server",
712    ///     Arc::new(MemoryServerProtocol)
713    /// ).await?;
714    /// # Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
715    /// # };
716    /// ```
717    pub async fn add_protocol(
718        &mut self,
719        protocol_name: &str,
720        protocol: Arc<dyn ToolProtocol>,
721    ) -> Result<(), Box<dyn Error + Send + Sync>> {
722        // Discover tools from this protocol
723        let discovered_tools = protocol.list_tools().await?;
724
725        // Register the protocol
726        self.protocols
727            .insert(protocol_name.to_string(), protocol.clone());
728
729        // Register discovered tools
730        for tool_meta in discovered_tools {
731            let tool_name = tool_meta.name.clone();
732
733            // Create a Tool that routes through this protocol
734            let tool = Tool::new(
735                tool_name.clone(),
736                tool_meta.description.clone(),
737                protocol.clone(),
738            );
739
740            // Copy over any additional parameters and metadata
741            let mut tool = tool;
742            for param in &tool_meta.parameters {
743                tool = tool.with_parameter(param.clone());
744            }
745            for (key, value) in &tool_meta.protocol_metadata {
746                tool = tool.with_protocol_metadata(key.clone(), value.clone());
747            }
748
749            // Register the tool and its routing
750            self.tools.insert(tool_name.clone(), tool);
751            self.tool_to_protocol
752                .insert(tool_name, protocol_name.to_string());
753        }
754
755        Ok(())
756    }
757
758    /// Remove a protocol and all its tools from the registry.
759    pub fn remove_protocol(&mut self, protocol_name: &str) {
760        self.protocols.remove(protocol_name);
761
762        // Collect tool names to remove
763        let tools_to_remove: Vec<String> = self
764            .tool_to_protocol
765            .iter()
766            .filter(|(_, pn)| *pn == protocol_name)
767            .map(|(tn, _)| tn.clone())
768            .collect();
769
770        // Remove the tools
771        for tool_name in tools_to_remove {
772            self.tools.remove(&tool_name);
773            self.tool_to_protocol.remove(&tool_name);
774        }
775    }
776
777    /// Insert or replace a tool definition (for manual tool registration).
778    pub fn add_tool(&mut self, tool: Tool) {
779        self.tools.insert(tool.metadata.name.clone(), tool);
780    }
781
782    /// Remove a tool by name returning the owned entry if present.
783    pub fn remove_tool(&mut self, name: &str) -> Option<Tool> {
784        self.tool_to_protocol.remove(name);
785        self.tools.remove(name)
786    }
787
788    /// Borrow a tool by name.
789    pub fn get_tool(&self, name: &str) -> Option<&Tool> {
790        self.tools.get(name)
791    }
792
793    /// List metadata for registered tools (iteration order follows the underlying map).
794    pub fn list_tools(&self) -> Vec<&ToolMetadata> {
795        self.tools.values().map(|t| &t.metadata).collect()
796    }
797
798    /// Discover tools from the primary protocol (for single-protocol registries).
799    ///
800    /// This is useful after registering tools with the protocol to populate the registry.
801    /// For multi-protocol registries, use `add_protocol()` instead.
802    pub async fn discover_tools_from_primary(
803        &mut self,
804    ) -> Result<(), Box<dyn Error + Send + Sync>> {
805        if let Some(protocol) = &self.primary_protocol {
806            let discovered_tools = protocol.list_tools().await?;
807            for tool_meta in discovered_tools {
808                let tool_name = tool_meta.name.clone();
809                let tool = Tool::new(
810                    tool_name.clone(),
811                    tool_meta.description.clone(),
812                    protocol.clone(),
813                );
814
815                // Copy over parameters and metadata
816                let mut tool = tool;
817                for param in &tool_meta.parameters {
818                    tool = tool.with_parameter(param.clone());
819                }
820                for (key, value) in &tool_meta.protocol_metadata {
821                    tool = tool.with_protocol_metadata(key.clone(), value.clone());
822                }
823
824                self.tools.insert(tool_name.clone(), tool);
825                self.tool_to_protocol
826                    .insert(tool_name, "primary".to_string());
827            }
828            Ok(())
829        } else {
830            Err("No primary protocol available".into())
831        }
832    }
833
834    /// Get which protocol handles a specific tool.
835    pub fn get_tool_protocol(&self, tool_name: &str) -> Option<&str> {
836        self.tool_to_protocol.get(tool_name).map(|s| s.as_str())
837    }
838
839    /// Get all registered protocol names.
840    pub fn list_protocols(&self) -> Vec<&str> {
841        self.protocols.keys().map(|s| s.as_str()).collect()
842    }
843
844    /// Execute a named tool with serialized parameters.
845    pub async fn execute_tool(
846        &self,
847        tool_name: &str,
848        parameters: serde_json::Value,
849    ) -> Result<ToolResult, Box<dyn Error + Send + Sync>> {
850        let tool = self
851            .tools
852            .get(tool_name)
853            .ok_or_else(|| ToolError::NotFound(tool_name.to_string()))?;
854
855        tool.execute(parameters).await
856    }
857
858    /// Borrow the primary protocol implementation (for single-protocol mode).
859    ///
860    /// Returns None if registry was created with `empty()` or has multiple protocols.
861    pub fn protocol(&self) -> Option<&Arc<dyn ToolProtocol>> {
862        self.primary_protocol.as_ref()
863    }
864
865    /// Returns all registered tools as [`ToolDefinition`]s ready to pass to the LLM.
866    ///
867    /// Iterates over every tool in the registry, calling
868    /// [`ToolMetadata::to_tool_definition`] on each, and returns the results as a `Vec`.
869    /// An empty `Vec` is returned when no tools have been registered.
870    ///
871    /// # Example
872    ///
873    /// ```ignore
874    /// use async_trait::async_trait;
875    /// use mcp::{Tool, ToolDefinition, ToolMetadata, ToolProtocol, ToolRegistry, ToolResult};
876    /// use std::sync::Arc;
877    ///
878    /// struct DemoProtocol;
879    ///
880    /// #[async_trait]
881    /// impl ToolProtocol for DemoProtocol {
882    ///     async fn execute(
883    ///         &self,
884    ///         _tool_name: &str,
885    ///         _parameters: serde_json::Value,
886    ///     ) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
887    ///         Ok(ToolResult::success(serde_json::json!({"ok": true})))
888    ///     }
889    ///
890    ///     async fn list_tools(
891    ///         &self,
892    ///     ) -> Result<Vec<ToolMetadata>, Box<dyn std::error::Error + Send + Sync>> {
893    ///         Ok(vec![])
894    ///     }
895    /// }
896    ///
897    /// # #[tokio::main]
898    /// # async fn main() {
899    /// let protocol = Arc::new(DemoProtocol);
900    /// let mut registry = ToolRegistry::new(protocol.clone());
901    ///
902    /// let tool = Tool::new("calculator", "Evaluates math", protocol);
903    /// registry.add_tool(tool);
904    ///
905    /// let defs = registry.to_tool_definitions();
906    /// assert_eq!(defs.len(), 1);
907    /// assert_eq!(defs[0].name, "calculator");
908    /// # }
909    /// ```
910    pub fn to_tool_definitions(&self) -> Vec<ToolDefinition> {
911        self.tools
912            .values()
913            .map(|t| t.metadata.to_tool_definition())
914            .collect()
915    }
916}
917
918#[cfg(test)]
919mod tests {
920    use super::*;
921
922    struct MockProtocol;
923
924    #[async_trait]
925    impl ToolProtocol for MockProtocol {
926        async fn execute(
927            &self,
928            tool_name: &str,
929            _parameters: serde_json::Value,
930        ) -> Result<ToolResult, Box<dyn Error + Send + Sync>> {
931            Ok(ToolResult::success(serde_json::json!({
932                "tool": tool_name,
933                "result": "mock_result"
934            })))
935        }
936
937        async fn list_tools(&self) -> Result<Vec<ToolMetadata>, Box<dyn Error + Send + Sync>> {
938            Ok(vec![])
939        }
940
941        async fn get_tool_metadata(
942            &self,
943            _tool_name: &str,
944        ) -> Result<ToolMetadata, Box<dyn Error + Send + Sync>> {
945            Ok(ToolMetadata::new("mock_tool", "A mock tool"))
946        }
947
948        fn protocol_name(&self) -> &str {
949            "mock"
950        }
951    }
952
953    // ── Private-field tests (must stay inline) ────────────────────────────
954
955    #[tokio::test]
956    async fn test_get_tool_protocol() {
957        let protocol = Arc::new(MockProtocol);
958        let mut registry = ToolRegistry::empty();
959
960        registry
961            .add_protocol("local", protocol.clone())
962            .await
963            .unwrap();
964
965        let tool = Tool::new("calculator", "Performs calculations", protocol.clone());
966        registry.add_tool(tool);
967        registry
968            .tool_to_protocol
969            .insert("calculator".to_string(), "local".to_string());
970
971        assert_eq!(registry.get_tool_protocol("calculator"), Some("local"));
972        assert_eq!(registry.get_tool_protocol("nonexistent"), None);
973    }
974
975    #[tokio::test]
976    async fn test_remove_protocol_removes_tools() {
977        let protocol = Arc::new(MockProtocol);
978        let mut registry = ToolRegistry::empty();
979
980        registry
981            .add_protocol("protocol1", protocol.clone())
982            .await
983            .unwrap();
984
985        let tool1 = Tool::new("tool1", "First tool", protocol.clone());
986        registry.add_tool(tool1);
987        registry
988            .tool_to_protocol
989            .insert("tool1".to_string(), "protocol1".to_string());
990
991        let tool2 = Tool::new("tool2", "Second tool", protocol.clone());
992        registry.add_tool(tool2);
993        registry
994            .tool_to_protocol
995            .insert("tool2".to_string(), "protocol1".to_string());
996
997        assert_eq!(registry.list_tools().len(), 2);
998
999        registry.remove_protocol("protocol1");
1000
1001        assert_eq!(registry.list_tools().len(), 0);
1002        assert_eq!(registry.get_tool_protocol("tool1"), None);
1003        assert_eq!(registry.get_tool_protocol("tool2"), None);
1004    }
1005}