Skip to main content

hanzo_llm_mcp/
lib.rs

1//! Model Context Protocol (MCP) Client Implementation
2//!
3//! This crate provides a comprehensive client implementation for the Model Context Protocol (MCP),
4//! enabling AI assistants to connect to and interact with external tools and resources through
5//! standardized server interfaces.
6//!
7//! # Overview
8//!
9//! The MCP client supports multiple transport protocols and provides automatic tool discovery,
10//! registration, and execution. It seamlessly integrates with tool calling systems,
11//! allowing MCP tools to be used alongside built-in tools.
12//!
13//! # Features
14//!
15//! - **Multiple Transport Protocols**: HTTP, WebSocket, and Process-based connections
16//! - **Automatic Tool Discovery**: Discovers and registers tools from connected MCP servers
17//! - **Bearer Token Authentication**: Supports authentication for secured MCP servers
18//! - **Concurrent Tool Execution**: Handles multiple tool calls efficiently
19//! - **Resource Access**: Access to MCP server resources like files and data
20//! - **Tool Naming Prefix**: Avoid conflicts with customizable tool name prefixes
21//!
22//! # Transport Protocols
23//!
24//! ## HTTP Transport
25//!
26//! For MCP servers accessible via HTTP endpoints with JSON-RPC over HTTP.
27//! Supports both regular JSON responses and Server-Sent Events (SSE).
28//!
29//! ## WebSocket Transport
30//!
31//! For real-time bidirectional communication with MCP servers over WebSocket.
32//! Ideal for interactive applications requiring low-latency tool calls.
33//!
34//! ## Process Transport
35//!
36//! For local MCP servers running as separate processes, communicating via stdin/stdout
37//! using JSON-RPC messages.
38//!
39//! # Example Usage
40//!
41//! ## Simple Configuration
42//!
43//! ```rust,no_run
44//! use hanzo_llm_mcp::{McpClientConfig, McpServerConfig, McpServerSource, McpClient};
45//!
46//! #[tokio::main]
47//! async fn main() -> anyhow::Result<()> {
48//!     // Simple configuration with minimal settings
49//!     // Most fields use sensible defaults (enabled=true, UUID for id/prefix, no timeouts)
50//!     let config = McpClientConfig {
51//!         servers: vec![
52//!             McpServerConfig {
53//!                 name: "Hugging Face MCP Server".to_string(),
54//!                 source: McpServerSource::Http {
55//!                     url: "https://hf.co/mcp".to_string(),
56//!                     timeout_secs: None,
57//!                     headers: None,
58//!                 },
59//!                 bearer_token: Some("hf_xxx".to_string()),
60//!                 ..Default::default()
61//!             },
62//!         ],
63//!         ..Default::default()
64//!     };
65//!     
66//!     // Initialize MCP client
67//!     let mut client = McpClient::new(config);
68//!     client.initialize().await?;
69//!     
70//!     // Get tool callbacks for integration with model builder
71//!     let tool_callbacks = client.get_tool_callbacks_with_tools();
72//!     println!("Registered {} MCP tools", tool_callbacks.len());
73//!     
74//!     Ok(())
75//! }
76//! ```
77//!
78//! ## Advanced Configuration
79//!
80//! ```rust,no_run
81//! use hanzo_llm_mcp::{McpClientConfig, McpServerConfig, McpServerSource, McpClient};
82//! use std::collections::HashMap;
83//!
84//! #[tokio::main]
85//! async fn main() -> anyhow::Result<()> {
86//!     // Configure MCP client with multiple servers and custom settings
87//!     let config = McpClientConfig {
88//!         servers: vec![
89//!             // HTTP server with Bearer token
90//!             McpServerConfig {
91//!                 id: "web_search".to_string(),
92//!                 name: "Web Search MCP".to_string(),
93//!                 source: McpServerSource::Http {
94//!                     url: "https://api.example.com/mcp".to_string(),
95//!                     timeout_secs: Some(30),
96//!                     headers: None,
97//!                 },
98//!                 enabled: true,
99//!                 tool_prefix: Some("web".to_string()),
100//!                 resources: None,
101//!                 bearer_token: Some("your-api-token".to_string()),
102//!             },
103//!             // WebSocket server
104//!             McpServerConfig {
105//!                 id: "realtime_data".to_string(),
106//!                 name: "Real-time Data MCP".to_string(),
107//!                 source: McpServerSource::WebSocket {
108//!                     url: "wss://realtime.example.com/mcp".to_string(),
109//!                     timeout_secs: Some(60),
110//!                     headers: None,
111//!                 },
112//!                 enabled: true,
113//!                 tool_prefix: Some("rt".to_string()),
114//!                 resources: None,
115//!                 bearer_token: Some("ws-token".to_string()),
116//!             },
117//!             // Process-based server
118//!             McpServerConfig {
119//!                 id: "filesystem".to_string(),
120//!                 name: "Filesystem MCP".to_string(),
121//!                 source: McpServerSource::Process {
122//!                     command: "mcp-server-filesystem".to_string(),
123//!                     args: vec!["--root".to_string(), "/tmp".to_string()],
124//!                     work_dir: None,
125//!                     env: None,
126//!                 },
127//!                 enabled: true,
128//!                 tool_prefix: Some("fs".to_string()),
129//!                 resources: Some(vec!["file://**".to_string()]),
130//!                 bearer_token: None,
131//!             },
132//!         ],
133//!         auto_register_tools: true,
134//!         tool_timeout_secs: Some(30),
135//!         max_concurrent_calls: Some(5),
136//!     };
137//!     
138//!     // Initialize MCP client
139//!     let mut client = McpClient::new(config);
140//!     client.initialize().await?;
141//!     
142//!     // Get tool callbacks for integration with model builder
143//!     let tool_callbacks = client.get_tool_callbacks_with_tools();
144//!     println!("Registered {} MCP tools", tool_callbacks.len());
145//!     
146//!     Ok(())
147//! }
148//! ```
149
150pub mod client;
151pub mod tools;
152pub mod transport;
153pub mod types;
154
155pub use client::{McpClient, McpServerConnection};
156pub use tools::{
157    AgentPermission, AgentToolApprovalNotifier, AgentToolApprovalRequest, AgentToolKind,
158    AgentToolMetadata, AgentToolSource, CalledFunction, CodeExecutionApprovalNotifier,
159    CodeExecutionApprovalRequest, CodeExecutionPermission, Function, MultimodalToolCallback, Tool,
160    ToolCallContext, ToolCallback, ToolCallbackKind, ToolCallbackWithTool, ToolCallbacksWithTools,
161    ToolFile, ToolOutput, ToolType,
162};
163pub use types::McpToolResult;
164
165pub use rust_mcp_schema;
166
167use serde::{Deserialize, Serialize};
168use std::collections::HashMap;
169use uuid::Uuid;
170
171/// Supported MCP server transport sources
172///
173/// Defines the different ways to connect to MCP servers, each optimized for
174/// specific use cases and deployment scenarios.
175#[derive(Debug, Clone, Serialize, Deserialize)]
176#[serde(tag = "type")]
177pub enum McpServerSource {
178    /// HTTP-based MCP server using JSON-RPC over HTTP
179    ///
180    /// Best for: Public APIs, RESTful services, servers behind load balancers
181    /// Features: SSE support, standard HTTP semantics, easy debugging
182    Http {
183        /// Base URL of the MCP server (http:// or https://)
184        url: String,
185        /// Optional timeout in seconds for HTTP requests
186        /// Defaults to no timeout if not specified.
187        timeout_secs: Option<u64>,
188        /// Optional headers to include in requests (e.g., API keys, custom headers)
189        headers: Option<HashMap<String, String>>,
190    },
191    /// Local process-based MCP server using stdin/stdout communication
192    ///
193    /// Best for: Local tools, development servers, sandboxed environments
194    /// Features: Process isolation, no network overhead, easy deployment
195    Process {
196        /// Command to execute (e.g., "mcp-server-filesystem")
197        command: String,
198        /// Arguments to pass to the command
199        args: Vec<String>,
200        /// Optional working directory for the process
201        work_dir: Option<String>,
202        /// Optional environment variables for the process
203        env: Option<HashMap<String, String>>,
204    },
205    /// WebSocket-based MCP server for real-time bidirectional communication
206    ///
207    /// Best for: Interactive applications, real-time data, low-latency requirements
208    /// Features: Persistent connections, server-initiated notifications, minimal overhead
209    WebSocket {
210        /// WebSocket URL (ws:// or wss://)
211        url: String,
212        /// Optional timeout in seconds for connection establishment
213        /// Defaults to no timeout if not specified.
214        timeout_secs: Option<u64>,
215        /// Optional headers for the WebSocket handshake
216        headers: Option<HashMap<String, String>>,
217    },
218}
219
220/// Configuration for MCP client integration
221///
222/// This structure defines how the MCP client should connect to and manage
223/// multiple MCP servers, including authentication, tool registration, and
224/// execution policies.
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct McpClientConfig {
227    /// List of MCP servers to connect to
228    pub servers: Vec<McpServerConfig>,
229    /// Whether to automatically register discovered tools with the model
230    ///
231    /// When enabled, tools from MCP servers are automatically converted to
232    /// the internal Tool format and registered for automatic tool calling.
233    pub auto_register_tools: bool,
234    /// Timeout for individual tool execution in seconds
235    ///
236    /// Controls how long to wait for a tool call to complete before timing out.
237    /// Defaults to 30 seconds if not specified.
238    pub tool_timeout_secs: Option<u64>,
239    /// Maximum number of concurrent tool calls across all MCP servers
240    ///
241    /// Limits resource usage and prevents overwhelming servers with too many
242    /// simultaneous requests. Defaults to 10 if not specified.
243    pub max_concurrent_calls: Option<usize>,
244}
245
246/// Configuration for an individual MCP server
247///
248/// Defines connection parameters, authentication, and tool management
249/// settings for a single MCP server instance.
250#[derive(Debug, Clone, Serialize, Deserialize)]
251#[serde(default)]
252pub struct McpServerConfig {
253    /// Unique identifier for this server
254    ///
255    /// Used internally to track connections and route tool calls.
256    /// Must be unique across all servers in a single MCP client configuration.
257    /// Defaults to a UUID if not specified.
258    #[serde(default = "generate_uuid")]
259    pub id: String,
260    /// Human-readable name for this server
261    ///
262    /// Used for logging, debugging, and user-facing displays.
263    pub name: String,
264    /// Transport-specific connection configuration
265    pub source: McpServerSource,
266    /// Whether this server should be activated
267    ///
268    /// Disabled servers are ignored during client initialization.
269    /// Defaults to true if not specified.
270    #[serde(default = "default_true")]
271    pub enabled: bool,
272    /// Optional prefix to add to all tool names from this server
273    ///
274    /// Helps prevent naming conflicts when multiple servers provide
275    /// tools with similar names. For example, with prefix "web",
276    /// a tool named "search" becomes "web_search".
277    /// Defaults to a UUID-based prefix if not specified.
278    #[serde(default = "generate_uuid_prefix")]
279    pub tool_prefix: Option<String>,
280    /// Optional resource URI patterns this server provides
281    ///
282    /// Used for resource discovery and subscription.
283    /// Supports glob patterns like "file://**" for filesystem access.
284    pub resources: Option<Vec<String>>,
285    /// Optional Bearer token for authentication
286    ///
287    /// Automatically included as `Authorization: Bearer <token>` header
288    /// for HTTP and WebSocket connections. Process connections typically
289    /// don't require authentication tokens.
290    pub bearer_token: Option<String>,
291}
292
293/// Information about a tool discovered from an MCP server
294#[derive(Debug, Clone)]
295pub struct McpToolInfo {
296    /// Name of the tool as reported by the MCP server
297    pub name: String,
298    /// Optional human-readable description of what the tool does
299    pub description: Option<String>,
300    /// JSON schema describing the tool's input parameters
301    pub input_schema: serde_json::Value,
302    /// ID of the server this tool comes from
303    ///
304    /// Used to route tool calls to the correct MCP server connection.
305    pub server_id: String,
306    /// Display name of the server for logging and debugging
307    pub server_name: String,
308}
309
310impl Default for McpClientConfig {
311    fn default() -> Self {
312        // `None` keeps the effective defaults aligned with the client's `unwrap_or` fallbacks
313        // (30s timeout, 10 concurrent calls) and with `serde_json::from_str::<McpClientConfig>("{}")`.
314        Self {
315            servers: Vec::new(),
316            auto_register_tools: true,
317            tool_timeout_secs: None,
318            max_concurrent_calls: None,
319        }
320    }
321}
322
323fn generate_uuid() -> String {
324    Uuid::new_v4().to_string()
325}
326
327fn default_true() -> bool {
328    true
329}
330
331fn generate_uuid_prefix() -> Option<String> {
332    Some(format!("mcp_{}", Uuid::new_v4().simple()))
333}
334
335impl Default for McpServerConfig {
336    fn default() -> Self {
337        Self {
338            id: generate_uuid(),
339            name: String::new(),
340            source: McpServerSource::Http {
341                url: String::new(),
342                timeout_secs: None,
343                headers: None,
344            },
345            enabled: true,
346            tool_prefix: generate_uuid_prefix(),
347            resources: None,
348            bearer_token: None,
349        }
350    }
351}