- 教程
- 文档
- 人工智能
【免费下载链接】mcp-for-beginners
This open-source curriculum introduces the fundamentals of Model Context Protocol (MCP) through real-world, cross-language examples in .NET, Java, TypeScript, JavaScript, Rust and Python. Designed for developers, it focuses on practical techniques for building modular, scalable, and secure AI workflows from session setup to service orchestration.
导读
本文以 mcp-for-beginners 课程中「创建带 LLM 的客户端」(03-llm-client)一节的 Rust 解决方案为主线,讲解如何在 Rust 中把 MCP 服务器暴露的工具能力交给大语言模型(LLM),让用户用自然语言完成对 MCP 工具的调用。读完本文,你将掌握 Rust 环境下 LLM 客户端的完整搭建流程:配置 Microsoft Foundry 模型端点、通过 stdio 拉起 calculator MCP 服务器、拉取并转换工具清单、驱动 LLM 发起函数调用(function calling),最终看到Calling tool: add与计算结果输出。
为什么客户端需要接入 LLM
在前面的课程(02-client)中,客户端虽然已经能够连接服务器并显式列出 tools、resources 和 prompts,但这种「手动点名调用」的交互方式并不实用。用户期待的是用自然语言与系统对话,而不是关心底层是否使用 MCP 承载能力。解决方案就是:在客户端一侧接入 LLM,把 MCP 服务器注册的能力及其 JSON Schema 转换成 LLM 可以理解的形式,让 LLM 自主决定调用哪个工具、传入什么参数。
整体交互流程如下:
- 与 MCP 服务器建立连接(本示例通过 stdio 拉起 calculator 服务器进程);
- 列出服务器的 capabilities、工具清单并保存其 schema;
- 将保存的能力与 schema 转换成 LLM 的函数调用格式;
- 把用户提示词连同工具清单交给 LLM,由 LLM 决定是否触发工具调用,再经由 MCP 客户端回传结果。
Rust 解决方案的完整代码位于 solution/rust/src/main.rs,工程配置见 Cargo.toml。
运行前置条件
本示例是 Rust 编写的 LLM 客户端,需要满足以下前置条件:
- 已安装 Rust toolchain(
cargo与rustc可用),并确保cargo在 PATH 中; - 一个可通过 Azure OpenAI v1 端点访问的 Microsoft Foundry 模型部署,即已部署的活跃模型(如
gpt-5.1); - 一个可运行的 calculator MCP 服务器。若尚未创建,请先完成 01-first-server 课程,其 Rust 实现位于 01-first-server/solution/rust,服务器通过 stdio 暴露一个
add工具。
提示:课程主文档(03-llm-client/README.md)中其他语言的客户端方案各不相同——Java 客户端示例仍通过传统 HTTP+SSE 传输并针对 MCP
2025-11-25SDK;新的远程客户端应使用2026-07-28兼容 SDK 与 Streamable HTTP。Rust 方案则使用 rmcp 的客户端特性与子进程传输(transport-child-process)。
第一步:配置 Microsoft Foundry 环境变量
客户端通过 Azure OpenAI v1 端点调用已部署的模型。运行前必须设置三个环境变量:
# zsh/bash export AZURE_OPENAI_ENDPOINT="https://<resource-name>.openai.azure.com" export AZURE_OPENAI_API_KEY="<api-key>" export AZURE_OPENAI_DEPLOYMENT="gpt-5.1"PowerShell 下使用对应语法:
# PowerShell $env:AZURE_OPENAI_ENDPOINT = "https://<resource-name>.openai.azure.com" $env:AZURE_OPENAI_API_KEY = "<api-key>" $env:AZURE_OPENAI_DEPLOYMENT = "gpt-5.1"三个变量的作用与取值要点如下:
| 变量 | 用途 | 说明 |
|---|---|---|
AZURE_OPENAI_ENDPOINT | 资源端点 | 形如https://<resource-name>.openai.azure.com,客户端会在此基础上拼接/openai/v1 |
AZURE_OPENAI_API_KEY | 访问密钥 | 用于 Azure OpenAI 兼容端点的鉴权 |
AZURE_OPENAI_DEPLOYMENT | 部署名称 | 传给 LLM 的model参数;注意它是部署名而非底层模型名,二者可能不同 |
在源码中,AZURE_OPENAI_DEPLOYMENT是可选的,缺省值为gpt-5.1:
let model = std::env::var("AZURE_OPENAI_DEPLOYMENT") .unwrap_or_else(|_| "gpt-5.1".to_string());(见 main.rs)
选择模型前建议先查阅 Microsoft Foundry 的模型退役时间表;如果部署名与底层模型名不一致,API 调用中必须使用部署名。
第二步:构建示例
进入 Rust 解决方案目录后执行:
cargo build依赖声明位于 Cargo.toml,核心依赖如下:
[dependencies] async-openai = { version = "0.29.0", features = ["byot"] } rmcp = { version = "1.4.0", features = ["client", "transport-child-process"] } serde_json = "1.0.141" tokio = { version = "1.46.1", features = ["rt-multi-thread"] }各依赖的角色:
async-openai:社区维护的 OpenAI API Rust 客户端。官方并未提供 Rust 版 OpenAI 库,这是社区中常用的替代方案。启用byot(bring your own tools)特性以支持自定义工具定义(function calling);rmcp:Rust 实现的 MCP 客户端/服务端库,启用client与transport-child-process特性,用于建立 MCP 客户端并管理子进程传输;serde_json:处理工具清单与 LLM 响应中的 JSON 数据;tokio:异步运行时(rt-multi-thread)。
第三步:运行示例并观察工具调用
cargo run程序启动后依次完成以下动作:拉起 calculator MCP 服务器进程、获取工具列表、调用部署模型、由模型决定调用add工具。预期输出中应能看到工具调用日志(例如⚡ Calling tool: add)以及该次调用的计算结果。
源码级解析:main.rs 如何串起「MCP + LLM」链路
接下来按执行顺序拆解 main.rs 的实现,说明每一步与文档命令的对应关系。
1. 初始化用户消息与 OpenAI 客户端
let mut messages = vec![json!({"role": "user", "content": "What is the sum of 3 and 2?"})]; let endpoint = std::env::var("AZURE_OPENAI_ENDPOINT")?; let api_key = std::env::var("AZURE_OPENAI_API_KEY")?; let openai_client = Client::with_config( OpenAIConfig::new() .with_api_base(format!("{}/openai/v1", endpoint.trim_end_matches('/'))) .with_api_key(api_key), );(见 main.rs)
要点:
messages保存完整的对话历史,起始是一条用户消息,后续会不断追加助手消息与工具结果消息;endpoint末尾的多余斜杠会被trim_end_matches('/')去除,再拼上/openai/v1,得到 Azure OpenAI 兼容端点;OpenAIConfig同时注入 API Key,用于后续completions().create_byot(...)请求鉴权。
2. 以子进程方式启动 MCP 服务器
let server_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) .parent() // solution .and_then(|p| p.parent()) // 03-llm-client .and_then(|p| p.parent()) // 03-GettingStarted .map(|p| p.join("01-first-server/solution/rust")) .expect("Failed to resolve server directory path"); let mcp_client = () .serve( TokioChildProcess::new(Command::new("cargo").configure(|cmd| { cmd.arg("run").current_dir(server_dir); })) .map_err(RmcpError::transport_creation::<TokioChildProcess>)?, ) .await?;(见 main.rs)
这里通过CARGO_MANIFEST_DIR向上回溯,定位到课程根目录下的01-first-server/solution/rust,然后用cargo run以子进程方式启动服务器,建立 stdio 传输。也就是说,cargo run一条命令会同时拉起服务器进程与客户端逻辑——服务器端add工具的实现见 01-first-server/solution/rust/src/main.rs,其核心定义如下:
#[tool(description = "Adds a and b")] async fn add( &self, Parameters(CalculatorRequest { a, b }): Parameters<CalculatorRequest>, ) -> String { (a + b).to_string() }参数结构CalculatorRequest { a: f64, b: f64 }通过schemars::JsonSchema自动生成 JSON Schema,这正是后续format_tools所需inputSchema的来源。
3. 获取 MCP 工具清单
let tools = mcp_client.list_tools(Default::default()).await?;(见 main.rs)
返回的ListToolsResult中包含服务器注册的add工具及其 JSON Schema。
4. 把 MCP 工具转换为 LLM 可理解的格式
MCP 的工具定义不能直接交给 LLM,需要转换成 OpenAI 兼容的 function calling 结构。format_tools完成这一转换:
async fn format_tools(tools: &ListToolsResult) -> Result<Vec<Value>, Box<dyn Error>> { let tools_json = serde_json::to_value(tools)?; let Some(tools_array) = tools_json.get("tools").and_then(|t| t.as_array()) else { return Ok(vec![]); }; let formatted_tools = tools_array .iter() .filter_map(|tool| { let name = tool.get("name")?.as_str()?; let description = tool.get("description")?.as_str()?; let schema = tool.get("inputSchema")?; Some(json!({ "type": "function", "function": { "name": name, "description": description, "parameters": { "type": "object", "properties": schema.get("properties").unwrap_or(&json!({})), "required": schema.get("required").unwrap_or(&json!([])) } } })) }) .collect(); Ok(formatted_tools) }(见 main.rs)
转换规则要点:
- 每个工具被映射为
{"type": "function", "function": {...}}结构; - 从
inputSchema中提取properties(参数属性)与required(必填参数)两个关键字段; - 缺失
properties或required时分别回退为空对象与空数组,保证格式完整性。
5. 调用 LLM 并让其决定是否调用工具
async fn call_llm( client: &Client<OpenAIConfig>, messages: &[Value], tools: &ListToolsResult, ) -> Result<Value, Box<dyn Error>> { let model = std::env::var("AZURE_OPENAI_DEPLOYMENT") .unwrap_or_else(|_| "gpt-5.1".to_string()); let response = client .completions() .create_byot(json!({ "messages": messages, "model": model, "tools": format_tools(tools).await?, })) .await?; Ok(response) }(见 main.rs)
请求体同时携带对话历史、部署名(model)与格式化后的tools数组,LLM 根据用户问题决定是否返回tool_calls。
6. 处理 LLM 响应并回传工具结果
process_llm_response是整个回路的核心:解析choices[0].message,若含content则打印模型文本;若含tool_calls则逐条执行:
if let Some(tool_calls) = message.get("tool_calls").and_then(|tc| tc.as_array()) { messages.push(message.clone()); // Add assistant message for tool_call in tool_calls { let (tool_id, name, args) = extract_tool_call_info(tool_call)?; println!("⚡ Calling tool: {}", name); let result = mcp_client .call_tool(CallToolRequestParam { name: name.into(), arguments: serde_json::from_str::<Value>(&args)?.as_object().cloned(), }) .await?; messages.push(json!({ "role": "tool", "tool_call_id": tool_id, "content": serde_json::to_string_pretty(&result)? })); } let response = call_llm(openai_client, messages, mcp_tools).await?; Box::pin(process_llm_response( &response, mcp_client, openai_client, mcp_tools, messages, )) .await?; }(见 main.rs)
关键细节:
- 工具调用日志
⚡ Calling tool: add即文档中「Calling tool: add」期望输出的来源; - 每次工具调用后,以
role: "tool"消息(携带tool_call_id与序列化结果)追加进对话历史; - 随后递归调用
call_llm把工具结果交还给模型,让模型基于真实计算结果生成最终答复,实现完整的「思考—调用—再思考」循环。
extract_tool_call_info负责从工具调用对象中拆出id、function.name与function.arguments(JSON 字符串),供call_tool使用(见 main.rs)。
与课程整体脉络的对应关系
本 Rust 方案完整覆盖了课程(03-llm-client/README.md)中「创建带 LLM 的客户端」的全部四个步骤:连接服务器(子进程 stdio)、列出 capabilities(list_tools)、转换 schema(format_tools)、处理用户提示词(call_llm+process_llm_response)。与 TypeScript、Python、.NET、Java 等兄弟方案相比(各语言实现分别位于 solution 目录),Rust 方案的特点是:
- 以
rmcp直接内嵌启动服务器进程,无需预先单独启动; - 借助
async-openai的byot特性,以原始 JSON 形式构建工具定义与请求体,不依赖框架层面的自动绑定; - 循环逻辑显式可见,方便理解 function calling 协议本身。
验证与排错要点
- 运行
cargo run后若未出现工具调用日志,首先确认AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_API_KEY已正确导出,且AZURE_OPENAI_DEPLOYMENT对应的部署处于可用状态; - 若服务器路径解析失败,程序会输出
Failed to resolve server directory path,请确认在 solution/rust 目录下运行(CARGO_MANIFEST_DIR的路径推导依赖该相对布局); - 修改 Cargo.toml 后需重新
cargo build; - 与 calculator 服务器相关的工具定义、JSON Schema 生成细节可对照 01-first-server/solution/rust/src/main.rs 阅读,
add工具的入参{a, b}即CalculatorRequest的字段。
至此,一个「自然语言提问 → LLM 决策 → MCP 工具执行 → 结果回传模型」的完整闭环已经在 Rust 中跑通,你可以在此基础上替换为任意 MCP 服务器与工具集,扩展自己的 LLM Agent 应用。
- 教程
- 文档
- 人工智能
【免费下载链接】mcp-for-beginners
This open-source curriculum introduces the fundamentals of Model Context Protocol (MCP) through real-world, cross-language examples in .NET, Java, TypeScript, JavaScript, Rust and Python. Designed for developers, it focuses on practical techniques for building modular, scalable, and secure AI workflows from session setup to service orchestration.
相关推荐
mcp-for-beginners 实战:使用 .NET 构建接入 LLM 的 MCP 客户端
mcp for beginners 实战:使用 .NET 构建接入 LLM 的 MCP 客户端 在本篇指南中,你将基于 mcp for beginners 开源
教程文档人工智能mcp-for-beginners 实战:为 MCP 客户端接入 LLM,构建自然语言驱动的工具调用闭环(TypeScript / Python / .NET / Java / Rust)
mcp for beginners 实战:为 MCP 客户端接入 LLM,构建自然语言驱动的工具调用闭环(TypeScript / Python / .NET
教程文档人工智能mcp-for-beginners 实战:为 Python MCP 客户端接入 LLM,让用户用自然语言调用 MCP 工具
mcp for beginners 实战:为 Python MCP 客户端接入 LLM,让用户用自然语言调用 MCP 工具 本文基于开源课程 mcp for b
教程文档人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考