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}