在 .NET 中构建 MCP 客户端:以 awesome-copilot 的 dotnet-mcp-builder 技能为参考的消费端实战指南
2026/9/12 17:59:39 网站建设 项目流程

在 .NET 中构建 MCP 客户端:以 awesome-copilot 的 dotnet-mcp-builder 技能为参考的消费端实战指南

【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot

本指南以本仓库 skills/dotnet-mcp-builder 技能体系中的客户端参考文档为核心,系统讲解如何在 C#/.NET 中编写 Model Context Protocol(MCP)客户端——用于测试自己的 MCP 服务器、搭建 Agent 执行框架,或将 MCP 工具接入 Semantic Kernel / Microsoft.Extensions.AI 流水线。读完本文,你将掌握基于官方ModelContextProtocol2.x NuGet 包的完整客户端开发技能:从 STDIO/Streamable HTTP 两种传输方式的连接建立,到工具、提示词、资源的列出与调用,再到通知订阅、能力协商、会话恢复等进阶能力。

为什么需要一份“消费端”参考

在 dotnet-mcp-builder 技能中,绝大多数内容面向MCP 服务器的构建(STDIO / Streamable HTTP 传输、工具/提示词/资源原语、elicitation 等),而client.md专门补上了另一端:如何从 .NET 程序消费(consume)一个 MCP 服务器。根据 SKILL.md 中的决策树,当任务是“编写一个消费 MCP 服务器的 .NET 程序”时,应加载 references/client.md。它适用的典型场景包括:

  • 测试自己写的服务器:先以客户端身份连接,验证工具列表、参数绑定和返回内容是否符合预期;
  • 构建 Agent 宿主(harness):让代理程序通过 MCP 调用外部工具集;
  • 接入 AI 流水线:把 MCP 工具作为IChatClient的函数工具(function tools)交给 LLM 自动调度。

如果只需要“运行服务器”,则完全不需要关心本文内容——这是文档开头就明确划清的边界。

第一步:选对 NuGet 包

客户端项目的包选择是整个工程的地基。官方 C# SDK 由 MCP 项目与微软共同维护,2.x 是当前稳定版本线(文章写作时最新为 2.2.0),对齐 MCP 2026-07-28 规范。根据 packages.md,客户端有两种引用粒度:

# 最小化:仅客户端 + 传输层 dotnet add package ModelContextProtocol.Core --version 2.2.0 # 或:额外引入 DI/托管辅助能力 dotnet add package ModelContextProtocol --version 2.2.0
适用场景附带能力
ModelContextProtocol.Core纯客户端、自定义宿主、低层场景,不想引入Microsoft.Extensions.*依赖仅协议 + 传输层 + 低层McpClient.CreateAsync
ModelContextProtocol需要在客户端侧使用 DI/托管(hosting)Core +Microsoft.Extensions.Hosting集成

选型规则同样写在该参考中:纯客户端程序 →ModelContextProtocol.Core;若希望客户端也走 DI/托管模式,则用ModelContextProtocol。新建项目默认目标框架推荐 .NET 10(SDK 本身面向 .NET 8 与 netstandard2.0,因此 .NET 8/9/10 均可运行)。

通过 STDIO 连接:启动一个服务器子进程

STDIO 传输适用于服务器以客户端子进程形式运行的场景(Claude Desktop、VS Code、MCP Inspector、自定义 CLI)。客户端负责拉起可执行文件,通过 stdin/stdout 交换 JSON-RPC 帧。核心代码如下:

using ModelContextProtocol.Client; var transport = new StdioClientTransport(new StdioClientTransportOptions { Command = "dotnet", Arguments = ["run", "--project", "../MyMcpServer"], EnvironmentVariables = new() { ["MY_API_KEY"] = "..." }, ShutdownTimeout = TimeSpan.FromSeconds(10), StandardErrorLines = line => Console.Error.WriteLine($"[server] {line}") }); await using var client = await McpClient.CreateAsync(transport);

各配置项的作用:

  • Command/Arguments:指定要启动的可执行文件及其参数。dotnet run --project是开发期最方便的写法;生产环境建议替换为发布后的单文件可执行路径;
  • EnvironmentVariables:向子进程注入环境变量,等价于在服务器端通过Environment.GetEnvironmentVariable读取——服务器侧如何消费这些变量可参考 transport-stdio.md;
  • ShutdownTimeout:客户端断开时给服务器的优雅退出宽限期;
  • StandardErrorLines:把服务器 stderr 输出实时转发出来。这是绝佳的调试利器——你能即时看到服务器日志。

关于 STDIO 有一个必须牢记的“陷阱”:stdout 是 JSON-RPC 通道,任何非协议帧的 stdout 输出(Console.WriteLine、默认 console 日志 sink、库的启动横幅)都会导致客户端解析失败而断连。这也是 SKILL.md 中列出的首要卡点排查项(“STDIO:有东西在写 stdout”)。服务器侧应在一切之前把日志阈值配置到 stderr(LogToStandardErrorThreshold = LogLevel.Trace),客户端侧则利用StandardErrorLines回显服务器日志来确认一切正常。

通过 HTTP 连接:Streamable 传输

对于远程托管、多租户或需要横向扩展的服务器,使用 Streamable HTTP 传输。它的特点是单一端点通过 HTTP POST 接受 JSON-RPC,并在需要返回多条消息时以 Server-Sent Events 流式回传:

using ModelContextProtocol.Client; var transport = new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("https://my-server.example.com/mcp"), TransportMode = HttpTransportMode.StreamableHttp, ConnectionTimeout = TimeSpan.FromSeconds(30), AdditionalHeaders = new Dictionary<string, string> { ["Authorization"] = "Bearer ..." } }); await using var client = await McpClient.CreateAsync(transport);

要点说明:

  • TransportMode默认是AutoDetect——先尝试 Streamable HTTP,失败后回退到 SSE。文档建议新代码显式固定为StreamableHttp,让失败尽早暴露,而不是悄悄降级到已弃用的老协议;
  • AdditionalHeaders用于附加认证头等自定义请求头,适合配合 transport-http.md 中介绍的 ASP.NET Core 端 JWT Bearer / API Key 中间件方案;
  • 端点路径必须与服务器端app.MapMcp("/mcp/v1")的挂载路径精确一致,否则会得到 404——这是文档中反复强调的排查点。

列出并调用工具

连接建立后,最常用的操作就是枚举服务器暴露的工具并执行调用:

IList<McpClientTool> tools = await client.ListToolsAsync(); foreach (var t in tools) Console.WriteLine($"- {t.Name}: {t.Description}"); var echo = tools.First(t => t.Name == "Echo"); CallToolResult result = await echo.CallAsync(new Dictionary<string, object?> { ["message"] = "hello" }); if (result.IsError == true) { var msg = result.Content.OfType<TextContentBlock>().FirstOrDefault()?.Text; Console.Error.WriteLine($"Tool failed: {msg}"); return; }

参数以Dictionary<string, object?>传入,键必须与服务器端工具方法的参数名(JSON-RPCarguments的键)一致——这正是 SKILL.md 排查清单第 4 条“参数未绑定”的根源:参数名不匹配或复杂类型绑定问题。从 SDK 的机制看,服务器由方法签名加[Description]生成 JSON Schema,客户端则按此 Schema 构造调用。

处理返回的内容块

调用结果CallToolResult.Content是一个内容块(content block)集合,需要按类型分发处理:

foreach (var block in result.Content) { switch (block) { case TextContentBlock text: Console.WriteLine(text.Text); break; case ImageContentBlock image: File.WriteAllBytes("out.png", image.DecodedData.ToArray()); break; } }

TextContentBlock承载文本输出,ImageContentBlock承载二进制图像数据(通过DecodedData拿到字节流)。先检查result.IsError再解析内容,是稳健客户端的基本姿态。

列出提示词与资源

除了工具,MCP 服务器还可以暴露提示词(prompts)和资源(resources)。客户端的调用方式同样直观:

IList<McpClientPrompt> prompts = await client.ListPromptsAsync(); GetPromptResult pr = await client.GetPromptAsync("code_review", new Dictionary<string, object?> { ["language"] = "csharp", ["code"] = "..." }); IList<McpClientResource> resources = await client.ListResourcesAsync(); ReadResourceResult rr = await client.ReadResourceAsync("config://app/settings");
  • ListPromptsAsync+GetPromptAsync:枚举并获取提示词模板的渲染结果,GetPromptAsync的第二参数字典填充模板参数;
  • ListResourcesAsync+ReadResourceAsync:枚举并读取资源内容,资源 URI 采用如config://app/settings的自定义 scheme 或file://...

这三类原语(tools/prompts/resources)在服务端的完整构建方式,分别对应技能仓库中的 tool-primitive.md、prompt-primitive.md 与 resource-primitive.md——客户端侧仅需关注其对外暴露的接口形态。

订阅服务器通知

服务器可以主动推送通知(例如工具列表发生变化)。客户端通过注册通知处理器来响应:

client.RegisterNotificationHandler( NotificationMethods.ToolListChangedNotification, async (notification, ct) => { var updated = await client.ListToolsAsync(cancellationToken: ct); Console.WriteLine($"Tool list changed; now {updated.Count} tools."); });

这段代码演示了通知处理的经典范式:收到ToolListChangedNotification后,以cancellationToken重新拉取最新工具列表并刷新本地缓存。与之对称的服务器端场景(如 roots 列表变化通知)可见 roots.md 中的NotificationHandlers配置。

版本协商:discovery-first 与自动回退

2.x 时代,McpClient.CreateAsync先向服务器探测server/discover方法,对不支持的下级版本服务器自动回退到传统initialize握手——整个过程无需客户端做任何配置。这在 transport-http.md 中被描述为“discovery-first 协商”:v2 客户端通过server/discover学习能力,同时 SDK 对旧版本对端(2025-11-25 及更早)自动兼容。因此在抓包时看到server/discover流量是正常现象,不必惊慌。

处理服务器到客户端的请求:sampling、elicitation、roots

某些服务器功能需要“反向调用”客户端:sampling(借客户端的 LLM)、elicitation(向用户提问)、roots(读取客户端通告的项目根目录)。如果服务器用到了这些能力,客户端必须提供对应处理器

需要注意版本背景:在 2026-07-28 规范中,sampling 与 roots 已被弃用(2.x 上会看到MCP9005警告),但仍需处理器以与使用这些旧能力的服务器互通。创建客户端时通过McpClientOptions.Capabilities配置:

await using var client = await McpClient.CreateAsync(transport, new McpClientOptions { Capabilities = new() { Sampling = new() { SamplingHandler = async (req, progress, ct) => { // 将 req.Messages 转发给你的 IChatClient,返回 CreateMessageResult。 var response = await myChatClient.GetResponseAsync(/* convert */, ct); return new CreateMessageResult { /* fill in */ }; } }, Elicitation = new() { ElicitationHandler = async (req, ct) => { // 把 req.Message + req.RequestedSchema 展示给用户,收集输入。 return new ElicitResult { Action = "accept", Content = collectedValues }; } }, Roots = new() { RootsHandler = async (req, ct) => { return new ListRootsResult { Roots = new[] { new Root { Uri = "file:///workspace", Name = "Workspace" } } }; } } } });

三个处理器的分工:

  • SamplingHandler:把req.Messages路由到任意IChatClient实现,返回CreateMessageResult。注意 sampling 在 2026-07-28 规范中已弃用——新设计应让服务器直接调用模型,而不是借道客户端(详见 sampling.md 的弃用说明);
  • ElicitationHandler:向用户呈现问题与请求的 JSON Schema,收集输入后返回ElicitResultAction取值accept/reject/cancel)。elicitation不在v2 弃用清单中,是当前规范支持的交互能力(见 elicitation.md);
  • RootsHandler:返回客户端允许服务器访问的根目录集合。

关键行为:如果你不提供处理器而服务器恰好调用了该能力,调用会以 “method not supported” 错误失败。这与 testing.md 中的诊断项完全对应——“sampling/elicitation 抛 method not supported”正是客户端未通告该能力所致。测试时可用内存管道(InMemoryTransport)把真实服务器与真实客户端在同一进程内对接,并注册确定性 mock 处理器(例如让SamplingHandler直接返回"MOCK SUMMARY"),从而在不依赖真实 LLM 的情况下验证服务器行为。

把 MCP 工具接入IChatClient函数调用

如果要将 MCP 集成进Microsoft.Extensions.AI流水线,最简单的方式是把 MCP 工具暴露为AIFunction

using Microsoft.Extensions.AI; IList<McpClientTool> mcpTools = await client.ListToolsAsync(); var chatOptions = new ChatOptions { Tools = mcpTools.Cast<AITool>().ToList() }; var chatClient = new MyChatClient(...); // 任意 IChatClient 实现 var response = await chatClient.GetResponseAsync(messages, chatOptions);

这里的底层机制是:McpClientTool实现了AIFunction,因此函数调用中间件(function-calling middleware)能自动挑选正确的工具执行,并把结果回传给 LLM。这意味着你可以:

  • 把远端 MCP 工具当成普通函数工具交给任意IChatClient(如 Semantic Kernel 或 Microsoft.Extensions.AI 生态的实现);
  • 复用Microsoft.Extensions.AI的中间件能力(限流、重试、遥测、函数调用),与 sampling.md 中AsSamplingChatClient()的设计理念一脉相承——整个 .NET AI 生态共享同一套IChatClient抽象。

恢复会话:面向长生命周期 Agent 的状态保持

在状态化(stateful)HTTP 场景下,客户端可以携带已知会话 ID 恢复与服务器的会话,避免因瞬时网络中断而丢失上下文:

var transport = new HttpClientTransport(new HttpClientTransportOptions { Endpoint = new Uri("https://my-server.example.com/mcp"), KnownSessionId = previousSessionId }); await using var client = await McpClient.ResumeSessionAsync(transport, new ResumeClientSessionOptions { ServerCapabilities = previousServerCapabilities, ServerInfo = previousServerInfo });

适用场景是跨越瞬时网络中断存活的长时间运行 Agent 进程:客户端持久化SessionIdServerCapabilitiesServerInfo,重建时通过KnownSessionIdResumeSessionAsync无缝接回。

需要同时理解服务器侧的状态语义:根据 transport-http.md,2.x 中 HTTP 服务器默认是无状态的Stateless = true,v2 破坏性变更)——不跟踪Mcp-Session-Id、不暴露 SSE 会话端点,每个 POST 相互独立;只有显式设置Stateless = false才恢复状态化模式(代价是只能服务旧版本initialize握手)。因此,面向 2026-07-28 规范的现代服务器通常无需(也无法)走状态化会话恢复路径;会话恢复主要服务于仍运行 stateful 模式的旧式部署。

小结:一个健壮 .NET MCP 客户端的要素清单

关注点关键 API / 行为参考
包选择ModelContextProtocol.Core(纯客户端)/ModelContextProtocol(含 DI)packages.md
STDIO 连接StdioClientTransport+McpClient.CreateAsync,善用StandardErrorLinesclient.md
HTTP 连接HttpClientTransport+StreamableHttp,路径与服务器MapMcp一致transport-http.md
原语调用ListToolsAsync/CallAsyncListPromptsAsync/GetPromptAsyncListResourcesAsync/ReadResourceAsync对应原语参考文档
通知RegisterNotificationHandlerclient.md
协商自动server/discoverinitialize回退client.md
反向能力Sampling / Elicitation / Roots 处理器,缺省则 “method not supported”sampling.md、elicitation.md、roots.md
AI 集成McpClientTool实现AIFunction,直接作为ChatOptions.Toolsclient.md
会话恢复KnownSessionId+McpClient.ResumeSessionAsync(stateful 场景)client.md
测试验证InMemoryTransport进程内对接 + mock 能力处理器testing.md

无论是编写服务器测试脚手架、搭建 Agent 执行框架,还是把 MCP 工具编织进Microsoft.Extensions.AI流水线,这份客户端参考都是 dotnet-mcp-builder 技能体系中不可缺失的“消费端拼图”。对照 SKILL.md 的决策树按需加载对应参考,即可少踩版本、传输模式与弃用 API 的坑,写出生产级质量的 .NET MCP 客户端。

【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询