- 教程
- 文档
- 人工智能
【免费下载链接】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 的客户端(Creating a client with LLM)」的 .NET 解决方案为主体,讲解如何让客户端不再硬编码调用 MCP Server 的工具,而是通过自然语言让 LLM 自主决策、动态调用 MCP 工具。读完本文,你将掌握 .NET 下ModelContextProtocolSDK + OpenAI SDK 的完整接入流程:建立 stdio 连接、列出服务器能力、把 MCP 工具 schema 转换为 LLM 认识的工具定义,并在对话循环中执行模型发起的工具调用,最终拿到类似Sum 6的结果。
课程背景与整体思路
在之前的课程中,我们已经分别创建过 MCP Server 和 MCP Client。但那种客户端是"显式"的:调用方必须知道服务器上有哪些工具、资源、提示词,并一条条手动调用。这并不符合当下 Agentic 时代用户的使用习惯——用户希望用自然语言对话,而不关心能力背后是否由 MCP 承载。
解决方案就是:给客户端加一个 LLM。整体交互流程(见 课程主文档)分四步:
- 与 MCP Server 建立连接;
- 列出服务器的能力(工具、资源、提示词),并把 schema 保存下来;
- 接入 LLM,把保存的能力与 schema 转换成 LLM 能理解的形式传给它;
- 处理用户提示词:连同客户端列出的工具一起交给 LLM,由 LLM 决定调用哪些工具、传什么参数。
本指南聚焦其中.NET(C#)的完整实现,源码位于 03-llm-client/solution/dotnet,核心文件为 Program.cs。
前置准备:部署模型并配置环境变量
运行示例前需要一个可用的 OpenAI 兼容模型服务。课程文档要求部署一个活动模型(例如gpt-5.1),并配置以下三个环境变量:
# 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 $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_DEPLOYMENT是部署名,用于 API 调用,可能与底层模型名不同。从 Program.cs 的源码可以看到它的读取逻辑:
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-5.1"; if (string.IsNullOrWhiteSpace(endpoint) || string.IsNullOrWhiteSpace(apiKey)) { Console.WriteLine("Please set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY."); return; }即:AZURE_OPENAI_DEPLOYMENT缺省回退到gpt-5.1;而AZURE_OPENAI_ENDPOINT与AZURE_OPENAI_API_KEY缺失时会直接提示并退出。另外,解决方案 README 还提示:若在 GitHub Codespaces 中运行则无需本地令牌;若在本地运行,需要为GITHUB_TOKEN配置个人访问令牌(PAT):
# zsh/bash export GITHUB_TOKEN="{{YOUR_GITHUB_PAT}}"# PowerShell $env:GITHUB_TOKEN = "{{YOUR_GITHUB_PAT}}"安装依赖库
示例是一个控制台应用,先还原 NuGet 包:
dotnet restore翻译版文档列出的库名为 Azure AI Inference、Azure Identity、Microsoft.Extension、Model.Hosting、ModelContextProtocol;而以当前仓库实际为准,dotnet.csproj 声明的包引用如下:
<ItemGroup> <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.*-*" /> <PackageReference Include="ModelContextProtocol" Version="0.*-*" /> <PackageReference Include="OpenAI" Version="2.10.0" /> </ItemGroup>工程目标框架为net9.0,启用了ImplicitUsings与Nullable。三个包各司其职:
- ModelContextProtocol:提供
McpClient、StdioClientTransport以及TextContentBlock等 MCP 客户端类型; - OpenAI(2.10.0):提供
ChatClient、ChatTool、ChatCompletionOptions等,用于与模型服务对话; - Microsoft.Extensions.Hosting:宿主框架支持(该示例主要使用它支撑依赖与日志等基础设施)。
运行示例与预期输出
依赖还原完成后直接运行:
dotnet run文档给出的预期输出如下:
Setting up stdio transport Listing tools Connected to server with tools: Add Tool description: Adds two numbers Tool parameters: {"title":"Add","description":"Adds two numbers","type":"object","properties":{"a":{"type":"integer"},"b":{"type":"integer"}},"required":["a","b"]} Tool definition: Azure.AI.Inference.ChatCompletionsToolDefinition Properties: {"a":{"type":"integer"},"b":{"type":"integer"}} MCP Tools def: 0: Azure.AI.Inference.ChatCompletionsToolDefinition Tool call 0: Add with arguments {"a":2,"b":4} Sum 6说明:上面输出中的
Tool definition类型名称因 SDK 版本而异。翻译文档记录的是早期 Azure.AI.Inference 时代的结果;当前仓库源码基于 OpenAI SDK,Program.cs 与英文版 solution README 中的实际输出为OpenAI.Chat.ChatTool。
输出中大部分内容只是调试信息,真正重要的是这条链路:从 MCP Server 列出工具 → 转换成 LLM 能理解的工具定义 → LLM 发起工具调用 → MCP 客户端执行并返回Sum 6。
源码逐段剖析:从 stdio 连接到工具执行
1. 建立 ChatClient 与 MCP stdio 连接
Program.cs 同时创建了两个客户端:
var client = new ChatClient( model: deployment, credential: new ApiKeyCredential(apiKey), options: new OpenAIClientOptions { Endpoint = new Uri($"{endpoint.TrimEnd('/')}/openai/v1/") }); var chatHistory = new List<ChatMessage> { new SystemChatMessage("You are a helpful assistant that knows about AI") }; var clientTransport = new StdioClientTransport(new() { Name = "Demo Server", Command = $"{Path.Combine(AppContext.BaseDirectory, "../../../../../../", "02-client/solution/server/bin/Debug/net9.0/server")}", Arguments = [], }); Console.WriteLine("Setting up stdio transport"); await using var mcpClient = await McpClient.CreateAsync(clientTransport);要点解析:
ChatClient通过ApiKeyCredential认证,并把endpoint末尾的/去掉后拼接/openai/v1/作为 API 路径;StdioClientTransport通过子进程方式启动 MCP Server,Command指向上一课 02-client 的 .NET Server 构建产物;McpClient.CreateAsync(clientTransport)完成 MCP 握手并返回可用的McpClient。
2. 列出 MCP 工具并转换为 LLM 工具
MCP 返回的工具格式(含Name、Description、JsonSchema)并不能直接喂给 LLM,需要先做一次"翻译"。GetMcpTools方法完成列出与转换:
ChatTool ConvertFrom(string name, string description, JsonElement jsonElement) { return ChatTool.CreateFunctionTool( functionName: name, functionDescription: description, functionParameters: BinaryData.FromString(jsonElement.GetRawText())); } async Task<List<ChatTool>> GetMcpTools() { Console.WriteLine("Listing tools"); var tools = await mcpClient.ListToolsAsync(); List<ChatTool> toolDefinitions = []; 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}"); var def = ConvertFrom(tool.Name, tool.Description, tool.JsonSchema); Console.WriteLine($"Tool definition: {def}"); toolDefinitions.Add(def); } return toolDefinitions; }关键转换位于ConvertFrom:ChatTool.CreateFunctionTool接收函数名、函数描述、函数参数 JSON三个要素,正是 OpenAI 工具调用(function calling)所需的信息。这对应课程中"将 MCP Server 响应转换为 LLM 可理解的格式"这一步——MCP 的JsonSchema在这里被原样作为 function parameters 透传给 LLM。
3. 发起 LLM 对话并处理工具调用
接下来进入真正的对话环节(Program.cs):
var userMessage = "add 2 and 4"; chatHistory.Add(new UserChatMessage(userMessage)); var options = new ChatCompletionOptions { Tools = { tools[0] } }; ChatCompletion response = await client.CompleteChatAsync(chatHistory, options); var content = response.Content.FirstOrDefault()?.Text; for (int i = 0; i < response.ToolCalls.Count; i++) { var call = response.ToolCalls[i]; Console.WriteLine($"Tool call {i}: {call.FunctionName} with arguments {call.FunctionArguments}"); //Tool call 0: add with arguments {"a":2,"b":4} var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(call.FunctionArguments); var result = await mcpClient.CallToolAsync( call.FunctionName, dict!, cancellationToken: CancellationToken.None ); var textBlock = result.Content.OfType<TextContentBlock>().FirstOrDefault(); if (textBlock != null) { Console.WriteLine(textBlock.Text); } } Console.WriteLine($"Assistant response: {content}");这段代码的六步流程清晰可见:
- 把用户消息
"add 2 and 4"加入对话历史; - 用
ChatCompletionOptions.Tools把之前转换好的ChatTool(这里是tools[0],即Add)交给模型; CompleteChatAsync向模型发起补全请求——模型会决定是否调用工具;- 检查响应中的
response.ToolCalls,若有工具调用,则打印调用名与参数; - 把 LLM 返回的参数反序列化为字典,通过
mcpClient.CallToolAsync回传给 MCP Server 真正执行; - 从
result.Content中取出TextContentBlock并打印,即得到Sum 6。
4. 被调用的 MCP Server 长什么样
本示例连接的 MCP Server 来自上一课,其核心实现见 02-client/solution/server/Program.cs:
[McpServerToolType] public static class CalculatorTool { [McpServerTool, Description("Adds two numbers")] public static string Add(int a, int b) => $"Sum {a + b}"; }Add方法用[McpServerTool]特性暴露为 MCP 工具,Description("Adds two numbers")正是客户端打印出的Tool description,方法签名int a, int b则自动生成 JSON Schema 中的properties与required字段。这也解释了为什么输出中工具参数为{"a":{"type":"integer"},"b":{"type":"integer"}}。服务端还通过LogToStandardErrorThreshold = LogLevel.Trace把所有日志导向 stderr,避免污染与客户端之间的 stdio 协议通道。
学习要点与延伸练习
- 给客户端加 LLM 是更好的用户交互方式:用户只需说 "add 2 and 4",无需知道
Add(a, b)这样的客户端命令,甚至意识不到背后有 MCP Server 被调用; - 必须做格式转换:MCP Server 返回的工具列表与 schema 需要转换成 LLM(OpenAI function calling)能理解的
ChatTool定义,转换的核心是把 JSON Schema 透传为 function parameters; - 工具执行回环:LLM 决定调用哪些工具 → 客户端解析参数 →
CallToolAsync调回 MCP Server → 把TextContentBlock文本返回给用户。
课程还给出了延伸练习(Assignment):参照本节代码为 Server 增加更多工具,再用带 LLM 的客户端配合不同提示词测试,确保所有 Server 工具都能被动态、自动地调用。如果你需要对照其他语言实现,可查看同目录下的 TypeScript、Python、Java、Rust 解决方案,以及 解决方案总览。
下一步可以继续学习 使用 Visual Studio Code 消费 MCP Server,把带 LLM 的客户端能力延伸到 IDE 场景中。
- 教程
- 文档
- 人工智能
【免费下载链接】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 实战:在 Python 中运行带 LLM 的 MCP 客户端(03-llm-client 示例详解)
mcp for beginners 实战:在 Python 中运行带 LLM 的 MCP 客户端(03 llm client 示例详解) 本文围绕 mcp fo
教程文档人工智能为 MCP 客户端接入 LLM:mcp-for-beginners 课程 .NET 示例的配置、运行与工具转换原理
为 MCP 客户端接入 LLM:mcp for beginners 课程 .NET 示例的配置、运行与工具转换原理 本文基于 mcp for beginners
教程文档人工智能
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考