ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

.NET 8实战:用MCP协议让AI助手直接调用真实业务接口

.NET 8实战:用MCP协议让AI助手直接调用真实业务接口 最近在给团队做内部 AI 助手遇到一个很实际的痛点大模型再聪明它也看不到我们系统里的真实数据。你说“帮我查一下订单 20250101001 到哪了”模型只能一脸认真地编一个物流轨迹出来因为它压根没有权限、也没有通道去调我们的订单接口。为了解决“AI 只能聊天、不能办事”这个问题我把目光放到了 MCP 上。MCP 的全称是 Model Context Protocol当前热度非常高它不是某个大模型厂商的私有协议而是一个开放标准专门用来打通 AI 应用和外部工具、数据源、业务接口之间的通道。简单说它让 AI 可以直接“调你的 .NET 接口”而不是靠人复制粘贴或者让 AI 瞎猜。这篇内容我会完整记录一套 MCP 服务端与客户端的落地过程基于 .NET 8 实现核心内容包括MCP 协议的基本概念、如何把现有 .NET 接口包装成 MCP Server、如何用 C# 写一个 MCP Client 让 AI 对话直接触发真实接口调用以及我在生产环境里踩过的坑和总结的排查技巧。内容面向 .NET 开发者尤其是想给 AI 应用接入真实业务能力的同学看完可以直接抄作业。1. 项目概述与核心思路1.1 这个项目到底是什么我们先把这个项目拆开看。“让 AI 直接调你的 .NET 接口”这句话包含三个关键部分AI、.NET 接口、调用通道。AI 指的是大语言模型应用比如你基于 OpenAI、Claude 或者国内大模型封装出来的对话机器人。.NET 接口就是你业务系统里已有的 HTTP API、Service 方法或者仓储层能力比如查订单、提交工单、获取用户信息。调用通道是本次的核心它解决的是模型怎么知道有哪些接口可以调模型怎么按接口要求的格式传参调用结果怎么回到对话里继续生成回答。在没有 MCP 之前大家普遍的做法是把接口封装成 Function Calling 的 JSON Schema 丢给模型。这个方法能用但问题很多Schema 靠手工维护接口一变就要重新定义多个系统各自定义一套AI 应用根本无法统一对接鉴权、上下文、工具列表的管理全部要自己写。而 MCP 把这一整套东西标准化了服务端负责暴露“工具”客户端负责连接服务端AI 应用只需要按 MCP 协议列举工具、调用工具、获取结果。整个链路是活的工具列表可以动态刷新接口改动只需要改服务端AI 应用零感知。这个项目的落地目标是在一个 .NET 解决方案里同时实现 MCP Server 和 MCP ClientServer 侧暴露业务方法Client 侧连接 Server 并模拟 AI 调用工具最终让一个自然语言请求可以真实触发 .NET 接口并返回结果。1.2 为什么选 MCP 而不是纯 Function Calling这里先给结论如果你的 AI 应用只需要调用两三个固定接口Function Calling 手工配一下倒也够用。但一旦接口数量超过十个、参与者超过一个团队、接口还经常变Function Calling 的维护成本就会爆炸。MCP 最大的优势是“解耦”。服务端把业务能力抽象成标准化的工具客户端只依赖 MCP 协议不依赖具体业务。打个比方Function Calling 像是你告诉 AI“我的冰箱里有三个鸡蛋你去拿”MCP 则是给了 AI 一套标准插座任何符合协议的电器插上就能用。你不需要每次换一个电器就重新教 AI 一遍这个电器怎么用。另外MCP 在生态上已经赢了一半。现在 Claude Desktop、各类 IDE 插件、甚至一些 Agents 框架都在原生支持 MCP。官方和社区涌现了大量现成的 MCP Server比如 figs 设计稿相关的、浏览器自动化的、数据库查询的还有各类开发工具链的。你只要会写一个 .NET 的 MCP Server这些生态能力也能以同样的方式接入到你的 AI 应用里学习成本是复用的。还有一点MCP 对“工具上下文”的处理更成熟。模型在调用工具前需要知道工具的用途、参数格式、返回结构MCP 通过tools/list和tools/call方法把这些信息标准化地提供给客户端再由客户端决定如何与模型交互。这套机制天然适合 AI Agent 场景。1.3 这个方案适合谁用如果你属于下面这几类人这个项目对你会有直接帮助第一类是业务系统开发。你手里有大量 .NET 接口想让内部 AI 助手能查询业务数据或者执行简单操作比如客服助手查订单、运营助手查报表。第二类是 AI 应用开发者。你正在搭建 Agent 或者对话机器人不想被具体某个大模型厂商的 Function Calling 格式绑死希望有一套统一的工具接入层。第三类是架构师或平台负责人。你在做企业内部 AI 中台需要让多个业务系统以一种标准方式共享给 AI 应用同时做好权限管控和审计。一句话总结MCP 解决的是 AI 从“能说”到“能做”的问题.NET 开发者需要掌握这套协议的服务端和客户端两端的写法。下面我会从服务端开始一步步带你把接口接入 MCP。2. 环境准备与基础概念2.1 MCP 协议到底长什么样MCP 的协议层基于 JSON-RPC 2.0传输层支持 stdio 和 HTTP包括 SSE 流式。通俗讲它就是一套规定了“AI 应用怎么向外部服务要工具列表”、“外部服务怎么返回工具定义”、“AI 应用怎么请求执行某个工具”、“执行结果怎么回传”的接口规范。协议里有几个核心概念Server也就是 MCP 服务端持有实际业务能力把能力暴露为 tools、resources 或 prompts。tools 是最常用的对应“可执行的动作”比如查订单、发邮件。ClientMCP 客户端负责与 Server 建立连接获取工具列表调用工具。它不直接理解业务只是一个符合协议的消息转发器。Transport传输层。stdio 适合本地进程间通信比如 AI 应用在本地启动一个 dotnet 子进程进行通信HTTP/SSE 适合远程服务比如把 MCP Server 部署在服务器上让多个 AI 应用通过网络访问。方法initialize握手、tools/list获取工具列表、tools/call调用工具、notifications/initialized等。我画个最朴素的流程图AI 应用收到用户消息后通过模型判断需要调用工具客户端向 Server 发tools/call请求Server 执行业务代码返回结果模型根据结果组织自然语言回复。MCP 协议就是在“模型判断”和“实际执行”之间搭建的那座桥。2.2 .NET 生态下的技术选型在 .NET 里做 MCP现状比我预想的要成熟。官方有ModelContextProtocol.AspNetCore和ModelContextProtocol这些包虽然是预发布版本但已经覆盖了服务端、客户端、stdio、HTTP 等主要能力。社区也有不少实现但我建议直接跟官方包走因为接口设计和长期维护都会有保障。我这次用的包版本是基于 .NET 8 的 SDK 预发布版。创建解决方案后分别建了三个项目McpOrderServerMCP Server引用ModelContextProtocol.AspNetCore用来暴露订单相关接口。McpOrderClient控制台应用引用ModelContextProtocol连接 Server 并调用工具。McpOrder.Shared共享 DTO放订单查询的输入输出模型。如果你的接口已经是一个 ASP.NET Core Web API直接在原项目里加 MCP Server 配置也可以不需要单独开一个服务。但把 Server 独立出来有一个好处stdio 传输模式要求进程启动后通过标准输入输出通信跟 Web API 的 HTTP 请求混在一起容易干扰独立进程更干净。2.3 开发环境与初始化我假设你已经具备 .NET 8 SDK开发工具用 Visual Studio 2022 或者 Rider 都行。创建一个空解决方案然后执行命令添加项目dotnet new sln -n McpOrderDemo dotnet new web -n McpOrderServer -f net8.0 dotnet new console -n McpOrderClient -f net8.0 dotnet new classlib -n McpOrder.Shared -f net8.0 dotnet sln add McpOrderServer McpOrderClient McpOrder.Shared然后给服务端添加 MCP 包cd McpOrderServer dotnet add package ModelContextProtocol.AspNetCore --prerelease客户端添加基础包cd McpOrderClient dotnet add package ModelContextProtocol --prerelease现在的包版本更新频率很快记得用--prerelease拉最新预览版。不同版本之间 API 可能有一些微调我代码里写的用法基于我当时使用的版本如果你拿到的新版本编译报错优先查一下对应版本的 API 变更整体思路是不变的。3. 服务端实现让 .NET 接口暴露给 AI3.1 用 McpServer 特性把方法暴露成工具先看一个最简服务端骨架。在McpOrderServer里Program.cs 写入using ModelContextProtocol.AspNetCore; var builder WebApplication.CreateBuilder(args); builder.Services.AddMcpServer(options { options.DefaultTransport TransportTypes.Stdio; }) .WithToolsFromAssembly(); var app builder.Build(); app.MapMcp(); app.Run();注意WithToolsFromAssembly()这个扩展方法它会自动扫描程序集里所有标记了[McpToolType]特性的类并把类里的[McpTool]方法注册为 MCP 工具。这意味着你不需要手工维护工具清单新增接口后只要加上特性就能自动暴露非常方便。帮大家区分一下两个传输模式TransportTypes.Stdio标准输入输出通道客户端启动服务端进程通过 stdin/stdout 发 JSON-RPC 消息。适合本机调用部署简单。HTTP 模式同样用AddMcpServer但底层走 HTTP 传输。适合服务端部署在远程客户端通过网络访问生产环境基本都走这个模式。我写了一个订单服务对应“AI 查订单”的真实业务。定义输入输出 DTOpublic class OrderQueryInput { public string OrderNo { get; set; } } public class OrderQueryResult { public bool Success { get; set; } public string Message { get; set; } public OrderInfo Data { get; set; } } public class OrderInfo { public string OrderNo { get; set; } public string Status { get; set; } public string StatusText { get; set; } public decimal Amount { get; set; } public ListLogisticsTrace Traces { get; set; } } public class LogisticsTrace { public DateTime Time { get; set; } public string Description { get; set; } }然后在工具类里写上查询方法[McpToolType] public class OrderTools { private readonly IOrderService _orderService; public OrderTools(IOrderService orderService) { _orderService orderService; } [McpTool(get_order_info, 根据订单号查询订单状态和物流信息)] public async TaskOrderQueryResult GetOrderInfo( [McpToolParameter(description 订单号例如 ORD20250101001, required true)] string orderNo, CancellationToken cancellationToken) { var order await _orderService.GetByOrderNoAsync(orderNo, cancellationToken); if (order null) { return new OrderQueryResult { Success false, Message $订单 {orderNo} 不存在, Data null }; } return new OrderQueryResult { Success true, Message 查询成功, Data order }; } }这段代码里有几个点值得强调。[McpTool]特性的第一个参数是工具名称第二个参数是描述。工具名称要小写加下划线风格方便模型识别描述一定要写清楚这个工具是做什么的因为大模型要靠描述来决策什么时候调用它。[McpToolParameter]特性的description同样重要它告诉模型这个参数是什么意思直接影响模型传参的准确率。3.2 注册业务服务和自动扫描配置上面的OrderTools构造函数注入了IOrderService这是业务服务接口。我在这里用一个简单实现模拟查询方便本地演示public interface IOrderService { TaskOrderInfo GetByOrderNoAsync(string orderNo, CancellationToken cancellationToken); } public class OrderService : IOrderService { public TaskOrderInfo GetByOrderNoAsync(string orderNo, CancellationToken cancellationToken) { if (orderNo ORD20250101001) { return Task.FromResult(new OrderInfo { OrderNo orderNo, Status shipping, StatusText 运输中, Amount 299.00m, Traces new ListLogisticsTrace { new LogisticsTrace { Time DateTime.Now.AddHours(-5), Description 已从上海转运中心发出 }, new LogisticsTrace { Time DateTime.Now.AddHours(-2), Description 快件已到达北京转运中心 }, new LogisticsTrace { Time DateTime.Now.AddMinutes(-30), Description 派送中请保持电话畅通 } } }); } return Task.FromResultOrderInfo(null); } }然后回到 Program.cs把业务服务也注册进容器builder.Services.AddScopedIOrderService, OrderService(); builder.Services.AddMcpServer(options { options.DefaultTransport TransportTypes.Stdio; }) .WithToolsFromAssembly();这里WithToolsFromAssembly()会扫描调用程序集你不需要把[McpToolType]类一个个注册。注意工具类方法的生命周期默认由依赖注入容器管理所以构造函数注入服务是完全可以的。3.3 如何把现有 Web API 改成 MCP Server很多人会问我已有的 .NET Web API 接口和业务逻辑怎么最快变成 MCP 工具答案是不用把整个 Web API 推倒重来只需要在现有项目里做两件事第一添加 MCP Server 的包和配置把传输模式调整为 HTTP 或 stdio。第二把你希望 AI 调用的方法抽到带[McpToolType]的特性的类里。如果你的方法逻辑已经在某个 Service 里直接在工具类里注入这个 Service调用同样的方法即可。你不需要把原来的 Web API Controller 删掉两边可以并行存在。但是这里有一个架构层面的建议不要直接把 Controller 方法暴露为 MCP 工具而是抽出一个薄薄的门面层。原因是 Controller 的入参往往和 HTTP 请求模型耦合比如带分页参数、排序字段、用户上下文等这些不适合直接丢给模型。你定义给 MCP 的工具参数应该是语义化的比如“订单号”而不是“pageIndex、pageSize、sortField”。3.4 服务端启动与本地验证服务端写完后可以通过命令行验证它是否能正常响应 MCP 握手。首先启动服务端进程看看会不会崩cd McpOrderServer dotnet run因为配置的是 stdio 模式直接运行看起来好像没有反应但实际上它正在等待标准输入上的 JSON-RPC 消息。你可以用控制台给它发一段initialize消息来验证。用 PowerShell 或者 cmd 操作 stdio 不太方便建议直接用后面步骤的客户端来测。如果你要测试 HTTP 模式把DefaultTransport改成 HTTP然后程序会开启一个监听端口浏览器可能看到接口信息但 MCP 的请求报文和普通 HTTP 不同直接访问大概率是 405。这个行为是正常的别误以为写错了。4. 客户端实现让 AI 能调用 MCP Server4.1 客户端的两个层级客户端这块我们得分清楚两个概念。一个是最底层的 MCP Client它只知道“如何跟 Server 建立连接、列举工具、发起调用”它不包含任何大模型逻辑。另一个是 AI Agent 应用它负责调用大模型、解析模型意图、在适当时机调用 MCP Client 的工具。直接写一个裸的 MCP Client 相对简单复杂的是“让 AI 决定调哪个工具、参数怎么填”。完整实现一个 Agent 要涉及大模型 API 调用、工具结果回传、多轮对话管理工程量大。所以这篇内容里我聚焦底层 MCP Client用控制台程序演示连接、列举、调用这部分做通了上层接任何大模型都只是拼积木的事。4.2 用 C# 写一个 MCP Client在McpOrderClient里我们建立一个控制台应用来调用上面写好的 Server。关键代码如下using ModelContextProtocol; using ModelContextProtocol.Client; await using var mcpClient await McpClientFactory.CreateAsync( new McpClientOptions { ClientInfo new Implementation { Name OrderConsoleClient, Version 1.0.0 } }, new McpClientTransportOptions { Transport TransportTypes.Stdio, StdioServerProcess new McpStdioServerProcess { Command dotnet, Arguments new[] { run, --project, ../McpOrderServer/McpOrderServer.csproj } } }, cancellationToken: CancellationToken.None);然后列举工具var tools await mcpClient.ListToolsAsync(CancellationToken.None); foreach (var tool in tools) { Console.WriteLine($工具名称: {tool.Name}); Console.WriteLine($工具描述: {tool.Description}); Console.WriteLine($输入Schema: {tool.InputSchema}); Console.WriteLine(); }调用get_order_infovar result await mcpClient.CallToolAsync( get_order_info, new Dictionarystring, object? { [orderNo] ORD20250101001 }, CancellationToken.None); foreach (var content in result.Content) { Console.WriteLine(content); } if (result.IsError) { Console.WriteLine(工具调用返回错误); }运行后客户端会启动dotnet run --project ../McpOrderServer/...作为子进程通过 stdio 和它通信。你能看到控制台先打印工具列表然后打印查询订单的返回结果。这里有一个细节StdioServerProcess里的Command和Arguments要有正确路径。如果服务端项目不在相对路径最好用绝对路径或者项目编译后的 dll 路径。实际生产环境里客户端和服务端往往在不同的机器stdio 只适合开发调试这很正常。4.3 在 AI 应用里串联工具调用真正的 AI 应用不会像上面这么直白地写死“调用 get_order_info”。常见的流程是用户说“帮我查一下订单”。AI 应用把用户消息连同 MCP 工具列表一起发给大模型。大模型判断需要调用工具返回一个结构化指令比如{tool: get_order_info, arguments: {orderNo: ORD20250101001}}。AI 应用拿到这个指令后使用 MCP Client 的CallToolAsync执行调用。调用结果回传给大模型模型生成最终回复。在这个流程里MCP Client 的职责就是第 4 步。你完全可以把上面的客户端代码封装成一个服务类暴露ListToolsAsync和CallToolAsync方法然后在 AI 应用的后端调用。我用伪代码描述一下调用链路的形态public class McpToolExecutor { private readonly IMcpClient _mcpClient; public async TaskIReadOnlyListMcpTool ListToolsAsync() await _mcpClient.ListToolsAsync(); public async TaskCallToolResponse CallAsync(string toolName, IReadOnlyDictionarystring, object? args) await _mcpClient.CallToolAsync(toolName, args, CancellationToken.None); }你的大模型服务层只需要引用McpToolExecutor不需要关心协议细节。这就是 MCP 对上层应用最大的价值工具接入层的封装与业务隔离。4.4 与现成 AI 客户端/桌面的集成如果你不想自己写 Agent 编排也可以直接用支持 MCP 的客户端来连接你的 .NET MCP Server。例如Claude Desktop 支持通过配置文件添加 MCP 服务器把 command 配置成运行你的服务端的命令行即可。这样在对话里直接输入“查一下订单 ORD20250101001”就能看到模型实际调用了你 .NET 方法后给出的回答。这种方式特别适合团队内部快速试点先用现成 MCP 客户端验证服务端工具定义和返回格式确认没问题后再在你的 AI 应用里集成 MCP Client避免一上来就在业务系统里铺大工程。需要注意的是不同客户端对 MCP 的配置格式稍有差异但底层都是要求提供 transport 类型、command、arguments、env 这些信息。建议在配置前认真读一下客户端官方文档中关于 MCP server 的章节。5. 完整落地一个用户查询订单接口的实战5.1 从对话到数据一次真实的调用过程为了让整个链路更直观我把上面代码串成一个真实场景跑一遍。启动客户端后模拟 AI 应用收到一条用户消息“帮我查一下订单 ORD20250101001 到哪了”。在完整 AI 应用里模型会先判断应该调用get_order_info然后返回调用参数。这里为了方便演示我们直接让客户端调用工具。客户端输出工具列表时你会看到工具名称: get_order_info 工具描述: 根据订单号查询订单状态和物流信息 输入Schema: {type:object,properties:{orderNo:{type:string,description:订单号例如 ORD20250101001}},required:[orderNo]}这行输出其实非常关键。InputSchema就是发给模型的“调用说明书”模型靠它知道这个工具需要什么参数。如果你的 Schema 里把参数描述写清楚模型基本不会填错。相反如果描述空缺或者含糊模型就会瞎填。然后调用CallToolAsync(get_order_info, ...)返回内容如下[{type:text,text:{\success\:true,\message\:\查询成功\,\data\:{\orderNo\:\ORD20250101001\,\status\:\shipping\,\statusText\:\运输中\,\amount\:299.00,\traces\:[{\time\:\...\,\description\:\已从上海转运中心发出\},...]}}}]这个 JSON 文本会被 AI 应用当作“工具执行结果”回传给大模型模型再把它组织成自然语言回复“您的订单 ORD20250101001 目前运输中最新物流轨迹显示派送中请保持电话畅通。”到这里AI 就不再是“编造”“幻觉”一个物流结果了而是真正读取了你 .NET 接口里的实时数据。5.2 多工具参数与复杂入参处理实际业务里接口不可能只有一个字符串参数。比如“查询订单列表”需要时间范围、订单状态、页码“提交售后”需要订单号、原因、图片地址。MCP 工具的参数更多的时候是一个 JSON 对象而不是扁平字符串。用CallToolAsync传复杂参数只需要把 dictionary 里的值换成复杂对象即可var result await mcpClient.CallToolAsync( query_orders, new Dictionarystring, object? { [startTime] 2025-01-01, [endTime] 2025-01-31, [status] shipping, [page] 1, [pageSize] 20 }, CancellationToken.None);服务端对应工具方法签名可以是[McpTool(query_orders, 按时间和状态分页查询订单列表)] public async TaskOrderListResult QueryOrders( [McpToolParameter(description 开始日期如 2025-01-01)] string startTime, [McpToolParameter(description 结束日期如 2025-01-31)] string endTime, [McpToolParameter(description 订单状态pending/shipping/completed, required false)] string? status, [McpToolParameter(description 页码, required false)] int page 1, [McpToolParameter(description 每页数量, required false)] int pageSize 20, CancellationToken cancellationToken default)注意MCP 工具的参数本质上映射的是 JSON Schema所以服务端推荐用基础类型加描述的方式。如果参数是一个复杂对象模型很难自然生成嵌套 JSON建议拆成扁平参数能显著提升调用成功率。这是一个实战中非常有用的经验。5.3 服务端与客户端项目联调步骤联调时我建议按以下顺序操作能减少很多不必要的排查时间先编译整个解决方案确保没有编译错误。单独运行服务端项目确认它不会立即退出说明进程在等待 stdio 消息。运行客户端项目确认能列出工具。在客户端里调用一个最简单的工具确认返回内容。最后再把调用逻辑接进 AI 应用。如果你在第 2 步发现服务端运行后直接退出大概率是依赖注入或者程序集扫描时报错。可以临时在服务端 Program.cs 里加一个Console.WriteLine(MCP Server Started);通过标准输出观察是否正常启动。但要注意stdio 模式下标准输出被 MCP 协议占用正式环境里不要随便往控制台打印内容否则会污染协议消息流导致客户端解析失败。第 3 步列不出工具最常见的原因服务端进程根本没起来。建议先把服务端作为一个独立进程运行一次确保它能正常启动然后再用 MCP 客户端去连。6. 常见问题与排查技巧实录这里我在实际开发中积累的问题比较多专门整理成表格方便大家按图索骥。问题现象可能原因排查与解决方案客户端连接后工具列表为空服务端程序集扫描不到[McpToolType]类确认工具类是否在服务端项目程序集确认类和方法是否加了正确特性确认工具方法是否为 public调用工具时返回Method not found或-32601工具名称拼写错误或未重新扫描用ListToolsAsync打印实际工具名称复制过来用不要手打stdio 模式下进程刚启动就退出服务端内部错误在服务端单独运行看异常注意不要阻塞标准输入检查依赖注入注册是否完整中文信息乱码或 JSON 格式异常编码问题或返回类型被序列化成字符串确认 MCP 返回的 Content 类型建议返回对象而不是手工拼字符串dotnet run启动服务端太慢导致超时stdio 连接需要等待进程就绪服务端用已编译 dll 启动减少编译耗时或者改用 HTTP 传输调用工具返回错误但无详细信息工具内部异常未处理在工具方法内捕获异常并返回OrderQueryResult { Success false, Message ex.Message }方便模型理解下面是几个我反复踩过的坑专门拿出来说一下。第一个坑是工具方法里不要抛异常。MCP 工具调用如果抛异常客户端拿到的结果往往是一个笼统的错误模型无法根据这个错误给用户一个友好的回复。更好的做法是返回一个结果对象用Success和Message字段来表达业务错误。这样模型可以把错误信息组织成自然语言用户体验完全不一样。第二个坑是Description一定要写“人话”。有团队把工具描述写成“本方法用于对订单数据进行查询操作”模型看着似懂非懂。更好的描述是“根据订单号查询订单当前状态和物流轨迹订单号格式如 ORD20250101001”。描述越具体模型判断越准确尤其是多个工具功能相似的时候描述几乎决定了模型会不会用错工具。第三个坑是参数类型尽量用 string 接收然后在服务端自己做解析。为什么因为大模型生成的参数有时候是“看上去合理但格式不太对”的比如把日期传成2025-1-1你定义的 DateTime 类型可能解析失败。我是用 string 接收再用DateTime.TryParse解析解析失败时直接返回“日期格式不正确请使用 yyyy-MM-dd 格式”。这个体验比抛异常好得多。还有一个生产环境才遇到的问题服务端接口耗时太长。MCP 客户端调用工具时如果服务端方法里调第三方 API 或者数据库耗时会很大。AI 应用等待模型生成最终回复时是有超时限制的所以工具本身要尽量快。遇到慢接口建议在 MCP 工具层做缓存或者加超时控制避免把整个 AI 链路拖垮。7. 安全性与生产落地要点7.1 工具鉴权与最小权限MCP 把接口暴露给 AI 之后安全问题会比以前更突出原因在于 AI 会主动发起调用且调用链路由模型自动决策无法像传统 Web 接口那样靠长期的签权流程来限制。生产环境里我非常建议在 MCP Server 侧增加一层独立鉴权不要直接透传业务系统的登录用户身份。对 AI 应用来说需要一个“服务账号”这个账号的权限应该是最小权限集能查订单但不一定能改订单能读报表但不一定能删除数据。MCP 的工具方法要做成操作级别的权限控制而不是整个服务一把钥匙。具体实现上可以在工具方法内部注入一个ICurrentUserContext从 MCP 请求头里提取调用方身份然后做权限检查。如果服务端走 HTTP 传输客户端可以在请求里带上 Token服务端在中间件里解析。7.2 对外暴露时的输入清洗与 CSP 策略当 AI 能够调用你的接口并把结果写入数据库或者页面时你还需要考虑内容的二次清洗。比如某个工具会把用户提供的富文本内容保存到系统里这样模型生成的内容就可能夹带脚本片段。热词里提到的“增加 CSP(script-src self) 和对富文本字段的服务端白名单清洗”正是这个场景的安全手段。CSP 策略是浏览器端最后一道防线你需要在返回页面时设置Content-Security-Policy: script-src self阻止页面加载未授权的脚本。但 CSP 不能替代服务端清洗因为数据在存储层已经污染了。正确的做法是所有进入 MCP 工具的输入都要做服务端白名单校验尤其是富文本字段只允许白名单内的标签和属性其余一律剥离输出到前端时再配合 CSP 做双重保障。给一个简单的服务端清洗示意public static string SanitizeRichText(string html) { var allowedTags new HashSetstring { p, strong, em, ul, ol, li, br }; // 用 HtmlAgilityPack 解析后删除非白名单节点和属性 // 这里略去具体实现 return cleanedHtml; }别小看这一步。模型生成的内容如果没人校验真的可能把一段script写进业务系统而内部用户一旦访问就是一次存储型 XSS。MCP 服务端必须成为这道闸门。7.3 幂等、限流与审计AI 应用的重试机制和人类操作不同模型在推理过程中可能会多次调用同一个工具尤其当模型觉得自己第一次没有得到答案时会反复尝试。这意味着你的 MCP 工具设计要考虑幂等性。以“创建工单”这种非幂等操作为例建议要求客户端传入一个requestId服务端按requestId去重。如果同一个requestId重复到达直接返回上一次的结果而不是重复创建。这能避免模型重试时产生脏数据。限流也要做。AI 应用调接口的频率可能远高于人类用户因为模型可以并发发起多个工具调用。在 MCP Server 层按账号或者 IP 做限流比如每秒不超过 N 次防止个别异常调用拖垮后端业务系统。审计日志是 MCP 落地时最容易被忽略的部分。建议把每次工具调用的时间、工具名称、参数、调用方、返回结果摘要都记下来。为什么因为 AI 的调用行为不完全可控一旦出了数据异常你需要能回溯是哪个代理、哪个会话、哪句话触发了这个调用。7.4 服务端部署模式建议如果你只是本地方案stdio 模式够用。但生产环境我建议优先考虑 HTTP 模式。原因很简单服务端可以独立部署多客户端共享网络隔离更容易做防火墙规则清晰可以用容器编排管理 MCP Server 的生命周期日志、监控、链路追踪都能用现有基础设施。HTTP 模式下客户端只需要把Transport改成 HTTP然后传服务端地址就行await using var mcpClient await McpClientFactory.CreateAsync( new McpClientOptions { ClientInfo new Implementation { Name WebApp, Version 1.0.0 } }, new McpClientTransportOptions { Transport TransportTypes.Http, HttpClient new HttpClient { BaseAddress new Uri(http://localhost:5000/mcp) } }, CancellationToken.None);这里路径/mcp需要和服务端的app.MapMcp()对应。HTTP 模式下服务端接收 TCP 连接不再使用标准输入输出日志可以正常打印部署和调试都更友好。我个人在实际操作中的体会是MCP 服务端和客户端的代码实现并不难难的是把“AI 可能干什么”想清楚。工具一旦暴露出去模型就可能在你没预料到的场景里调用它。所以每一批工具上线前我都会自己先用客户端跑一遍正常场景和异常场景确认返回的 Message 对模型足够友好再把它开放给 AI 应用。最后再分享一个小技巧如果你后续要继续扩展可以把你所有的 MCP Server 按领域拆分比如订单域一个、用户域一个、内容域一个客户端侧根据需求连接多个 Server。MCP 允许客户端同时挂多个服务端工具列表会合并AI 应用在面对复杂任务时就有了更大的调度空间。这种“服务端按域拆分、客户端统一编排”的架构是我目前觉得最舒服的落地形态。
返回列表