1. 为什么我要把 .NET 接口直接交给 AI 来调先说结论MCP 不是又一个AI 插件协议的营销词它解决的是一个非常具体的工程问题——让大模型用统一的方式发现并调用你已有的后端能力而不是每次都在提示词里手写接口文档。我在一个中型 SaaS 项目里维护着两百多个 ASP.NET Core 接口前端、移动端、内部工具都在调。过去半年团队尝试把 AI 接进工作流最头疼的不是模型能力而是模型不知道我们有哪些接口、参数长什么样、返回结构是什么。每次都要把 Swagger 导出的 JSON 塞进上下文token 烧得飞快接口一改就全废。后来接触到 MCPModel Context Protocol思路一下就通了把接口能力抽象成工具让 AI 客户端通过标准协议去发现和调用而不是靠提示词硬灌。这篇内容适合三类人看一是手里有现成 .NET 后端、想让 AI 直接复用的开发者二是正在评估 MCP 到底值不值得投入的技术负责人三是想搞明白AI Agent 调后端这条链路到底怎么跑通的工程师。我会从协议理解、服务端落地、客户端接入、联调排错四个层面把我在实际项目里踩过的坑和验证过的方案完整讲一遍。全文基于 ASP.NET Core 8 官方 MCP C# SDK 的实践代码可以直接抄。需要提前说明的是MCP 目前生态还在快速演进SDK 版本迭代比较频繁我写的时候用的是相对稳定的版本你在复现时如果遇到 API 签名不一致优先去看官方仓库的 release note别硬套本文代码。2. MCP 到底是什么用一句话讲清协议本质2.1 从提示词塞文档到协议化调用的转变传统做法是这样的你把 Swagger 的 OpenAPI JSON 转成文本拼进 system prompt然后告诉模型你可以调用这些接口。问题在于接口一多上下文直接爆炸接口一改提示词就过期模型还可能幻觉出不存在的参数。这本质上是把结构化能力降维成了自然语言描述信息损耗极大。MCP 的做法完全不同。它定义了一套基于 JSON-RPC 2.0 的通信协议客户端比如 AI 应用和服务端你的 .NET 程序之间通过标准消息交互。服务端负责声明我有哪些工具、每个工具需要什么参数客户端负责把这些工具描述转成模型能理解的格式模型决定调用哪个工具后客户端再把调用请求转发给服务端执行最后把结果回传给模型。打个比方以前的模式像是你给新员工一本厚厚的接口手册让他自己翻MCP 模式像是给新员工配了一个前台他只需要说我要查订单前台就帮他把请求转给对应部门拿回结果。手册会过期前台不会。2.2 MCP 的三个核心概念Tools、Resources、PromptsMCP 协议里最常打交道的三个原语我用大白话解释一下Tools工具可被模型主动调用的动作比如查询用户创建订单。这是最核心的部分也是本文重点。工具是有副作用的模型调用它相当于执行一次操作。Resources资源可被读取的数据比如一份配置文件、一段日志。资源通常是被动的客户端读取后作为上下文提供给模型。Prompts提示模板预定义的提示词模板服务端可以提供客户端可以选择使用。这个在实际项目里用得相对少。对于让 AI 调 .NET 接口这个场景99% 的工作都集中在 Tools 上。你的每个业务接口理论上都可以包装成一个 Tool。但注意不是所有接口都适合暴露给 AI后面我会讲筛选原则。2.3 传输层stdio 和 HTTP 怎么选MCP 支持多种传输方式最常用的是两种传输方式适用场景优点缺点stdio本地进程、IDE 插件、桌面工具零网络配置、启动快只能本机、无法远程共享Streamable HTTP服务端部署、多客户端共享可远程、可鉴权、可扩展需要处理网络和会话我一开始用的是 stdio因为本地调试最省事。但项目要落地到团队共享时stdio 就不行了——总不能每个人都本地跑一份服务端。所以最终生产方案是Streamable HTTP服务端部署在内网客户端通过 URL 接入。本文两种都会讲但重点放在 HTTP 上因为那才是能真正落地的形态。提示如果你只是自己本地玩stdio 起步最快一旦涉及多人协作或远程调用直接上 HTTP别走弯路。3. 服务端落地把 ASP.NET Core 接口包装成 MCP Tools3.1 项目结构与依赖准备我用的方案是在现有 ASP.NET Core 项目里直接挂载 MCP 服务端而不是单独起一个进程。这样做的好处是接口逻辑复用、鉴权体系复用、部署单元不变。坏处是耦合度略高但对于大多数团队来说利大于弊。先看依赖。官方提供了ModelContextProtocol.AspNetCore这个包直接 NuGet 装上dotnet add package ModelContextProtocol.AspNetCore如果你用的是 stdio 模式装的是ModelContextProtocol这个包。两个包的核心 API 基本一致区别在于宿主方式。项目结构上我建议单独建一个Mcp目录把工具类集中管理别和业务 Controller 混在一起。原因很简单工具类是给 AI 看的Controller 是给人看的两者的参数设计哲学完全不同。人看的接口可以有复杂的嵌套 DTOAI 看的工具参数越扁平越好。3.2 定义一个 Tool从方法签名到协议描述定义一个 MCP Tool 的核心是[McpServerTool]特性。看一个我实际项目里的例子这是一个查询订单状态的工具using ModelContextProtocol.Server; using System.ComponentModel; [McpServerToolType] public class OrderTools { private readonly IOrderService _orderService; public OrderTools(IOrderService orderService) { _orderService orderService; } [McpServerTool, Description(根据订单号查询订单的当前状态和物流信息)] public async TaskOrderStatusResult GetOrderStatus( [Description(订单号格式为 ORD 开头的 16 位字符串)] string orderId) { var order await _orderService.GetByIdAsync(orderId); if (order null) { return new OrderStatusResult { Found false, Message 订单不存在 }; } return new OrderStatusResult { Found true, Status order.Status, LastUpdate order.UpdatedAt, TrackingNo order.TrackingNumber }; } }这里有几个关键点我逐个拆解第一[McpServerToolType]标记类[McpServerTool]标记方法。只有被标记的方法才会被注册为工具。这给了你精确控制权——不是所有 public 方法都要暴露。第二[Description]是给模型看的不是给人看的。这句话我要强调三遍。模型决定是否调用这个工具、怎么填参数完全依赖这段描述。描述写得烂模型就调不对。我见过有人写[Description(获取订单)]结果模型经常传错参数因为描述里没说清楚参数格式。第三参数类型要简单。上面这个例子只接收一个 string返回一个扁平的结果对象。如果你传一个复杂的嵌套对象模型很容易构造失败。经验法则工具参数不超过 5 个每个参数都是基础类型或简单枚举。第四返回值要可序列化且语义清晰。我特意加了一个Found字段而不是直接返回 null。因为模型看到 null 会困惑看到{Found: false, Message: 订单不存在}就能明确知道发生了什么可以据此回复用户。3.3 工具描述怎么写才能让模型调得准这是整个服务端落地里最容易被低估的环节。我踩过的坑是工具描述写得太简略模型要么不调用要么参数乱填。后来我总结了一套写法实测调用准确率从六成提升到九成以上。核心原则是把模型当成一个聪明但完全不了解你系统的实习生。你要在描述里回答三个问题这个工具是干什么的一句话说清业务语义别用内部黑话。什么时候该用它给出触发场景帮模型做决策。参数怎么填格式、范围、示例一个都不能少。对比一下两种写法// 差的写法 [McpServerTool, Description(查询订单)] public async TaskOrderStatusResult GetOrderStatus(string orderId) { ... } // 好的写法 [McpServerTool, Description(根据订单号查询订单的当前状态、物流单号和最后更新时间。当用户询问某个订单的进度、是否发货、物流信息时使用此工具。)] public async TaskOrderStatusResult GetOrderStatus( [Description(订单号必须以 ORD 开头后跟 13 位数字例如 ORD2024011500001)] string orderId) { ... }好的写法里当用户询问...时使用这句话极其关键它直接告诉模型决策边界。没有这句话模型可能在用户问我的账户余额时也去调订单查询工具。注意描述里不要出现可能大概也许这类模糊词模型会放大这种不确定性。用确定的、命令式的语言。3.4 挂载到 ASP.NET CoreHTTP 传输的完整配置工具定义好了接下来是把它挂到 Web 宿主上。在Program.cs里var builder WebApplication.CreateBuilder(args); // 注册业务服务 builder.Services.AddScopedIOrderService, OrderService(); // 注册 MCP 服务端 builder.Services .AddMcpServer() .WithHttpTransport() .WithToolsFromAssembly(); // 自动扫描当前程序集里的 Tool var app builder.Build(); // 映射 MCP 端点 app.MapMcp(/mcp); app.Run();WithToolsFromAssembly()会自动扫描所有带[McpServerToolType]的类并注册。MapMcp(/mcp)把 MCP 端点挂到/mcp路径上。启动后客户端就可以通过http://your-host/mcp接入。这里有个实操心得如果你的项目已经有全局鉴权中间件MCP 端点默认也会走这套鉴权。这通常是好事但要注意 MCP 客户端可能不支持复杂的鉴权流程比如需要交互式登录的 OAuth。我的做法是给 MCP 端点单独配一个 API Key 鉴权简单可靠app.MapMcp(/mcp).RequireAuthorization(McpApiKey);然后在鉴权策略里校验请求头里的 API Key。这样既安全又不会因为鉴权流程太复杂导致客户端接不上。3.5 哪些接口不该暴露给 AI筛选原则不是所有接口都适合包装成 Tool。我总结了三条红线第一有不可逆副作用的接口要谨慎。比如删除用户清空数据这类操作一旦模型误判就是灾难。如果非要暴露一定要加二次确认机制或者在描述里明确写此操作不可逆调用前必须向用户确认。第二参数过于复杂的接口不适合。如果一个接口需要传 10 个参数、其中 3 个是嵌套对象模型构造请求的成功率会很低。这种接口更适合在服务端再包一层简化版工具。第三高频内部接口不要暴露。有些接口是给内部服务调用的语义不面向用户暴露给 AI 只会增加噪音。工具列表越精简模型选择越准确。我建议一个 MCP 服务端的工具数量控制在 20 个以内超过就要考虑拆分。4. 客户端接入让 AI 真正用起来4.1 客户端形态选择IDE 插件、桌面应用还是自研MCP 客户端的选择取决于你的使用场景。我实际用过的有三类IDE 内置客户端比如某些代码编辑器已经原生支持 MCP配置一个 JSON 就能接入。适合开发者日常使用零开发成本。桌面 AI 应用一些桌面端 AI 工具支持配置 MCP 服务端适合非开发人员使用。自研客户端用官方 SDK 自己写一个适合需要深度集成到业务系统的场景。我的项目最终选了自研客户端因为需要把 MCP 调用和内部工单系统打通。但如果你只是想快速验证强烈建议先用现成的 IDE 客户端跑通链路确认服务端没问题后再考虑自研。4.2 用官方 SDK 写一个最小客户端自研客户端的核心代码其实很短。以 C# 为例using ModelContextProtocol.Client; var transport new HttpClientTransport(new HttpClientTransportOptions { Endpoint new Uri(http://your-host/mcp), AdditionalHeaders new Dictionarystring, string { [X-Api-Key] your-api-key } }); await using var client await McpClient.CreateAsync(transport); // 列出所有可用工具 var tools await client.ListToolsAsync(); foreach (var tool in tools) { Console.WriteLine(${tool.Name}: {tool.Description}); } // 调用某个工具 var result await client.CallToolAsync( GetOrderStatus, new Dictionarystring, object? { [orderId] ORD2024011500001 } ); Console.WriteLine(result.Content.First().Text);这段代码做了三件事建立连接、发现工具、调用工具。发现工具这一步是 MCP 的精髓——客户端不需要预先知道服务端有哪些能力运行时动态获取即可。这意味着你服务端加了新工具客户端不用改代码就能用。4.3 把工具喂给大模型Function Calling 的桥接客户端拿到工具列表后需要把它们转换成大模型能理解的 function calling 格式。不同模型的格式略有差异但核心结构一致var toolDefinitions tools.Select(t new { type function, function new { name t.Name, description t.Description, parameters t.InputSchema // MCP 提供的 JSON Schema } }).ToList();MCP 的工具描述里自带 JSON Schema直接就能用不需要你手写参数定义。这是 MCP 相比自己拼 function calling 的一大优势——Schema 由服务端维护客户端零成本同步。然后就是标准的 Agent 循环把用户消息和工具定义一起发给模型模型返回工具调用请求客户端执行后把结果回传模型生成最终回复。这个循环我在项目里封装成了一个McpAgent类核心逻辑大概一百行这里不展开重点讲几个坑。4.4 多轮调用与上下文管理模型有时候需要连续调用多个工具才能回答一个问题。比如用户问我上周买的那个订单到哪了模型可能先调查询用户最近订单拿到订单号再调查询订单状态。这要求客户端支持多轮工具调用循环。我的实现里设了一个最大轮次限制默认 5 轮防止模型陷入死循环。同时每一轮的工具调用结果都要追加到对话历史里否则模型会忘记自己刚才查到了什么。提示工具返回结果如果太长记得做截断或摘要。我遇到过工具返回一个几百行的 JSON直接把上下文撑爆的情况。服务端返回时就应该控制体积。5. 联调排错那些文档里不会写的坑5.1 工具发现失败从日志入手最常见的第一个问题是客户端连上了但ListToolsAsync返回空列表。排查顺序如下确认工具类被扫描到WithToolsFromAssembly()只扫描当前程序集如果你的工具类在另一个项目里需要显式指定程序集。确认特性没写错[McpServerToolType]在类上[McpServerTool]在方法上两个都不能少。确认方法是 public 且非静态除非你用的是静态工具模式。看服务端日志MCP SDK 会输出注册了哪些工具日志级别调到 Debug 就能看到。我踩过的一个坑是工具类构造函数依赖了一个没注册的服务导致整个工具类实例化失败但异常被吞掉了表现就是工具列表为空。后来我养成了习惯MCP 服务端启动后先手动调一次 ListTools 验证。5.2 参数传递错误类型不匹配的隐蔽问题模型传过来的参数永远是看起来对的。比如它可能把数字传成字符串123把布尔传成true。如果你的工具方法签名是int count反序列化就会失败。解决方案有两个一是在工具方法里用宽松类型接收内部再转换二是在描述里明确写清类型。我通常两个都用[McpServerTool, Description(查询最近 N 天的订单N 为 1 到 30 之间的整数)] public async TaskListOrder GetRecentOrders( [Description(天数整数例如 7 表示最近 7 天)] int days) { days Math.Clamp(days, 1, 30); // ... }Math.Clamp这行是防御性编程防止模型传个 999 进来把数据库查爆。5.3 超时与长任务异步工具的设计有些工具执行时间较长比如生成报表同步等待容易超时。MCP 协议本身支持异步任务模式但实现起来复杂。我的折中方案是工具内部启动后台任务立即返回一个任务 ID再提供另一个工具查询任务状态。这样模型可以先调启动报表生成拿到任务 ID过一会儿再调查询任务状态。虽然多了一次往返但避免了超时用户体验也更可控。5.4 常见问题速查表现象可能原因排查方向客户端连不上端点路径错误、鉴权失败检查 URL、API Key、防火墙工具列表为空特性缺失、程序集未扫描看 Debug 日志、确认特性模型不调用工具描述不清、工具太多优化描述、精简工具数量参数反序列化失败类型不匹配用宽松类型、加 Clamp调用超时工具执行太久改异步任务模式返回结果被截断内容过长服务端控制返回体积6. 生产落地的几点经验6.1 安全边界别让 AI 拿到不该拿的权限MCP 服务端本质上是把你的后端能力开放给了一个不可完全预测的调用方。所以最小权限原则必须贯彻。我的做法是MCP 端点用独立的 API Key和业务系统的用户鉴权分离。工具内部再做一次权限校验不能因为请求来自 MCP 就跳过。敏感操作改数据、发通知加审计日志记录是谁在什么时候通过 AI 触发的。6.2 可观测性日志和指标怎么埋MCP 调用链路比普通 HTTP 请求长出问题时定位困难。我在服务端埋了三类日志工具注册日志、工具调用入参日志、工具执行结果日志。客户端侧则记录模型决策日志为什么选这个工具。这些日志在排查模型为什么调错工具时是救命稻草。6.3 版本演进接口变了怎么办业务接口会变工具定义也要跟着变。我的策略是工具定义和业务接口解耦——工具方法内部调用业务服务业务服务变了只改工具方法的实现工具签名尽量保持稳定。如果签名必须变就在描述里标注版本并保留旧工具一段时间做过渡。这套方案我在项目里跑了三个月目前团队内部已经有多个 AI 工作流依赖它。回头看MCP 最大的价值不是技术多先进而是它把AI 调后端这件事从一次性集成变成了可持续演进的能力。接口加一个工具加一个客户端自动就能用这种体验是以前拼提示词时代完全没法比的。如果你正准备动手我的建议是先用 stdio 模式在本地跑通一个最简单的工具确认链路通了再上 HTTP、再加鉴权、再考虑生产部署。别一上来就搞复杂架构MCP 的学习曲线其实很平缓难的是想清楚哪些能力值得开放给 AI。
企业数字化 ERP 产品动态
相关推荐
老系统升级MySQL 8:Hibernate 3别名失效的根因与三层修复 说实话,让一个跑了近十年的老系统从 MySQL 5.5 升级到 MySQL 8,我一开始以为最难的会是数据迁移或者新硬件驱动不兼容。真正动手之后才发现,第一束火星是从 Hibernate 3 生成的 SQL 里冒出来的。标题里的“别名失效”这四个字,概括… · 2026/9/26 5:51:14
Blender摄影机控制完全指南:从视角切换到动画路径 1. 摄影机控制的核心思路:先把“视角”这件事想明白很多新手学 Blender 时,最容易懵掉的一个点就是:明明在视图里拖得挺欢,结果一按 F12 渲染出来的画面跟你在视口里看的根本不是一回事。原因很简单——你在视口里用的是“自由视角… · 2026/9/26 5:51:14
MySQL UPDATE执行全链路:从加锁到日志的底层原理与优化 一条 UPDATE 语句写下去,MySQL 背后到底干了多少活?很多同学写 SELECT 已经轻车熟路,一碰到 UPDATE 就心里发虚:为什么我明明建了索引还是慢?为什么两条互不相关的 UPDATE 会互相锁住?为什么 rows affected… · 2026/9/26 5:51:14
从零搭建GitHub镜像站:Gitea同步原理与实战指南 GitHub镜像站这四个字,在代码托管和开源协作圈子里,一直是个高频需求。所谓镜像,就是把你关心的GitHub仓库复制到自己的服务器上,保存一份内容一致的副本,并提供Web查看和克隆的入口。这件事能解决的问题很具体&#x… · 2026/9/26 6:25:34
银河麒麟桌面系统安装实战指南:镜像选择、硬件适配与避坑要点 1. 项目概述:为什么现在还要认真学装银河麒麟桌面系统?“麒麟操作系统安装教程:从零开始安装银河麒麟桌面系统”——这个标题看着平实,但背后藏着一个正在快速落地的现实:国产操作系统已不再是实验室里的演示品&#x… · 2026/9/26 6:25:34
导师推荐!盘点2026年领军级的一键生成论文工具 一天写完毕业论文在2026年已不再是天方夜谭。以下是2026年最炸裂、实测能大幅提速的一键生成论文工具,覆盖选题构思、文献整理、内容生成、降重润色四大核心场景,助你高效搞定论文。
一、全流程王者:一站式搞定论文全链路(一天定稿… · 2026/9/26 6:25:27
无密码卸载ThreatbookAgent:注册表深度清理实战指南 1. 项目概述:为什么“无密码卸载ThreatbookAgent”是个真实存在的刚需场景ThreatbookAgent 是国内某主流威胁情报与终端安全平台部署的轻量级探针客户端,常用于企业内网资产测绘、行为日志采集和EDR联动响应。它不是传统意义上的杀毒软件,而更… · 2026/9/26 6:25:03
多Agent开发实战:拆分与复制,让每个Agent专注一件事 做Agent开发这两年,我最大的体会就是:一个Agent什么都能干,往往最后什么都干不好。你把资料搜集、数据清洗、图表生成、报告撰写全塞进一个Agent里,提示词写到五千字,工具配了七八个,结果它要么在中间步骤跑… · 2026/9/26 6:25:03
SpringBoot+Vue社区维修平台:接单并发与状态同步实战 简介:本资源为基于SpringBoot与Vue的社区维修平台毕业设计完整项目,面向计算机相关专业需要完成课程设计、毕业设计或期末大作业的学生。项目采用前后端分离架构,后端以SpringBoot(或SSM)搭建,数据库使用My… · 2026/9/26 6:25:03
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46