news 2026/10/23 23:42:54

MCP for Beginners:构建接入 LLM 的 MCP 客户端——从工具列举到自然语言交互的完整实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MCP for Beginners:构建接入 LLM 的 MCP 客户端——从工具列举到自然语言交互的完整实战指南
  • 教程
  • 文档
  • 人工智能

【免费下载链接】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.

项目地址:https://gitcode.com/GitHub_Trending/mc/mcp-for-beginners
点击查看免费下载

本教程是 mcp-for-beginners 开源课程 03-GettingStarted/03-llm-client 一节的实战成文。在之前的两节课里,你已经学会分别创建 MCP 服务器和显式调用服务器能力的客户端;本节课将把大语言模型(LLM)接入客户端,让用户用自然语言(而不是精确的客户端命令)与 MCP 服务器交互,并借助 Function Calling 让 LLM 自主决定何时调用服务器上的工具。完成后,你将掌握一套跨 TypeScript、Python、.NET、Java、Rust 五种语言的“MCP + LLM”客户端搭建方法论。

为什么要给 MCP 客户端加一个 LLM

在前面的课程中,客户端虽然能列出服务器的工具(tools)、资源(resources)和提示词(prompts),但调用方式仍是显式、命令式的。用户的真实诉求是:直接输入一句自然语言,例如“帮我计算 2 加 3 的和”,剩下的交给系统去完成。用户不关心你用什么协议或框架存储这些能力,只期望用自然语言交互。

因此,本节的目标非常明确:

  • 创建一个带 LLM 的 MCP 客户端;
  • 让客户端通过 LLM 与 MCP 服务器无缝对话;
  • 提升最终用户在客户端侧的体验。

总体思路:四个步骤让 LLM 学会“使用”MCP 工具

把 LLM 接进客户端听起来简单,但关键在于让 LLM 能“看见”并“调用”服务器暴露的能力。整体流程可以拆成四步:

  1. 建立与服务器的连接——创建 MCP 客户端并完成握手;
  2. 列举服务器能力——列出工具、资源、提示词,并把工具的参数模式(inputSchema)保存下来;
  3. 把能力转换成 LLM 可理解的格式——将 MCP 的inputSchema映射为 LLM Function Calling 标准工具定义;
  4. 处理用户提示——把用户提示连同工具定义一起发给 LLM,解析返回的tool_calls,再回拨 MCP 服务器执行对应工具。

认证准备:使用 GitHub 个人访问令牌调用模型

捷克语版教程(本文章的主体文档)采用 GitHub Models 推理端点完成 LLM 调用,需要先准备一个 GitHub 个人访问令牌(PAT):

  • 进入 GitHubSettings(点击右上角头像 → Settings);
  • 滚动到底部进入Developer settings;
  • 选择Personal access tokens→Fine-grained tokens→Generate new token;
  • 为令牌添加说明(note)、设置过期时间,并勾选所需权限(scope);本教程场景务必勾选Models权限;
  • 点击Generate token后立即复制保存,因为令牌不会再次完整显示。

仓库内 03-GettingStarted/03-llm-client/solution 下的解决方案已更新为 Microsoft Foundry 部署方式(GitHub Models 已于 2026-07-30 退役),改用AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_API_KEY、AZURE_OPENAI_DEPLOYMENT三个环境变量,示例见 solution/python/README.md 与 solution/rust/README.md。本文正文以捷克语原文档的 GitHub token 写法为主线,两种认证方式在代码结构上完全一致。

第一步:连接 MCP 服务器

TypeScript

使用官方@modelcontextprotocol/sdk创建客户端,同时用openaiSDK 配置 LLM 实例:

import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import OpenAI from "openai"; import { z } from "zod"; // 导入 zod 用于 schema 校验 class MCPClient { private openai: OpenAI; private client: Client; constructor(){ this.openai = new OpenAI({ baseURL: "https://models.inference.ai.azure.com", apiKey: process.env.GITHUB_TOKEN, }); this.client = new Client( { name: "example-client", version: "1.0.0" }, { capabilities: { prompts: {}, resources: {}, tools: {} } } ); } }

这里完成了三件事:导入所需库;创建包含client与openai两个成员的类,分别管理 MCP 会话与 LLM 通信;把baseURL指向 GitHub Models 推理 API,以GITHUB_TOKEN作为密钥。

Python

Python 侧基于mcpSDK 的stdio_client与ClientSession:

from mcp import ClientSession, StdioServerParameters, types from mcp.client.stdio import stdio_client # 为 stdio 连接创建服务器参数 server_params = StdioServerParameters( command="mcp", # 可执行文件 args=["run", "server.py"], # 可选命令行参数 env=None, # 可选环境变量 ) async def run(): async with stdio_client(server_params) as (read, write): async with ClientSession( read, write ) as session: # 初始化连接 await session.initialize()

StdioServerParameters的command、args、env三个字段分别对应子进程可执行文件、命令行参数与环境变量;session.initialize()完成 MCP 协议握手。

.NET

.NET 侧通过ModelContextProtocol.Client命名空间创建基于 stdio 传输的 MCP 客户端:

using ModelContextProtocol.Client; var clientTransport = new StdioClientTransport(new() { Name = "Demo Server", Command = "/workspaces/mcp-for-beginners/03-GettingStarted/02-client/solution/server/bin/Debug/net8.0/server", Arguments = [], }); await using var mcpClient = await McpClient.CreateAsync(clientTransport);

StdioClientTransport的Name用于标识服务器,Command指向上一节课 02-client 中构建好的服务器可执行文件路径。

Java(LangChain4j)

Java 示例使用 LangChain4j 框架,先在pom.xml中加入 MCP 集成与 OpenAI 兼容 API 依赖:

<properties> <langchain4j.version>1.0.0-beta3</langchain4j.version> </properties> <dependencies> <!-- LangChain4j MCP 集成 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-mcp</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- OpenAI 官方 API 客户端 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai-official</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- Spring Boot Starter(可选,生产环境应用) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>

接着配置 MiniMax API 密钥、端点与模型。MINIMAX_MODEL_ID支持MiniMax-M3与MiniMax-M2.7;未设置OPENAI_BASE_URL时,MINIMAX_REGION支持global_en与cn_zh:

export OPENAI_API_KEY=your_minimax_api_key_here export OPENAI_BASE_URL=https://api.minimax.io/v1 export MINIMAX_MODEL_ID=MiniMax-M3

如需按区域选择端点,则不设置OPENAI_BASE_URL:

unset OPENAI_BASE_URL export MINIMAX_REGION=cn_zh

随后构建 Java 客户端类LangChain4jClient,其中resolveBaseUrl()与resolveModelName()两个辅助方法负责解析环境变量并校验取值,非法值会抛出带支持列表的异常:

public class LangChain4jClient { private static final String DEFAULT_BASE_URL = "https://api.minimax.io/v1"; private static final String DEFAULT_MODEL_ID = "MiniMax-M3"; private static final Map<String, String> REGIONAL_BASE_URLS = Map.of( "global_en", "https://api.minimax.io/v1", "cn_zh", "https://api.minimaxi.com/v1"); private static final Set<String> SUPPORTED_MODEL_IDS = Set.of("MiniMax-M3", "MiniMax-M2.7"); public static void main(String[] args) throws Exception { ChatLanguageModel model = OpenAiOfficialChatModel.builder() .baseUrl(resolveBaseUrl()) .apiKey(requireEnv("OPENAI_API_KEY")) .timeout(Duration.ofSeconds(60)) .modelName(resolveModelName()) .build(); // 创建 MCP 传输层,通过 SSE 连接服务器 McpTransport transport = new HttpMcpTransport.Builder() .sseUrl("http://localhost:8080/sse") .timeout(Duration.ofSeconds(60)) .logRequests(true) .logResponses(true) .build(); // 创建 MCP 客户端 McpClient mcpClient = new DefaultMcpClient.Builder() .transport(transport) .build(); } // resolveBaseUrl / resolveModelName / requireEnv 实现略 }

这一段代码的要点是:引入 LangChain4j 依赖;创建ChatLanguageModel并指向 MiniMax;设置基于 SSE(Server-Sent Events)的 HTTP 传输层;创建负责与服务器通信的 MCP 客户端;最终依赖 LangChain4j 内建的 MCP 支持来简化 LLM 与 MCP 服务器的集成。

Rust

Rust 示例假设已有一个可运行的 Rust MCP 服务器(若没有,请先完成 01-first-server 课程)。在服务器同级目录初始化新项目:

mkdir calculator-llmclient cd calculator-llmclient cargo init

在Cargo.toml中加入依赖:

[dependencies] async-openai = { version = "0.29.0", features = ["byot"] } rmcp = { version = "0.5.0", features = ["client", "transport-child-process"] } serde_json = "1.0.141" tokio = { version = "1.46.1", features = ["rt-multi-thread"] }

[!NOTE] Rust 没有 OpenAI 官方库,async-openai是社区维护且被广泛使用的 crate。

把src/main.rs替换为如下基础骨架:

use async_openai::{Client, config::OpenAIConfig}; use rmcp::{ RmcpError, model::{CallToolRequestParam, ListToolsResult}, service::{RoleClient, RunningService, ServiceExt}, transport::{ConfigureCommandExt, TokioChildProcess}, }; use serde_json::{Value, json}; use std::error::Error; use tokio::process::Command; #[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { // 初始消息 let mut messages = vec![json!({"role": "user", "content": "What is the sum of 3 and 2?"})]; // 配置 LLM 客户端 let api_key = std::env::var("OPENAI_API_KEY")?; let openai_client = Client::with_config( OpenAIConfig::new() .with_api_base("https://models.github.ai/inference/chat") .with_api_key(api_key), ); // 配置 MCP 客户端(通过子进程启动服务器) let server_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) .parent() .unwrap() .join("calculator-server"); 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?; // TODO: 获取 MCP 工具列表 // TODO: LLM 对话与工具调用 Ok(()) }

[!IMPORTANT] 运行前务必设置环境变量OPENAI_API_KEY(值为 GitHub token)。

完整可运行的 Rust 版本(含工具列表获取、call_llm、process_llm_response、extract_tool_call_info等函数)位于 solution/rust/src/main.rs。

第二步:列举服务器能力

连接建立后,向服务器请求其能力清单。

TypeScript

在同一个类中添加connectToServer与run两个方法:

async connectToServer(transport: Transport) { await this.client.connect(transport); this.run(); console.error("MCPClient started on stdin/stdout"); } async run() { console.log("Asking server for available tools"); // 列举工具 const toolsResult = await this.client.listTools(); }

connectToServer负责连接;run负责编排应用流程,目前只列举工具,后续会不断扩充。

Python

同时列举资源与工具,并打印工具的inputSchema(后续转换要用):

# 列出可用资源 resources = await session.list_resources() print("LISTING RESOURCES") for resource in resources: print("Resource: ", resource) # 列出可用工具 tools = await session.list_tools() print("LISTING TOOLS") for tool in tools.tools: print("Tool: ", tool.name) print("Tool", tool.inputSchema["properties"])

.NET

async Task<List<ChatCompletionsToolDefinition>> GetMcpTools() { Console.WriteLine("Listing tools"); var tools = await mcpClient.ListToolsAsync(); List<ChatCompletionsToolDefinition> toolDefinitions = new List<ChatCompletionsToolDefinition>(); foreach (var tool in tools) { Console.WriteLine($"Connected to server with tools: {tool.Name}"); Console.WriteLine($"Tool description: {tool.Description}"); Console.WriteLine($"Tool parameters: {tool.JsonSchema}"); // TODO: 将 MCP 工具定义转换为 LLM 工具 } return toolDefinitions; }

这里为每个工具打印名称、描述与 JSON Schema——最后一项正是下一步调用工具的关键。

Java

Java 侧无需手工列举,LangChain4j 提供McpToolProvider自动完成发现与注册:

// 创建工具提供器,自动发现 MCP 工具 ToolProvider toolProvider = McpToolProvider.builder() .mcpClients(List.of(mcpClient)) .build(); // MCP 工具提供器自动处理: // - 从 MCP 服务器列举可用工具 // - 将 MCP 工具 schema 转换为 LangChain4j 格式 // - 管理工具执行与响应

McpToolProvider把“手工列举 + 手工转换”的复杂度完全抽象掉了。

Rust

通过list_tools方法获取工具列表:

// 获取 MCP 工具列表 let tools = mcp_client.list_tools(Default::default()).await?;

第三步:把服务器能力转换为 LLM 工具

LLM 不认识 MCP 的inputSchema,需要把工具定义映射为 Function Calling 标准格式:type: "function"+function.name+function.parameters。

TypeScript

定义openAiToolAdapter适配器,并让run方法遍历转换:

openAiToolAdapter(tool: { name: string; description?: string; input_schema: any; }) { // 基于 input_schema 创建 zod schema const schema = z.object(tool.input_schema); return { type: "function" as const, // 显式将类型设置为 "function" function: { name: tool.name, description: tool.description, parameters: { type: "object", properties: tool.input_schema.properties, required: tool.input_schema.required, }, }, }; } async run() { console.log("Asking server for available tools"); const toolsResult = await this.client.listTools(); const tools = toolsResult.tools.map((tool) => { return this.openAiToolAdapter({ name: tool.name, description: tool.description, input_schema: tool.inputSchema, }); }); }

Python

def convert_to_llm_tool(tool): tool_schema = { "type": "function", "function": { "name": tool.name, "description": tool.description, "type": "function", "parameters": { "type": "object", "properties": tool.inputSchema["properties"] } } } return tool_schema

再更新客户端代码,把每个工具都交给该函数处理:

functions = [] for tool in tools.tools: print("Tool: ", tool.name) print("Tool", tool.inputSchema["properties"]) functions.append(convert_to_llm_tool(tool))

.NET

ConvertFrom接收名称、描述和输入 schema,构造 LLM 能理解的函数定义:

ChatCompletionsToolDefinition ConvertFrom(string name, string description, JsonElement jsonElement) { // 将工具转换为函数定义 FunctionDefinition functionDefinition = new FunctionDefinition(name) { Description = description, Parameters = BinaryData.FromObjectAsJson(new { Type = "object", Properties = jsonElement }, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }) }; // 创建工具定义 ChatCompletionsToolDefinition toolDefinition = new ChatCompletionsToolDefinition(functionDefinition); return toolDefinition; }

注意:inputSchema中的参数位于properties属性下,因此调用方需要先提取该子节点,再传入ConvertFrom:

JsonElement propertiesElement; tool.JsonSchema.TryGetProperty("properties", out propertiesElement); var def = ConvertFrom(tool.Name, tool.Description, propertiesElement); Console.WriteLine($"Tool definition: {def}"); toolDefinitions.Add(def);

Java

Java 通过声明一个自然语言交互接口Bot,再由AiServices把 LLM 与工具提供器绑定:

// 创建用于自然语言交互的 Bot 接口 public interface Bot { String chat(String prompt); } // 使用 LLM 与 MCP 工具配置 AI 服务 Bot bot = AiServices.builder(Bot.class) .chatLanguageModel(model) .toolProvider(toolProvider) .build();

LangChain4j 在幕后自动完成工具 schema 转换与函数调用,彻底免去手工转换。

Rust

添加辅助函数format_tools,把ListToolsResult序列化为 LLM 认识的工具数组:

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) }

第四步:处理用户提示,驱动工具调用

这是核心环节:把用户提示连同工具定义发给 LLM,解析返回的tool_calls,回拨 MCP 服务器执行工具,并把结果返回给用户。

TypeScript

先添加callTools方法,遍历 LLM 返回的工具调用并执行:

async callTools( tool_calls: OpenAI.Chat.Completions.ChatCompletionMessageToolCall[], toolResults: any[] ) { for (const tool_call of tool_calls) { const toolName = tool_call.function.name; const args = tool_call.function.arguments; console.log(`Calling tool ${toolName} with args ${JSON.stringify(args)}`); // 2. 调用服务器工具 const toolResult = await this.client.callTool({ name: toolName, arguments: JSON.parse(args), }); console.log("Tool result: ", toolResult); // 3. 对结果做后续处理 // TODO } }

然后在run方法中组装消息、调用 LLM、检查tool_calls:

// 1. 构造 LLM 的输入消息 const prompt = "What is the sum of 2 and 3?" const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [ { role: "user", content: prompt, }, ]; console.log("Querying LLM: ", messages[0].content); // 2. 调用 LLM let response = this.openai.chat.completions.create({ model: "gpt-4.1-mini", max_tokens: 1000, messages, tools: tools, }); let results: any[] = []; // 3. 遍历 LLM 响应,检查每个 choice 是否包含工具调用 (await response).choices.map(async (choice: { message: any; }) => { const message = choice.message; if (message.tool_calls) { console.log("Making tool call") await this.callTools(message.tool_calls, results); } });

完整的 TypeScript 实现(含类定义与 stdio 传输启动逻辑)位于 solution/typescript/src/client.ts。

Python

先补充 LLM 调用所需的导入:

# llm import os from azure.ai.inference import ChatCompletionsClient from azure.ai.inference.models import SystemMessage, UserMessage from azure.core.credentials import AzureKeyCredential import json

再实现call_llm:以 GitHub token 访问推理端点,把转换后的工具定义作为tools传给模型,并从响应中解析出待调用的函数列表:

# llm def call_llm(prompt, functions): token = os.environ["GITHUB_TOKEN"] endpoint = "https://models.inference.ai.azure.com" model_name = "gpt-4o" client = ChatCompletionsClient( endpoint=endpoint, credential=AzureKeyCredential(token), ) print("CALLING LLM") response = client.complete( messages=[ { "role": "system", "content": "You are a helpful assistant.", }, { "role": "user", "content": prompt, }, ], model=model_name, tools = functions, # 可选参数 temperature=1., max_tokens=1000, top_p=1. ) response_message = response.choices[0].message functions_to_call = [] if response_message.tool_calls: for tool_call in response_message.tool_calls: print("TOOL: ", tool_call) name = tool_call.function.name args = json.loads(tool_call.function.arguments) functions_to_call.append({ "name": name, "args": args }) return functions_to_call

最后在主流程里调用 LLM 并执行其建议的工具:

prompt = "Add 2 to 20" # 询问 LLM 应调用哪些工具(如有) functions_to_call = call_llm(prompt, functions) # 调用建议的函数 for f in functions_to_call: result = await session.call_tool(f["name"], arguments=f["args"]) print("TOOLS result: ", result.content)

完整可运行版本见 solution/python/client.py,运行后的典型输出(来自 solution/python/README.md)形如:

LISTING RESOURCES Resource: ('meta', None) Resource: ('nextCursor', None) Resource: ('resources', []) LISTING TOOLS Tool: add Tool {'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}} CALLING LLM TOOL: {'function': {'arguments': '{"a":2,"b":20}', 'name': 'add'}, 'id': 'call_BCbyoCcMgq0jDwR8AuAF9QY3', 'type': 'function'} TOOLS result: [TextContent(type='text', text='22', annotations=None)]

可见 LLM 自主决定调用add工具并传入{"a":2,"b":20},服务器返回22。

.NET

先获取工具、构造对话历史与请求选项:

var tools = await GetMcpTools(); for (int i = 0; i < tools.Count; i++) { var tool = tools[i]; Console.WriteLine($"MCP Tools def: {i}: {tool}"); } // 0. 定义对话历史与用户消息 var userMessage = "add 2 and 4"; chatHistory.Add(new ChatRequestUserMessage(userMessage)); // 1. 定义工具 ChatCompletionsToolDefinition def = CreateToolDefinition(); // 2. 定义选项,包括工具 var options = new ChatCompletionsOptions(chatHistory) { Model = "gpt-4.1-mini", Tools = { tools[0] } }; // 3. 调用模型 ChatCompletions? response = await client.CompleteAsync(options); var content = response.Content;

再检查响应是否包含函数调用,并据此回拨 MCP 服务器:

// 4. 检查响应是否包含函数调用 ChatCompletionsToolCall? calls = response.ToolCalls.FirstOrDefault(); for (int i = 0; i < response.ToolCalls.Count; i++) { var call = response.ToolCalls[i]; Console.WriteLine($"Tool call {i}: {call.Name} with arguments {call.Arguments}"); //Tool call 0: add with arguments {"a":2,"b":4} var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(call.Arguments); var result = await mcpClient.CallToolAsync( call.Name, dict!, cancellationToken: CancellationToken.None ); Console.WriteLine(result.Content.First(c => c.Type == "text").Text); }

完整代码(含 GitHub token 认证与GetMcpTools)参见 solution/dotnet/Program.cs。

Java

Java 侧只需用自然语言提示词调用bot.chat(...),LangChain4j 自动完成“提示→工具调用→结果回填→自然语言回复”的全流程:

try { // 发起自然语言请求,自动使用 MCP 工具 String response = bot.chat("Calculate the sum of 24.5 and 17.3 using the calculator service"); System.out.println(response); response = bot.chat("What's the square root of 144?"); System.out.println(response); response = bot.chat("Show me the help for the calculator service"); System.out.println(response); } finally { mcpClient.close(); }

LangChain4j 在幕后完成:把用户提示在必要时转换为工具调用;根据 LLM 决策调用对应 MCP 工具;管理 LLM 与 MCP 服务器之间的对话流转。用户完全感知不到 MCP 的存在。完整实现见 solution/java/src/main/java/com/microsoft/mcp/sample/client/LangChain4jClient.java。

Rust

Rust 侧大部分工作在“循环对话”上:调用 LLM → 解析响应 → 若有tool_calls则执行并回填消息 → 再次调用 LLM,直到得到最终答案。

封装 LLM 调用的函数:

async fn call_llm( client: &Client<OpenAIConfig>, messages: &[Value], tools: &ListToolsResult, ) -> Result<Value, Box<dyn Error>> { let response = client .completions() .create_byot(json!({ "messages": messages, "model": "openai/gpt-4.1", "tools": format_tools(tools).await?, })) .await?; Ok(response) }

处理 LLM 响应(递归驱动工具调用)的函数:

async fn process_llm_response( llm_response: &Value, mcp_client: &RunningService<RoleClient, ()>, openai_client: &Client<OpenAIConfig>, mcp_tools: &ListToolsResult, messages: &mut Vec<Value>, ) -> Result<(), Box<dyn Error>> { let Some(message) = llm_response .get("choices") .and_then(|c| c.as_array()) .and_then(|choices| choices.first()) .and_then(|choice| choice.get("message")) else { return Ok(()); }; // 有内容则打印 if let Some(content) = message.get("content").and_then(|c| c.as_str()) { println!("🤖 {}", content); } // 处理工具调用 if let Some(tool_calls) = message.get("tool_calls").and_then(|tc| tc.as_array()) { messages.push(message.clone()); // 追加助手消息 // 执行每个工具调用 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?; } Ok(()) }

提取工具调用信息的辅助函数:

fn extract_tool_call_info(tool_call: &Value) -> Result<(String, String, String), Box<dyn Error>> { let tool_id = tool_call .get("id") .and_then(|id| id.as_str()) .unwrap_or("") .to_string(); let function = tool_call.get("function").ok_or("Missing function")?; let name = function .get("name") .and_then(|n| n.as_str()) .unwrap_or("") .to_string(); let args = function .get("arguments") .and_then(|a| a.as_str()) .unwrap_or("{}") .to_string(); Ok((tool_id, name, args)) }

最后在main中串联整个流程:

// LLM 对话与工具调用 let response = call_llm(&openai_client, &messages, &tools).await?; process_llm_response( &response, &mcp_client, &openai_client, &tools, &mut messages, ) .await?;

练习与验证

作业:基于本节练习代码,为服务器添加更多工具,然后照练习方式构建带 LLM 的客户端,用不同提示词测试,确保服务器上的所有工具都能被动态调用。这样构建的客户端,最终用户只需使用自然语言提示,全程感知不到任何 MCP 服务器调用。

参考实现:各语言的完整解决方案汇总在 03-GettingStarted/03-llm-client/solution,并分别提供:

  • TypeScript 客户端(源码见src/client.ts);
  • Python 客户端(含虚拟环境创建、依赖安装pip install "mcp[cli]" openai、环境变量配置与运行步骤);
  • .NET 客户端;
  • Java 客户端;
  • Rust 客户端(含cargo build/cargo run步骤,运行输出会显示 “Calling tool: add” 及工具结果)。

仓库 03-GettingStarted/samples 还收录了各语言的计算器(Calculator)样例,可作为扩展练习的服务器参考:Java、.NET(C#)、JavaScript、TypeScript、Python、Rust。

核心要点回顾

  • 把 LLM 加进客户端,是让用户以自然语言与 MCP 服务器交互、获得更好体验的关键路径;
  • MCP 服务器的工具响应必须转换为 LLM 可理解的 Function Calling 格式(type: "function"+name+parameters),才能交给 LLM 使用;
  • 转换后的工具定义随用户提示一起发给 LLM,LLM 返回的tool_calls再映射回client.callTool/session.call_tool/mcpClient.CallToolAsync等 MCP 客户端 API 执行,从而形成“用户 → LLM → MCP 服务器”的完整闭环。

下一步

  • 继续学习 在 Visual Studio Code 中使用服务器,把本节课构建的 LLM 客户端接入 VS Code 的 MCP 生态进行调试与演示。
  • 教程
  • 文档
  • 人工智能

【免费下载链接】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.

项目地址:https://gitcode.com/GitHub_Trending/mc/mcp-for-beginners
点击查看免费下载

相关推荐

上一篇:原神抽卡数据分析工具:如何精准掌握你的抽卡概率与历史记录
下一篇:原神抽卡数据分析工具:你的祈愿记录永久保存方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 14:47:16

数据结构作业银行排队系统:链式队列与事件驱动模拟实现

简介&#xff1a;一套以“银行排队系统”为场景的数据结构期末作业完整方案&#xff0c;面向需掌握队列应用与多队列优先级处理的学生&#xff0c;也适合作为课程设计参考。资源围绕队列先进先出&#xff08;FIFO&#xff09;特性&#xff0c;实现了VIP用户优先于普通用户的多队…

作者头像 李华
网站建设 2026/10/20 7:41:56

Unity射击游戏开发:从对象池到碰撞检测的完整实现路径

简介&#xff1a;这是一份基于 Unity 3D 实现的太空飞机射击游戏期末作业项目&#xff0c;面向正在学习 Unity 或需要完成同类课程设计的学生。工程覆盖了游戏开发的主要环节&#xff1a;太空场景搭建、飞机移动控制、子弹射击与对象池、碰撞检测、分数与游戏状态管理&#xff…

作者头像 李华
网站建设 2026/10/14 12:31:12

CCNA中版PDF实验指南:从模拟器选型到VLAN/OSPF/ACL排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华