Skip to main content

mofa_kernel/agent/components/
mcp.rs

1//! MCP (Model Context Protocol) 组件
2//!
3//! 定义 MCP 客户端接口,用于连接外部 MCP 服务器并使用其工具。
4//!
5//! # 概述
6//!
7//! MCP 是 Anthropic 提出的标准化协议,允许 AI Agent 与外部工具服务器通信。
8//! 本模块定义了 MoFA 框架中的 MCP 客户端抽象。
9//!
10//! # 架构
11//!
12//! ```text
13//! ┌─────────────────────────────────────────────┐
14//! │              MoFA Agent                      │
15//! │  ┌─────────────────────────────────────────┐│
16//! │  │          ToolRegistry                    ││
17//! │  │  ┌───────────┐  ┌───────────────────┐   ││
18//! │  │  │ Built-in  │  │  McpToolAdapter   │   ││
19//! │  │  │   Tools   │  │  (per MCP tool)   │   ││
20//! │  │  └───────────┘  └────────┬──────────┘   ││
21//! │  └──────────────────────────┼──────────────┘│
22//! └─────────────────────────────┼───────────────┘
23//!                               │
24//!                    ┌──────────▼──────────┐
25//!                    │    McpClient        │
26//!                    │  (manages MCP       │
27//!                    │   connections)      │
28//!                    └──────────┬──────────┘
29//!                               │
30//!              ┌────────────────┼────────────────┐
31//!              ▼                ▼                 ▼
32//!     ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
33//!     │ MCP Server A │ │ MCP Server B │ │ MCP Server C │
34//!     │  (stdio)     │ │   (HTTP)     │ │  (stdio)     │
35//!     └──────────────┘ └──────────────┘ └──────────────┘
36//! ```
37
38use async_trait::async_trait;
39use serde::{Deserialize, Serialize};
40use std::collections::HashMap;
41
42// ============================================================================
43// MCP Transport Configuration
44// ============================================================================
45
46/// MCP 服务器传输配置
47///
48/// 定义如何连接到 MCP 服务器
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub enum McpTransportConfig {
51    /// 通过标准输入输出连接 (启动子进程)
52    ///
53    /// # 示例
54    ///
55    /// ```rust,ignore
56    /// let config = McpTransportConfig::Stdio {
57    ///     command: "npx".to_string(),
58    ///     args: vec!["-y".to_string(), "@modelcontextprotocol/server-github".to_string()],
59    ///     env: HashMap::from([("GITHUB_TOKEN".to_string(), "ghp_xxx".to_string())]),
60    /// };
61    /// ```
62    Stdio {
63        /// 可执行命令
64        command: String,
65        /// 命令参数
66        args: Vec<String>,
67        /// 环境变量
68        env: HashMap<String, String>,
69    },
70
71    /// 通过 HTTP/SSE 连接
72    ///
73    /// # 示例
74    ///
75    /// ```rust,ignore
76    /// let config = McpTransportConfig::Http {
77    ///     url: "http://localhost:8080/mcp".to_string(),
78    /// };
79    /// ```
80    Http {
81        /// 服务器 URL
82        url: String,
83    },
84}
85
86// ============================================================================
87// MCP Server Configuration
88// ============================================================================
89
90/// MCP 服务器配置
91///
92/// 定义单个 MCP 服务器的完整配置
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct McpServerConfig {
95    /// 服务器名称 (用于标识)
96    pub name: String,
97    /// 传输配置
98    pub transport: McpTransportConfig,
99    /// 是否自动连接
100    pub auto_connect: bool,
101}
102
103impl McpServerConfig {
104    /// 创建 Stdio 类型的 MCP 服务器配置
105    pub fn stdio(
106        name: impl Into<String>,
107        command: impl Into<String>,
108        args: Vec<String>,
109    ) -> Self {
110        Self {
111            name: name.into(),
112            transport: McpTransportConfig::Stdio {
113                command: command.into(),
114                args,
115                env: HashMap::new(),
116            },
117            auto_connect: true,
118        }
119    }
120
121    /// 创建 HTTP 类型的 MCP 服务器配置
122    pub fn http(name: impl Into<String>, url: impl Into<String>) -> Self {
123        Self {
124            name: name.into(),
125            transport: McpTransportConfig::Http { url: url.into() },
126            auto_connect: true,
127        }
128    }
129
130    /// 设置环境变量 (仅对 Stdio 有效)
131    pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
132        if let McpTransportConfig::Stdio { ref mut env, .. } = self.transport {
133            env.insert(key.into(), value.into());
134        }
135        self
136    }
137
138    /// 设置是否自动连接
139    pub fn with_auto_connect(mut self, auto_connect: bool) -> Self {
140        self.auto_connect = auto_connect;
141        self
142    }
143}
144
145// ============================================================================
146// MCP Tool Information
147// ============================================================================
148
149/// MCP 工具信息
150///
151/// 从 MCP 服务器发现的工具元信息
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct McpToolInfo {
154    /// 工具名称
155    pub name: String,
156    /// 工具描述
157    pub description: String,
158    /// 输入参数 JSON Schema
159    pub input_schema: serde_json::Value,
160}
161
162// ============================================================================
163// MCP Server Information
164// ============================================================================
165
166/// MCP 服务器信息
167///
168/// MCP 服务器在初始化握手时返回的元信息
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct McpServerInfo {
171    /// 服务器名称
172    pub name: String,
173    /// 服务器版本
174    pub version: String,
175    /// 服务器说明
176    pub instructions: Option<String>,
177}
178
179// ============================================================================
180// MCP Client Trait
181// ============================================================================
182
183/// MCP 客户端 Trait
184///
185/// 定义与 MCP 服务器通信的接口。
186/// 具体实现在 `mofa-foundation` 层。
187///
188/// # 示例
189///
190/// ```rust,ignore
191/// use mofa_kernel::agent::components::mcp::*;
192///
193/// let config = McpServerConfig::stdio(
194///     "github",
195///     "npx",
196///     vec!["-y".into(), "@modelcontextprotocol/server-github".into()],
197/// );
198///
199/// let mut client = MyMcpClient::new();
200/// client.connect(config).await?;
201///
202/// let tools = client.list_tools("github").await?;
203/// for tool in &tools {
204///     println!("{}: {}", tool.name, tool.description);
205/// }
206///
207/// let result = client.call_tool("github", "list_repos", json!({"owner": "mofa-org"})).await?;
208/// ```
209#[async_trait]
210pub trait McpClient: Send + Sync {
211    /// 连接到 MCP 服务器
212    ///
213    /// 使用给定配置建立与 MCP 服务器的连接。
214    /// 连接建立后,可以通过 `server_name` 引用该服务器。
215    async fn connect(&mut self, config: McpServerConfig) -> crate::agent::error::AgentResult<()>;
216
217    /// 断开与 MCP 服务器的连接
218    async fn disconnect(&mut self, server_name: &str) -> crate::agent::error::AgentResult<()>;
219
220    /// 列出 MCP 服务器提供的工具
221    async fn list_tools(
222        &self,
223        server_name: &str,
224    ) -> crate::agent::error::AgentResult<Vec<McpToolInfo>>;
225
226    /// 调用 MCP 服务器上的工具
227    async fn call_tool(
228        &self,
229        server_name: &str,
230        tool_name: &str,
231        arguments: serde_json::Value,
232    ) -> crate::agent::error::AgentResult<serde_json::Value>;
233
234    /// 获取 MCP 服务器信息
235    async fn server_info(
236        &self,
237        server_name: &str,
238    ) -> crate::agent::error::AgentResult<McpServerInfo>;
239
240    /// 获取所有已连接的服务器名称
241    fn connected_servers(&self) -> Vec<String>;
242
243    /// 检查服务器是否已连接
244    fn is_connected(&self, server_name: &str) -> bool;
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    #[test]
252    fn test_mcp_server_config_stdio() {
253        let config = McpServerConfig::stdio(
254            "test-server",
255            "node",
256            vec!["server.js".to_string()],
257        )
258        .with_env("API_KEY", "test-key")
259        .with_auto_connect(false);
260
261        assert_eq!(config.name, "test-server");
262        assert!(!config.auto_connect);
263
264        if let McpTransportConfig::Stdio { command, args, env } = &config.transport {
265            assert_eq!(command, "node");
266            assert_eq!(args, &["server.js"]);
267            assert_eq!(env.get("API_KEY"), Some(&"test-key".to_string()));
268        } else {
269            panic!("Expected Stdio transport");
270        }
271    }
272
273    #[test]
274    fn test_mcp_server_config_http() {
275        let config = McpServerConfig::http("api-server", "http://localhost:8080/mcp");
276
277        assert_eq!(config.name, "api-server");
278        assert!(config.auto_connect);
279
280        if let McpTransportConfig::Http { url } = &config.transport {
281            assert_eq!(url, "http://localhost:8080/mcp");
282        } else {
283            panic!("Expected Http transport");
284        }
285    }
286
287    #[test]
288    fn test_mcp_tool_info_serialization() {
289        let tool = McpToolInfo {
290            name: "list_repos".to_string(),
291            description: "List GitHub repositories".to_string(),
292            input_schema: serde_json::json!({
293                "type": "object",
294                "properties": {
295                    "owner": { "type": "string" }
296                },
297                "required": ["owner"]
298            }),
299        };
300
301        let json = serde_json::to_string(&tool).unwrap();
302        let deserialized: McpToolInfo = serde_json::from_str(&json).unwrap();
303        assert_eq!(deserialized.name, "list_repos");
304    }
305
306    #[test]
307    fn test_mcp_server_info() {
308        let info = McpServerInfo {
309            name: "github".to_string(),
310            version: "1.0.0".to_string(),
311            instructions: Some("GitHub MCP Server".to_string()),
312        };
313
314        assert_eq!(info.name, "github");
315        assert_eq!(info.version, "1.0.0");
316        assert_eq!(info.instructions, Some("GitHub MCP Server".to_string()));
317    }
318}