☰
mcp-for-beginners 实战:使用 .NET 构建接入 LLM 的 MCP 客户端(OpenAI SDK + stdio 传输)
2026/10/11 5:03:32 网站建设 项目流程
  • 教程
  • 文档
  • 人工智能

【免费下载链接】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-llm-client」一节的 .NET 解决方案展开,讲解如何把一个大语言模型(LLM)接入 MCP 客户端,让用户用自然语言而非精确的客户端命令与 MCP Server 交互。读完本文,你将掌握:如何配置 Azure OpenAI / Microsoft Foundry 模型环境变量、通过dotnet restore/dotnet run运行示例、理解Program.cs中「列出 MCP 工具 → 转换为 LLM 工具 → 让 LLM 决定调用 → 回传结果」的完整调用链,以及如何将 MCP 工具 JSON Schema 转换为 OpenAIChatTool格式。

为什么要在 MCP 客户端中加入 LLM

在前面的课程(02-client)中,客户端已经可以显式调用服务器,列出并调用其工具(tools)、资源(resources)与提示词(prompts)。但这并不是一种很实用的交互方式——你的用户身处 Agent 时代,期望用自然语言与 AI 沟通,他们并不关心你内部是否用 MCP 组织能力。解决方案就是:给客户端加上一个 LLM。

课程主文档 03-llm-client/README.md 给出了客户端与服务器交互的四步总体思路:

  1. 与服务器建立连接;
  2. 列出服务器的能力(prompts、resources、tools)并保存其 schema;
  3. 接入 LLM,把已保存的能力与 schema 转换为 LLM 能理解的格式;
  4. 处理用户 prompt:把它连同客户端列出的工具一起交给 LLM,由 LLM 决定是否调用工具。

本节的 .NET 示例正是这条思路的最小落地实现,完整源码位于 solution/dotnet/Program.cs。

环境准备:配置模型端点与密钥

本示例依赖一个已部署的 LLM 模型。当前仓库的 .NET 解决方案使用Microsoft Foundry(Azure OpenAI v1 端点)下的模型部署(例如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_ENDPOINTFoundry/Azure OpenAI 资源地址,形如https://<resource-name>.openai.azure.com必填,缺失时程序直接退出
AZURE_OPENAI_API_KEY资源 API 密钥必填,缺失时程序直接退出
AZURE_OPENAI_DEPLOYMENT模型部署名可选,缺省为gpt-5.1;注意它可能不同于底层模型名

从源码看,Program.cs 先读取这三个变量,若endpoint或apiKey为空会打印Please set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY.并直接返回;deployment则通过?? "gpt-5.1"提供默认值。

需要说明的是:本节捷克语译本(translations/cs/03-GettingStarted/03-llm-client/solution/dotnet/README.md)描述的是一套更早的配置方式——基于 GitHub Codespaces +GITHUB_TOKEN个人访问令牌访问 GitHub Models:

# zsh/bash export GITHUB_TOKEN="{{YOUR_GITHUB_PAT}}"
# PowerShell $env:GITHUB_TOKEN = "{{YOUR_GITHUB_PAT}}"

如果你使用旧版本(基于Azure.AI.Inference与https://models.inference.ai.azure.com端点)的代码,则按上述方式配置;若使用当前仓库版本(基于 OpenAI .NET SDK 与 Foundry v1 端点),则使用AZURE_OPENAI_*三件套。两套认证信息不可混用。

工程结构:依赖与前置服务器

解决方案目录 03-llm-client/solution/dotnet/ 下包含:

  • Program.cs— 全部客户端逻辑(顶层语句风格);
  • dotnet.csproj— 项目文件与 NuGet 依赖;
  • dotnet.sln— 解决方案文件;
  • README.md— 本示例的运行说明。

项目文件 dotnet.csproj 的关键配置:

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net9.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.*-*" /> <PackageReference Include="ModelContextProtocol" Version="0.*-*" /> <PackageReference Include="OpenAI" Version="2.10.0" /> </ItemGroup> </Project>
NuGet 包作用
ModelContextProtocol(0.*-*)MCP 官方 .NET SDK,提供McpClient、StdioClientTransport等客户端 API
OpenAI(2.10.0)OpenAI 官方 .NET 客户端,提供ChatClient、ChatTool等类型
Microsoft.Extensions.Hosting(9.*-*)泛型主机依赖,用于支撑 MCP SDK 的宿主环境

捷克语译本中提到的旧版依赖为「Azure AI Inference、Azure Identity、Microsoft.Extension、Model.Hosting、ModelContextProtocol」,即旧版基于Azure.AI.Inference.ChatCompletionsClient;当前仓库已迁移到 OpenAI .NET SDK,两者 API 存在差异,请以当前源码为准。

前置条件:本示例通过 stdio 拉起一个 .NET MCP Server。该服务器就是上一课 02-client 的产物,位于 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、描述为 "Adds two numbers"、入参为两个整数a、b,返回值是形如Sum 6的字符串。客户端将针对这个工具完成「列举 → 转换 → 调用」的完整演示。

运行示例:安装依赖并启动

进入解决方案目录后,先安装依赖:

dotnet restore

这会安装上文列出的 NuGet 包。然后运行:

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: OpenAI.Chat.ChatTool MCP Tools def: 0: OpenAI.Chat.ChatTool Tool call 0: Add with arguments {"a":2,"b":4} Sum 6

捷克语译本记录的是旧版输出:Tool definition/MCP Tools def显示为Azure.AI.Inference.ChatCompletionsToolDefinition,并在工具参数后多打印一行Properties: {"a":{"type":"integer"},"b":{"type":"integer"}}。二者核心流程一致:都是「列出 MCP Server 的工具 → 转换为 LLM 工具 → 得到 MCP 客户端响应Sum 6」。

大部分输出只是调试信息,真正重要的是最后三行:LLM 根据用户提示词决定调用Add工具(参数{"a":2,"b":4}),客户端回传 MCP Server 执行,最终拿到文本结果Sum 6。

源码剖析:Program.cs 全流程

Program.cs 用 C# 顶层语句写成,逻辑清晰,可分七个阶段拆解。

1. 读取环境变量并校验

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

(Program.cs)这里在启动阶段就做硬校验,避免把空配置传给 LLM 客户端。

2. 构造 OpenAI ChatClient

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

(Program.cs)关键细节:endpoint.TrimEnd('/')去掉尾部斜杠后再拼接/openai/v1/,确保无论用户是否以/结尾都能得到正确的 v1 API 路径。chatHistory以一条系统消息开头。

3. 建立 stdio 传输并创建 MCP 客户端

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

(Program.cs)StdioClientTransport让 .NET 客户端以子进程方式启动之前编译好的 MCP Server 可执行文件,并通过标准输入/输出进行 JSON-RPC 通信。Command使用相对仓库根目录的路径拼接(旧版文档里是绝对路径/workspaces/mcp-for-beginners/...,在 Codespaces 中同样成立)。创建后的mcpClient负责与服务器完成初始化握手。

4. 列出 MCP Server 的工具并转换为 LLM 工具

先看转换函数:

ChatTool ConvertFrom(string name, string description, JsonElement jsonElement) { return ChatTool.CreateFunctionTool( functionName: name, functionDescription: description, functionParameters: BinaryData.FromString(jsonElement.GetRawText())); }

(Program.cs)ConvertFrom接收 MCP 工具的名称、描述和 JSON Schema,通过ChatTool.CreateFunctionTool生成 OpenAI SDK 的ChatTool——这正是「把 MCP 工具转换为 LLM 工具」的关键桥接点:MCP 工具 schema(JSON Schema 格式)与 OpenAI 函数工具 schema 本质都是 JSON,只需搬运并包装。

再看出现在GetMcpTools中的调用:

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

(Program.cs)ListToolsAsync()返回服务器的工具清单,循环中打印Name、Description和JsonSchema(即运行输出里那段{"title":"Add",...}),随后逐条转换为ChatTool并收集到列表中。主流程接着打印每个转换结果:

var tools = await GetMcpTools(); for (int i = 0; i < tools.Count; i++) { var tool = tools[i]; Console.WriteLine($"MCP Tools def: {i}: {tool}"); }

(Program.cs)

5. 把用户提示词与工具一起交给 LLM

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;

(Program.cs)用户提示词是add 2 and 4。ChatCompletionOptions.Tools只传入tools[0](即唯一的Add工具),随后调用CompleteChatAsync。此时 LLM 并不会直接回答数字,而是可能返回一个函数调用意图(ToolCalls)——因为工具列表告诉它「有个 Add 函数可以做加法」。

6. 处理 LLM 返回的 ToolCalls 并回调 MCP Server

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

(Program.cs)这是整条链路的闭环:

  1. 解析 LLM 返回的ToolCalls,得到函数名Add和参数 JSON{"a":2,"b":4};
  2. 用JsonSerializer.Deserialize把参数 JSON 反序列化为Dictionary<string, object>;
  3. 通过mcpClient.CallToolAsync把该调用请求转发给 MCP Server 执行;
  4. 从result.Content中取出TextContentBlock,打印服务器返回的文本——即Sum 6。

这一步体现了 MCP 的价值:LLM 并不真正执行计算,而是「决定调用哪个工具、传什么参数」,实际能力始终由 MCP Server 提供,工具 schema 由客户端统一转换,二者解耦。

7. 打印最终的通用回复

Console.WriteLine($"Assistant response: {content}");

(Program.cs)最后打印 LLM 对用户提示词的通用文本回复(本示例中该内容可能为空或辅助性文案,真正的算术结果来自工具调用)。

验证与调试要点

  • 依赖是否装全:dotnet restore成功后,Program.cs中用到的ModelContextProtocol.Client、OpenAI.Chat、System.ClientModel、System.Text.Json均有对应包支撑;若提示缺少类型,请确认 dotnet.csproj 中OpenAI版本不低于2.10.0。
  • Server 是否就绪:StdioClientTransport会在运行客户端时自动拉起 02-client/solution/server/ 编译产物。请先按上一课说明编译该服务器,否则Command指向的可执行文件不存在会报错。
  • 输出定位问题:运行日志中,Setting up stdio transport之前的报错多半是环境变量缺失;Listing tools之后没有工具,则多半是服务器未正常启动或传输配置错误;能列出Add却拿不到Sum 6,问题集中在 LLM 是否返回ToolCalls以及参数解析是否成功。
  • 模型可用性:部署名不一定等于底层模型名;选择模型前建议核对 Foundry 的模型退役计划(课程文档 03-llm-client/README.md 有明确提示),并部署仍在活跃期的模型(如gpt-5.1)。

核心收获

  • 给 MCP 客户端接入 LLM,能把「精确的命令式调用」升级为「自然语言交互」,终端用户无需感知 MCP 的存在。
  • 最关键的一步是schema 转换:MCP Server 返回的工具(name+description+JsonSchema)必须转换为 LLM 认识的工具格式(本例为 OpenAIChatTool),LLM 才有能力决定何时调用。
  • 转换完成后,执行权仍在 MCP Server:LLM 只负责「决策」,客户端负责「转发与回传」,这种分工正是 Agent 化应用的标准范式。

关联内容与后续学习

本示例只是 03-llm-client 课程的 .NET 解法,同一思路还有多语言实现,可对照学习:

  • 课程完整文档(含 TypeScript / Python / Java / Rust 的分步讲解):03-GettingStarted/03-llm-client/README.md
  • Python 完整客户端实现:solution/python/client.py(配套服务器 solution/python/server.py,内含add工具与greeting://{name}资源)
  • TypeScript 运行说明:solution/typescript/README.md
  • Java(LangChain4j + MiniMax)运行说明:solution/java/README.md
  • Rust 运行说明:solution/rust/README.md

前置与后续课程:

  • 先学习如何创建 MCP Server:01-first-server
  • 再学习纯客户端(不含 LLM)的调用方式:02-client
  • 本示例依赖的 .NET MCP Server 源码:02-client/solution/server/Program.cs
  • 下一步:在 Visual Studio Code 中消费 MCP Server:04-vscode

更完整的跨语言计算器示例(.NET / Java / TypeScript / Python / Rust)可参考 03-GettingStarted/samples 目录下的各语言 README。

  • 教程
  • 文档
  • 人工智能

【免费下载链接】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
点击查看免费下载
上一篇:AReaL 检查点系统完全指南:Saver 模型导出与 RecoverHandler 容错恢复实战
下一篇:NS-USBloader终极指南:三步搞定Switch游戏安装与破解

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询