资讯动态

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

发布时间:2026/9/12 17:59:50 来源:尧图企业网站定制
在 .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 ProtocolMCP客户端——用于测试自己的 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.CreateAsyncModelContextProtocol需要在客户端侧使用 DI/托管hostingCore Microsoft.Extensions.Hosting集成选型规则同样写在该参考中纯客户端程序 →ModelContextProtocol.Core若希望客户端也走 DI/托管模式则用ModelContextProtocol。新建项目默认目标框架推荐 .NET 10SDK 本身面向 .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.mdShutdownTimeout客户端断开时给服务器的优雅退出宽限期StandardErrorLines把服务器 stderr 输出实时转发出来。这是绝佳的调试利器——你能即时看到服务器日志。关于 STDIO 有一个必须牢记的“陷阱”stdout 是 JSON-RPC 通道任何非协议帧的 stdout 输出Console.WriteLine、默认 console 日志 sink、库的启动横幅都会导致客户端解析失败而断连。这也是 SKILL.md 中列出的首要卡点排查项“STDIO有东西在写 stdout”。服务器侧应在一切之前把日志阈值配置到 stderrLogToStandardErrorThreshold 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 Dictionarystring, 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——这是文档中反复强调的排查点。列出并调用工具连接建立后最常用的操作就是枚举服务器暴露的工具并执行调用IListMcpClientTool 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 Dictionarystring, object? { [message] hello }); if (result.IsError true) { var msg result.Content.OfTypeTextContentBlock().FirstOrDefault()?.Text; Console.Error.WriteLine($Tool failed: {msg}); return; }参数以Dictionarystring, 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。客户端的调用方式同样直观IListMcpClientPrompt prompts await client.ListPromptsAsync(); GetPromptResult pr await client.GetPromptAsync(code_review, new Dictionarystring, object? { [language] csharp, [code] ... }); IListMcpClientResource resources await client.ListResourcesAsync(); ReadResourceResult rr await client.ReadResourceAsync(config://app/settings);ListPromptsAsyncGetPromptAsync枚举并获取提示词模板的渲染结果GetPromptAsync的第二参数字典填充模板参数ListResourcesAsyncReadResourceAsync枚举并读取资源内容资源 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.mdRootsHandler返回客户端允许服务器访问的根目录集合。关键行为如果你不提供处理器而服务器恰好调用了该能力调用会以 “method not supported” 错误失败。这与 testing.md 中的诊断项完全对应——“sampling/elicitation 抛 method not supported”正是客户端未通告该能力所致。测试时可用内存管道InMemoryTransport把真实服务器与真实客户端在同一进程内对接并注册确定性 mock 处理器例如让SamplingHandler直接返回MOCK SUMMARY从而在不依赖真实 LLM 的情况下验证服务器行为。把 MCP 工具接入IChatClient函数调用如果要将 MCP 集成进Microsoft.Extensions.AI流水线最简单的方式是把 MCP 工具暴露为AIFunctionusing Microsoft.Extensions.AI; IListMcpClientTool mcpTools await client.ListToolsAsync(); var chatOptions new ChatOptions { Tools mcpTools.CastAITool().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 的状态保持在状态化statefulHTTP 场景下客户端可以携带已知会话 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 进程客户端持久化SessionId、ServerCapabilities与ServerInfo重建时通过KnownSessionId与ResumeSessionAsync无缝接回。需要同时理解服务器侧的状态语义根据 transport-http.md2.x 中 HTTP 服务器默认是无状态的Stateless truev2 破坏性变更——不跟踪Mcp-Session-Id、不暴露 SSE 会话端点每个 POST 相互独立只有显式设置Stateless false才恢复状态化模式代价是只能服务旧版本initialize握手。因此面向 2026-07-28 规范的现代服务器通常无需也无法走状态化会话恢复路径会话恢复主要服务于仍运行 stateful 模式的旧式部署。小结一个健壮 .NET MCP 客户端的要素清单关注点关键 API / 行为参考包选择ModelContextProtocol.Core纯客户端/ModelContextProtocol含 DIpackages.mdSTDIO 连接StdioClientTransportMcpClient.CreateAsync善用StandardErrorLinesclient.mdHTTP 连接HttpClientTransportStreamableHttp路径与服务器MapMcp一致transport-http.md原语调用ListToolsAsync/CallAsync、ListPromptsAsync/GetPromptAsync、ListResourcesAsync/ReadResourceAsync对应原语参考文档通知RegisterNotificationHandlerclient.md协商自动server/discover→initialize回退client.md反向能力Sampling / Elicitation / Roots 处理器缺省则 “method not supported”sampling.md、elicitation.md、roots.mdAI 集成McpClientTool实现AIFunction直接作为ChatOptions.Toolsclient.md会话恢复KnownSessionIdMcpClient.ResumeSessionAsyncstateful 场景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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价