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}