☰
用 C 构建生产级 MCP Server:awesome-copilot 中 C MCP Server Expert 的完整实践指南
2026/10/20 10:49:31 网站建设 项目流程

用 C# 构建生产级 MCP Server:awesome-copilot 中 C# MCP Server Expert 的完整实践指南

【免费下载链接】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

awesome-copilot 是一个社区贡献的 GitHub Copilot 指令、Agent 与技能集合仓库。其中的csharp-mcp-expert.agent.md定义了一个专注于使用 C# SDK 构建 Model Context Protocol(MCP)服务器的专家型 Agent:它以ModelContextProtocol系列 NuGet 包为核心,覆盖 .NET 依赖注入、异步编程、协议级错误处理、工具/提示词/资源三类原语的设计,以及 stdio 传输与协议错误的调试。本文以该 Agent 定义为主干,结合仓库内 同主题指令文档 与 dotnet-mcp-builder 技能库 的源码级细节,完整还原一套可直接落地的 C# MCP Server 开发方法论——读完你既能按规范搭出第一个可运行的 MCP 服务器,也能掌握工具(Tool)、提示词(Prompt)、资源(Resource)三大原语的生产级写法与常见排障手段。

一、先理解这篇 Agent 的定位与它面向的技术栈

在 awesome-copilot 的 agents 目录 中,csharp-mcp-expert.agent.md是一份“角色即能力清单”的系统提示词文件,front matter 声明了它的名称(C# MCP Server Expert)与模型建议(GPT-4.1),正文把一个“世界级 C# MCP 服务器专家”应掌握的知识拆成了四块:

  • 核心包掌握:ModelContextProtocol、ModelContextProtocol.AspNetCore、ModelContextProtocol.Core三个 NuGet 包;
  • .NET 架构功底:Microsoft.Extensions.Hosting、依赖注入、服务生命周期管理;
  • MCP 协议理解:客户端-服务器通信、tool/prompt/resource 三类模式;
  • 工程素养:async/await、CancellationToken、安全、错误处理、日志、测试、可维护性。

这与仓库内配套的 csharp-mcp-server.instructions.md 互相印证:前者负责“专家怎么写代码”,后者以.cs/.csproj为作用域定义硬性规则(applyTo: '**/*.cs, **/*.csproj')。两者的共同结论是:一个 .NET MCP Server,本质上是一个普通的Microsoft.Extensions.Hosting(或 ASP.NET Core WebApplication)应用,通过依赖注入把 MCP 服务器接进去而已。

二、包选型:三个 NuGet 包分别什么时候用

Agent 与指令文档都强调先选对包。仓库技能库 packages.md 对三者做了精确分工:

包适用场景附带能力
ModelContextProtocol大多数项目与 STDIO 服务器的默认选择引入Core与Microsoft.Extensions.Hosting集成,提供AddMcpServer、WithToolsFromAssembly等属性发现机制
ModelContextProtocol.AspNetCore托管在 ASP.NET Core 中的 HTTP(Streamable)服务器上述全部 +WithHttpTransport与MapMcp
ModelContextProtocol.Core纯客户端、自定义宿主、低层场景(不想引入Microsoft.Extensions.*)仅协议 + 传输 + 低层McpServer.Create/McpClient.CreateAsync

针对“当前应该用哪个版本”,仓库内存在两条并列的证据链,使用时需要按场景选择:

  • Agent 文档与配套指令写于 SDK 预发布时代,明确要求始终带--prerelease标志安装:dotnet add package ModelContextProtocol --prerelease;
  • 而面向最新 2.x 稳定版的 dotnet-mcp-builder 技能 则给出固定稳定版本(写作时为 2.x 线)的工程实践,并警告0.x预览版存在破坏性差异、1.x虽可编译但早于 2026-07-28 规范修订。

新建项目时可用dotnet search ModelContextProtocol --prerelease查看最新可用版本。技能库给出的三条脚手架命令是最佳起点:

# STDIO 服务器 dotnet new console -n MyMcpServer -f net10.0 dotnet add package ModelContextProtocol dotnet add package Microsoft.Extensions.Hosting # HTTP(Streamable)服务器 —— dotnet new web 正好是 MapMcp 需要的宿主 dotnet new web -n MyMcpServer -f net10.0 dotnet add package ModelContextProtocol.AspNetCore # 纯客户端 dotnet new console -n MyMcpClient -f net10.0 dotnet add package ModelContextProtocol.Core

SDK 的目标框架是.NET 8.0与netstandard2.0,因此可运行在 .NET 8 LTS、.NET 9、.NET 10 LTS 上;若写 HTTP 服务器则需要支持 ASP.NET Core 的 TFM(即 .NET 8/9/10)。

三、服务器骨架:DI 优先 + stderr 日志 + 传输注册

Agent 的“Your Approach”反复强调Dependency Injection First。指令文档给出一份可整体复制的基准服务器(见 csharp-mcp-server.instructions.md 的 Common Patterns):

var builder = Host.CreateApplicationBuilder(args); builder.Logging.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace); builder.Services .AddMcpServer() .WithStdioServerTransport() .WithToolsFromAssembly(); await builder.Build().RunAsync();

这段代码包含四条黄金法则,Agent 文档逐条列明:

  1. 用Host.CreateApplicationBuilder获得完整的 DI 容器与生命周期管理,而不是手写裸McpServer;
  2. 日志一律写 stderr:LogToStandardErrorThreshold = LogLevel.Trace。原因是 STDIO 传输下stdout 是 JSON-RPC 通信信道,任何Console.WriteLine、默认控制台日志输出或第三方库启动横幅都会污染协议流,直接导致客户端解析失败断开连接。这是仓库内 transport-stdio.md 标注的“STDIO 头号坑”,Agent 也把“诊断 stdio 传输问题”列为核心专长之一;
  3. 用.WithToolsFromAssembly()自动发现当前程序集中所有标注[McpServerToolType]的类;
  4. 原语即方法:工具、提示词、资源都是普通 C# 方法,SDK 依据方法签名与[Description]自动生成 JSON Schema,参数从 JSON-RPC 绑定。

技能库中“30 秒心智模型”补充了一个可选维度:提示词与资源也可以按需显式注册WithPrompts<MyPrompts>()/WithResources<MyResources>()。需要给服务器自定义身份时,可在AddMcpServer(options => ...)中设置ServerInfo(Name/Version/Title)。

四、三类原语的专家级规范

Agent 文档把最佳实践拆成 Tools、Prompts、Resources 三组,每组都有明确的属性与返回类型约束。以下逐类展开,并与仓库技能库的实现细节对齐。

4.1 Tools:函数即工具,名字用 snake_case

核心规则(Agent 原文 + 指令文档):

  • 类上标[McpServerToolType](方法上只有[McpServerTool]是不会被WithToolsFromAssembly发现的);
  • 方法上标[McpServerTool(Name = "tool_name")],命名遵循 snake_case;技能库补充说明工具显示名默认就是方法名(PascalCase 不会自动转 snake_case),需要明确改名时使用Name属性;
  • 把相关工具按类分组(Agent 举例如ComponentListTools、ComponentDetailTools,即列表类与详情类各一组);
  • 返回简单类型(string)或可 JSON 序列化的对象;技能库进一步列出返回类型与“LLM 看到什么”的对应表:
    • string/int/bool等 → 单文本内容块;
    • DTO(record/class)→ 文本块中的 JSON + 面向新客户端的structuredContent;
    • IEnumerable<DTO>→ JSON 数组;
    • ContentBlock系列(ImageContentBlock/AudioContentBlock/EmbeddedResourceBlock)→ 原样透传的内容块;
    • CallToolResult→ 完全掌控Content/StructuredContent/IsError;
  • 所有工具与参数都加[Description](来自System.ComponentModel),这是 LLM 决定“何时调用、如何传参”的唯一依据,描述含糊是工具不被使用的第一大原因;
  • 输出格式化为 Markdown,并在结果里附带使用提示(Agent 原话示例:“Use GetComponentDetails(componentName) for more information”),引导 LLM 进入下一步调用。

一个可复制的“标准工具”形状(指令文档原例):

[McpServerToolType] public static class MyTools { [McpServerTool, Description("Description of what the tool does")] public static string ToolName( [Description("Parameter description")] string param) => $"Result: {param}"; }

async、CancellationToken 与依赖注入(指令文档原例):工具方法可以是同步或异步的,SDK 会识别并“特判”下列参数类型——它们不会进入工具 Schema,而是从环境注入:

[McpServerTool, Description("Fetches data from a URL")] public static async Task<string> FetchData( HttpClient httpClient, // 从 DI 注入的服务 [Description("The URL to fetch")] string url, // 真正的 JSON-RPC 参数 CancellationToken cancellationToken) => // 由 SDK 从请求传播 await httpClient.GetStringAsync(url, cancellationToken);

技能库 tool-primitive.md 补充了完整的“被特判参数”清单:IMcpServer/McpServer、CancellationToken、RequestContext<CallToolRequestParams>、IServiceProvider,以及任何 DI 可解析且不被识别为原始负载的服务。其余参数一律作为 JSON-RPC 参数进入 Schema。

4.2 Prompts:一个提示词类一个提示词,返回 ChatMessage

Agent 对 Prompts 的约束与 Tools 明显不同——提示词是给“用户”而不是给 LLM 从菜单挑选触发的,相当于聊天客户端里的“斜杠命令”。核心规则:

  • 类上标[McpServerPromptType],方法标[McpServerPrompt(Name = "prompt_name")](snake_case);
  • 每个提示词单独一个类,便于组织与维护;
  • 方法返回ChatMessage而非string,以符合 MCP 协议;代表用户指令的提示用ChatRole.User。技能库的对应表给出四个层级:ChatMessage(单条)、IEnumerable<ChatMessage>(多轮会话种子)、PromptMessage/IEnumerable<PromptMessage>(需要细粒度控制内容块时)、GetPromptResult(完全掌控Messages与Description);
  • 提示词内容中应包含充足上下文(组件细节、示例、准则),Agent 建议用StringBuilder拼装复杂多段提示;
  • 用[Description]说清“该提示生成什么、何时使用”;
  • 可选参数带默认值,实现灵活的提示定制。

一个同时覆盖“多消息 + 结构化”的示例(技能库 prompt-primitive.md 原例):

[McpServerPromptType] public class CodePrompts { [McpServerPrompt, Description("Generates a code review prompt.")] public static IEnumerable<ChatMessage> CodeReview( [Description("The programming language")] string language, [Description("The code to review")] string code) => [ new(ChatRole.User, $"Please review the following {language} code:\n\n```{language}\n{code}\n```"), new(ChatRole.Assistant, "I'll review the code for correctness, style, and potential improvements.") ]; }

注册时别忘了显式接线——新增了[McpServerPromptType]类却漏掉.WithPrompts<...>()(或.WithPromptsFromAssembly())是技能库“金科玉律”第 7 条点名的常见遗漏,会让提示词“隐形”。

4.3 Resources:用 UriTemplate 暴露静态与动态内容

Resources 是通过 URI 寻址、由服务器暴露的“事物”,宿主列出后由用户挑选附加到对话。Agent 给出的[McpServerResource]四个关键属性要逐一对齐到实现上:

  • UriTemplate:带可选参数的 URI 模式,如"myapp://component/{name}";
  • Name:资源唯一标识;
  • Title:人类可读标题;
  • MimeType:内容类型(通常"text/markdown"或"application/json")。

两种形态(resource-primitive.md):

  • 静态资源用固定 URI,适合单例配置,如"projectname://guides";
  • 动态资源模板用带占位符的 URI,如"projectname://component/{name}",占位符按名字映射到方法参数。
[McpServerResourceType] public class DocumentResources { [McpServerResource( UriTemplate = "docs://articles/{id}", Name = "Article", MimeType = "text/markdown")] [Description("Returns an article by its ID.")] public static ResourceContents GetArticle(string id) { string? content = LoadArticle(id); if (content is null) throw new McpException($"Article not found: {id}"); return new TextResourceContents { Uri = $"docs://articles/{id}", MimeType = "text/markdown", Text = content }; } }

返回类型与工具同样有规则:string会被包装为TextResourceContents,byte[]包装为BlobResourceContents,也可以直接返回TextResourceContents/BlobResourceContents(二进制用BlobResourceContents.FromBytes(...))或多段IEnumerable<ResourceContents>。把相关资源分组到同一类(Agent 例:GuideResources、ComponentResources),文档类资源返回格式化 Markdown,并包含指向相关资源的导航提示与链接。资源不存在时优雅地报错(如上面的McpException),而不是返回空串。

安全要点:若资源直接暴露文件系统,绝不能信任 URI 原样拼接路径——必须先做路径规整再校验前缀,防止路径穿越(技能库中的file://workspace/{*relativePath}示例在组合路径后执行Path.GetFullPath+StartsWith(root)双重防护)。

4.4 三类原语的选择边界

仓库技能库给出一组极易混淆的判定准则:

  • Tool:由 LLM 主动调用以“做某事”(取数或改状态),适合get_weather、create_issue;
  • Prompt:由用户从菜单触发并自行提供参数,输出是消息而非数据,适合/summarize、/code-review、/draft-email;
  • Resource:为对话附加只读、可寻址、可枚举的上下文(文档、配置、Schema),宿主决定何时加载;
  • 如果既想被搜索又想被引用,就同时暴露两者:search_articles工具负责返回 URI 列表,docs://articles/{id}资源模板负责按 URI 取内容。

五、错误处理与协议级异常

Agent 文档对错误处理有非常具体的协议要求,指令文档将其落成两个规则:

  1. 参数校验失败:抛McpProtocolException+McpErrorCode.InvalidParams;
  2. 工具内部业务失败:抛普通异常即可,SDK 会捕获并返回IsError = true的CallToolResult,消息进入文本块——LLM 能读到并据此纠正后重试。

技能库 tool-primitive.md 给出了这两条规则的判定启发式:如果 LLM 应该换参数重试,抛普通异常;如果调用本身在协议层就是畸形的(LLM 无法修复),才抛McpProtocolException。

[McpServerTool, Description("…")] public static string Process(string input) { if (string.IsNullOrWhiteSpace(input)) throw new McpProtocolException("Missing required input", McpErrorCode.InvalidParams); return $"Processed: {input}"; }

一个隐蔽的反模式也被技能库单独点出:把失败当作字符串"failed"返回会被 SDK 当作一次成功调用。隐藏错误必须走“抛异常”或显式构造CallToolResult { IsError = true }两条路。

六、需要“反打回客户端”的场景:Sampling 与高级模式

工具若需要调用客户端的 LLM(而非自己内置模型),Agent 与指令文档给出的标准做法是McpServer.AsSamplingChatClient()——把服务器自身转成一个采样聊天客户端向远端发起请求:

[McpServerTool, Description("Analyzes content using the client's LLM")] public static async Task<string> Analyze( McpServer server, [Description("Content to analyze")] string content, CancellationToken cancellationToken) { var messages = new ChatMessage[] { new(ChatRole.User, $"Analyze this: {content}") }; return await server.AsSamplingChatClient() .GetResponseAsync(messages, cancellationToken: cancellationToken); }

需要注意版本语境:面向 2026-07-28 规范修订的 dotnet-mcp-builder 技能 明确指出 sampling、roots 与 MCP 信道日志已被规范弃用,SDK 2.x 将其标为[Obsolete](编译警告MCP9005),新设计应优先使用多轮往返的InputRequiredException(input_required)模式与ILogger日志,仅在与低版本客户端互通时按文档化过渡手段抑制该警告。这也是“专家 Agent 配置”与“持续演进的新版技能库”共存于 awesome-copilot 的价值:Agent 给出心智框架,技能库给出针对最新规范版本的勘误。

七、生产化增强:过滤中间件、进度通知与服务指令

在工具/提示词/资源之外,server-features.md 展示了 Agent 所称“Best Practices:安全、错误处理、日志、测试、可维护性”在实现层的落地手段:

  • 调用过滤器(Filters):类似 ASP.NET Core 中间件,用.WithCallToolFilter(async (ctx, next) => ...)包裹工具调用,统一做鉴权、遥测、限流与审计——例如用Stopwatch记录每个工具的耗时日志;
  • 进度通知:长耗时工具中按RequestContext<CallToolRequestParams>里的Meta.ProgressToken判定客户端是否在监听,是则通过SendNotificationAsync发送ProgressNotification更新进度条;
  • 服务指令:在AddMcpServer(options => ...)中设置ServerInstructions,作为初始化时下发给宿主、可能被拼进系统提示的说明;技能库提醒它“每个 token 都由用户买单”,务必精简;
  • 能力宣告裁剪:通过options.Capabilities中的Tools/Prompts/Resources显式决定宣告哪些能力(默认全部宣告已注册项)。

八、调试与测试:Agent 的“Common Scenarios”在仓库中的答案

Agent 文档把“诊断 stdio 传输问题、序列化问题、协议错误”列为强项。仓库技能库 testing.md 给出了与该心智完全对应的三条排障路径:

1. MCP Inspector 交互调试:启动服务器并连接后,可逐一手动调用工具、查看资源、检查原始 JSON-RPC 帧,特别适合在没有真实 LLM时验证工具描述是否清晰。

2. 进程内集成测试(推荐):用Pipe+StreamServerTransport/StreamClientTransport把真实的服务器和真实的客户端在同一进程内接起来,直接对“客户端可见的暴露行为”做断言——不依赖子进程与网络,任何能跑dotnet test的环境(CI 同样适用)都能执行:

[Fact] public async Task GetWeather_returns_text() { var clientToServer = new Pipe(); var serverToClient = new Pipe(); await using var server = McpServer.Create( new StreamServerTransport( clientToServer.Reader.AsStream(), serverToClient.Writer.AsStream()), new McpServerOptions { ToolCollection = [McpServerTool.Create( (string city) => $"{city}: 18°C", new() { Name = "GetWeather" })] }); var serverTask = server.RunAsync(); await using var client = await McpClient.CreateAsync( new StreamClientTransport( clientToServer.Writer.AsStream(), serverToClient.Reader.AsStream())); var tools = await client.ListToolsAsync(); var tool = tools.Single(t => t.Name == "GetWeather"); var result = await tool.CallAsync(new Dictionary<string, object?> { ["city"] = "Brussels" }); Assert.False(result.IsError); Assert.Equal("Brussels: 18°C", result.Content.OfType<TextContentBlock>().Single().Text); }

3. 纯逻辑单测:[McpServerTool]在 MCP 装配之外不改变方法行为——工具方法只是普通方法,可直接单测,无需任何 MCP 设施。

高频问题对照表(技能库 + 指令文档共同覆盖 Agent 所说的“stdio 传输问题 / 序列化错误 / 协议错误”三类故障):

症状根因与对策
STDIO 服务器无响应/连接被断开有东西写入了 stdout(Console.WriteLine、默认日志 sink、库启动横幅);把所有日志切到 stderr
工具不出现在客户端类上缺[McpServerToolType],或漏了.WithToolsFromAssembly()/.WithTools<T>()注册
参数绑定失败方法参数名必须与 JSON-RPCarguments的键一致;复杂类型走System.Text.Json绑定
HTTP 根路径 404app.MapMcp()挂在根,app.MapMcp("/mcp")则路径是http://host/mcp
LLM 总是误用工具用 Inspector 看 LLM 视角的 Schema 与描述——多数情况是[Description]写得太含糊
升级 2.x 后出现MCP9005警告代码用了已被弃用的 sampling/roots/logging API,规划迁移而非永久压制

九、输出风格与协作预期:Agent 赋予你的“交付标准”

Agent 文档的 “Response Style” 定义了 MCP 服务器领域里“高质量交付”的检查清单,既是读者判断该 Agent 输出质量的标尺,也可反过来当作你自己编码时的验收标准:

  • 给出可直接复制运行的完整代码示例,含必要的using与命名空间声明;
  • 对复杂或不直观的逻辑加内联注释,解释“为什么这样设计”;
  • 主动指出潜在陷阱与常见错误(例如 stdout 污染、漏注册、参数命名不一致);
  • 相关时提供改进建议与替代方案、常见问题的排障提示、规范的缩进与留白;
  • 安全始终在场:凡是访问文件、网络或系统资源的工具,都要先想安全影响;
  • 用对 LLM 友好的描述,把“何时用、怎么用、返回什么”讲清楚。

十、总结:在 awesome-copilot 中快速上手 C# MCP Server

围绕agents/csharp-mcp-expert.agent.md这条专家 Agent,awesome-copilot 还配套了可直接引用的三份关联资产,建议按序使用:

  1. csharp-mcp-expert.agent.md:角色与最佳实践总纲,适合在编码前建立心智模型;
  2. csharp-mcp-server.instructions.md:针对.cs/.csproj的规则清单,含基础服务器、简单工具、DI 工具、Sampling 四个可直接复制的 Common Patterns;
  3. dotnet-mcp-builder 技能库 及其 references 分册:面向最新规范/版本的勘误与逐项参考(传输、工具、提示词、资源、测试等)。

把这套资产配合使用,你既能得到 Agent 文档所倡导的“DI 优先、属性驱动、[Description]全覆盖、stderr 日志、McpProtocolException协议错误”的开发范式,也能用技能库中的最新版本约束与测试脚手架把服务器打磨到可交付状态——这正是该仓库“instructions、agents、skills 一体”设计意图的直接体现。

【免费下载链接】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),仅供参考

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

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

立即咨询