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
351    /// [`ClientWrapper::send_message`](crate::client_wrapper::ClientWrapper::send_message)
352    /// as part of the `tools` slice.  Parameters marked `required` are collected into the
353    /// JSON Schema `"required"` array automatically.
354    ///
355    /// # Example
356    ///
357    /// ```rust
358    /// use mcp::{ToolMetadata, ToolParameter, ToolParameterType};
359    ///
360    /// let meta = ToolMetadata::new("calculator", "Evaluates a math expression")
361    ///     .with_parameter(
362    ///         ToolParameter::new("expression", ToolParameterType::String)
363    ///             .with_description("The expression to evaluate")
364    ///             .required(),
365    ///     );
366    ///
367    /// let def = meta.to_tool_definition();
368    /// assert_eq!(def.name, "calculator");
369    /// let schema = &def.parameters_schema;
370    /// assert_eq!(schema["type"], "object");
371    /// assert!(schema["properties"]["expression"].is_object());
372    /// ```
373    pub fn to_tool_definition(&self) -> ToolDefinition {
374        let mut properties = serde_json::Map::new();
375        let mut required: Vec<String> = Vec::new();
376
377        for param in &self.parameters {
378            properties.insert(param.name.clone(), param.to_json_schema());
379            if param.required {
380                required.push(param.name.clone());
381            }
382        }
383
384        let parameters_schema = serde_json::json!({
385            "type": "object",
386            "properties": properties,
387            "required": required
388        });
389
390        ToolDefinition {
391            name: self.name.clone(),
392            description: self.description.clone(),
393            parameters_schema,
394        }
395    }
396}
397
398/// Trait for implementing tool execution protocols
399#[async_trait]
400pub trait ToolProtocol: Send + Sync {
401    /// Execute a tool with the given parameters
402    async fn execute(
403        &self,
404        tool_name: &str,
405        parameters: serde_json::Value,
406    ) -> Result<ToolResult, Box<dyn Error + Send + Sync>>;
407
408    /// Get metadata about available tools
409    async fn list_tools(&self) -> Result<Vec<ToolMetadata>, Box<dyn Error + Send + Sync>>;
410
411    /// Get metadata about a specific tool
412    async fn get_tool_metadata(
413        &self,
414        tool_name: &str,
415    ) -> Result<ToolMetadata, Box<dyn Error + Send + Sync>>;
416
417    /// Protocol identifier (e.g., "mcp", "custom", "openai-functions")
418    fn protocol_name(&self) -> &str;
419
420    /// Initialize/connect to the tool protocol
421    async fn initialize(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
422        Ok(())
423    }
424
425    /// Cleanup/disconnect from the tool protocol
426    async fn shutdown(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
427        Ok(())
428    }
429
430    /// List available resources (MCP Resource support)
431    ///
432    /// Resources are application-provided contextual data that agents can read.
433    /// This method is optional and defaults to returning an empty list.
434    async fn list_resources(&self) -> Result<Vec<ResourceMetadata>, Box<dyn Error + Send + Sync>> {
435        Ok(Vec::new())
436    }
437
438    /// Read the content of a resource by URI (MCP Resource support)
439    ///
440    /// This method is optional and defaults to returning NotFound.
441    async fn read_resource(&self, uri: &str) -> Result<String, Box<dyn Error + Send + Sync>> {
442        Err(format!("Resource not found: {}", uri).into())
443    }
444
445    /// Check if this protocol supports resources
446    fn supports_resources(&self) -> bool {
447        false
448    }
449}
450
451/// Error types for tool operations
452#[derive(Debug, Clone)]
453pub enum ToolError {
454    /// Requested tool is not registered in the current registry/protocol.
455    NotFound(String),
456    /// Tool execution completed with an application level failure.
457    ExecutionFailed(String),
458    /// The provided JSON parameters failed validation or deserialization.
459    InvalidParameters(String),
460    /// A lower level protocol/transport error occurred.
461    ProtocolError(String),
462}
463
464impl fmt::Display for ToolError {
465    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
466        match self {
467            ToolError::NotFound(name) => write!(f, "Tool not found: {}", name),
468            ToolError::ExecutionFailed(msg) => write!(f, "Tool execution failed: {}", msg),
469            ToolError::InvalidParameters(msg) => write!(f, "Invalid parameters: {}", msg),
470            ToolError::ProtocolError(msg) => write!(f, "Protocol error: {}", msg),
471        }
472    }
473}
474
475impl Error for ToolError {}
476
477/// A tool that can be used by agents
478pub struct Tool {
479    /// Metadata describing the tool interface.
480    metadata: ToolMetadata,
481    /// Underlying protocol implementation that actually executes the tool.
482    protocol: Arc<dyn ToolProtocol>,
483}
484
485impl Tool {
486    /// Create a new tool bound to the supplied protocol implementation.
487    pub fn new(
488        name: impl Into<String>,
489        description: impl Into<String>,
490        protocol: Arc<dyn ToolProtocol>,
491    ) -> Self {
492        Self {
493            metadata: ToolMetadata::new(name, description),
494            protocol,
495        }
496    }
497
498    /// Add a parameter definition to the tool builder.
499    pub fn with_parameter(mut self, param: ToolParameter) -> Self {
500        self.metadata.parameters.push(param);
501        self
502    }
503
504    /// Attach protocol specific metadata to the tool builder.
505    pub fn with_protocol_metadata(
506        mut self,
507        key: impl Into<String>,
508        value: serde_json::Value,
509    ) -> Self {
510        self.metadata.protocol_metadata.insert(key.into(), value);
511        self
512    }
513
514    /// Borrow the static metadata for the tool.
515    pub fn metadata(&self) -> &ToolMetadata {
516        &self.metadata
517    }
518
519    /// Execute the tool using the configured protocol.
520    pub async fn execute(
521        &self,
522        parameters: serde_json::Value,
523    ) -> Result<ToolResult, Box<dyn Error + Send + Sync>> {
524        self.protocol.execute(&self.metadata.name, parameters).await
525    }
526}
527
528/// Registry for managing tools available to agents
529///
530/// Supports single or multiple tool protocols, enabling agents to transparently
531/// access tools from multiple sources (local functions, MCP servers, etc.)
532///
533/// # Single Protocol
534///
535/// ```ignore
536/// use async_trait::async_trait;
537/// use mcp::{ToolMetadata, ToolProtocol, ToolRegistry, ToolResult};
538/// use std::sync::Arc;
539///
540/// struct DemoProtocol;
541///
542/// #[async_trait]
543/// impl ToolProtocol for DemoProtocol {
544///     async fn execute(
545///         &self,
546///         _tool_name: &str,
547///         _parameters: serde_json::Value,
548///     ) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
549///         Ok(ToolResult::success(serde_json::json!({"ok": true})))
550///     }
551///
552///     async fn list_tools(
553///         &self,
554///     ) -> Result<Vec<ToolMetadata>, Box<dyn std::error::Error + Send + Sync>> {
555///         Ok(vec![])
556///     }
557/// }
558///
559/// let protocol = Arc::new(DemoProtocol);
560/// let registry = ToolRegistry::new(protocol);
561/// ```
562///
563/// # Multiple Protocols
564///
565/// ```ignore
566/// use async_trait::async_trait;
567/// use mcp::{ToolMetadata, ToolProtocol, ToolRegistry, ToolResult};
568/// use std::sync::Arc;
569///
570/// struct LocalProtocol;
571/// struct RemoteProtocol;
572///
573/// #[async_trait]
574/// impl ToolProtocol for LocalProtocol {
575///     async fn execute(
576///         &self,
577///         _tool_name: &str,
578///         _parameters: serde_json::Value,
579///     ) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
580///         Ok(ToolResult::success(serde_json::json!({"source": "local"})))
581///     }
582///
583///     async fn list_tools(
584///         &self,
585///     ) -> Result<Vec<ToolMetadata>, Box<dyn std::error::Error + Send + Sync>> {
586///         Ok(vec![])
587///     }
588/// }
589///
590/// #[async_trait]
591/// impl ToolProtocol for RemoteProtocol {
592///     async fn execute(
593///         &self,
594///         _tool_name: &str,
595///         _parameters: serde_json::Value,
596///     ) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
597///         Ok(ToolResult::success(serde_json::json!({"source": "remote"})))
598///     }
599///
600///     async fn list_tools(
601///         &self,
602///     ) -> Result<Vec<ToolMetadata>, Box<dyn std::error::Error + Send + Sync>> {
603///         Ok(vec![])
604///     }
605/// }
606///
607/// # async {
608/// let mut registry = ToolRegistry::empty();
609///
610/// // Add local tools
611/// registry.add_protocol(
612///     "local",
613///     Arc::new(LocalProtocol)
614/// ).await.ok();
615///
616/// // Add remote MCP server
617/// registry.add_protocol(
618///     "youtube",
619///     Arc::new(RemoteProtocol)
620/// ).await.ok();
621///
622/// // Agent transparently accesses both
623/// # };
624/// ```
625pub struct ToolRegistry {
626    /// All discovered tools from all protocols
627    tools: HashMap<String, Tool>,
628    /// Mapping of tool_name -> protocol_name for routing
629    tool_to_protocol: HashMap<String, String>,
630    /// All registered protocols
631    protocols: HashMap<String, Arc<dyn ToolProtocol>>,
632    /// Primary protocol (for backwards compatibility with single-protocol code)
633    primary_protocol: Option<Arc<dyn ToolProtocol>>,
634}
635
636impl ToolRegistry {
637    /// Build a registry powered by a single protocol implementation.
638    ///
639    /// This is the traditional single-protocol mode. Use `empty()` and `add_protocol()`
640    /// for multi-protocol support.
641    pub fn new(protocol: Arc<dyn ToolProtocol>) -> Self {
642        Self {
643            tools: HashMap::new(),
644            tool_to_protocol: HashMap::new(),
645            protocols: {
646                let mut m = HashMap::new();
647                m.insert("primary".to_string(), protocol.clone());
648                m
649            },
650            primary_protocol: Some(protocol),
651        }
652    }
653
654    /// Create an empty registry ready to accept multiple protocols.
655    ///
656    /// Use `add_protocol()` to register protocols.
657    pub fn empty() -> Self {
658        Self {
659            tools: HashMap::new(),
660            tool_to_protocol: HashMap::new(),
661            protocols: HashMap::new(),
662            primary_protocol: None,
663        }
664    }
665
666    /// Register a protocol and discover its tools.
667    ///
668    /// # Arguments
669    ///
670    /// * `protocol_name` - Unique identifier for this protocol (e.g., "local", "youtube", "github")
671    /// * `protocol` - The ToolProtocol implementation
672    ///
673    /// # Tool Discovery
674    ///
675    /// This method calls `protocol.list_tools()` to discover available tools
676    /// and automatically registers them in the registry.
677    ///
678    /// # Conflicts
679    ///
680    /// If a tool with the same name already exists, it will be replaced.
681    /// The new protocol's tool takes precedence.
682    ///
683    /// # Example
684    ///
685    /// ```ignore
686    /// use async_trait::async_trait;
687    /// use mcp::{ToolMetadata, ToolProtocol, ToolRegistry, ToolResult};
688    /// use std::sync::Arc;
689    ///
690    /// struct MemoryServerProtocol;
691    ///
692    /// #[async_trait]
693    /// impl ToolProtocol for MemoryServerProtocol {
694    ///     async fn execute(
695    ///         &self,
696    ///         _tool_name: &str,
697    ///         _parameters: serde_json::Value,
698    ///     ) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
699    ///         Ok(ToolResult::success(serde_json::json!({"ok": true})))
700    ///     }
701    ///
702    ///     async fn list_tools(
703    ///         &self,
704    ///     ) -> Result<Vec<ToolMetadata>, Box<dyn std::error::Error + Send + Sync>> {
705    ///         Ok(vec![])
706    ///     }
707    /// }
708    ///
709    /// # async {
710    /// let mut registry = ToolRegistry::empty();
711    /// registry.add_protocol(
712    ///     "memory_server",
713    ///     Arc::new(MemoryServerProtocol)
714    /// ).await?;
715    /// # Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
716    /// # };
717    /// ```
718    pub async fn add_protocol(
719        &mut self,
720        protocol_name: &str,
721        protocol: Arc<dyn ToolProtocol>,
722    ) -> Result<(), Box<dyn Error + Send + Sync>> {
723        // Discover tools from this protocol
724        let discovered_tools = protocol.list_tools().await?;
725
726        // Register the protocol
727        self.protocols
728            .insert(protocol_name.to_string(), protocol.clone());
729
730        // Register discovered tools
731        for tool_meta in discovered_tools {
732            let tool_name = tool_meta.name.clone();
733
734            // Create a Tool that routes through this protocol
735            let tool = Tool::new(
736                tool_name.clone(),
737                tool_meta.description.clone(),
738                protocol.clone(),
739            );
740
741            // Copy over any additional parameters and metadata
742            let mut tool = tool;
743            for param in &tool_meta.parameters {
744                tool = tool.with_parameter(param.clone());
745            }
746            for (key, value) in &tool_meta.protocol_metadata {
747                tool = tool.with_protocol_metadata(key.clone(), value.clone());
748            }
749
750            // Register the tool and its routing
751            self.tools.insert(tool_name.clone(), tool);
752            self.tool_to_protocol
753                .insert(tool_name, protocol_name.to_string());
754        }
755
756        Ok(())
757    }
758
759    /// Remove a protocol and all its tools from the registry.
760    pub fn remove_protocol(&mut self, protocol_name: &str) {
761        self.protocols.remove(protocol_name);
762
763        // Collect tool names to remove
764        let tools_to_remove: Vec<String> = self
765            .tool_to_protocol
766            .iter()
767            .filter(|(_, pn)| *pn == protocol_name)
768            .map(|(tn, _)| tn.clone())
769            .collect();
770
771        // Remove the tools
772        for tool_name in tools_to_remove {
773            self.tools.remove(&tool_name);
774            self.tool_to_protocol.remove(&tool_name);
775        }
776    }
777
778    /// Insert or replace a tool definition (for manual tool registration).
779    pub fn add_tool(&mut self, tool: Tool) {
780        self.tools.insert(tool.metadata.name.clone(), tool);
781    }
782
783    /// Remove a tool by name returning the owned entry if present.
784    pub fn remove_tool(&mut self, name: &str) -> Option<Tool> {
785        self.tool_to_protocol.remove(name);
786        self.tools.remove(name)
787    }
788
789    /// Borrow a tool by name.
790    pub fn get_tool(&self, name: &str) -> Option<&Tool> {
791        self.tools.get(name)
792    }
793
794    /// List metadata for registered tools (iteration order follows the underlying map).
795    pub fn list_tools(&self) -> Vec<&ToolMetadata> {
796        self.tools.values().map(|t| &t.metadata).collect()
797    }
798
799    /// Discover tools from the primary protocol (for single-protocol registries).
800    ///
801    /// This is useful after registering tools with the protocol to populate the registry.
802    /// For multi-protocol registries, use `add_protocol()` instead.
803    pub async fn discover_tools_from_primary(
804        &mut self,
805    ) -> Result<(), Box<dyn Error + Send + Sync>> {
806        if let Some(protocol) = &self.primary_protocol {
807            let discovered_tools = protocol.list_tools().await?;
808            for tool_meta in discovered_tools {
809                let tool_name = tool_meta.name.clone();
810                let tool = Tool::new(
811                    tool_name.clone(),
812                    tool_meta.description.clone(),
813                    protocol.clone(),
814                );
815
816                // Copy over parameters and metadata
817                let mut tool = tool;
818                for param in &tool_meta.parameters {
819                    tool = tool.with_parameter(param.clone());
820                }
821                for (key, value) in &tool_meta.protocol_metadata {
822                    tool = tool.with_protocol_metadata(key.clone(), value.clone());
823                }
824
825                self.tools.insert(tool_name.clone(), tool);
826                self.tool_to_protocol
827                    .insert(tool_name, "primary".to_string());
828            }
829            Ok(())
830        } else {
831            Err("No primary protocol available".into())
832        }
833    }
834
835    /// Get which protocol handles a specific tool.
836    pub fn get_tool_protocol(&self, tool_name: &str) -> Option<&str> {
837        self.tool_to_protocol.get(tool_name).map(|s| s.as_str())
838    }
839
840    /// Get all registered protocol names.
841    pub fn list_protocols(&self) -> Vec<&str> {
842        self.protocols.keys().map(|s| s.as_str()).collect()
843    }
844
845    /// Execute a named tool with serialized parameters.
846    pub async fn execute_tool(
847        &self,
848        tool_name: &str,
849        parameters: serde_json::Value,
850    ) -> Result<ToolResult, Box<dyn Error + Send + Sync>> {
851        let tool = self
852            .tools
853            .get(tool_name)
854            .ok_or_else(|| ToolError::NotFound(tool_name.to_string()))?;
855
856        tool.execute(parameters).await
857    }
858
859    /// Borrow the primary protocol implementation (for single-protocol mode).
860    ///
861    /// Returns None if registry was created with `empty()` or has multiple protocols.
862    pub fn protocol(&self) -> Option<&Arc<dyn ToolProtocol>> {
863        self.primary_protocol.as_ref()
864    }
865
866    /// Returns all registered tools as [`ToolDefinition`]s ready to pass to the LLM.
867    ///
868    /// Iterates over every tool in the registry, calling
869    /// [`ToolMetadata::to_tool_definition`] on each, and returns the results as a `Vec`.
870    /// An empty `Vec` is returned when no tools have been registered.
871    ///
872    /// # Example
873    ///
874    /// ```ignore
875    /// use async_trait::async_trait;
876    /// use mcp::{Tool, ToolDefinition, ToolMetadata, ToolProtocol, ToolRegistry, ToolResult};
877    /// use std::sync::Arc;
878    ///
879    /// struct DemoProtocol;
880    ///
881    /// #[async_trait]
882    /// impl ToolProtocol for DemoProtocol {
883    ///     async fn execute(
884    ///         &self,
885    ///         _tool_name: &str,
886    ///         _parameters: serde_json::Value,
887    ///     ) -> Result<ToolResult, Box<dyn std::error::Error + Send + Sync>> {
888    ///         Ok(ToolResult::success(serde_json::json!({"ok": true})))
889    ///     }
890    ///
891    ///     async fn list_tools(
892    ///         &self,
893    ///     ) -> Result<Vec<ToolMetadata>, Box<dyn std::error::Error + Send + Sync>> {
894    ///         Ok(vec![])
895    ///     }
896    /// }
897    ///
898    /// # #[tokio::main]
899    /// # async fn main() {
900    /// let protocol = Arc::new(DemoProtocol);
901    /// let mut registry = ToolRegistry::new(protocol.clone());
902    ///
903    /// let tool = Tool::new("calculator", "Evaluates math", protocol);
904    /// registry.add_tool(tool);
905    ///
906    /// let defs = registry.to_tool_definitions();
907    /// assert_eq!(defs.len(), 1);
908    /// assert_eq!(defs[0].name, "calculator");
909    /// # }
910    /// ```
911    pub fn to_tool_definitions(&self) -> Vec<ToolDefinition> {
912        self.tools
913            .values()
914            .map(|t| t.metadata.to_tool_definition())
915            .collect()
916    }
917}
918
919#[cfg(test)]
920mod tests {
921    use super::*;
922
923    struct MockProtocol;
924
925    #[async_trait]
926    impl ToolProtocol for MockProtocol {
927        async fn execute(
928            &self,
929            tool_name: &str,
930            _parameters: serde_json::Value,
931        ) -> Result<ToolResult, Box<dyn Error + Send + Sync>> {
932            Ok(ToolResult::success(serde_json::json!({
933                "tool": tool_name,
934                "result": "mock_result"
935            })))
936        }
937
938        async fn list_tools(&self) -> Result<Vec<ToolMetadata>, Box<dyn Error + Send + Sync>> {
939            Ok(vec![])
940        }
941
942        async fn get_tool_metadata(
943            &self,
944            _tool_name: &str,
945        ) -> Result<ToolMetadata, Box<dyn Error + Send + Sync>> {
946            Ok(ToolMetadata::new("mock_tool", "A mock tool"))
947        }
948
949        fn protocol_name(&self) -> &str {
950            "mock"
951        }
952    }
953
954    // ── Private-field tests (must stay inline) ────────────────────────────
955
956    #[tokio::test]
957    async fn test_get_tool_protocol() {
958        let protocol = Arc::new(MockProtocol);
959        let mut registry = ToolRegistry::empty();
960
961        registry
962            .add_protocol("local", protocol.clone())
963            .await
964            .unwrap();
965
966        let tool = Tool::new("calculator", "Performs calculations", protocol.clone());
967        registry.add_tool(tool);
968        registry
969            .tool_to_protocol
970            .insert("calculator".to_string(), "local".to_string());
971
972        assert_eq!(registry.get_tool_protocol("calculator"), Some("local"));
973        assert_eq!(registry.get_tool_protocol("nonexistent"), None);
974    }
975
976    #[tokio::test]
977    async fn test_remove_protocol_removes_tools() {
978        let protocol = Arc::new(MockProtocol);
979        let mut registry = ToolRegistry::empty();
980
981        registry
982            .add_protocol("protocol1", protocol.clone())
983            .await
984            .unwrap();
985
986        let tool1 = Tool::new("tool1", "First tool", protocol.clone());
987        registry.add_tool(tool1);
988        registry
989            .tool_to_protocol
990            .insert("tool1".to_string(), "protocol1".to_string());
991
992        let tool2 = Tool::new("tool2", "Second tool", protocol.clone());
993        registry.add_tool(tool2);
994        registry
995            .tool_to_protocol
996            .insert("tool2".to_string(), "protocol1".to_string());
997
998        assert_eq!(registry.list_tools().len(), 2);
999
1000        registry.remove_protocol("protocol1");
1001
1002        assert_eq!(registry.list_tools().len(), 0);
1003        assert_eq!(registry.get_tool_protocol("tool1"), None);
1004        assert_eq!(registry.get_tool_protocol("tool2"), None);
1005    }
1006}