基于.NET 8与MCP协议构建智能体:实战Agent框架与工具集成

📅 2026/8/6 15:10:06
基于.NET 8与MCP协议构建智能体:实战Agent框架与工具集成
在实际 AI 应用开发中构建一个能够理解复杂指令、调用外部工具并完成特定任务的智能体Agent正从前沿探索走向工程实践。传统的单一模型调用模式在面对需要多步骤推理、动态工具选择和状态管理的场景时显得力不从心。Agent 框架的出现旨在为这类复杂智能应用提供一套标准化的开发范式而模型上下文协议Model Context Protocol, MCP则为智能体安全、高效地接入外部工具和数据源提供了统一接口。结合 .NET 生态的稳健性和高性能开发者可以构建出既智能又可靠的企业级应用。本文将以一个具体的实战项目为例带你从零开始基于主流的 Agent 框架、MCP 协议和 .NET 8 平台构建一个具备联网搜索和代码执行能力的智能体。我们将深入理解 Agent 的工作流、MCP 服务器的实现原理并完成一个可运行、可验证的完整案例。无论你是希望将 AI 能力集成到现有 .NET 系统中的开发者还是对智能体架构感兴趣的工程师都能通过本文获得一套可直接复现的工程方案。1. 理解智能体Agent的核心架构与 MCP 协议在深入代码之前必须厘清几个核心概念什么是 Agent 框架MCP 协议解决了什么问题以及 .NET 在其中扮演何种角色。1.1 Agent 框架超越简单模型调用一个智能体Agent通常不是指单个大语言模型LLM而是一个由 LLM 作为“大脑”驱动的系统。这个系统包含几个关键组件规划器Planner解析用户意图将复杂任务分解为可执行的子步骤序列。工具Tools智能体可以调用的外部函数例如搜索网络、查询数据库、执行代码、调用 API 等。这是智能体与外界交互、获取实时信息或执行操作的关键。记忆Memory存储对话历史、工具执行结果和中间状态为后续决策提供上下文。执行引擎Execution Engine协调整个流程根据规划调用工具处理工具返回结果并决定下一步行动继续、重试或结束。Agent 框架如 Semantic Kernel, LangChain, AutoGen 等封装了这些组件的通用模式让开发者无需从零构建任务调度、状态管理和工具调用的复杂逻辑。1.2 MCP 协议标准化工具与数据接入模型上下文协议Model Context Protocol, MCP是一个开放协议它定义了一套标准使得任何兼容 MCP 的客户端如 AI 应用、Agent 框架能够以统一的方式发现、描述和调用来自不同服务器提供的工具和资源。它的核心价值在于解耦与安全解耦工具提供者如一个内部数据库服务、一个计算服务只需实现一个 MCP 服务器任何支持 MCP 的客户端就能立即使用这些工具无需为每个客户端编写特定的集成代码。安全MCP 服务器运行在独立的进程中甚至独立的机器上。客户端通过进程间通信如 stdio或网络与服务器交互。这意味着敏感的数据源如生产数据库或危险的操作如 shell 命令可以被隔离在受控的服务器环境中客户端只能通过定义良好的协议接口进行有限度的访问大大提升了系统安全性。1.3 .NET 生态的定位.NET特别是最新的 .NET 8为构建此类 AI 应用提供了强大的基础设施高性能运行时对于需要处理大量数据或高并发请求的 Agent 后端服务.NET 的性能优势明显。丰富的库支持通过Microsoft.SemanticKernel等官方库可以便捷地集成 Azure OpenAI 或 OpenAI 的模型并构建 Agent。成熟的工程化能力依赖注入、配置管理、日志记录、健康检查等 .NET 原生特性使得构建稳定、可维护的生产级 AI 应用更加容易。跨平台开发的智能体服务可以运行在 Windows、Linux 或 macOS 上。在本项目中我们将使用Semantic Kernel作为 Agent 框架因为它与 .NET 生态集成最紧密并且官方支持 MCP。我们将分别构建一个 MCP 服务器提供工具和一个 .NET 控制台应用作为 Agent 客户端。2. 环境准备与项目初始化开始编码前需要确保本地开发环境就绪并创建项目结构。2.1 开发环境要求请确保你的机器上已安装以下软件组件版本要求说明.NET SDK8.0 或更高用于构建和运行 .NET 应用程序。IDE / 编辑器Visual Studio 2022 / VS Code推荐使用 Visual Studio 或安装 C# 插件的 VS Code。OpenAI API 密钥-用于调用 GPT 模型。你也可以使用 Azure OpenAI 端点。打开终端使用以下命令验证 .NET 环境dotnet --version预期应输出8.0.x或更高版本。2.2 创建解决方案与项目我们将创建一个解决方案包含两个项目一个 MCP 服务器项目和一个智能体客户端项目。# 创建解决方案目录并进入 mkdir McpAgentDemo cd McpAgentDemo # 创建解决方案文件 dotnet new sln -n McpAgentDemo # 创建 MCP 服务器项目类库 dotnet new classlib -n McpDemo.Server dotnet sln add McpDemo.Server/McpDemo.Server.csproj # 创建智能体客户端项目控制台应用 dotnet new console -n McpDemo.Agent dotnet sln add McpDemo.Agent/McpDemo.Agent.csproj # 为客户端项目添加对服务器项目的引用可选仅为代码共享 dotnet add McpDemo.Agent reference McpDemo.Server2.3 安装必要的 NuGet 包我们需要为两个项目分别安装核心依赖。对于 MCP 服务器项目 (McpDemo.Server)进入McpDemo.Server目录安装 MCP 协议的基础包。cd McpDemo.Server dotnet add package ModelContextProtocol.Server --version 1.0.0-preview.3注意MCP 相关包可能处于预览版版本号请以 NuGet 上的最新稳定版或预览版为准。对于智能体客户端项目 (McpDemo.Agent)进入McpDemo.Agent目录安装 Semantic Kernel 和 MCP 客户端包。cd ../McpDemo.Agent dotnet add package Microsoft.SemanticKernel --version 1.13.0 dotnet add package Microsoft.SemanticKernel.Agents.Core --version 1.13.0-alpha dotnet add package ModelContextProtocol.Transport.Stdio --version 1.0.0-preview.3 dotnet add package ModelContextProtocol.SemanticKernel --version 1.0.0-preview.3注意Agents.Core包在撰写本文时可能仍为 Alpha 版本用于提供最新的 Agent 抽象。请根据 Semantic Kernel 的官方发布情况调整。安装完成后建议运行dotnet restore确保所有依赖正确解析。3. 实现 MCP 服务器提供工具能力MCP 服务器的核心是声明一系列Tool工具并实现其执行逻辑。我们将创建一个提供“计算器”和“获取当前时间”两个简单工具的服务器。3.1 定义工具接口在McpDemo.Server项目中创建一个新的 C# 类文件CalculatorTool.cs。// McpDemo.Server/CalculatorTool.cs using System.Text.Json.Serialization; using ModelContextProtocol.Server; namespace McpDemo.Server; // 工具输入参数的强类型定义 public class CalculatorInput { [JsonPropertyName(a)] public double A { get; set; } [JsonPropertyName(b)] public double B { get; set; } [JsonPropertyName(operation)] public string Operation { get; set; } ; // 默认加法 } // 工具本身的实现类继承自 McpTool public class CalculatorTool : McpToolCalculatorInput, string { // 工具名称客户端将通过此名称调用 public override string Name calculator; // 工具描述用于帮助 LLM 理解工具的用途 public override string Description Performs basic arithmetic operations (add, subtract, multiply, divide) on two numbers.; // 工具的执行逻辑 protected override Taskstring ExecuteAsync(CalculatorInput input, CancellationToken cancellationToken) { double result; switch (input.Operation) { case : result input.A input.B; break; case -: result input.A - input.B; break; case *: result input.A * input.B; break; case /: if (input.B 0) { throw new ArgumentException(Division by zero is not allowed.); } result input.A / input.B; break; default: throw new ArgumentException($Unsupported operation: {input.Operation}); } return Task.FromResult($The result of {input.A} {input.Operation} {input.B} is {result}.); } }再创建一个GetCurrentTimeTool.cs文件。// McpDemo.Server/GetCurrentTimeTool.cs using ModelContextProtocol.Server; namespace McpDemo.Server; // 此工具无需输入参数使用 McpToolWithoutInput 基类 public class GetCurrentTimeTool : McpToolWithoutInputstring { public override string Name get_current_time; public override string Description Gets the current date and time in UTC.; protected override Taskstring ExecuteAsync(CancellationToken cancellationToken) { return Task.FromResult($The current UTC time is: {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}); } }3.2 创建服务器入口点修改McpDemo.Server项目中的Program.cs文件如果不存在则创建。这是服务器的启动入口。// McpDemo.Server/Program.cs using ModelContextProtocol.Server; using McpDemo.Server; // 1. 创建 MCP 服务器构建器 var builder McpServerBuilder.Create(); // 2. 注册我们定义的工具 builder.AddToolCalculatorTool(); builder.AddToolGetCurrentTimeTool(); // 3. 构建服务器 var server builder.Build(); // 4. 运行服务器使用标准输入输出作为传输层。 // 这是 MCP 的典型用法客户端将启动此进程并通过管道通信。 await server.RunAsync();这个服务器现在可以通过标准输入输出stdio与客户端通信。当客户端请求调用calculator或get_current_time工具时服务器会执行相应的ExecuteAsync方法并返回结果。3.3 生成服务器可执行文件在McpDemo.Server目录下发布项目为一个独立可执行文件方便客户端调用。dotnet publish -c Release -r win-x64 --self-contained true /p:PublishSingleFiletrue-r win-x64: 指定目标运行时为 Windows x64。如果是 Linux可改为linux-x64macOS 可改为osx-x64。--self-contained true: 包含 .NET 运行时使得生成的可执行文件无需在目标机器安装 .NET 即可运行。/p:PublishSingleFiletrue: 打包成单个可执行文件。发布完成后在bin/Release/net8.0/win-x64/publish目录下会找到McpDemo.Server.exeWindows或McpDemo.ServerLinux/macOS。记下这个路径稍后客户端需要启动它。4. 构建智能体客户端集成 MCP 与 Semantic Kernel客户端负责启动 MCP 服务器进程加载其提供的工具并利用 Semantic Kernel 构建一个能够使用这些工具的智能体。4.1 配置 MCP 服务器连接在McpDemo.Agent项目中首先需要配置如何连接到我们刚刚构建的 MCP 服务器。修改Program.cs。// McpDemo.Agent/Program.cs using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Chat; using ModelContextProtocol.SemanticKernel; using ModelContextProtocol.Transport.Stdio; // 请替换为你的 OpenAI API 密钥和端点 string apiKey your-openai-api-key; string modelId gpt-4o-mini; // 或 gpt-4-turbo, gpt-3.5-turbo string endpoint https://api.openai.com/v1/chat/completions; // Azure OpenAI 用户需更换 // 1. 创建 Semantic Kernel 内核 var kernel Kernel.CreateBuilder() .AddOpenAIChatCompletion(modelId, apiKey, endpoint: endpoint) .Build(); // 2. 创建 MCP 客户端配置指定服务器可执行文件路径 // 注意你需要将路径替换为实际发布的可执行文件路径 var serverPath C:\path\to\your\McpDemo.Server.exe; // Windows 示例 // var serverPath /home/user/path/to/McpDemo.Server; // Linux 示例 var mcpClientConfig new StdioMcpClientConfig(serverPath); var mcpClient new StdioMcpClient(mcpClientConfig); // 3. 从 MCP 服务器加载工具到 Kernel var mcpToolKernelPlugin await kernel.ImportMcpToolsAsync(mcpClient); Console.WriteLine($Loaded tools from MCP server: {string.Join(, , mcpToolKernelPlugin.Select(t t.Name))});关键点解释StdioMcpClientConfig: 配置了 MCP 服务器的启动方式这里是通过标准输入输出启动一个本地进程。StdioMcpClient: 是 MCP 协议的客户端实现负责与服务器进程通信。ImportMcpToolsAsync: 这是 Semantic Kernel 的扩展方法它会向 MCP 服务器请求工具列表并将这些工具转换为 Kernel 可以识别的KernelFunction封装成一个KernelPlugin。之后Agent 就可以像调用普通 Kernel 函数一样调用这些工具。4.2 创建并运行智能体接下来我们使用 Semantic Kernel 的 Agents 抽象来创建一个简单的聊天智能体。// 接上一段代码 // 4. 创建一个使用这些工具的智能体 var agent new ChatCompletionAgent( name: MathAndTimeAssistant, instructions: 你是一个乐于助人的助手擅长数学计算和提供时间信息。 当用户需要计算时请使用 calculator 工具。 当用户询问时间时请使用 get_current_time 工具。 如果用户的问题不涉及这些请直接基于你的知识回答。 请清晰、有条理地回复。 , kernel: kernel // 内核中已包含从 MCP 加载的工具 ); // 5. 创建代理聊天线程并开始交互 var thread new AgentGroupChat([agent]); var chatHistory thread.AddUserMessage(请计算一下 125 乘以 48 等于多少); try { // 执行聊天Agent 会自动决定是否以及何时调用工具 await foreach (var message in thread.InvokeAsync()) { Console.WriteLine($[{message.Sender?.Name ?? System}]: {message.Content}); } } catch (Exception ex) { Console.WriteLine($Error during agent execution: {ex.Message}); }运行这段代码智能体会分析用户消息“请计算一下 125 乘以 48 等于多少”识别出这是一个计算任务然后自动调用calculator工具传入a125, b48, operation*获取工具返回的结果最后组织成自然语言回复给用户。4.3 完整客户端程序示例将以上步骤整合并添加一些交互逻辑形成一个完整的演示程序。// McpDemo.Agent/Program.cs (完整版) using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.Agents.Chat; using ModelContextProtocol.SemanticKernel; using ModelContextProtocol.Transport.Stdio; // 配置 var config new { OpenAIApiKey your-api-key-here, ModelId gpt-4o-mini, Endpoint https://api.openai.com/v1/chat/completions, McpServerPath C:\Dev\McpAgentDemo\McpDemo.Server\bin\Release\net8.0\win-x64\publish\McpDemo.Server.exe }; // 初始化 Kernel 和 MCP 客户端 var kernel Kernel.CreateBuilder() .AddOpenAIChatCompletion(config.ModelId, config.OpenAIApiKey, endpoint: config.Endpoint) .Build(); var mcpClientConfig new StdioMcpClientConfig(config.McpServerPath); var mcpClient new StdioMcpClient(mcpClientConfig); Console.WriteLine(正在连接 MCP 服务器并加载工具...); var mcpPlugin await kernel.ImportMcpToolsAsync(mcpClient); Console.WriteLine($工具加载成功: {string.Join(, , mcpPlugin.Select(t t.Name))}); // 创建智能体 var agent new ChatCompletionAgent( name: Assistant, instructions: 你是一个助手可以帮用户进行数学计算和查询当前时间。请根据需要使用相应的工具。, kernel: kernel ); var thread new AgentGroupChat([agent]); Console.WriteLine(\n智能体已就绪。输入您的问题输入 exit 退出:\n); // 交互循环 while (true) { Console.Write(用户: ); var userInput Console.ReadLine(); if (string.IsNullOrWhiteSpace(userInput) || userInput.Equals(exit, StringComparison.OrdinalIgnoreCase)) { break; } thread.AddUserMessage(userInput); Console.Write(助手: ); try { string fullResponse ; await foreach (var message in thread.InvokeAsync()) { if (message.Sender is ChatCompletionAgent) { // 流式输出 Agent 的回复 Console.Write(message.Content); fullResponse message.Content; } } Console.WriteLine(); // 换行 } catch (Exception ex) { Console.WriteLine($\n[系统错误] {ex.Message}); } }5. 运行验证与结果分析现在让我们运行整个系统观察智能体如何工作。5.1 启动与验证步骤构建并发布 MCP 服务器确保已按照 3.3 节的命令成功发布McpDemo.Server。配置客户端在McpDemo.Agent的Program.cs中正确设置OpenAIApiKey、ModelId和McpServerPath。运行客户端在终端中导航到McpDemo.Agent项目目录执行dotnet run观察输出程序启动后你应该看到类似以下的日志表明 MCP 服务器连接成功工具已加载正在连接 MCP 服务器并加载工具... 工具加载成功: calculator, get_current_time 智能体已就绪。输入您的问题输入 exit 退出5.2 测试用例与预期结果输入不同的指令验证智能体的行为用户输入预期行为与输出关键特征计算 98 加上 17 等于多少Agent 应识别计算意图调用calculator工具并返回 “The result of 98 17 is 115.” 或类似表述。现在几点了Agent 应识别时间查询意图调用get_current_time工具并返回包含当前 UTC 时间的句子。请介绍一下你自己。Agent 应识别此问题无需工具直接利用 LLM 的知识生成自我介绍。先计算 12*5再告诉我现在的时间。Agent 应进行规划先调用计算工具再调用时间工具最后将两个结果整合成连贯回复。5.3 深入分析执行流程通过在上述客户端代码的关键位置添加日志或在支持 SK 日志记录的情况下运行可以清晰地看到 Agent 的思考与执行链接收用户消息“计算 98 加上 17 等于多少”LLM 规划Agent 内部的 LLM 分析消息判断需要调用calculator工具并生成符合工具输入格式的参数 JSON{a: 98, b: 17, operation: }。工具调用Semantic Kernel 通过 MCP 客户端将调用请求发送给 MCP 服务器进程。服务器执行MCP 服务器收到请求找到CalculatorTool执行ExecuteAsync方法计算9817得到115。返回结果服务器将结果字符串“The result of 98 17 is 115.”通过 MCP 协议返回给客户端。结果处理客户端将工具执行结果作为上下文再次交给 LLM。生成最终回复LLM 结合原始问题和工具结果生成面向用户的自然语言回复如“98 加上 17 等于 115。”。这个流程完美诠释了 Agent 的“思考-行动-观察”循环。6. 常见问题排查在实际运行中你可能会遇到以下问题。这里提供排查路径。6.1 MCP 服务器连接失败现象客户端启动时卡在“正在连接 MCP 服务器...”或抛出异常提示无法启动进程或通信失败。排查步骤检查路径确认McpServerPath变量指向的确实是发布后的可执行文件而不是 DLL 或项目目录。检查文件权限确保当前运行客户端程序的用户有权限执行该服务器文件。手动测试服务器尝试在终端中直接运行服务器可执行文件。它应该启动并等待标准输入可能没有输出。如果能正常启动说明服务器本身无问题。检查依赖如果发布时未使用--self-contained请确保运行客户端的环境已安装对应版本的 .NET 运行时。查看异常信息捕获并打印ImportMcpToolsAsync或StdioMcpClient初始化时的异常详细信息通常包含操作系统级别的错误码。6.2 工具加载成功但调用失败现象客户端启动时显示工具已加载但用户提问后Agent 报错或无法正确调用工具。排查步骤检查工具描述确保 MCP 服务器中工具类的Name和Description属性清晰明确。模糊的描述可能导致 LLM 无法正确匹配用户意图。查看内核日志启用 Semantic Kernel 的日志记录查看 LLM 生成的规划步骤和工具调用参数是否正确。// 在 KernelBuilder 后添加 builder.Services.AddLogging(c c.AddConsole().SetMinimumLevel(LogLevel.Debug));验证参数格式检查 LLM 生成的工具调用参数 JSON 是否完全符合CalculatorInput类的定义。类型不匹配如字符串传给数字会导致服务器端反序列化失败。服务器端日志在 MCP 服务器的ExecuteAsync方法中添加Console.WriteLine输出观察调用是否到达以及参数值。6.3 Agent 不调用工具直接回答现象对于明显应该使用工具的问题如“123*456”Agent 却尝试直接计算并给出一个可能错误的答案。排查步骤强化指令Instructions检查创建ChatCompletionAgent时的instructions参数。指令必须清晰、强制性地要求 Agent 在特定场景下使用工具。可以像示例中那样明确写出“当用户需要计算时请使用 calculator 工具。”检查工具描述工具描述 (Description) 需要让 LLM 能准确理解其能力边界。例如“Performs basic arithmetic operations” 就比 “A tool” 好得多。使用更强大的模型如果使用gpt-3.5-turbo对于复杂或隐含的工具调用场景其规划能力可能不如gpt-4或gpt-4o系列。尝试升级模型。提供少量示例Few-shot在instructions中可以加入一两个用户提问和正确调用工具的示例引导模型学习调用模式。6.4 性能与超时问题现象响应缓慢或出现超时错误。排查步骤网络延迟如果使用远程 OpenAI/Azure OpenAI 端点网络延迟是主要因素。考虑使用地理位置更近的端点。工具执行耗时检查 MCP 服务器中工具的执行逻辑。如果工具需要访问网络或进行复杂计算可能导致超时。需要在工具实现中加入超时控制和优化。MCP 通信开销进程间通信stdio有一定开销。对于超低延迟场景可以考虑使用基于 Socket 的 MCP 传输层或将工具直接以内置函数形式集成到客户端牺牲隔离性。客户端超时设置检查 Semantic Kernel 和 MCP 客户端是否有可配置的超时设置并根据需要调整。7. 生产环境最佳实践与扩展方向将上述演示项目升级为生产可用系统还需要考虑以下方面。7.1 安全性强化MCP 服务器隔离生产环境中MCP 服务器应运行在独立的、权限受限的容器或进程中。特别是提供数据库访问、系统命令执行等敏感工具时。输入验证与消毒在 MCP 服务器的ExecuteAsync方法中必须对输入参数进行严格的验证和消毒防止注入攻击。访问控制实现 MCP 服务器的认证机制确保只有授权的客户端可以连接和调用工具。MCP 协议本身支持 TLS 和认证扩展。API 密钥管理切勿将 OpenAI API 密钥硬编码在代码中。使用 .NET 的配置系统如appsettings.json、环境变量或 Azure Key Vault 等安全存储服务。7.2 可观测性与可靠性结构化日志在 MCP 服务器和客户端中集成如 Serilog 这样的日志库记录工具调用请求、参数、结果、耗时和错误便于监控和调试。指标收集使用 Application Insights、OpenTelemetry 等收集工具调用次数、延迟、错误率等指标。错误处理与重试在客户端代码中对工具调用和 LLM 调用实现完善的错误处理如网络抖动、速率限制和重试策略使用 Polly 库。进程健康检查监控 MCP 服务器进程的健康状态如果崩溃客户端应能感知并尝试重启或告警。7.3 架构扩展工具动态发现本示例是静态注册工具。更复杂的系统可以从配置文件或数据库加载工具定义实现热更新。多工具服务器一个 MCP 服务器可以提供多个相关工具组如所有数据库操作工具。也可以为不同领域计算、搜索、绘图部署独立的 MCP 服务器客户端按需连接。复杂 Agent 编排Semantic Kernel Agents 支持更复杂的编排模式如多个专家 Agent 协作AgentGroupChat、具有持久化记忆的 Agent 等。可以根据业务需求设计工作流。流式响应对于生成内容较长的场景实现 Agent 回复的流式输出提升用户体验。7.4 下一步学习路径在掌握本实战项目的基础上你可以沿着以下方向深入探索更多工具尝试实现一个调用真实网络搜索 API如 Bing Search的 MCP 工具或一个执行安全沙箱内 Python 代码的工具。集成向量数据库为 Agent 添加长期记忆能力使用 Semantic Kernel 的插件将对话历史或知识库存入如 Qdrant、Weaviate 等向量数据库。研究提示工程优化 Agent 的instructions和工具的description这是提升 Agent 意图理解和工具调用准确率的关键。部署为服务将智能体客户端封装为 ASP.NET Core Web API提供 HTTP 接口前端通过聊天界面与之交互。深入 MCP 协议阅读 MCP 官方规范了解其资源Resources、提示模板Prompts等高级特性构建更强大的工具生态。通过本次实战你已经掌握了基于 Agent 框架和 MCP 协议构建可扩展、安全智能体的核心模式。这套架构将 LLM 的推理能力、外部工具的功能性以及 .NET 的工程可靠性结合在一起为开发下一代 AI 原生应用奠定了坚实的基础。在实际项目中从这个小而全的原型出发逐步迭代和强化各个组件是通向成功的最佳路径。