1. 为什么 .NET 开发者现在要动手写一个 MCP ServerMCP 协议Model Context Protocol是 Anthropic 提出的开放协议你可以把它理解成 AI 世界的 USB-C 接口大模型是主机MCP Server 就是插上去的摄像头、麦克风、移动硬盘。它标准化了模型与外部工具、数据源之间的调用方式让同一个工具能被 Claude Desktop、Cursor、Cline 等不同客户端复用。对 .NET 开发者来说这意味着你写的 C# 方法可以零改动地暴露给 AI 调用而不需要为每个客户端单独适配。我这次的目标很明确用官方 csharp-sdk 从零搭一个可调试的最小 MCP Server跑通 stdio 传输再用 TaoToken 统一 Key 通道验证模型侧调用。为什么强调可调试因为 MCP Server 默认走标准输入输出日志和 Console.WriteLine 会污染 JSON-RPC 通道第一次跑很容易卡在客户端连上了但工具列表是空的这种问题上。所以我会把工程结构、依赖清单、本地联调、报错排查全部拆开讲让你照着敲就能跑起来。适合谁看有 .NET 8 基础、用过依赖注入、想给 AI 客户端接自己业务工具的开发者。不需要你懂 JSON-RPC 细节SDK 会处理。整篇按问题场景 → 前置准备 → 可复制配置 → 验证请求 → 错排查 → 通道统一推进代码都能直接粘。2. 前置准备csharp-sdk 工程结构与 TaoToken 统一 Key 通道先说 SDK 选型。.NET 生态里 MCP 实现有几个官方 csharp-sdk微软维护已发 preview、MCPSharp、mcpdotnet已归档、ModelContextProtocol.NET。新项目直接上官方 csharp-sdk归档项目别碰社区库等官方稳定后再评估。我实测下来官方 SDK 的AddMcpServer().WithStdioServerTransport().WithToolsFromAssembly()三行就能起服务工具注册靠特性学习成本最低。工程结构建议这样分别把所有工具塞一个文件McpDemo.Server/ ├── Program.cs // 宿主与 DI 配置 ├── Tools/ │ ├── EchoTool.cs // 最小验证工具 │ └── OrderTool.cs // 业务工具示例 ├── Services/ │ └── OrderService.cs // 业务逻辑注入进工具 ├── appsettings.json └── McpDemo.Server.csproj依赖清单csproj 关键部分Project SdkMicrosoft.NET.Sdk PropertyGroup OutputTypeExe/OutputType TargetFrameworknet8.0/TargetFramework Nullableenable/Nullable /PropertyGroup ItemGroup PackageReference IncludeModelContextProtocol Version0.1.0-preview* / PackageReference IncludeMicrosoft.Extensions.Hosting Version8.0.* / /ItemGroup /Project然后是模型侧通道。MCP Server 本身不调模型但你要验证工具能被模型正确调用就需要一个能走 Anthropic 兼容接口的客户端。TaoToken 在这里的作用是统一 Key 和 API 通道你不用在代码里散落多个厂商的 KeyBase URL 指向https://taotoken.net/api模型 ID 用 Claude 系列客户端 SDK 照常初始化即可。这样 MCP 工具列表和模型调用走同一套凭证调试时少一层变量。去控制台建 Keyhttps://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 生成后存到用户机密别写进代码dotnet user-secrets init dotnet user-secrets set TAOTOKEN_API_KEY sk-你的key模型 ID 和可用列表在文档里查https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。如果你后面要做长期编码 Agent可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。3. 可复制配置Program.cs、工具注册与客户端 settings 片段先写宿主。stdio 模式下任何写到 stdout 的日志都会破坏协议所以日志必须重定向到 stderrusing Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; var builder Host.CreateApplicationBuilder(args); // 关键日志走 stderrstdout 留给 JSON-RPC builder.Logging.AddConsole(o o.LogToStandardErrorThreshold LogLevel.Trace); builder.Services.AddSingletonOrderService(); builder.Services .AddMcpServer() .WithStdioServerTransport() .WithToolsFromAssembly(); await builder.Build().RunAsync();工具类用特性注册。[McpServerToolType]标类[McpServerTool]标方法[Description]给模型看描述——描述写不清楚模型就不会选你的工具using System.ComponentModel; using ModelContextProtocol.Server; [McpServerToolType] public static class EchoTool { [McpServerTool, Description(把输入消息原样回显用于连通性验证)] public static string Echo( [Description(要回显的文本)] string message) $hello {message}; }带依赖注入的业务工具构造函数注入服务方法参数由模型填[McpServerToolType] public class OrderTool(OrderService svc) { [McpServerTool, Description(按订单号查询订单状态)] public async Taskstring GetOrderStatus( [Description(订单号例如 A1001)] string orderId) { var order await svc.FindAsync(orderId); return order is null ? 未找到该订单 : $状态{order.Status}; } }客户端侧如果你用 Cline 或 Claude Desktop 这类支持 MCP 的客户端配置片段长这样以 stdio 为例{ mcpServers: { mcp-demo: { command: dotnet, args: [run, --project, /abs/path/McpDemo.Server], env: { TAOTOKEN_API_KEY: sk-你的key } } } }注意command和args必须是绝对路径或可执行命令相对路径在客户端启动时工作目录不同会找不到。如果你用 Cline MCP 面板字段名一致粘贴即可。三件套记牢Base URLhttps://taotoken.net/api、Key 走环境变量、Model ID 填 Claude 系列。4. 验证请求本地联调与成功结果长什么样先单独跑 Server确认它能启动且不往 stdout 吐日志dotnet build dotnet run --project McpDemo.Server正常表现是进程挂起等待输入终端没有多余输出。如果看到一堆info:日志说明日志没重定向回去检查LogToStandardErrorThreshold。接着用官方 SDK 写个最小客户端做端到端验证比直接塞进 GUI 客户端好排查using ModelContextProtocol.Client; await using var client await McpClientFactory.CreateAsync(new() { Id demo-server, Name Demo Server, TransportType TransportTypes.StdIo, TransportOptions new() { [command] dotnet, [arguments] run --project /abs/path/McpDemo.Server } }); var tools await client.ListToolsAsync(); foreach (var t in tools) Console.WriteLine($发现工具: {t.Name}); var result await client.CallToolAsync( echo, new Dictionarystring, object? { [message] MCP! }, CancellationToken.None); Console.WriteLine(result.Content.First(c c.Type text).Text);成功输出应该是发现工具: echo 发现工具: get_order_status hello MCP!工具列表里能看到两个工具说明WithToolsFromAssembly()扫描成功hello MCP!说明调用链路通了。这一步过了再把它挂到 Cline 或 Claude Desktop 里模型就能在对话中自动选择get_order_status。模型侧验证时客户端初始化指向 TaoTokenvar anthropic new AnthropicClient( new APIAuthentication(Environment.GetEnvironmentVariable(TAOTOKEN_API_KEY))) .Messages.AsBuilder().UseFunctionInvocation().Build(); var options new ChatOptions { MaxOutputTokens 1000, ModelId claude-3-5-sonnet-latest, Tools [.. tools] };Tools直接吃ListToolsAsync()的返回值模型就能看到你的 C# 工具。想先在网页里确认模型和 Key 是否正常用模型对话页试一句https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth401 Unauthorized九成是 Key 没读到。检查dotnet user-secrets list是否有值环境变量名是否和代码里Environment.GetEnvironmentVariable一致。如果客户端配置里写了env确认 JSON 没有多余逗号。TaoToken 的 Key 在控制台可重新生成https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。local proxy failed / connection refused客户端启动 Server 时命令找不到。command用dotnet时args里的项目路径必须是绝对路径Windows 下路径反斜杠要转义或改用正斜杠。先在终端手动执行一遍同样的命令能跑通再填进配置。reading choices / unexpected end of JSON input模型返回体解析失败通常是 Base URL 写错或少了/api。确认是https://taotoken.net/api不要带尾部斜杠也不要把对话页地址填进去。另外检查请求是否被中间层改写。OAuth / authentication_error客户端 SDK 版本和接口不匹配或者把 Anthropic 原生鉴权头和 TaoToken 的 Key 混用。统一用APIAuthentication传 Key别手动塞x-api-key和Authorization两个头。工具列表为空Server 起来了但ListToolsAsync返回 0。检查工具类是否有[McpServerToolType]、方法是否public static或可被 DI 构造、程序集是否就是WithToolsFromAssembly()所在程序集。日志重定向到 stderr 后用2 server.log把错误捞出来看。调用超时工具方法里有阻塞 IO 或死循环。MCP 调用有超时业务逻辑尽量 async长任务拆成轮询式工具。6. 把 Key 和通道收口到一处继续扩展你的 MCP 工具集跑通最小服务后下一步是扩展。我的做法是把所有外部依赖数据库、内部 API、文件系统都包成 Service 注入进工具类工具方法只做参数校验和结果格式化业务逻辑留在 Service 里这样单测好写MCP 层也薄。工具描述要像写给同事看的 API 文档模型选错工具基本都是描述太含糊。Key 管理上别在每个项目里复制粘贴。统一走 TaoToken 的 API 通道Base URL 固定https://taotoken.net/apiKey 从环境变量或用户机密读模型 ID 集中放 appsettings。这样你换模型、加工具、接新客户端时只改一处配置。接入细节和参数说明看文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。如果你要接 Claude Code 这类编码 Agent 做长期开发Anthropic 兼容接入方式在https://taotoken.net/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。需要跑批量任务或 Agent 工作流Coding Plan 更合适https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。最后留个实用习惯每次改完工具先跑一遍最小客户端脚本确认ListToolsAsync和CallToolAsync都正常再挂 GUI 客户端。GUI 的报错信息往往被吞掉命令行验证能省你半小时。工具注册、日志重定向、绝对路径这三件事做对MCP Server 基本不会出玄学问题。
阅读完成 · 觉得有帮助?