1. 引言

Model Context Protocol (MCP) 是一种新兴的协议标准,旨在为 AI 模型提供结构化的上下文交互能力。Rust MCP 项目则是这一协议在 Rust 语言中的实现,通过类型安全和并发优势,为开发者提供了一个强大而灵活的框架,用于构建能够与 AI 模型智能交互的应用程序。

本文将深入探讨 Rust MCP 项目的核心概念、实现细节以及实际应用案例,帮助读者全面了解这一技术栈的价值和潜力。

2. MCP 基础概念

2.1. Tools

定义:Tools 是 AI 可以调用的外部能力或函数,用于执行特定任务(如查询数据库、调用 API、执行代码、发送邮件等)。

MCP 中的作用:

  • 提供"行动能力",让模型不仅能"说",还能"做"。
  • 在 MCP 协议中,tools 通常以结构化方式注册(如 JSON Schema),供模型在推理时动态选择和调用。
  • 支持工具链(tool chaining)和条件调用,实现复杂工作流。

2.2. Prompt

定义:Prompt 是引导模型行为的指令或上下文,包括系统提示(system prompt)、用户输入、对话历史等。

MCP 中的作用:

  • 定义模型的角色、目标、约束和行为规范。
  • 在 MCP 中,prompt 通常被结构化为"上下文模板",动态注入用户请求、工具描述、资源信息等。
  • 控制模型何时调用工具、如何解释结果、如何回应用户。
2.2.1. SystemPrompt

作用:定义 AI 的角色、行为准则、能力边界、输出格式等全局指令。

特点:

  • 通常由开发者或系统设定,对用户不可见(或部分可见)。
  • 在整个对话会话中保持不变(除非主动更新)。
  • 是模型"人格"和"规则"的来源。

核心价值:控制模型的"身份"和"行为规范",是安全、一致、可控对话的基础。

2.2.2. UserPrompt

作用:代表用户当前的输入或请求,是模型需要响应的直接任务。

特点:

  • 由用户在每轮对话中提供。
  • 内容多变,可以是问题、指令、陈述等。
  • 是触发模型推理的"输入信号"。

核心价值:表达用户意图,驱动对话进展。

2.2.3. AssistantPrompt

作用:记录模型在上一轮(或多轮)中生成的回复内容,用于维持对话历史和上下文连贯性。

特点:

  • 由模型生成,系统自动记录。
  • 在多轮对话中作为历史上下文的一部分,帮助模型理解对话脉络。
  • 通常不会由用户直接编辑。

核心价值:构建对话记忆,避免重复提问或逻辑断裂。

2.2.4. 示例
[
  {"role": "system", "content": "你是一个电商客服助手..."},
  {"role": "user",   "content": "我的订单还没发货,能帮我查一下吗?"},
  {"role": "assistant", "content": "请提供您的订单号,我将为您查询物流状态。"},
  {"role": "user",   "content": "订单号是 #123456。"}
]

2.3. Resources

定义:Resources 是 AI 在对话中可访问的静态或动态数据资产,如知识库、文档、用户配置、会话状态、上下文记忆等。

MCP 中的作用:

  • 为模型提供"背景知识"或"个性化上下文"。
  • 在 MCP 中,resources 通常通过检索增强(RAG)或上下文注入方式提供给模型。
  • 可与 tools 结合(如工具查询数据库后更新资源缓存)。

2.4. 三者协同工作流程(MCP 典型交互)

  1. 用户输入 → 触发 MCP 服务。
  2. Prompt 引擎:加载系统提示 + 用户输入 + 历史对话。
  3. Resource 注入:检索相关知识/用户数据,注入上下文。
  4. Tool 注册:声明当前可用的工具列表(含功能描述)。
  5. 模型推理:
    • 基于 prompt 理解任务;
    • 利用 resources 获取背景信息;
    • 决定是否调用 tool(如需要实时数据)。
    • Tool 执行 → 返回结果 → 更新 resources(如缓存)。
    • 生成最终回复 → 返回用户。

2.5. 总结

组件 在 MCP 中的作用 在对话中的角色 核心价值
Tools 提供可执行能力 执行器 让 AI 能"做事"
Prompt 控制行为逻辑 指令中枢 让 AI “知道怎么做”
Resources 提供上下文数据 记忆与知识库 让 AI “记得住、懂你”

3. 案例分析

使用依赖:

rmcp = {version = "0.6.4", features = [
    "server", 
    "macros", 
    "client", 
    "transport-sse-server", 
    "transport-sse-client-reqwest", 
    "transport-io",
    "transport-streamable-http-server",
    "transport-child-process",
    "schemars"
    ]}
    
serde = {version="1.0", features = ["derive"]}
serde_json = "1.0.142"
schemars = "1.0"

想要了解schemars请阅读:https://blog.csdn.net/qq_45515182/article/details/151934054

3.1. 计数器服务器案例

案例概述

计数器服务器是一个简单的 MCP 服务器实现,提供了基本的计数器功能,包括增加计数和获取当前计数值。这个案例展示了如何使用 Rust MCP 框架创建一个简单的工具服务器。

代码实现
use std::sync::Arc;
use std::sync::Mutex;

use rmcp::ErrorData as McpError;
use rmcp::ServerHandler;
use rmcp::ServiceExt;
use rmcp::handler::server::tool::ToolRouter;
use rmcp::model::CallToolResult;
use rmcp::model::Content;
use rmcp::model::Implementation;
use rmcp::model::ProtocolVersion;
use rmcp::model::ServerCapabilities;
use rmcp::model::ServerInfo;
use rmcp::tool;
use rmcp::tool_handler;
use rmcp::tool_router;
use rmcp::transport::stdio;

#[derive(Clone)]
struct Counter {
    counter: Arc<Mutex<i32>>,
    tool_router: ToolRouter<Counter>,
}

/**
 * #[tool_router] 宏是 rmcp (Rust Model Context Protocol) 库中的一个过程宏,
 * 用于在 MCP (Model Context Protocol) 服务器实现中自动生成工具路由逻辑。它的主要作用包括:
 *      1、该宏会扫描 impl 块中所有使用 #[tool] 标记的方法,并为它们自动生成路由逻辑。
 *      2、创建ToolRouter 实例。
 *      3、将所有标记为 #[tool] 的方法注册到路由器中,使得当客户端请求特定的工具时,服务器能够正确地调用对应的处理函数。
 */
#[tool_router]
impl Counter {
    pub fn new() -> Self {
        Self {
            counter: Arc::new(Mutex::new(0)),
            tool_router: Self::tool_router(),
        }
    }

    /**
     * tool宏用于将方法标记为 MCP 工具。
     */
    #[tool(description = "将计数器增加 1")]
    async fn increment(&self) -> Result<CallToolResult, McpError> {
        let mut count = self.counter.lock().unwrap();
        *count += 1;
        Ok(CallToolResult::success(vec![Content::text(format!(
            "{}",
            count
        ))]))
    }

    #[tool(description = "获取当前计数器值")]
    async fn get_value(&self) -> Result<CallToolResult, McpError> {
        let count = self.counter.lock().unwrap();
        Ok(CallToolResult::success(vec![Content::text(format!(
            "{}",
            count
        ))]))
    }
}

/**
 * #[tool_handler] 宏是 rmcp (Rust Model Context Protocol) 库中的一个过程宏,
 * 用于在 MCP (Model Context Protocol) 服务器实现中自动处理工具调用的逻辑,主要作用包括:
 *      1、该宏为 Counter 结构体自动实现 ServerHandler trait,这是处理 MCP 服务器请求所需的核心 trait。
 *      2、#[tool_handler] 宏与 #[tool_router] 宏协同工作。
 *         #[tool_router] 负责创建工具路由逻辑,而 #[tool_handler] 则负责将这些路由集成到服务器的请求处理流程中,
 *         使得服务器能够正确地接收和分发工具调用请求。
 *      3、当客户端发起工具调用请求时, #[tool_handler] 宏生成的代码会自动将请求路由到正确的处理函数(如 increment 或 get_value 方法),而不需要开发者手动编写请求分发逻辑。
 *      4、使用这个宏可以大大简化 MCP 服务器的实现,开发者只需要专注于实现具体的工具逻辑和服务器基本信息(如 get_info 方法),而不需要手动处理底层协议细节。
 */
#[tool_handler]
impl ServerHandler for Counter {
    fn get_info(&self) -> rmcp::model::ServerInfo {
        ServerInfo {
            protocol_version: ProtocolVersion::default(),
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            server_info: Implementation::default(),
            instructions: Some("此服务器提供一个计数器工具。计数器从 0 开始,可以增加。使用 'get_value' 检查当前计数。".to_string()),
        }
    }
}

#[tokio::main]
async fn main() {
    let server = Counter::new().serve(stdio()).await.unwrap();
    server.waiting().await.unwrap();
}
优点
  1. 简单直观:代码结构清晰,易于理解和实现。
  2. 状态管理:使用 Arc<Mutex> 安全地管理共享状态,适合多线程环境。
  3. 宏系统:利用 #[tool_router]#[tool_handler] 宏简化了路由和处理逻辑的编写。
  4. 类型安全:Rust 的类型系统确保了工具调用的安全性。
缺点
  1. 功能有限:仅提供基本的计数功能,缺乏复杂的业务逻辑。
  2. 持久化缺失:计数器值仅保存在内存中,服务器重启后数据会丢失。
  3. 扩展性不足:没有提供扩展机制,难以添加更多功能。
  4. 错误处理简单:错误处理较为基础,没有详细的错误分类和处理策略。

3.2. LLM 采样服务器案例

案例概述

LLM 采样服务器是一个更复杂的 MCP 服务器实现,它演示了如何通过 MCP 协议与大型语言模型(LLM)进行交互。这个服务器提供了一个 ask_LLM 工具,允许客户端向 LLM 提问并获取回答。

代码实现
use std::sync::Arc;

use rmcp::model::{
    CallToolResult, Content, ContextInclusion, CreateMessageRequestParam, ErrorCode,
    Implementation, ListToolsResult, ModelHint, ModelPreferences, ProtocolVersion, SamplingMessage,
    ServerCapabilities, ServerInfo, Tool,
};
use rmcp::transport::stdio;
use rmcp::{ErrorData, ServerHandler, ServiceExt};

/// 一个简单的MCP(Model Context Protocol)服务器,演示了工具的使用
/// 和LLM采样功能。
#[derive(Clone, Debug, Default)]
struct SimpleServer;

impl ServerHandler for SimpleServer {
    /// 返回服务器信息,包括协议版本、功能、实现详情和使用说明。
    fn get_info(&self) -> ServerInfo {
        ServerInfo{
            protocol_version: ProtocolVersion::default(),   // MCP协议版本
            capabilities: ServerCapabilities::builder().enable_tools().build(),    // 定义MCP服务器支持的功能和能力
            server_info: Implementation::from_build_env(),  // 服务器实现信息,包括名称、版本、作者等
            // 提供服务器的使用说明或指导信息
            instructions: Some(concat!(
                "This is a demo server that requests sampling from clients. It provides tools that use LLM capabilities.\n\n",
                "IMPORTANT: This server requires a client that supports the 'sampling/createMessage' method. ",
                "Without sampling support, the tools will return errors."
            ).into()),
        }
    }

    /// 处理来自客户端的工具调用。目前支持'ask_LLM'工具,
    /// 该工具通过客户端采样向LLM发送提示。
    async fn call_tool(
        &self,
        request: rmcp::model::CallToolRequestParam,
        context: rmcp::service::RequestContext<rmcp::RoleServer>,
    ) -> Result<rmcp::model::CallToolResult, rmcp::ErrorData> {
        match request.name.as_ref() {
            "ask_LLM" => {
                // 从请求参数中提取问题,如果没有提供则使用默认值
                let prompt = request
                    .arguments
                    .as_ref()
                    .and_then(|args| args.get("question"))
                    .and_then(|q| q.as_str())
                    .unwrap_or("Hello, LLM!");

                // 创建用于LLM采样的消息请求
                let message_request = CreateMessageRequestParam {
                    messages: vec![SamplingMessage {
                        role: rmcp::model::Role::User,  // 消息发送者的角色,这里表示用户消息
                        content: Content::text(prompt), // 消息内容,将提示文本转换为内容格式
                    }],
                    // 模型偏好设置,用于指导客户端选择合适的LLM模型
                    model_preferences: Some(ModelPreferences {
                        // 模型提示,指定偏好使用的模型
                        hints: Some(vec![ModelHint {
                            name: Some("claude".to_string()), // 指定使用Claude模型
                        }]),
                        cost_priority: Some(0.3), // 成本优先级,值越低表示越注重成本控制
                        speed_priority: Some(0.8), // 速度优先级,值越高表示越注重响应速度
                        intelligence_priority: Some(0.7), // 智能优先级,值越高表示越注重回答质量
                    }),
                    // 系统提示,用于设定LLM的角色和行为准则
                    system_prompt: Some("You are a helpful assistant.".to_string()),
                    // 是否包含上下文信息,None表示不包含之前的对话上下文
                    include_context: Some(ContextInclusion::None),
                    // 温度参数,控制LLM输出的随机性,0.7表示中等偏上的随机性
                    temperature: Some(0.7),
                    // 最大令牌数,限制LLM响应的长度,256个令牌约等于200-300个英文单词
                    max_tokens: 256,
                    // 停止序列,当LLM生成这些序列时停止生成,None表示不设置停止序列
                    stop_sequences: None,
                    // 元数据,可以包含任何额外的请求信息,None表示没有额外的元数据
                    metadata: None,
                };

                // 向客户端发送采样请求并处理响应
                match context.peer.create_message(message_request).await {
                    Ok(response) => {
                        let text = format!(
                            "Question: {}\nAnswer:{}",
                            prompt,
                            response
                                .message
                                .content
                                .as_text()
                                .map(|t| &t.text)
                                .unwrap_or(&"No text response".to_string())
                        );
                        Ok(CallToolResult::success(vec![Content::text(text)]))
                    }
                    Err(e) => Err(ErrorData::new(
                        ErrorCode::INTERNAL_ERROR,
                        format!("Sampling request failed: {}", e),
                        None,
                    )),
                }
            }
            // 对未知工具返回错误
            _ => Err(ErrorData::new(
                ErrorCode::INTERNAL_ERROR,
                format!("Unknown tool: {}", request.name),
                None,
            )),
        }
    }

    /// 返回此服务器提供的可用工具列表。
    /// 目前只提供用于LLM交互的'ask_LLM'工具。
    async fn list_tools(
        &self,
        _request: Option<rmcp::model::PaginatedRequestParam>,
        _context: rmcp::service::RequestContext<rmcp::RoleServer>,
    ) -> Result<ListToolsResult, ErrorData> {
        // 定义'ask_LLM'工具及其模式
        let tool = Tool {
            name: "ask_LLM".into(),
            title: Some("Ask LLM".into()),
            description: Some("Ask a question to the LLM through sampling".into()),
            input_schema: Arc::new(
                serde_json::from_value(serde_json::json!({
                    "type": "object",
                    "properties": {
                        "question": {
                            "type": "string",
                            "description": "The question to ask the LLM"
                        }
                    },
                    "required": ["question"]
                }))
                .unwrap(),
            ),
            output_schema: None,
            annotations: None,
            icons: None,
        };
        Ok(ListToolsResult {
            tools: vec![tool],
            next_cursor: None,
        })
    }
}

#[tokio::main]
async fn main() {
    // 使用stdio传输创建并服务SimpleServer
    let server = SimpleServer.serve(stdio()).await.unwrap();
    // 等待服务器完成(这将阻塞直到服务器停止)
    server.waiting().await.unwrap();
}
优点
  1. LLM 集成:展示了如何将 MCP 与大型语言模型集成,实现智能问答功能。
  2. 参数控制:提供了丰富的参数控制,如模型偏好、温度、最大令牌数等,可以精细调整 LLM 的行为。
  3. 错误处理:实现了较为完善的错误处理机制,能够优雅地处理各种异常情况。
  4. 工具描述:通过 JSON Schema 详细描述了工具的输入参数,提高了 API 的可发现性和可用性。
缺点
  1. 依赖客户端:服务器本身不直接与 LLM 交互,而是依赖客户端提供采样功能,增加了系统复杂性。
  2. 模型固定:代码中硬编码了使用 Claude 模型,缺乏灵活性。
  3. 上下文管理简单:没有实现复杂的上下文管理机制,难以维持多轮对话的连贯性。
  4. 安全性考虑不足:没有对输入进行验证和过滤,可能存在安全风险。

3.3. 提示模板服务器案例

案例概述

提示模板服务器是一个高级的 MCP 服务器实现,它展示了如何使用 MCP 的提示(prompt)功能创建结构化的对话模板。这个服务器提供了多种提示模板,包括代码审查、数据分析、写作助手、调试助手和学习路径等。

代码实现
use rmcp::{
    ErrorData as McpError, RoleServer, ServerHandler, ServiceExt,
    handler::server::{router::prompt::PromptRouter, wrapper::Parameters},
    model::*,
    prompt, prompt_handler, prompt_router,
    schemars::JsonSchema,
    service::RequestContext,
    transport::stdio,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;

#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[schemars(description = "Code review parameters")]
pub struct CodeReviewArgs {
    #[schemars(description = "Programming language of the code")]
    pub language: String,
    #[schemars(description = "Path to the file or code snippet")]
    pub file_path: String,
    #[schemars(description = "Focus areas for the review")]
    pub focus_areas: Option<Vec<String>>,
}

/// Arguments for the data analysis prompt
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[schemars(description = "Data analysis parameters")]
pub struct DataAnalysisArgs {
    #[schemars(description = "Type of data: 'csv', 'json', 'logs', etc.")]
    pub data_type: String,
    #[schemars(description = "What kind of analysis to perform")]
    pub analysis_type: String,
    #[schemars(description = "Additional context about the data")]
    pub context: Option<String>,
}

/// Arguments for the writing assistant prompt
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[schemars(description = "Writing assistant parameters")]
pub struct WritingAssistantArgs {
    #[schemars(description = "Type of content: 'email', 'documentation', 'blog', etc.")]
    pub content_type: String,
    #[schemars(description = "Target audience")]
    pub audience: String,
    #[schemars(description = "Writing tone: 'formal', 'casual', 'technical', etc.")]
    pub tone: Option<String>,
    #[schemars(description = "Key points to cover")]
    pub key_points: Vec<String>,
}

/// Arguments for the debug assistant prompt
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[schemars(description = "Debug assistant parameters")]
pub struct DebugAssistantArgs {
    #[schemars(description = "Error message or symptom")]
    pub error_message: String,
    #[schemars(description = "Technology stack involved")]
    pub stack: Vec<String>,
    #[schemars(description = "Steps already tried")]
    pub tried_solutions: Option<Vec<String>>,
}

/// Simple prompt server demonstrating various prompt patterns
#[derive(Clone)]
pub struct PromptServer {
    /// Stores user preferences that can be used in prompts
    user_preferences: Arc<RwLock<UserPreferences>>,
    prompt_router: PromptRouter<PromptServer>,
}

#[derive(Debug, Clone)]
struct UserPreferences {
    preferred_language: String,
    expertise_level: String,
}

impl Default for UserPreferences {
    fn default() -> Self {
        Self {
            preferred_language: "English".to_string(),
            expertise_level: "intermediate".to_string(),
        }
    }
}

impl PromptServer {
    pub fn new() -> Self {
        Self {
            user_preferences: Arc::new(RwLock::new(UserPreferences::default())),
            prompt_router: Self::prompt_router(),
        }
    }
}

impl Default for PromptServer {
    fn default() -> Self {
        Self::new()
    }
}

/**
 * 1、自动生成路由逻辑 :该宏会扫描 impl 块中所有使用 #[prompt] 标记的方法,并为它们自动生成路由逻辑。
 * 2、创建 PromptRouter 实例 :从代码中可以看到, PromptServer 结构体中有一个 prompt_router: PromptRouter<PromptServer> 字段,这个宏负责创建和配置这个路由器。
 * 3、注册提示处理函数 :将所有标记为 #[prompt] 的方法注册到路由器中,使得当客户端请求特定的提示时,服务器能够正确地调用对应的处理函数。
 */
#[prompt_router]
impl PromptServer {
    /**
     * 1、定义提示模板 :该宏将一个普通的方法转换为一个可调用的提示模板,每个提示都有一个名称和描述。
     * 2、参数处理 :宏会处理方法的参数,将它们转换为提示模板可以接受的格式。例如, Parameters(args): Parameters<CodeReviewArgs> 这种参数形式就是由宏处理的,它会自动将传入的 JSON 数据反序列化为 CodeReviewArgs 结构体。
     * 3、返回值处理 :宏会处理方法的返回值,将其转换为 MCP 服务器可以发送的格式。例如, Vec<PromptMessage> 类型的返回值会被转换为一个包含多个 PromptMessage 的 JSON 数组。
     */
    #[prompt(
        name = "greeting",
        description = "A simple greeting prompt to start conversations"
    )]
    async fn greeting(&self) -> Vec<PromptMessage> {
        vec![
            PromptMessage::new_text(
                PromptMessageRole::User,
                "Hello! I'd like to start our conversation.",
            ),
            PromptMessage::new_text(
                PromptMessageRole::Assistant,
                "Hello! I'm here to help. What would you like to discuss today?",
            ),
        ]
    }

    /// Code review prompt with typed parameters
    #[prompt(
        name = "code_review",
        description = "Structured code review with language-specific best practices"
    )]
    async fn code_review(
        &self,
        Parameters(args): Parameters<CodeReviewArgs>,
    ) -> Result<GetPromptResult, McpError> {
        let prefs = self.user_preferences.read().await;
        let focus_areas = args
            .focus_areas
            .unwrap_or_else(|| vec!["correctness".to_string(), "performance".to_string()]);

        let messages = vec![
            PromptMessage::new_text(
                PromptMessageRole::Assistant,
                format!(
                    "You are an expert {} code reviewer. The user's expertise level is {}.",
                    args.language, prefs.expertise_level
                ),
            ),
            PromptMessage::new_text(
                PromptMessageRole::User,
                format!(
                    "Please review the {} code at '{}'. Focus on: {}",
                    args.language,
                    args.file_path,
                    focus_areas.join(", ")
                ),
            ),
            PromptMessage::new_text(
                PromptMessageRole::Assistant,
                format!(
                    "I'll review your {} code focusing on {}. Let me analyze the code at '{}'...",
                    args.language,
                    focus_areas.join(" and "),
                    args.file_path
                ),
            ),
        ];

        Ok(GetPromptResult {
            description: Some(format!(
                "Code review for {} file focusing on {}",
                args.language,
                focus_areas.join(", ")
            )),
            messages,
        })
    }

    /// Data analysis prompt demonstrating context usage
    #[prompt(
        name = "data_analysis",
        description = "Analyze data with context-aware suggestions"
    )]
    async fn data_analysis(
        &self,
        Parameters(args): Parameters<DataAnalysisArgs>,
        ctx: RequestContext<RoleServer>,
    ) -> Result<Vec<PromptMessage>, McpError> {
        // Could use ctx to check for capabilities or metadata
        let _request_id = &ctx.id;

        let context = args
            .context
            .unwrap_or_else(|| "General analysis requested".to_string());

        Ok(vec![
            PromptMessage::new_text(
                PromptMessageRole::User,
                format!(
                    "I have {} data that needs {} analysis. Context: {}",
                    args.data_type, args.analysis_type, context
                ),
            ),
            PromptMessage::new_text(
                PromptMessageRole::Assistant,
                format!(
                    "I'll help you analyze your {} data using {} techniques. Based on your context, \
                     I'll focus on providing actionable insights.",
                    args.data_type, args.analysis_type
                ),
            ),
        ])
    }

    /// Writing assistant with multiple conversation turns
    #[prompt(
        name = "writing_assistant",
        description = "Multi-turn writing assistance with style guidance"
    )]
    async fn writing_assistant(
        &self,
        Parameters(args): Parameters<WritingAssistantArgs>,
    ) -> GetPromptResult {
        let tone = args.tone.unwrap_or_else(|| "professional".to_string());

        let mut messages = vec![
            PromptMessage::new_text(
                PromptMessageRole::Assistant,
                format!(
                    "You are a writing assistant helping create {} content for {}. \
                     Use a {} tone.",
                    args.content_type, args.audience, tone
                ),
            ),
            PromptMessage::new_text(
                PromptMessageRole::User,
                format!(
                    "I need help writing {} for {}. Key points to cover: {}",
                    args.content_type,
                    args.audience,
                    args.key_points.join(", ")
                ),
            ),
            PromptMessage::new_text(
                PromptMessageRole::Assistant,
                "I'll help you create that content. Let me structure it based on your key points.",
            ),
        ];

        // Add a message for each key point
        for (i, point) in args.key_points.iter().enumerate() {
            messages.push(PromptMessage::new_text(
                PromptMessageRole::User,
                format!("For point {}: {}, what would you suggest?", i + 1, point),
            ));
            messages.push(PromptMessage::new_text(
                PromptMessageRole::Assistant,
                format!("For '{}', I recommend...", point),
            ));
        }

        GetPromptResult {
            description: Some(format!(
                "Writing {} for {} audience with {} tone",
                args.content_type, args.audience, tone
            )),
            messages,
        }
    }

    /// Debug assistant demonstrating error handling patterns
    #[prompt(
        name = "debug_assistant",
        description = "Interactive debugging help with solution tracking"
    )]
    async fn debug_assistant(
        &self,
        Parameters(args): Parameters<DebugAssistantArgs>,
    ) -> Result<GetPromptResult, McpError> {
        if args.stack.is_empty() {
            return Err(McpError::invalid_params(
                "Technology stack cannot be empty",
                None,
            ));
        }

        let mut messages = vec![
            PromptMessage::new_text(
                PromptMessageRole::Assistant,
                format!(
                    "You are a debugging expert for {}. Help diagnose and fix issues.",
                    args.stack.join(", ")
                ),
            ),
            PromptMessage::new_text(
                PromptMessageRole::User,
                format!(
                    "I'm encountering this error: {}\nStack: {}",
                    args.error_message,
                    args.stack.join(", ")
                ),
            ),
        ];

        // Add tried solutions if any
        if let Some(tried) = args.tried_solutions {
            if !tried.is_empty() {
                messages.push(PromptMessage::new_text(
                    PromptMessageRole::User,
                    format!("I've already tried: {}", tried.join(", ")),
                ));
                messages.push(PromptMessage::new_text(
                    PromptMessageRole::Assistant,
                    "I see you've already attempted some solutions. Let me suggest different approaches.",
                ));
            }
        }

        messages.push(PromptMessage::new_text(
            PromptMessageRole::Assistant,
            "Let's debug this systematically. First, let me understand the error context better.",
        ));

        Ok(GetPromptResult {
            description: Some(format!(
                "Debugging {} error in {}",
                args.error_message.chars().take(50).collect::<String>(),
                args.stack.first().map(|s| s.as_str()).unwrap_or("unknown")
            )),
            messages,
        })
    }

    /// Learning path prompt that uses server state
    #[prompt(
        name = "learning_path",
        description = "Generate a personalized learning path based on user preferences"
    )]
    async fn learning_path(&self) -> Result<Vec<PromptMessage>, McpError> {
        let prefs = self.user_preferences.read().await;

        Ok(vec![
            PromptMessage::new_text(
                PromptMessageRole::Assistant,
                format!(
                    "Create a learning path for someone at {} level who prefers {} language explanations.",
                    prefs.expertise_level, prefs.preferred_language
                ),
            ),
            PromptMessage::new_text(
                PromptMessageRole::User,
                "What should I learn next to improve my programming skills?",
            ),
            PromptMessage::new_text(
                PromptMessageRole::Assistant,
                format!(
                    "Based on your {} expertise level, I recommend the following learning path...",
                    prefs.expertise_level
                ),
            ),
        ])
    }
}

/**
 * 1、实现服务器处理逻辑 :该宏为 PromptServer 实现 ServerHandler trait,这是处理 MCP (Model Context Protocol) 服务器请求所需的核心 trait。
 */
#[prompt_handler]
impl ServerHandler for PromptServer {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            capabilities: ServerCapabilities::builder().enable_prompts().build(),
            server_info: Implementation::from_build_env(),
            instructions: Some(
                "This server provides various prompt templates for code review, data analysis, \
                 writing assistance, debugging help, and personalized learning paths. \
                 All prompts are designed to provide structured, context-aware assistance."
                    .to_string(),
            ),
            ..Default::default()
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("MCP Prompt Server Example");
    println!("=======================");
    println!();
    println!("This server demonstrates various prompt patterns:");
    println!("- Simple prompts without parameters");
    println!("- Prompts with typed parameters");
    println!("- Prompts using server state");
    println!("- Multi-turn conversation prompts");
    println!("- Error handling in prompts");
    println!();
    println!("To test with MCP Inspector:");
    println!(
        "npx @modelcontextprotocol/inspector cargo run -p mcp-server-examples --example servers_prompt_stdio"
    );
    println!();

    let server = PromptServer::new();
    let service = server.serve(stdio()).await?;

    service.waiting().await?;
    Ok(())
}
优点
  1. 丰富的提示模板:提供了多种实用的提示模板,覆盖了代码审查、数据分析、写作助手、调试助手和学习路径等多个场景。
  2. 类型安全参数:使用结构体和 JsonSchema 宏定义参数,确保了类型安全和良好的文档化。
  3. 状态管理:通过 UserPreferences 结构体实现了用户偏好状态的管理,可以提供个性化的服务。
  4. 错误处理:实现了参数验证和错误处理,提高了系统的健壮性。
  5. 多轮对话支持:某些提示模板(如写作助手)支持多轮对话,提供了更自然的交互体验。
缺点
  1. 复杂性高:代码结构较为复杂,对于初学者来说理解和维护难度较大。
  2. 资源管理:用户偏好存储在内存中,没有持久化机制,重启后数据会丢失。
  3. 扩展性限制:添加新的提示模板需要修改服务器代码,缺乏动态扩展能力。
  4. 性能考虑:对于大量并发请求,当前的 RwLock 机制可能成为性能瓶颈。

3.4. SSE 邮件服务器案例

案例概述

SSE 邮件服务器是一个使用 Server-Sent Events (SSE) 作为传输层的 MCP 服务器实现,提供了发送邮件和获取联系人列表的功能。这个案例展示了如何在 Web 环境中使用 MCP 协议,以及如何处理更复杂的业务逻辑。

代码实现
use rmcp::model::{Implementation, ProtocolVersion, ServerCapabilities, ServerInfo};
use rmcp::{model::{CallToolResult, Content}, tool_handler, tool_router, transport::{SseServer}, ErrorData, ServerHandler};
use rmcp::tool;
use rmcp::handler::server::tool::ToolRouter;
use serde::{Deserialize, Serialize};
use rmcp::schemars::JsonSchema;
use rmcp::handler::server::wrapper::Parameters;

/// 发送邮件的参数
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[schemars(description = "发送邮件的参数")]
pub struct SendEmailArgs {
    #[schemars(description = "收件人邮箱地址")]
    pub to: String,
    #[schemars(description = "邮件主题")]
    pub subject: String,
    #[schemars(description = "邮件内容")]
    pub body: String,
}

/// 获取联系人列表的参数
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[schemars(description = "获取联系人列表的参数")]
pub struct ContactListArgs {
    #[schemars(description = "联系人分组名称,可选")]
    pub group: Option<String>,
    #[schemars(description = "返回的联系人数量限制,默认为10")]
    pub limit: Option<i32>,
}

/// 获取工具列表的参数(空参数)
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[schemars(description = "获取工具列表的参数")]
pub struct ListToolArgs {
    // 空结构体,不需要任何字段
}

#[tokio::main]
async fn main() {
    // 创建一个 SSE 服务器,绑定到指定的地址和端口,然后为其注册一个服务处理程序。SSE 是一种允许服务器向客户端推送事件的技术,在这里用于实现 MCP 协议的通信。
    let ct = SseServer::serve("127.0.0.1:8080".parse().unwrap()).await.unwrap().with_service(move || {
        EmailServer::new()
    });

    tokio::signal::ctrl_c().await.unwrap();
    ct.cancel();
}

struct EmailServer {
    // tool_router 字段是必须的,不然 tool_handler 会报错
    tool_router: ToolRouter<EmailServer>,
}

impl EmailServer {
    pub fn new() -> Self {
        Self {
            tool_router: Self::tool_router()
        }
    }
}

#[tool_router]
impl EmailServer {
    /**
     * 在 MCP 框架中,所有使用 #[tool] 宏标记的方法都需要有参数结构体,即使这些参数是空的。这是因为:
     *      1、协议规范要求 :MCP 协议要求每个工具都必须有一个输入模式(inputSchema),且类型必须是 "object"。
     *      2、框架设计 : #[tool] 宏会为每个工具方法生成相应的 schema,如果方法没有参数,就无法生成符合协议要求的 schema。
     */
    #[tool(description = "发送邮件")]
    async fn send_email(&self, Parameters(args): Parameters<SendEmailArgs>) -> Result<CallToolResult, ErrorData> {
        Ok(CallToolResult::success(vec![Content::text(format!(
            "邮件已发送至: {}, 主题: {}, 内容: {}", 
            args.to, args.subject, args.body
        ))]))
    }

    #[tool(description = "获取联系人列表")]
    async fn contact_list(&self, Parameters(args): Parameters<ContactListArgs>) -> Result<CallToolResult, ErrorData> {
        let limit = args.limit.unwrap_or(10);
        let group_info = args.group.map_or("所有联系人".to_string(), |g| format!("{}分组", g));
        
        let mut list = Vec::new();
        for i in 0..limit {
            let contact = Content::text(format!("{}{}", group_info, i));
            list.push(contact);
        }
        Ok(CallToolResult::success(list))
    }

    #[tool(description = "获取所有工具列表")]
    async fn list_tool(&self, Parameters(_args): Parameters<ListToolArgs>) -> Result<CallToolResult, ErrorData> {
        Ok(CallToolResult::success(vec![
            Content::text("send_email".to_string()),
            Content::text("contact_list".to_string()),
        ]))
    }
}

#[tool_handler]
impl ServerHandler for EmailServer {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            protocol_version: ProtocolVersion::default(),
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            server_info: Implementation::default(),
            instructions: Some("此服务器提供发送邮件和获取联系人列表的工具。".to_string()),
        }
    }
}
优点
  1. Web 友好:使用 SSE 作为传输层,适合在 Web 环境中部署和使用,便于与前端应用集成。
  2. 实时通信:SSE 支持服务器向客户端推送事件,可以实现实时通信和状态更新。
  3. 业务逻辑完整:提供了完整的邮件发送和联系人管理功能,展示了如何处理实际业务场景。
  4. 参数验证:通过结构体和 JsonSchema 宏实现了参数验证,确保了输入数据的有效性。
缺点
  1. 模拟实现:邮件发送和联系人管理功能只是模拟实现,没有与实际的邮件服务集成。
  2. 错误处理简单:错误处理较为基础,没有考虑网络故障、认证失败等复杂情况。
  3. 安全性考虑不足:没有实现认证和授权机制,存在安全风险。
  4. 扩展性限制:当前实现难以扩展到大规模邮件系统,缺乏队列、重试等机制。

4. 技术实现细节

4.1. 客户端实现

Rust MCP 的客户端实现主要围绕 ClientHandler trait 和各种传输层展开。客户端负责与 MCP 服务器建立连接,发送请求并处理响应。

核心组件
  1. ClientHandler trait:定义了客户端处理服务器响应的基本接口,包括 create_message 方法用于处理 LLM 采样请求。
impl ClientHandler for SimpleClient {
    async fn create_message(
        &self,
        params: rmcp::model::CreateMessageRequestParam,
        _context: rmcp::service::RequestContext<rmcp::RoleClient>,
    ) -> Result<CreateMessageResult, ErrorData> {
        let response_txt =
            self.mock_llm_response(&params.messages, params.system_prompt.as_deref());
        let result = CreateMessageResult {
            message: SamplingMessage {
                role: Role::Assistant,
                content: Content::text(response_txt),
            },
            model: "mock_llm".to_string(),
            stop_reason: Some(CreateMessageResult::STOP_REASON_END_TURN.to_string()),
        };
        Ok(result)
    }
}
  1. 传输层:Rust MCP 支持多种传输层,包括 stdio、SSE、子进程等。客户端可以根据需要选择合适的传输方式。
let client = client
    .serve(TokioChildProcess::new(Command::new("cargo").configure(
        |cmd| {
            cmd.arg("run")
                .arg("--example")
                .arg("servers_sampling_stdio")
                .current_dir(servers_dir);
        },
    ))?)
    .await?;
  1. 工具调用:客户端可以通过 call_tool 方法调用服务器提供的工具,并处理返回结果。
match client
    .call_tool(CallToolRequestParam {
        name: "ask_LLM".into(),
        arguments: Some(object!({
            "question": "Hello world"
        })),
    })
    .await
{
    Ok(result) => println!("Ask LLM result: {:?}", result),
    Err(e) => println!("Ask LLM error: {e}"),
}
客户端示例分析

client_sampling_stdio.rs 为例,它展示了一个完整的客户端实现:

  1. 定义客户端结构体SimpleClient 结构体实现了 ClientHandler trait。
  2. 实现 LLM 响应模拟mock_llm_response 方法模拟了 LLM 的响应,实际应用中可以替换为真实的 LLM 调用。
  3. 建立连接:使用 TokioChildProcess 传输层连接到服务器。
  4. 调用工具:列出可用工具并调用 ask_LLM 工具。

4.2. 服务端实现

Rust MCP 的服务端实现主要围绕 ServerHandler trait 和各种宏系统展开。服务端负责定义工具、处理请求并返回响应。

核心组件
  1. ServerHandler trait:定义了服务器处理客户端请求的基本接口,包括 get_infocall_toollist_tools 等方法。
impl ServerHandler for Counter {
    fn get_info(&self) -> rmcp::model::ServerInfo {
        ServerInfo {
            protocol_version: ProtocolVersion::default(),
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            server_info: Implementation::default(),
            instructions: Some("此服务器提供一个计数器工具。计数器从 0 开始,可以增加。使用 'get_value' 检查当前计数。".to_string()),
        }
    }
}
  1. 工具定义:使用 #[tool] 宏定义工具,包括工具名称、描述和参数。
#[tool(description = "将计数器增加 1")]
async fn increment(&self) -> Result<CallToolResult, McpError> {
    let mut count = self.counter.lock().unwrap();
    *count += 1;
    Ok(CallToolResult::success(vec![Content::text(format!(
        "{}",
        count
    ))]))
}
  1. 工具路由:使用 #[tool_router] 宏自动生成工具路由逻辑,将客户端请求分发到对应的处理函数。
#[tool_router]
impl Counter {
    // 工具方法定义
}
  1. 工具处理:使用 #[tool_handler] 宏自动实现 ServerHandler trait,处理工具调用请求。
#[tool_handler]
impl ServerHandler for Counter {
    // ServerHandler trait 实现
}
服务端示例分析

count_server_stdio.rs 为例,它展示了一个完整的服务端实现:

  1. 定义服务器结构体Counter 结构体包含计数器状态和工具路由器。
  2. 实现工具方法:使用 #[tool] 宏定义 incrementget_value 工具。
  3. 生成路由逻辑:使用 #[tool_router] 宏自动生成工具路由逻辑。
  4. 实现服务器处理:使用 #[tool_handler] 宏自动实现 ServerHandler trait。
  5. 启动服务器:使用 stdio 传输层启动服务器并等待请求。

4.3. 宏系统

Rust MCP 的宏系统是其核心特性之一,它大大简化了 MCP 服务器的实现。主要的宏包括 #[tool]#[tool_router]#[tool_handler]#[prompt]#[prompt_router]#[prompt_handler]

工具宏
  1. #[tool]:将方法标记为 MCP 工具,定义工具的名称、描述和参数。
#[tool(description = "将计数器增加 1")]
async fn increment(&self) -> Result<CallToolResult, McpError> {
    // 工具实现
}
  1. #[tool_router]:自动生成工具路由逻辑,将客户端请求分发到对应的处理函数。
#[tool_router]
impl Counter {
    // 工具方法定义
}
  1. #[tool_handler]:自动实现 ServerHandler trait,处理工具调用请求。
#[tool_handler]
impl ServerHandler for Counter {
    // ServerHandler trait 实现
}
提示宏
  1. #[prompt]:将方法标记为 MCP 提示,定义提示的名称、描述和参数。
#[prompt(
    name = "code_review",
    description = "Structured code review with language-specific best practices"
)]
async fn code_review(
    &self,
    Parameters(args): Parameters<CodeReviewArgs>,
) -> Result<GetPromptResult, McpError> {
    // 提示实现
}
  1. #[prompt_router]:自动生成提示路由逻辑,将客户端请求分发到对应的处理函数。
#[prompt_router]
impl PromptServer {
    // 提示方法定义
}
  1. #[prompt_handler]:自动实现 ServerHandler trait,处理提示调用请求。
#[prompt_handler]
impl ServerHandler for PromptServer {
    // ServerHandler trait 实现
}
宏系统的工作原理

Rust MCP 的宏系统在编译时展开,生成必要的代码来处理 MCP 协议的细节。以 #[tool_router] 为例,它会:

  1. 扫描 impl 块中所有使用 #[tool] 标记的方法。
  2. 为每个工具方法生成路由逻辑。
  3. 创建 ToolRouter 实例,并将所有工具注册到路由器中。
  4. 实现请求分发逻辑,将客户端请求路由到正确的处理函数。

这种设计大大简化了 MCP 服务器的实现,开发者只需要专注于工具或提示的业务逻辑,而不需要手动处理底层协议细节。

5. 优缺点分析

5.1. 优点

  1. 类型安全:Rust 的类型系统确保了 MCP 实现的类型安全,减少了运行时错误的可能性。通过结构体和 JsonSchema 宏定义参数,可以在编译时捕获许多错误。

  2. 高性能:Rust 的零成本抽象和高效并发模型使得 MCP 实现具有很高的性能,特别适合处理大量并发请求。

  3. 宏系统:Rust MCP 的宏系统大大简化了服务器的实现,开发者只需要专注于业务逻辑,而不需要手动处理底层协议细节。

  4. 多传输层支持:支持多种传输层,包括 stdio、SSE、子进程等,可以根据应用场景选择最合适的传输方式。

  5. 异步支持:基于 Tokio 异步运行时,支持高并发和异步 I/O,适合构建高性能的网络服务。

  6. 模块化设计:客户端和服务端的实现分离,工具和提示的处理逻辑模块化,便于维护和扩展。

  7. 丰富的示例:提供了多种示例代码,覆盖了不同的使用场景,便于开发者学习和参考。

  8. 文档完善:代码中的注释详细,解释了各个组件的作用和使用方法,降低了学习门槛。

5.2. 缺点

  1. 学习曲线陡峭:Rust 语言本身的学习曲线较陡,加上 MCP 协议的复杂性,对于初学者来说入门难度较大。

  2. 生态系统不成熟:Rust MCP 相对较新,生态系统还不够成熟,第三方库和工具支持有限。

  3. 错误处理复杂:Rust 的错误处理机制虽然强大,但在 MCP 实现中可能导致代码冗长和复杂。

  4. 编译时间长:Rust 的编译时间相对较长,特别是在使用大量宏的情况下,可能影响开发效率。

  5. 调试困难:宏系统生成的代码在调试时可能难以理解,增加了调试的难度。

  6. 资源消耗:Rust 程序的内存占用相对较高,对于资源受限的环境可能不是最佳选择。

  7. 文档不足:虽然有代码注释,但缺乏系统性的文档和教程,特别是高级特性的使用说明。

  8. 兼容性问题:与其他语言实现的 MCP 服务器可能存在兼容性问题,特别是在处理复杂的数据结构时。

6. 应用场景

Rust MCP 的应用场景广泛,特别适合以下情况:

  1. AI 助手开发:构建能够与大型语言模型交互的 AI 助手,提供代码审查、文档生成、问题解答等功能。

  2. 企业级应用:开发需要高性能和高可靠性的企业级应用,如客户服务系统、数据分析平台等。

  3. 实时通信系统:构建需要实时通信能力的系统,如聊天机器人、实时监控系统等。

  4. 微服务架构:在微服务架构中作为服务间的通信协议,提供结构化的上下文交互能力。

  5. DevOps 工具:开发自动化运维工具,如日志分析、错误诊断、性能监控等。

  6. 教育平台:构建智能教育平台,提供个性化的学习路径和实时的学习辅导。

  7. 内容创作:开发辅助内容创作的工具,如写作助手、翻译工具、内容摘要等。

  8. 游戏开发:在游戏开发中实现智能 NPC 和对话系统,提供更丰富的游戏体验。

7. 总结与展望

Rust MCP 是一个强大而灵活的框架,它将 Rust 的性能优势与 MCP 协议的上下文交互能力相结合,为开发者提供了一个构建智能应用的理想平台。通过本文的分析,我们可以看到 Rust MCP 在类型安全、高性能、宏系统等方面的优势,以及在学习曲线、生态系统成熟度等方面的挑战。

总结

  1. 技术优势:Rust MCP 充分利用了 Rust 语言的类型安全、高性能和并发特性,为 MCP 协议的实现提供了坚实的基础。

  2. 设计理念:通过宏系统简化了 MCP 服务器的实现,使开发者能够专注于业务逻辑,而不需要处理底层协议细节。

  3. 功能丰富:支持工具、提示和资源等 MCP 核心组件,提供了完整的上下文交互能力。

  4. 应用广泛:适用于 AI 助手、企业应用、实时通信等多种场景,具有广阔的应用前景。

展望

  1. 生态系统发展:随着 Rust MCP 的普及,预计会有更多的第三方库和工具出现,丰富生态系统。

  2. 性能优化:未来可能会有更多的性能优化,特别是在高并发和低延迟场景下的表现。

  3. 易用性提升:可能会提供更高级的抽象和更友好的 API,降低学习门槛,吸引更多开发者。

  4. 标准化进程:随着 MCP 协议的标准化进程,Rust MCP 可能会成为 Rust 生态中实现 MCP 协议的标准库。

  5. 跨语言互操作:可能会增强与其他语言实现的 MCP 服务器的互操作性,促进不同语言生态系统间的协作。

  6. 集成更多 AI 能力:随着 AI 技术的发展,Rust MCP 可能会集成更多先进的 AI 能力,如多模态交互、强化学习等。

总之,Rust MCP 是一个充满潜力的项目,它不仅展示了 Rust 在构建高性能网络服务方面的优势,也为 AI 与应用的集成提供了新的思路。随着技术的不断发展和生态系统的成熟,Rust MCP 有望成为构建智能应用的重要工具。

Logo

更多推荐