news 2026/9/25 3:02:37

.NET + Semantic Kernel 搭建 MCP 能力层实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
.NET + Semantic Kernel 搭建 MCP 能力层实战解析

MCP(Model Context Protocol)是2025年AI工程圈最绕不开的热词。如果你最近在做Agent相关项目,大概率已经发现,MCP把“工具怎么暴露给AI”这件事彻底标准化了。而.NET这一端,最有组合价值的就是Semantic Kernel(SK)。这篇文章不聊虚的概念,就聊我自己在一个实际项目里,用.NET + SK搭MCP能力层的完整方案:架构怎么拆、代码怎么写、坑在哪、上线后要注意什么。如果你正打算在公司内部做一个统一的AI工具接入层,或者想把现有.NET服务变成MCP Server暴露给各种AI客户端,这篇应该能帮你少踩不少雷。

1. 先想明白:MCP能力层解决什么问题

1.1 工具接入的“战国时代”

在MCP还没普及的时候,我做AI相关项目最头疼的就是“适配”。给OpenAI写function calling要一套JSON Schema,给Anthropic要换一种tool写法,给自家内部Agent框架又要来一遍工具定义。一个查询天气的小功能,硬生生适配了三四个客户端,代码里全是协议转换逻辑。这就像你出门要带一堆充电线——每个设备一种接口,虽然都能充,但就是烦。

MCP解决的正是这个问题。它把“AI应用如何发现工具、描述工具、调用工具”这个流程标准化了。无论你是Claude、Cursor、还是某个自研的Agent平台,只要支持MCP,就能通过一套协议连接到同一个工具服务。工具方只需要实现一次MCP服务端,所有支持MCP的客户端都能复用。放在企业内部,这其实是给AI能力建立了一个标准化的接入层。

协议层面,MCP的核心交互非常简洁:

  • initialize:客户端和服务端握手,确认协议版本和能力。
  • tools/list:客户端获取服务端暴露的工具清单(工具名、描述、参数Schema)。
  • tools/call:客户端发起一次具体的工具调用。
  • 底层消息基于JSON-RPC 2.0,服务器可以走stdio和HTTP两种传输。

理解这几个端点就够了,MCP并没有对你的业务代码做任何侵入,它只是在你和AI客户端之间加了一层“翻译官”。

1.2 什么是“能力层”

这里说的“能力层”,不等同于一个API网关,也不只是一个工具集合。它的关键点在于:能力层是AI可以理解和调用的能力集合。

传统API接口暴露出来之后,调用方自己决定调哪个、怎么拼参数。能力层不太一样,它考虑的是让AI自己“发现”并“编排”这些能力。比如一个用户问“查一下上周北京上海的销售数据,做成汇总发我”,这其实不是一次API调用能解决的,而是一连串动作:查数据、做汇总、触发推送。能力层背后需要有AI编排逻辑,而不只是API路由。

.NET生态里,用SK来做编排是顺理成章的选择。SK提供了Kernel、Plugins、Function Calling、Prompt模板这些基础设施,把“AI怎么理解意图、怎么决定调用哪些能力”这一层封装了起来。MCP则负责把SK编排好的能力,用标准协议暴露给所有AI客户端。

1.3 为什么是.NET + SK

有人可能会问:搞AI不是Python的天下吗?确实,Python在模型训练和POC阶段优势明显,但落地到企业系统,.NET的生态成熟度不容小觑。特别是已经有大量.NET存量系统、已经有规范的服务治理体系的企业,用.NET做MCP能力层,可以直接和现有系统打通,没有跨语言的鸿沟。SK对.NET开发者也比较友好,它把Prompt拼接、函数调用循环、工具Schema生成这些复杂逻辑都封装了,写起来很像普通的依赖注入加服务调用。

我在实际项目里体会最深的一点是:SK彻底消灭了自己维护Function Calling循环的脏活,而MCP解决了多方接入的适配问题。这两件事单独拎出来都只是效率提升,合在一起才真正形成了一套可以对外输出、对内统一管理的能力层。

2. 整体架构:SK与MCP怎么分工

2.1 三层模型

我落地时候把一个MCP能力层拆成了三层:

层级职责核心组件
接入层对外暴露MCP协议端点,接收工具调用MCP Server(stdio / HTTP传输)
编排层理解用户意图、规划工具调用链SK Kernel、Planner、上下文内存
执行层实际访问数据库、外部API、文件系统SK Plugins / 自定义工具集

这样分的好处是每层可以独立演进。接入层只关心协议,不关心业务逻辑;执行层只关心“干活”,不关心谁来调用它;编排层居中负责“翻译”。万一以后MCP协议要升级,或者SK要被换成别的Agent框架,动一层不动另两层,改动成本就小很多。

2.2 两种典型的集成模式

在实际落地时,我见过也用过两种模式,你需要先根据场景选好:

模式一:工具直通模式。把SK里的每个插件函数直接映射为MCP工具。客户端通过MCP调用工具时,实际执行的是SK插件的逻辑。这种模式适合“能力明确、工具边界清晰”的场景,AI客户端直接调用单个工具即可,不涉及复杂编排。

模式二:智能编排模式。MCP只暴露一个或少量“Agent入口”工具,客户端把用户问题抛给这个入口,由SK在服务端完成工具调用链。这种模式适合复杂任务,把AI能力从客户端收回到服务端,便于统一管理、统一运营养护。

两种模式可以混用。我最终线上环境的方案是:对外提供少量编排入口,同时把所有可独立调用的原子工具也暴露出来,让外部Agent既有“自由调用”的能力,又有“交给服务端代理”的选择。

2.3 传输方式怎么选

MCP协议支持stdio和HTTP两种传输,这直接影响部署形态:

  • stdio:进程内启动子进程,通过标准输入输出通信。适合本地工具包,比如给某个IDE插件配一个本地MCP工具,构建一个控制台应用即可。优点是免部署、免鉴权,缺点是只能服务单机上的客户端。
  • HTTP:MCP Server部署为HTTP服务,多个客户端共享访问,可以实现鉴权、监控、限流。适合企业级能力层。

我在项目里基本只用HTTP传输,因为能力层一定会有多个上游调用方,不止一个AI客户端。HTTP模式能复用已有运维设施:负载均衡、API网关、日志采集,这比把MCP塞进客户端进程里要省心得多。

提示:如果你只是给个人开发机上的Claude Desktop配工具,stdio没问题。但在团队协作场景里,强烈建议走HTTP,不然每个开发都要本地起一个服务,维护成本立刻失控。

3. 服务端落地:用SK插件直接暴露MCP工具

3.1 项目与依赖

先打开NuGet,给项目加上这几个包:

  • Microsoft.SemanticKernel:核心SDK,提供Kernel、Plugins、ChatCompletion抽象。
  • ModelContextProtocol.AspNetCore:MCP服务端的ASP.NET Core集成(HTTP传输)。
  • ModelContextProtocol:MCP核心SDK,一般会被上一个包自动带出来。

SK对.NET 8和.NET 9支持得都很好,自己项目里用的是.NET 8 LTS版本,稳。有一点要提醒:SK迭代速度非常快,包版本之间偶尔会有破坏性变更,你照着某个教程写代码时如果编译不过,先看包版本对不对。不要升级依赖时无脑升最高版,最好锁定一个经过验证的版本组合。

3.2 最小服务端骨架

用ASP.NET Core创建一个空Web项目,Program.cs里最核心的代码就这么几行:

var builder = WebApplication.CreateBuilder(args); // 注册MCP服务端,走HTTP传输 builder.Services.AddMcpServer() .WithHttpTransport(); var app = builder.Build(); // 把MCP端点映射到 /mcp app.MapMcp("/mcp"); app.Run();

就这么简单,一个满足MCP协议的服务端骨架就出来了。客户端连接/mcp,就能通过握手、ListTools拿到能力列表。这个骨架本身没有任何业务,真正的重点是接下来怎么把SK接进来。

3.3 定义SK插件并暴露为MCP工具

在这个骨架上加SK。先定义一个插件类:

public sealed class WeatherPlugin { [KernelFunction("get_weather")] [Description("获取指定城市的实时天气信息")] public async Task<string> GetWeatherAsync( [Description("城市名称,如:北京、上海")] string city, [Description("日期,格式yyyy-MM-dd,默认当天")] string? date = null) { // 实际逻辑:调用天气API、查第三方服务或走缓存 return await WeatherApi.GetAsync(city, date ?? DateTime.Now.ToString("yyyy-MM-dd")); } }

关键地方在[KernelFunction]和[Description]。SK会根据这两个特性生成工具描述和参数Schema,MCP Server在收到客户端tools/list请求时,把这些SK插件转成MCP的Tool定义返回。描述写得好不好,直接决定AI能不能正确调用。我总结的通用写法是:

  • 函数描述:动作 + 对象 + 适用场景,比如“获取指定城市的实时天气信息”。
  • 参数描述:说清格式和边界,比如“日期,格式yyyy-MM-dd,默认当天”。
  • 避免模糊词,比如“获取信息”这种太空泛的描述会让模型拿不准适用场景。

接下来在Program.cs里注册并挂载:

var builder = WebApplication.CreateBuilder(args); var kernel = builder.Services.AddKernel(); kernel.Plugins.AddFromType<WeatherPlugin>(); builder.Services.AddMcpServer() .WithHttpTransport() .WithTools(kernel.Plugins.SelectMany(plugin => plugin.Select(f => McpServerTool.Create(f))));

这段代码做的事情是:遍历Kernel中的每个插件,把每个函数转成MCP Server能识别的工具。客户端工具列表里就会出现get_weather,调用时直接执行对应插件函数,和AI客户端原生的工具调用体验完全一致。

注意:AddFromType会把公有方法中带[KernelFunction]的成员暴露出去。如果你的类里有一些公开但不想暴露给AI的方法,务必不要加[KernelFunction]特性。血泪教训,我一开始把类里一个内部工具方法也标了特性,结果客户端工具列表多了一个完全没有业务价值的内部函数,还差点被模型误调用。

3.4 让MCP工具走SK的调用通道

上面这种直接映射的方式,工具执行时并没有经过SK的完整链路。如果工具逻辑不复杂,没问题;但如果你希望工具的调用被完整记录、被上下文感知,或者想在工具执行前做统一预处理,就需要让MCP的工具调用进入SK的调用链。

我的做法是加一个“编排工具”:

public sealed class AgentTools { private readonly Kernel _kernel; public AgentTools(Kernel kernel) { _kernel = kernel; } [KernelFunction("ask_agent")] [Description("把任务交给AI Agent统一处理,适合需要多步骤协作的复杂请求")] public async Task<string> AskAsync( [Description("用户问题或指令文本")] string prompt, CancellationToken ct) { var result = await _kernel.InvokePromptAsync(prompt, cancellationToken: ct); return result.ToString(); } }

这样,客户端可以调具体的原子工具,也可以只调ask_agent,把复杂决策交给SK。工具直通模式和智能编排模式就打通了。

3.5 工具注册时为什么不做业务判断

很多人会问:在将SK插件转换成MCP工具的时候,能不能做点前置判断,比如某些工具只对某些客户端开放?

理论上可以。MCP协议本身不管理权限,但服务端代码可以在tools/list返回时做过滤。我的建议是:先把工具全量注册,把权限判断放到上层网关或授权中间件里去,不要在SK插件的转换层写业务分支。因为SK插件的核心价值是复用,如果转换层混入权限逻辑,插件换一个场景就要改代码,失去了能力层该有的独立性。权限是横切面,用中间件和拦截器处理更干净。

4. 客户端闭环:SK消费MCP与外部接入

4.1 用SK连接外部MCP Server

不只是对外暴露,SK也可以作为MCP客户端,去消费其他MCP Server的能力。这在做聚合能力层时非常实用:自己的能力层需要调用另一个部门提供的MCP工具,比如统一的用户查询工具。

代码很简单:

// 创建MCP客户端 var mcpClient = await McpClient.CreateAsync( new McpClientOptions { ClientName = "InternalCapabilityLayer", ClientVersion = "1.0.0" }, new McpClientTransportOptions { TransportType = "http", BaseUri = new Uri("https://internal-service/mcp") }); // 获取远端MCP工具 var mcpTools = await mcpClient.GetToolsAsync(); // 注入到SK内核 var kernel = Kernel.CreateBuilder().Build(); kernel.Plugins.AddFromFunctions("remote-mcp", mcpTools);

AddFromFunctions是SK里把外部函数当作本地插件来用的方式。SK在收到请求后,可以把“查询用户”这种任务分发给远端的MCP工具处理,本地无需重复实现。能力层就变成了“联邦式”的,这在大型组织里是特别实用的架构能力。

4.2 外部AI客户端接入

服务端和客户端都打通了,外部AI客户端接入就顺理成章了。不管你是给Claude Desktop配MCP,还是给Cursor配MCP,原理都一样:配置一个MCP Server地址,客户端启动时自动调用tools/list获取工具列表,用户通过对话触发工具调用。这就是MCP最大的价值:你不用为每个AI客户端单独写一套工具适配,能力层只要实现一次MCP协议,所有客户端自动获得这些能力。

一个典型的配置文件片段长这样:

{ "mcpServers": { "internal-capability-layer": { "type": "http", "url": "https://capability.internal.example.com/mcp" } } }

4.3 一条完整请求链路长什么样

以“智能编排模式”为例,一次真实调用长这样:

  • 用户在AI客户端输入“帮我查一下北京今天天气适不适合出行”。
  • 客户端通过MCP协议调用能力层的ask_agent工具,带上用户原文。
  • SK Kernel收到Prompt,内部通过Function Calling决策:需要调用get_weather。
  • SK调用天气插件,拿到结果后把结果拼入上下文并生成最终回复。
  • 回复内容通过MCP返回给AI客户端,客户端展示给用户。

决策在SK做,执行在插件上做,MCP只是一个“快递通道”。如果说MCP是交通规则,SK就是司机,工具集合是目的地。三者各司其职,链路才清晰可查。

5. 生产级实战:三个值得落地的场景

5.1 企业内部知识库检索

场景很常见:把内部Wiki、知识库、制度文档变成AI可调用的能力层。用户问“年假怎么申请”,AI自动调用检索工具,从知识库中寻找相关文档,再生成回答。

工具设计关键点:

  • 用向量检索做语义召回,用BM25做关键词召回,两路结果做RRF融合。
  • 检索工具返回“内容片段 + 来源链接 + 更新时间”,让模型有据可依。
  • 塞给模型的不是全文,而是截取的前N段高相关片段,并标注来源,方便用户校验。

这个场景里我犯过错误:最开始把整个文档内容都塞给模型,Token消耗爆炸,上下文被无关内容污染,回答还变差了。后来改成:先检索,再按相关性截取片段,最后把引用来源一起给模型。效果立刻稳定很多。提示词和工具返回值设计往往比模型选型更能影响体验。

5.2 数据库查询Agent

场景:让AI安全地查询业务库。用户问“上个月华东区订单总额是多少”,AI通过工具获取表结构信息、生成查询、执行并返回结果。

这个场景看起来简单,实际上风险最大。关键策略:

  • 工具只暴露“只读查询”能力,SQL执行连接一个只读账号,权限细化到库表。
  • 尽量不让模型自由生成SQL,而是从一组预置查询模板里选择,模板没有覆盖的需求走人工审核。
  • 所有查询日志全量记录,用于事后审计。

你在技术上再强,权限和审计永远是第一位的。模型自由发挥的边界要划清楚,尤其是在数据库操作这种不可逆场景里。

5.3 工单系统自动处理

场景:客服收到一个问题,AI判断是否需要创建工单、更新状态、指派负责人。这涉及多个工具的组合调用,正好用SK的编排能力。

把“创建工单”“查询工单状态”“更新工单优先级”拆成独立插件。SK根据对话内容自动决定调用哪些工具以及调用顺序。MCP对外只暴露一个入口,比如handle_service_request。外部AI客户端只需要调这一个工具,剩下的内部编排全部由SK处理。

这样做还有一个额外好处:工单系统相关的复杂Prompt模板、工具调用规则都收敛在服务端,客户端只负责触发。以后运营想调整流程,不用去改每个客户端配置。

6. 踩坑记录与排查实录

6.1 SK生成的工具描述被“截断”

现象:工具调用时,模型偶尔会误判参数。后来发现MCP返回的工具描述和SK里写的不一致,很多描述被客户端截断了。

排查:MCP协议对工具描述字段本身没有严格长度限制,但部分AI客户端对描述长度有限制。解决方案是控制描述长度:不写废话、把边界条件放在参数描述里而不是函数描述里。我习惯把函数描述控制在30字以内,参数描述控制在20字以内,效果明显提升。

6.2 工具多到一定程度,模型就“选择困难”了

现象:能力层挂了30多个工具,AI开始频繁选错工具或重复调用。

排查:AI客户端在选择工具时,会把所有工具Schema都塞给模型。工具过多、Schema过长会严重影响模型判断质量。方案是给工具按领域分组,或者按路由前缀暴露不同MCP Server端点,让每个端点的工具数量控制在10个以内。

比如你有一个订单工具组和一个用户工具组,不要把一个Server里塞30个工具,而是拆成/mcp/order和/mcp/user两个端点,外部Agent按场景接入。工具虽多,但对模型来说每次可选项是有限的。

6.3 参数类型映射问题

现象:SK里定义的int参数,MCP返回的JSON Schema类型是integer,但AI客户端可能把参数值传成字符串。

排查:MCP SDK的JSON Schema转换对C#类型比较严格,但模型本身对数值类型处理不总是可靠。所以我一般在工具入口做预校验和类型转换兜底。比如在插件函数入口处,不直接假设参数已经是int,而是先做一层Parse,失败则返回明确错误信息,让模型自己纠正。诚实的工具错误信息,比模型瞎猜要有效得多。

常见问题触发场景推荐处理方式
参数类型不对模型把int当string传工具入口做类型转换兜底
枚举值越界传了不在预期内的状态值参数描述中写死可选值,函数内校验
可选参数缺省客户端不传可空参数参数定义提供默认值

6.4 工具调用超时与幂等

MCP工具调用默认可能等待较长时间,SK编排和模型生成又叠加了延迟。我的做法是给每个工具设置明确的超时阈值,并让工具调用尽量幂等——重试多次不会产生副作用。

一个简单的超时计算公式:工具超时 = 模型决策时长 + 外部API最大等待时长 + 缓冲时间。

比如模型决策按15秒算,外部API最大等待10秒,那超时设到30秒比较合理。如果设太短,AI在工具执行过程中就返回异常;设太长,一次故障会把上游调用方全部拖垮。

尤其写操作类工具,必须提供一次性的request_id。比如创建工单、发起审批这类操作,重试时如果没有request_id,很容易重复创建,这是生产事故级别的问题。

6.5 鉴权与网络边界

MCP协议本身不携带认证逻辑,服务端和客户端之间也没有默认的安全握手。部署在企业内网能力层时,我把MCP Server放在API网管之后,由网关做认证鉴权、限流和审计。框架层虽然MCP SDK会校验一些头部,但业务层的鉴权还是得自己做。

一个原则:MCP Endpoint要像普通API Endpoint一样受网络边界保护,不要裸奔。至少要加一层Bearer Token校验,再配合网关做来源IP白名单。MCP协议解的是工具互通,不是安全框架,这个边界务必要清楚。

6.6 并发时模型上下文膨胀

现象:性能测试时,多个并发请求打到ask_agent后,服务端内存和Token开销快速增长。

排查:SK在编排时会维护上下文历史,如果每个请求都保留完整历史,并发一高内存立刻报警。解决方案是给每个请求独立的Kernel实例,并在任务结束后及时释放;上下文长度根据业务需要做截断,必要时用Token计数器做预算管理。

我自己用的是“请求级Kernel”模式:每个MCP调用进来,创建一个新的Kernel,用完即销毁。虽然会多一点点初始化开销,但隔离性极好,不会有上下文串扰的问题。

7. 一点个人体会

把这套能力层做完,我自己的感受是:MCP解决了工具互通的一部分问题,但真正的复杂度还在编排层和执行层。工具标准化只是起点,AI能不能稳定理解业务语义、工具执行是否能做到可控可观测,这两件事比协议本身难得多。

如果有人问我,什么时候适合搞一个.NET + SK的MCP能力层?我的回答是:公司内部有多个AI客户端要共用同一批工具,值得建;已经有成熟.NET系统,值得用.NET去接;如果只是给单项目接工具调用,直接用SK本地调用就够了,不要为了MCP而MCP,协议层的附加成本也是成本。

最后再分享一个小技巧:把能力层的工具命名和描述当成接口规范来管理,这件事比写代码更值得投入时间。工具描述的质量直接决定AI调用的准确率,你花半小时把描述写清楚,后面能省下无数次模型误调用带来的排查时间。我自己已经把这套命名规范写进了团队的开发约定里,后端同学提交的每个工具函数,都要经过一段“AI视角描述评审”,效果比想象中还要好。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 2:58:23

Python线程并发编程实战与性能优化

1. 为什么需要线程并发在Python中处理I/O密集型任务时&#xff0c;传统的同步编程方式会遇到明显的性能瓶颈。比如一个网络爬虫程序&#xff0c;如果采用顺序执行的方式下载100个网页&#xff0c;大部分时间都会浪费在等待网络响应上。这时候线程并发就能显著提升效率。我去年优…

作者头像 李华
网站建设 2026/9/25 2:58:21

安全行业变局:从卖盒子到卖能力,五大细分赛道暗藏黑马

1. 一张热搜词表折射出的行业变局&#xff1a;安全赛道正在"换引擎"如果你长期混迹在安全圈&#xff0c;一定会对近两年国内安全厂商的处境有种复杂的感觉。传统防火墙、WAF、入侵检测这类产品&#xff0c;卷了二十多年&#xff0c;功能越加越多&#xff0c;界面越做…

作者头像 李华
网站建设 2026/9/25 2:57:55

i3老机流畅运行Win11 26H2的底层优化实践

1. 项目概述&#xff1a;为什么“i3老机跑Win11 26H2”成了真实可行的工程问题&#xff0c;而不是一句空话“Win11 26H2让i3老机流畅运行”——这标题乍看像营销话术&#xff0c;但如果你真拆开Windows 11 26H2的系统镜像、翻过微软官方文档、在i3-4170&#xff08;2013年发布&…

作者头像 李华
网站建设 2026/9/25 2:57:49

MFC现代化UI实战:UIShop换肤与QQ式界面开发

简介&#xff1a;本资源是一套面向Windows桌面应用开发者的MFC专业GUI开发工具包&#xff0c;专为中高级C开发者设计&#xff0c;解决传统MFC界面开发效率低、现代化UI&#xff08;如QQ/360风格&#xff09;实现困难、换肤功能集成复杂等痛点。工具包含可视化所见即所得设计工具…

作者头像 李华