<!-- title: Rust构建MCP服务器实战 -->

# Rust 构建 MCP 服务器实战：用 rmcp 交付一个带状态的代码片段管家

## 概述

本教程带你用 Rust 官方 SDK `rmcp` 从零构建一个真正能跑、带持久化状态的 MCP（Model Context Protocol）服务器——`snippet-mcp` 代码片段管家。学完你将掌握：rmcp 的宏驱动开发模型、工具定义与参数 schema、有状态服务的线程安全设计、stdio 传输、MCP Inspector 调试，以及接入 Claude Code 的完整闭环。适合有 Rust 基础、想把高性能本地工具暴露给 AI 编程助手使用的开发者。

## 前置条件

- **Rust 工具链**：`rustc` / `cargo` 1.75 及以上（`rustup default stable` 即可），`rmcp` 依赖较新的 trait 语法与异步生态。
- **基础**：读过《Rust Book》前 1–10 章，了解所有权、`Result`、`async/await` 与 `Mutex`。不需要写过宏，因为 `rmcp` 的宏是声明式的，你只要会用即可。
- **Claude Code**：已安装并在终端可用 `claude` 命令，用于第 6 步的接入验证。
- **Node.js（可选）**：用于跑 MCP Inspector（`npx @modelcontextprotocol/inspector`）。

确认环境：

```bash
cargo --version    # cargo 1.75.0 或更高
claude --version   # Claude Code 已安装
```

## 一、为什么用 Rust 写 MCP 服务器

MCP 是 Anthropic 提出的、让 AI 模型以标准方式调用外部工具、读取资源、复用提示词的协议。社区已有 Python（FastMCP）、TypeScript、Go 等多套官方 SDK，它们上手快，但在三类场景里 Rust 优势明显：

1. **常驻进程、高并发工具调用**：MCP 服务器是长驻服务，模型可能在一轮对话里并发调用十几次工具。Rust 的零成本异步 + 无 GC，让 99 分位延迟稳定且可预测，适合需要实时性（如系统监控、代码索引、文件检索）的工具。
2. **单文件分发、零运行时依赖**：`cargo build --release` 产出一个静态链接的二进制，团队成员拷贝即用，不必担心 Python 版本或 Node 依赖地狱——这对"团队内分发 MCP 服务器"极其友好。
3. **类型即契约**：rmcp 用 `serde` + `schemars` 把 Rust 的类型直接编译成 JSON Schema，工具的输入校验和文档由编译器保证，模型拿到的 `inputSchema` 永远和实现一致。

代价是：写起来比 Python 啰嗦，异步借用要小心。本教程会用宏 + 简单状态把复杂度压到最低。

## 二、理解 MCP 与 rmcp 的最小心智模型

一个 MCP 服务器对外暴露三类原语：**Tools（工具，可执行副作用）**、**Resources（资源，只读数据）**、**Prompts（提示词模板）**。本教程聚焦最高频的 Tools。rmcp 把"声明工具"这件事做成了两个声明式宏：

- `#[tool_router]` 标在一个 `impl` 块上，编译期扫描块内所有 `#[tool]` 方法，自动生成 `list_tools`/`call_tool` 所需的路由表。
- `#[tool_handler]` 标在 `impl ServerHandler` 块上，把上述路由表接到协议层。

你只需要写"带 `#[tool]` 的普通 Rust 方法"，剩下的协议序列化、JSON-RPC 分发全部由宏完成。

```mermaid
flowchart LR
    subgraph Client["Claude Code / Inspector (MCP Client)"]
        A["模型决定调用工具"]
    end
    subgraph Transport["stdio (stdin/stdout JSON-RPC)"]
        T["行分隔 JSON-RPC 2.0"]
    end
    subgraph Server["你的 Rust MCP 服务器"]
        H["ServerHandler<br/>协议入口"] --> R["ToolRouter<br/>宏生成的路由表"]
        R --> M1["#[tool] add_snippet"]
        R --> M2["#[tool] search_snippets"]
        R --> M3["#[tool] delete_snippet"]
        M1 & M2 & M3 --> S["AppState<br/>tokio::Mutex + JSON 文件"]
    end
    A -->|call_tool| T --> H
    M3 -.持久化.-> S
```

记住这条链路：**模型 → 客户端 → JSON-RPC → `ServerHandler` → `ToolRouter` → 你的 `#[tool]` 方法 → 状态**。后面每一步都是在这条链路的一个环节上动手。

## 三、第 1 步：初始化工程与依赖

**操作说明**：

```bash
cargo new snippet-mcp
cd snippet-mcp
```

把 `Cargo.toml` 的 `[dependencies]` 替换为：

```toml
[dependencies]
rmcp = { version = "0.7", features = ["server", "transport-io"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[dev-dependencies]
cargo --version

[[bin]]
name = "snippet-mcp"
path = "src/main.rs"
```

> 注意：`rmcp` 版本演进较快（仓库正在向 1.0 迁移）。请到 `crates.io/crates/rmcp` 核对最新稳定版号再填。`features = ["server", "transport-io"]` 分别启用服务端角色和 stdio 传输；如果只用宏写工具，`server` 通常已包含宏支持，若编译报宏未找到再加 `macros`。

**预期结果**：`cargo check` 通过，无依赖解析错误。首次会下载 `tokio` 全家桶与 `rmcp`，约 1–2 分钟。

**常见问题**：

- *报错 `unresolved import rmcp::tool_router`*：说明缺少宏特性。把 features 改成 `["server", "macros", "transport-io"]`。
- *schemars 版本冲突*：rmcp 内部依赖特定版本的 `schemars`。如果你自己也要 `#[derive(JsonSchema)]`，直接用 rmcp 重导出的 `rmcp::schemars::JsonSchema`，而不是独立引 `schemars`，避免双版本。

## 四、第 2 步：定义带状态的服务与工具

**操作说明**：先写数据模型与状态。新建 `src/main.rs`（先放数据层，下一步再加服务层）：

```rust
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::PathBuf;
use tokio::sync::Mutex;

/// 一条代码片段
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Snippet {
    pub id: u64,
    pub title: String,
    pub language: String,
    pub code: String,
}

/// 可持久化的仓库：一个自增计数器 + 按 id 存储的片段表
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Store {
    pub next_id: u64,
    pub items: BTreeMap<u64, Snippet>,
}

/// MCP 服务的共享状态：内存镜像 + 落盘路径
pub struct AppState {
    pub store: Mutex<Store>,
    pub path: PathBuf,
}

impl AppState {
    pub fn new(path: PathBuf) -> anyhow::Result<Self> {
        let store = if path.exists() {
            serde_json::from_slice(&std::fs::read(&path)?)?
        } else {
            Store::default()
        };
        Ok(Self { store: Mutex::new(store), path })
    }

    pub async fn persist(&self) -> anyhow::Result<()> {
        let guard = self.store.lock().await;
        let bytes = serde_json::to_vec_pretty(&*guard)?;
        // 原子写：先写临时文件再 rename，避免半截写入
        let tmp = self.path.with_extension("json.tmp");
        std::fs::write(&tmp, bytes)?;
        std::fs::rename(&tmp, &self.path)?;
        Ok(())
    }
}
```

关键点：状态用 `tokio::sync::Mutex`（不是 `std::sync::Mutex`），因为我们要在 `async` 工具方法里 `.lock().await`；落盘用"临时文件 + rename"保证原子性，进程随时被 kill 也不会写坏 JSON。

现在写服务层与工具。`rmcp` 让你把工具写成结构体的方法，用宏登记：

```rust
use rmcp::{
    handler::server::tool::ToolRouter,
    model::{ServerCapabilities, ServerInfo},
    schemars,
    service::ServiceExt,
    tool, tool_handler, tool_router,
    Error as McpError,
};
use serde::Deserialize;

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct AddSnippetParams {
    /// 片段标题，例如 "二分查找"
    pub title: String,
    /// 语言，例如 "rust" / "python"
    pub language: String,
    /// 代码正文
    pub code: String,
}

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct SearchParams {
    /// 关键字，在 title 和 code 中做子串匹配
    pub query: String,
}

pub struct SnippetService {
    pub state: std::sync::Arc<AppState>,
    pub router: ToolRouter<SnippetService>,
}

#[tool_router]
impl SnippetService {
    pub fn new(state: std::sync::Arc<AppState>) -> Self {
        Self { state, router: Self::tool_router() }
    }

    #[tool(description = "保存一段代码片段到本地知识库，返回它的 id。")]
    async fn add_snippet(&self, #[tool(param)] params: AddSnippetParams) -> Result<String, McpError> {
        let mut store = self.state.store.lock().await;
        store.next_id += 1;
        let id = store.next_id;
        store.items.insert(id, Snippet {
            id,
            title: params.title,
            language: params.language,
            code: params.code,
        });
        drop(store);
        self.state.persist().await.map_err(|e| internal(e))?;
        Ok(format!("已保存，id = {id}"))
    }

    #[tool(description = "按关键字在标题与代码中搜索，返回匹配片段列表。")]
    async fn search_snippets(&self, #[tool(param)] params: SearchParams) -> Result<String, McpError> {
        let store = self.state.store.lock().await;
        let q = params.query.to_lowercase();
        let hits: Vec<&Snippet> = store.items.values()
            .filter(|s| s.title.to_lowercase().contains(&q) || s.code.to_lowercase().contains(&q))
            .collect();
        if hits.is_empty() {
            return Ok("没有匹配的片段。".into());
        }
        let out: Vec<String> = hits.iter()
            .map(|s| format!("[{}] ({}) {}", s.id, s.language, s.title))
            .collect();
        Ok(out.join("\n"))
    }

    #[tool(description = "按 id 删除片段。")]
    async fn delete_snippet(&self, #[tool(param)] id: u64) -> Result<String, McpError> {
        let mut store = self.state.store.lock().await;
        if store.items.remove(&id).is_none() {
            return Ok(format!("id {id} 不存在，未做任何删除。"));
        }
        drop(store);
        self.state.persist().await.map_err(|e| internal(e))?;
        Ok(format!("已删除 id {id}。"))
    }
}

/// 把任意 anyhow 错误转成 MCP 协议错误（供进阶按错误类型细分）
fn internal(e: impl std::fmt::Display) -> McpError {
    McpError::internal_error(format!("snippet-mcp: {e}"), None)
}
```

> 关于错误处理：`#[tool]` 方法支持返回 `Result<T, McpError>`，`T` 只要实现序列化输出（`String`、`i32`、或 `CallToolResult`）。这里用 `McpError::internal_error` 把落盘失败转成协议错误。若你想返回"业务上的非致命错误"（如片段不存在）让模型能继续推理，有两种选择：一是像 `delete_snippet` 那样返回一个描述性 `String`（语义为成功调用、内容是提示），二是返回 `CallToolResult { is_error: true, content: ... }`。本教程对"删除不存在"采用前者，对"落盘失败"采用后者，覆盖两种范式。

**预期结果**：`cargo check` 通过。`#[tool_router]` 宏会在编译期为 `SnippetService` 生成 `tool_router()` 关联函数，并把 `add_snippet`/`search_snippets`/`delete_snippet` 注册进路由表，对应的 `inputSchema` 由 `schemars` 从 `AddSnippetParams`/`SearchParams` 自动派生。

**常见问题**：

- *`#[derive(schemars::JsonSchema)]` 报错*：务必用 `rmcp::schemars`（即 `use rmcp::schemars;`），不要单独引 `schemars` 1.x，否则可能因为 rmcp 内部用的是 0.8 而出现两个不兼容版本。
- *`#[tool(param)]` 加在 `u64` 上行不行？*：可以，基本类型（`String`、`i32`、`u64`、`bool`、`Option<T>`）都能直接作为参数，rmcp 会自动派生 schema。只有当参数多于一个或需要分组时才用结构体 + `JsonSchema`。
- *想在 doc 注释里写参数说明*：用 `#[schemars(description = "...")]` 或直接写 doc comment，两者都会进入 `inputSchema`，模型据此决定如何填写。

## 五、第 3 步：实现 ServerHandler 与 stdio 主循环

**操作说明**：在 `main.rs` 末尾加上协议入口和 main：

```rust
use rmcp::{handler::server::ServerHandler, implementation};
use tokio::io::{stdin, stdout};

#[tool_handler]
impl ServerHandler for SnippetService {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            // implementation!() 宏帮你构造 Implementation{name, version}
            server_info: implementation!("snippet-mcp", env!("CARGO_PKG_VERSION")),
            capabilities: ServerCapabilities::builder()
                .enable_tools()
                .build(),
            instructions: Some("代码片段管家：用 add_snippet 存，用 search_snippets 查，用 delete_snippet 删。".into()),
            ..Default::default()
        }
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .with_writer(std::io::stderr)   // 日志只走 stderr，绝不污染 stdout
        .init();

    // 存储路径：优先用环境变量，否则落到用户数据目录
    let path = std::env::var("SNIPPET_STORE_PATH")
        .map(PathBuf::from)
        .unwrap_or_else(|_| {
            let mut p = dirs_or_home();
            p.push("snippet-mcp-store.json");
            p
        });

    let state = std::sync::Arc::new(AppState::new(path)?);
    let service = SnippetService::new(state);

    // 关键：serve(stdin, stdout) 启动 JSON-RPC over stdio 循环
    let running = service.serve(stdin(), stdout()).await?;
    running.waiting().await?;
    Ok(())
}

fn dirs_or_home() -> PathBuf {
    std::env::var("HOME")
        .map(PathBuf::from)
        .unwrap_or_else(|_| PathBuf::from("."))
}
```

`#[tool_handler]` 宏会自动实现 `list_tools` 与 `call_tool`，把它们转发给 `self.router`——你完全不用手写分发。`serve(stdin(), stdout())`（来自 `ServiceExt`，需 `transport-io` 特性）就是 MCP 的 stdio 传输：客户端写到服务器的 stdin，服务器把响应写到 stdout。

**预期结果**：`cargo build` 成功，产出 `target/debug/snippet-mcp`。

**常见问题**：

- *stdout 里混进了日志导致客户端解析失败*：MCP 协议要求 stdout 只能有 JSON-RPC 行。务必把日志重定向到 stderr（如上 `with_writer(std::io::stderr)`），任何 `println!` 都是 bug。
- *`stdin()`/`stdout()` 似乎被缓冲*：tokio 的异步 stdin 默认行缓冲，对 JSON-RPC 没问题。如果你的工具输出很大且客户端卡住，检查是否漏了换行。

## 六、第 4 步：cargo run 冒烟自测

MCP 服务器平时不打印东西，直接 `cargo run` 会"看起来卡住"——这是正常的，它在等 stdin。最快的自测是手发一条 `initialize` 报文：

```bash
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"manual","version":"0.0"}}}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
  | cargo run -q
```

**预期结果**：stdout 依次返回三条 JSON-RPC 响应：`initialize` 结果（含 `serverInfo` 名为 `snippet-mcp`）、（initialized 是通知，无响应）、`tools/list` 列出三个工具及其 `inputSchema`。看到 `tools/list` 里 `add_snippet`、`search_snippets`、`delete_snippet` 就说明链路通了。

## 七、第 5 步：用 MCP Inspector 交互测试

手动拼 JSON 太累。官方 Inspector 给了你一个 GUI：

```bash
npx -y @modelcontextprotocol/inspector cargo run -q
```

**预期结果**：自动打开浏览器（默认 `http://localhost:6274`）。在左侧 `Tools` 面板能看到三个工具，点开 `add_snippet`、填入 `title`/`language`/`code`、点 `Run Tool`，右侧返回 `已保存，id = 1`。再去 `search_snippets` 查一下，验证状态真的持久化了（关掉 Inspector 重开，数据还在）。

**常见问题**：

- *Inspector 连不上 / 一直 Connecting*：90% 是 stdout 被污染。在 Inspector 的日志面板检查 stderr，确认没有 `println!` 漏网；同时确认 `cargo run` 能在前一步冒烟自测里正常响应。
- *Inspector 里看不到工具*：检查 `get_info()` 的 `capabilities` 是否 `enable_tools()`，以及 `#[tool_handler]` 是否确实加在 `impl ServerHandler` 块上。

## 八、第 6 步：接入 Claude Code

**操作说明**：编译 release 版本，再用 `claude mcp add` 注册：

```bash
cargo build --release
# 项目级（写入当前项目 .mcp.json，随仓库共享）
claude mcp add snippet-mcp -- /home/you/snippet-mcp/target/release/snippet-mcp
# 或用户级（所有项目可用）
claude mcp add --scope user snippet-mcp -- /home/you/snippet-mcp/target/release/snippet-mcp
```

> 用绝对路径，避免在不同工作目录下找不到二进制。要传环境变量（如自定义存储路径），加 `-e SNIPPET_STORE_PATH=/path/store.json`。

**预期结果**：进入任意项目目录启动 `claude`，运行 `/mcp`，应看到 `snippet-mcp` 状态为 `connected`，并展开列出三个工具。然后试着对话：

```
> 帮我存一段快排，语言 rust，标题就叫 quicksort
> 搜一下我库里有没有 quicksort
> 把 quicksort 删掉
```

模型会自动选中 `add_snippet` → `search_snippets` → `delete_snippet`，你能看到每一步工具调用与返回。

**常见问题**：

- *`/mcp` 显示 `failed to connect`*：在 `claude` 里运行 `claude mcp get snippet-mcp` 看注册的命令，手动执行它确认能启动；再核对绝对路径与可执行权限。
- *`/mcp` 连接正常但模型不调工具*：通常是 `description` 写得太笼统。把每个工具的描述写成"动词 + 用途 + 何时用"，模型的选择准确率会显著上升。
- *Windows 上路径带空格*：把命令和参数用引号包好，或直接编辑 `.mcp.json` 用 JSON 数组形式写 `args`。

## 实战案例：完整运行演示

把前几步的代码拼起来就是一个完整可运行项目，目录结构：

```
snippet-mcp/
├── Cargo.toml
└── src/
    └── main.rs      # 数据层 + 服务层 + ServerHandler + main 全在一个文件
```

端到端验证脚本（保存为 `e2e.sh`）：

```bash
#!/usr/bin/env bash
set -e
export SNIPPET_STORE_PATH=/tmp/snippet-test.json
BIN=./target/release/snippet-mcp
[ -x "$BIN" ] || cargo build --release

# 存两条
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"add_snippet","arguments":{"title":"hello","language":"rust","code":"fn main(){println!(\"hi\")}"}}}' \
  '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_snippets","arguments":{"query":"hi"}}}' \
  | "$BIN"

echo "--- 持久化文件内容 ---"
cat "$SNIPPET_STORE_PATH"
```

跑通后你会看到：`add_snippet` 返回 `已保存，id = 1`，`search_snippets` 命中它，且 `/tmp/snippet-test.json` 里确实落盘了一条记录。这就闭环了——从模型决策到工具执行再到磁盘持久化，全程类型安全。

## 进阶：从 stdio 迈向 Streamable HTTP

stdio 适合本地单用户。当你想让远程的 Claude（或多台机器）共享同一个服务器时，换传输层即可，业务代码几乎不动。rmcp 把传输抽象成了 `serve(transport)`，因此只需替换传输实现：

```mermaid
flowchart TB
    subgraph Local["本地单机"]
        L1["Claude Code"] -->|"stdio"| S1["snippet-mcp"]
    end
    subgraph Remote["远程/多用户"]
        R1["远程 Claude"] -->|"HTTPS + SSE"| GW["axum HTTP 服务器"]
        GW --> S2["同一个 SnippetService"]
        GW -.鉴权.-> Auth["API Key / OAuth2 中间件"]
    end
    S1 ---|"同一份 #[tool] 实现"| S2
```

要点：

- rmcp 提供 `transport-streamable-http-server`（基于 axum）与 `transport-sse` 特性，把同一个 `SnippetService` 暴露成 HTTP 端点。
- 一旦上网络，**必须加鉴权**：最小化方案是在 axum 层加 API-Key 中间件；多租户则上 OAuth2（rmcp 对远程服务器的 OAuth2 流程有示例，可参考官方仓库 `examples`）。
- 有状态服务在多用户下要重新审视：当前 `AppState` 是全局单例，多用户会共享一个片段库。要隔离，把"当前用户"从请求上下文带进服务，或把状态改成 `Map<UserId, Store>`。

这部分你不需要现在动手——先用 stdio 跑通价值闭环，等真有远程需求时再切传输层，因为 rmcp 的传输抽象让你为此付出的迁移成本极低。

## 调优与安全清单

上线前逐条过一遍：

1. **stdout 纯净**：全程零 `println!`，日志只走 stderr。这是 MCP 服务的第一戒律。
2. **工具粒度**：一个工具做一件事。`add`/`search`/`delete` 分开，比一个 `manage_snippet(action, ...)` 大杂烩更容易被模型选准。
3. **描述即文档**：`description` 和 `#[schemars(description)]` 是模型唯一能看到的接口说明，写得越精确（含边界、单位、示例），误用越少。
4. **大输出分页**：`search` 命中上千条时不要全返回，加 `limit`/`offset` 参数，避免撑爆上下文窗口。
5. **副作用最小化与可逆**：`delete` 先做存在性检查、返回清晰提示；危险操作可在 `description` 里写明"不可逆"。
6. **输入校验**：虽然 schema 保证类型，但业务约束（标题非空、长度上限、语言白名单）要在方法体里再验一次，用 `McpError::invalid_params` 反馈。
7. **密钥**：若工具要调外部 API，密钥走环境变量/secret manager，绝不硬编码；二进制分发时附 `.env.example` 而非真值。
8. **超时与取消**：长任务工具要响应客户端取消（rmcp 的请求处理可感知 `Cancelled` 通知），别让一个慢查询拖死整个会话。

## 训练营适用性评估

- **适合的营型**：2–3 天的"系统编程 / 工具链"进阶营，或"AI 编程基础设施"专题营。不适合纯前端或零编程基础的入门营。
- **前置门槛**：学员需具备 Rust 基本读写能力（async/Mutex/Result）。可在开营前发《Rust Book》异步章节作为预习。
- **建议教学时长**：总计 6–8 小时。其中 rmcp 心智模型与第一个工具 1.5 小时，状态持久化与错误处理 2 小时，Inspector 调试与 Claude Code 接入 1.5 小时，学员独立扩展一个新工具（如 `list_languages` 统计、`export` 导出 Markdown）并互评 2 小时。
- **考核点**：能独立交付一个带至少 3 个工具、有持久化、stdout 纯净、`/mcp` 连接成功的 MCP 服务器，并用一段自然语言对话驱动它完成增删查的完整闭环。
- **延伸项目**：把本服务改成 Streamable HTTP + API Key 远程版；或把存储后端从 JSON 文件换成 SQLite（加 `rusqlite`），让学员体会"业务代码不动、只换底层"的抽象价值。

## 参考来源

1. [modelcontextprotocol/rust-sdk (GitHub)](https://github.com/modelcontextprotocol/rust-sdk) — rmcp 官方仓库，含 macros、transport、server/client 全套示例，最权威的 API 与特性列表来源。
2. [rmcp 文档 (docs.rs)](https://docs.rs/rmcp) — 各版本 API 参考与宏用法，核对 `ServerHandler`、`ServiceExt::serve`、错误类型的实时来源。
3. [Build an MCP Server in Rust with rmcp and Claude Code (systemprompt.io)](https://systemprompt.io/guides/build-mcp-server-rust) — 从 `cargo new` 到三个工具的端到端实战指南，本文第 1–3 步的组织方式参考了它的编排。
4. [Building MCP Servers in Rust: Model Context Protocol Guide for 2026 (rustify.rs)](https://rustify.rs/articles/rust-for-mcp-model-context-protocol-servers-2026) — 对 Rust 写 MCP 的性能与适用场景分析，支撑"为什么用 Rust"一节。
5. [Model Context Protocol 官方文档 (modelcontextprotocol.io)](https://modelcontextprotocol.io) — 协议规范、工具/Resources/Prompts 三类原语与传输层概念，理解 JSON-RPC 报文格式的第一手来源。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
