1. 为什么我要把 .NET 接口直接暴露给 AI1.1 从一个真实痛点说起去年底我接手了一个内部工单系统的维护工作前端是 Vue后端是 ASP.NET Core Web API数据库 SQL Server。日常最烦的事情不是写业务代码而是每天都有同事跑来问“帮我查一下订单号 XXX 的状态”“帮我把这个客户的所有历史工单导出来”“这个接口的入参格式是什么文档在哪”。这些事情本身不复杂但极其消耗时间而且提问的人往往不会用 Swagger也不愿意学。后来我尝试把大模型接进来让 AI 直接调用我现有的 .NET 接口。用户只需要在聊天框里说“帮我查一下订单 12345 的状态”AI 就能自动识别意图、调用对应的 API、把结果整理成自然语言返回。这个过程中MCPModel Context Protocol就是关键的桥梁。MCP 是什么简单说它是一套让 AI 模型能够发现并调用外部工具、数据源的协议标准。你可以把它理解成“AI 世界的 USB 接口”——只要你的服务实现了 MCP 协议任何支持 MCP 的 AI 客户端都能直接调用你的能力不需要为每个 AI 平台单独写适配层。对于 .NET 开发者来说这意味着你现有的 ASP.NET Core 接口、WPF 桌面工具、甚至 WinForm 里封装好的业务逻辑都有机会被 AI 直接调度。这篇文章适合谁看如果你手里有 .NET 项目想让 AI 帮你调用现有接口或者你想把自己的 .NET 服务包装成 AI 可调用的工具那接下来的内容应该能帮你少走不少弯路。我会从协议理解、服务端搭建、客户端对接、Swagger 集成、常见坑排查这几个维度把整套流程拆开讲清楚。1.2 MCP 到底解决了什么问题在没有 MCP 之前让 AI 调用外部接口通常有三种做法。第一种是写死提示词把接口文档塞进 System Prompt 里让模型自己生成 HTTP 请求。这种做法极其脆弱接口一改就全废而且模型经常编造不存在的参数。第二种是给每个 AI 平台写插件OpenAI 的 Function Calling 一套、Claude 的 Tool Use 一套、国内各家平台又各有一套维护成本高得离谱。第三种是搭一个中间层把接口包装成特定格式但中间层本身又需要大量胶水代码。MCP 的思路不一样。它定义了一套标准的服务端-客户端通信协议服务端负责暴露“工具”Tools、“资源”Resources和“提示”Prompts客户端负责把这些能力转译给具体的 AI 模型。服务端不需要关心对面是哪个模型客户端也不需要关心服务端用什么语言写的。这种解耦带来的直接好处就是我只需要在 .NET 这边实现一次 MCP 服务端之后不管团队用的是哪家 AI 客户端都能直接接进来。从协议层面看MCP 基于 JSON-RPC 2.0支持 stdio 和 HTTPSSE 两种传输方式。stdio 适合本地进程间通信比如 VS Code 插件调用本地工具HTTPSSE 适合远程服务比如把部署在服务器上的 .NET API 暴露给云端 AI 客户端。对于 .NET 开发者来说这两种方式都有对应的实现路径后面我会分别展开。1.3 整体方案选型与架构思路我最终落地的方案是这样的在现有的 ASP.NET Core 项目里增加一个 MCP 服务端模块通过 HTTPSSE 对外暴露工具能力同时写一个轻量的 MCP 客户端负责把 AI 客户端的请求转发到 MCP 服务端再把结果回传给 AI。Swagger 这边做了一层自动化转换把现有的 OpenAPI 描述自动映射成 MCP 工具定义省去手写工具描述的工作量。为什么选 HTTPSSE 而不是 stdio因为我的 .NET 服务是部署在内部服务器上的多个客户端需要同时访问stdio 那种进程绑定的模式不适合。HTTPSSE 虽然实现起来稍微复杂一点但扩展性和可维护性都好很多。另外SSE 是服务器推送事件天然适合 MCP 这种需要服务端主动通知客户端的场景。为什么用 Swagger 自动转换因为手写 MCP 工具定义太痛苦了。一个中等规模的 .NET 项目动辄几十上百个接口每个接口都要写工具名、描述、参数 schema写到最后肯定会出现描述不一致、参数遗漏的问题。Swagger 里已经有完整的 OpenAPI 描述直接解析 JSON 然后映射成 MCP 工具定义既准确又省事。当然自动转换不是万能的有些接口需要人工调整描述和参数约束这个后面会细说。架构上分三层最底层是现有的 ASP.NET Core Web API保持不动中间层是 MCP 服务端负责把 API 能力包装成 MCP 工具最上层是 MCP 客户端和 AI 客户端负责发起调用和展示结果。三层之间通过标准协议通信任何一层替换都不影响其他层。这种设计的好处是我可以在不修改现有业务代码的前提下快速把 AI 能力接进来。2. MCP 服务端在 .NET 里的核心实现细节2.1 项目结构与依赖选择我是在现有的 ASP.NET Core 8.0 项目里直接加 MCP 服务端模块的没有另起一个新项目。这样做的好处是共享现有的依赖注入容器、配置系统和日志组件减少重复代码。项目结构大致如下在原有项目下新建一个Mcp文件夹里面放McpServer.cs、McpToolRegistry.cs、SwaggerToMcpConverter.cs这几个核心文件然后在Program.cs里注册相关服务。依赖方面官方有ModelContextProtocol这个 NuGet 包但当时我用的时候版本还比较早期有些 API 不太稳定。后来我选择基于 JSON-RPC 2.0 自己实现核心通信层只用了System.Text.Json做序列化Microsoft.AspNetCore.SignalR做 SSE 推送。这样虽然多写了一些代码但可控性更强遇到问题也容易排查。如果你现在开始做建议先试试官方包如果满足需求就直接用不满足再考虑自己实现。提示MCP 协议本身不复杂核心就是 JSON-RPC 2.0 的消息格式加上几个约定的方法名。自己实现通信层并不难难的是把工具注册、参数校验、错误处理这些周边逻辑做扎实。2.2 工具注册与描述映射MCP 服务端的核心是“工具注册表”。每个工具需要包含名称、描述、输入参数的 JSON Schema、以及实际的执行逻辑。我设计了一个McpTool类来封装这些信息然后用一个McpToolRegistry来管理所有已注册的工具。工具名称我采用了“动词名词”的命名方式比如query_order_status、export_customer_tickets、get_api_documentation。这种命名方式对 AI 模型比较友好模型能直接从名称推断出工具的用途。描述字段我写的是自然语言尽量用完整的句子说明这个工具做什么、什么时候用、有什么限制。比如query_order_status的描述是“根据订单号查询订单的当前状态包括待付款、已付款、已发货、已完成、已取消五种状态。订单号必须是 12 位数字。”参数 Schema 我用System.Text.Json的JsonSchema相关 API 来构建支持 string、number、boolean、array、object 这几种基本类型。对于枚举类型的参数我会在 Schema 里用enum关键字列出所有可能的值这样模型在生成参数时就不会瞎编。对于必填参数用required数组标注。这些细节看起来琐碎但直接决定了 AI 调用接口的成功率。2.3 Swagger 自动转换的实现思路手写工具定义太累所以我写了一个SwaggerToMcpConverter从 Swagger 的 JSON 描述文件里自动提取接口信息转换成 MCP 工具定义。具体做法是在应用启动时通过ISwaggerProvider获取当前项目的 OpenAPI 文档对象然后遍历所有 Paths 和 Operations把每个 Operation 转换成一个 MCP 工具。转换规则是这样的工具名称取operationId如果没有就用“HTTP 方法路径”拼接生成工具描述取summary和description的拼接输入参数从parameters和requestBody里提取路径参数、查询参数、请求体参数分别处理参数类型映射到 JSON Schema 的基本类型。对于$ref引用的复杂类型我会递归展开直到拿到基本类型为止。这里有个坑要注意Swagger 里的operationId不一定唯一有些项目里多个接口用了同一个operationId转换的时候会冲突。我的处理方式是检测到重复时自动加后缀比如queryOrderStatus_1、queryOrderStatus_2。另外Swagger 里的参数描述经常是空的转换出来的工具描述会很干瘪影响 AI 的理解。我的做法是在转换后加一层人工审核对关键接口补充描述信息。2.4 参数校验与错误处理AI 模型生成的参数不一定符合预期所以服务端必须做严格的参数校验。我在McpTool的执行入口加了一个校验层按照 JSON Schema 检查参数类型、必填项、枚举值范围、字符串长度、数字范围等。校验不通过时返回结构化的错误信息告诉模型哪个参数有问题、期望什么格式。错误处理方面我把错误分成三类参数错误、业务错误、系统错误。参数错误返回 400 级别的提示业务错误返回具体的业务错误码和说明系统错误返回 500 级别的提示并记录日志。对于 AI 模型来说错误信息的可读性很重要所以我在返回错误时尽量用自然语言描述而不是只给一个错误码。比如“订单号格式不正确应该是 12 位数字你提供的是 8 位”这种描述模型能直接理解并修正。注意不要直接把 .NET 的异常堆栈返回给 AI 客户端一方面不安全另一方面模型也看不懂。统一转换成结构化的错误响应既安全又实用。3. MCP 客户端对接与 AI 集成实操3.1 客户端通信层实现MCP 客户端这边我实现了一个轻量的McpClient类负责和服务端建立 SSE 连接、发送 JSON-RPC 请求、接收响应和通知。核心逻辑是先通过 HTTP POST 发送initialize请求建立会话然后通过 SSE 长连接接收服务端推送的消息。每次调用工具时发送tools/call请求带上工具名和参数等待服务端返回结果。SSE 连接的维护是个关键点。网络抖动、服务端重启、客户端休眠都可能导致连接断开所以需要实现自动重连机制。我的做法是监听 SSE 连接的onerror和onclose事件触发重连时先尝试重新initialize如果失败就指数退避重试最多重试 5 次。重连成功后之前注册的工具列表需要重新拉取因为服务端可能已经更新了工具定义。超时处理也很重要。AI 模型调用工具时如果服务端响应太慢客户端不能一直等。我设置了 30 秒的默认超时超时后返回一个明确的错误信息给 AI 客户端让模型知道这次调用失败了可以尝试其他方式或告知用户。对于耗时较长的操作比如导出大量数据我会在服务端改成异步任务模式先返回任务 ID然后客户端轮询任务状态。3.2 与 AI 客户端的对接方式MCP 客户端最终要对接具体的 AI 客户端。目前主流的方式有两种一种是 AI 客户端原生支持 MCP比如某些代码编辑器和桌面 AI 工具直接配置 MCP 服务端地址就能用另一种是 AI 客户端不支持 MCP需要通过中间层转换把 MCP 工具定义转换成该客户端支持的 Function Calling 格式。我两种方式都试过。原生支持 MCP 的客户端接入最简单基本就是填个地址的事。不支持 MCP 的客户端我写了一个适配层把 MCP 工具定义转换成 OpenAI 风格的 Function Calling 定义然后把模型的调用请求转发到 MCP 服务端。这个适配层的核心是一个转换函数把 MCP 的inputSchema映射成 Function Calling 的parameters把tools/call的响应映射成function角色的消息。这里有个经验不同 AI 客户端对工具描述的长度限制不一样。有的客户端限制工具描述在 200 字符以内有的允许 1000 字符。我的做法是在 MCP 服务端维护两套描述一套详细版用于原生 MCP 客户端一套精简版用于 Function Calling 适配。精简版只保留最核心的信息把详细说明放到工具的examples字段里模型需要时可以通过resources/read拉取。3.3 多轮对话中的工具调用管理AI 调用工具往往不是一次性的而是多轮对话中的一环。比如用户问“帮我查一下订单 12345 的状态如果已发货就查一下物流信息”这需要先调用query_order_status根据返回结果判断是否已发货再决定是否调用query_logistics。MCP 客户端需要维护对话上下文把每次工具调用的结果关联到对应的对话轮次。我的做法是在客户端维护一个ConversationContext对象记录当前对话的历史消息、已调用的工具、工具返回的结果。每次 AI 客户端发起新的请求时把上下文一起传给模型让模型知道之前发生了什么。工具调用结果我做了结构化处理除了原始数据外还加了一个summary字段用自然语言概括结果方便模型快速理解。提示多轮对话中工具调用的顺序和依赖关系最好在工具描述里说明清楚。比如query_logistics的描述里写上“需要先调用 query_order_status 确认订单已发货”这样模型在规划调用步骤时就有据可依。3.4 安全与权限控制把 .NET 接口暴露给 AI 调用安全是绕不开的问题。我的做法是在 MCP 服务端加了三层防护。第一层是认证客户端连接时必须携带有效的 API Key服务端验证通过后才允许建立会话。第二层是授权每个工具都标注了所需的权限等级客户端只能调用其权限范围内的工具。第三层是审计所有工具调用都记录日志包括调用时间、客户端标识、工具名、参数、结果状态方便事后追溯。对于敏感操作比如删除数据、修改配置我额外加了一个确认机制。AI 模型调用这类工具时服务端不会直接执行而是返回一个“待确认”状态需要用户在 AI 客户端里明确确认后才真正执行。这个机制虽然增加了一步交互但能有效防止模型误操作。实测下来这个设计在内部系统里很受欢迎大家用起来放心很多。4. 常见问题排查与实战避坑指南4.1 Swagger 转换中的典型问题Swagger 转 MCP 工具定义的过程中我踩过不少坑。最常见的问题是 Swagger JSON 里缺少operationId导致工具名生成规则混乱。有些项目里operationId是自动生成的 GUID毫无可读性。我的处理方式是优先用operationId如果没有或者不可读就用“HTTP 方法路径”生成比如get_api_orders_id。生成后加一层人工审核把不合适的名字改掉。另一个问题是复杂类型的递归展开。Swagger 里经常有嵌套的对象类型比如订单对象里包含客户对象客户对象里又包含地址对象。递归展开时如果不加深度限制遇到循环引用就会死循环。我的做法是设置最大展开深度为 5 层超过深度就用object类型代替并在描述里说明“详细结构请参考接口文档”。这样既避免了死循环又保留了基本信息。还有一个坑是枚举值的处理。Swagger 里的枚举有时候是字符串有时候是数字有时候是$ref引用。我在转换时统一转成字符串枚举并在描述里说明原始类型。对于数字枚举我会在描述里加上映射关系比如“1 表示待付款2 表示已付款”这样模型生成参数时就不会搞错。4.2 连接与通信故障排查SSE 连接不稳定是最常见的问题。表现是客户端时不时报“连接已断开”或者工具调用超时。排查思路是这样的先看服务端日志确认是否有异常抛出再看网络层确认是否有代理或防火墙拦截了 SSE 长连接最后看客户端确认重连逻辑是否正常工作。我遇到过一次典型故障客户端每隔几分钟就断连一次服务端日志显示正常。后来发现是中间的负载均衡器设置了 60 秒的空闲超时SSE 连接在 60 秒内没有数据传输就被断开了。解决办法是在服务端定期发送心跳消息保持连接活跃。心跳间隔我设的是 30 秒比负载均衡器的超时时间短一半这样就不会被误断。另一个常见问题是 JSON-RPC 消息格式错误。MCP 协议对消息格式有严格要求jsonrpc字段必须是2.0id字段必须是字符串或数字method字段必须是字符串。我有一次因为id字段用了 GUID 对象而不是字符串导致服务端解析失败。排查这类问题时把原始消息打印出来逐字段检查基本都能定位到问题。4.3 工具调用失败的原因分析工具调用失败的原因五花八门我整理了一个速查表方便快速定位。现象可能原因排查方法解决方案模型不调用工具工具描述不清晰检查工具描述是否说明了用途和触发条件补充描述增加示例调用参数错误参数 Schema 不准确对比实际接口文档和 Schema 定义修正 Schema补充枚举值调用超时服务端处理太慢查看服务端日志确认耗时操作改异步任务增加超时提示返回结果模型看不懂结果格式太原始检查返回给模型的数据结构增加 summary 字段结构化输出工具名冲突多个工具同名检查工具注册表自动加后缀或人工重命名权限不足客户端权限配置错误检查客户端 API Key 和权限映射调整权限配置重新授权除了表格里的问题还有一个隐蔽的坑模型有时候会“幻觉”出不存在的工具名。比如我注册了query_order_status模型却调用了get_order_status。这种情况通常是工具描述和模型训练数据里的常见命名不一致导致的。解决办法是在工具描述里加上“别名”说明比如“本工具也可称为 get_order_status”这样模型就能正确匹配了。4.4 性能优化与扩展建议当工具数量增多、调用频率变高时性能问题会逐渐暴露。我做了几项优化效果比较明显。第一项是工具定义的缓存服务端启动时把 Swagger 转换结果缓存到内存后续请求直接读缓存避免每次重新解析。第二项是连接池MCP 客户端和服务端之间的 HTTP 连接复用减少握手开销。第三项是结果缓存对于查询类工具相同参数的请求在短时间内返回缓存结果降低后端压力。扩展性方面我建议把 MCP 服务端设计成可插拔的模块。工具注册、参数校验、权限控制、日志审计这些功能都做成独立的中间件需要时启用不需要时关闭。这样当项目规模变大时可以按需组合不会因为功能堆砌导致维护困难。另外工具定义最好支持热更新服务端更新工具后客户端能自动感知并刷新不需要重启整个服务。注意性能优化不要过早进行。先把功能跑通确认工具调用链路没问题再根据实际瓶颈做针对性优化。我一开始就想着做缓存、做连接池结果调试阶段因为缓存导致工具更新不生效白白浪费了半天时间排查。4.5 从 Swagger 到 MCP 的自动化流水线最后分享一个我觉得很实用的做法把 Swagger 转 MCP 工具定义做成自动化流水线。在 CI/CD 流程里加一个步骤每次 .NET 项目构建时自动生成最新的 Swagger JSON然后跑一遍转换脚本生成 MCP 工具定义文件最后部署到 MCP 服务端。这样接口一更新MCP 工具定义就自动同步不需要人工干预。转换脚本我用的是 .NET 控制台程序读取 Swagger JSON输出 MCP 工具定义 JSON。脚本里加了校验逻辑如果发现工具名冲突、参数类型不支持、描述为空等问题直接报错并终止流水线。这样能在早期发现潜在问题避免把有问题的工具定义部署到生产环境。实测下来这套流水线把工具定义的维护成本降低了八成以上接口更新后基本不需要人工介入。我在实际使用中发现MCP 这套东西虽然概念不复杂但落地时的细节特别多。从协议理解到服务端实现从客户端对接到问题排查每个环节都有坑。但只要把核心链路跑通一次后面就是不断优化和扩展的过程。对于 .NET 开发者来说现有的 ASP.NET Core 项目就是最好的起点不需要推倒重来加一个 MCP 模块就能让 AI 直接调用你的接口。这个投入产出比我觉得很划算。