使用MCP CSDK开发MCP Server + Client:从零开始构建智能工具链(TaoToken统一Key接入版) 1. 为什么我要用 MCP CSDK 搭一条最小智能工具链MCP 全称 Model Context Protocol说白了就是给大模型和外部工具之间定的一套“插座标准”。以前你写一个查天气、读数据库、跑脚本的能力每换一个模型客户端就得重写一遍对接逻辑现在只要按 MCP 协议把工具暴露成 Server任何支持 MCP 的 Client 都能直接调用。它适合谁适合手里有一堆零散脚本、想让模型自动编排调用的后端同学也适合想给自家产品加“工具调用”能力但不想被某一家模型绑死的团队。我这次要做的是一条最小闭环用 C# 的 MCP CSDK 写一个 Server里面挂一个返回当前时间的工具再写一个 Client通过 SSE 连上 Server把工具列表拉出来并真正调用一次。关键点在于——工具调用背后需要模型能力来决策“该调哪个工具、参数怎么填”这一步我统一走 TaoToken 的 Key 和 API 通道这样 Server、Client、模型三者的凭证收敛成一套环境变量不用在多个平台之间来回切换。整条链路跑通后你会得到三个可复用的东西一个能自动加载工具类的 Server 骨架、一个能发起工具调用的 Client 骨架、以及一套把模型请求接到工具链上的统一 Key 写法。下面从环境准备开始每一步都给可复制的代码和配置。2. TaoToken 前置准备统一 Key 与模型通道怎么接在写代码之前先把模型这一侧的入口理清楚。MCP 本身只负责“工具怎么描述、怎么调用”它不负责“模型怎么选工具”。真正让模型决定调用哪个工具的那次请求需要一个兼容 OpenAI 协议风格的模型接口。TaoToken 提供的正是这个统一入口一个 Base URL 加一个 Key就能访问多种模型省去为每个模型单独配一套凭证的麻烦。你需要准备两样东西。第一是 API Key去控制台创建https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_csdk_consoleutm_campaignrewrite 创建后复制那串以 sk- 开头的字符串。第二是确认 Base URL统一用 https://taotoken.net/api 注意这个地址后面不加任何多余路径SDK 会自己拼 /v1/chat/completions 这类端点。拿到之后不要硬编码进代码用环境变量管理。Windows 下在 PowerShell 里这样设$env:TAOTOKEN_API_KEYsk-你的Key $env:TAOTOKEN_BASE_URLhttps://taotoken.net/apimacOS 或 Linux 下写进 shell 配置export TAOTOKEN_API_KEYsk-你的Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api模型 ID 这块我实测下来用 claude-3-5-sonnet 这类通用对话模型做工具决策比较稳你也可以在模型对话页先试一下手感和可用性https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_csdk_modelsutm_campaignrewrite 。把模型 ID 也放进环境变量命名成 TAOTOKEN_MODEL_ID后面 Client 里读它。注意Key 只放环境变量或密钥管理服务别提交到 Git。团队协作时每个人用自己的 KeyBase URL 和模型 ID 可以共享。这一步做完你手里就有三件套Base URL、Key、Model ID。后面 Server 和 Client 都从环境变量读不出现明文。3. 可复制配置Server 与 Client 的 CSDK 落地片段先建工程。需要 .NET 8.0 及以上我用的 8.0。命令行建两个项目一个 Server 一个 Clientdotnet new webapi -n Edt.McpServer.WebHost dotnet new console -n Edt.McpClient进 Server 项目装 CSDK 包cd Edt.McpServer.WebHost dotnet add package ModelContextProtocol dotnet add package ModelContextProtocol.AspNetCoreServer 的 Program.cs 核心就几行重点是 WithToolsFromAssembly 自动扫描工具类MapMcp 暴露 SSE 端点var builder WebApplication.CreateBuilder(args); builder.Services.AddMcpServer().WithToolsFromAssembly(); var app builder.Build(); app.UseHttpsRedirection(); app.MapGet(/, () Hello MCP Server!); app.MapMcp(); app.Run();工具类放在 Tools 目录实现 ITool 接口。下面这个 TimeTool 返回当前时间注意返回结构要符合 MCP 的 ToolResponse 格式public class TimeTool : ITool { public async TaskToolResponse ExecuteAsync(ToolRequest request) { var now DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss); return new ToolResponse { Content new[] { new TextContent { Text now } } }; } }Client 这边连接参数从环境变量读传输类型用 SSELocation 指向 Server 的 /sse 端点var baseUrl Environment.GetEnvironmentVariable(TAOTOKEN_BASE_URL); var apiKey Environment.GetEnvironmentVariable(TAOTOKEN_API_KEY); var modelId Environment.GetEnvironmentVariable(TAOTOKEN_MODEL_ID); await using var mcpClient await McpClientFactory.CreateAsync(new McpClientOptions { Id time-client, Name Time MCP Client, TransportType TransportTypes.Sse, Location https://localhost:8443/sse });如果你用 Cline 或 Claude Code 这类已经支持 MCP 的客户端配置写法是 JSON把 Server 命令和统一 Key 一起塞进去。以 Cline 的 MCP 配置为例路径通常在客户端的 mcp_settings.json{ mcpServers: { edt-time-server: { command: dotnet, args: [run, --project, ./Edt.McpServer.WebHost], env: { TAOTOKEN_API_KEY: sk-你的Key, TAOTOKEN_BASE_URL: https://taotoken.net/api, TAOTOKEN_MODEL_ID: claude-3-5-sonnet } } } }Codex 用户如果走 auth.json把凭证写进对应字段Base URL 填 https://taotoken.net/api Key 填你的 sk- 串模型 ID 单独一项。三件套缺一不可少一个就会在工具决策阶段报鉴权或模型找不到的错。4. 验证请求一次端到端工具调用跑通配置写完先启动 Server。在 Server 目录执行dotnet run看到监听 8443 并且根路径返回 Hello MCP Server 就说明起来了。浏览器访问 https://localhost:8443 确认一下自签证书会提示不安全开发环境点继续即可。然后跑 Client。Client 里先拉工具列表再调用 TimeToolvar tools await mcpClient.ListToolsAsync(); foreach (var t in tools.Tools) Console.WriteLine($发现工具: {t.Name}); var result await mcpClient.CallToolAsync(TimeTool, new Dictionarystring, object { { command, get_current_time } }); var text result.Content.First(c c.Type text).Text; Console.WriteLine($工具返回: {text});执行dotnet run控制台应该先打印“发现工具: TimeTool”再打印一行当前时间类似“工具返回: 2025-01-15 14:32:07”。这一步成功意味着 Server 的工具注册、SSE 传输、Client 的调用解析全部打通。接下来把模型接进来做决策。在 Client 里加一段请求把工具列表作为上下文发给模型让它判断该不该调 TimeTool。请求走统一通道using var http new HttpClient(); http.DefaultRequestHeaders.Authorization new AuthenticationHeaderValue(Bearer, apiKey); var payload new { model modelId, messages new[] { new { role user, content 现在几点了如果需要工具请说明调用哪个。 } }, tools tools.Tools.Select(t new { type function, function new { name t.Name, description t.Description } }) }; var resp await http.PostAsJsonAsync(${baseUrl}/v1/chat/completions, payload); var body await resp.Content.ReadAsStringAsync(); Console.WriteLine(body);返回里如果出现 tool_calls 字段并指向 TimeTool说明模型已经通过统一 Key 正确识别了工具并给出调用意图。到这一步最小智能工具链就闭环了模型决策、Client 转发、Server 执行、结果回传。5. 本篇常见错排查401、local proxy failed 与 reading choices跑这条链路最容易卡在几个固定报错上我按实际遇到的顺序列一下。第一个是 401 Unauthorized。多数情况是环境变量没生效或者 Key 复制时带了空格。先在终端echo $TAOTOKEN_API_KEY确认值存在且以 sk- 开头。如果是在 Cline 的 mcp_settings.json 里配的注意 JSON 里不能有尾随逗号env 字段的 Key 名要和代码里读的完全一致。还有一种情况是 Base URL 写成了带 /v1 的地址导致 SDK 拼出 /v1/v1/chat/completions也会鉴权失败统一用 https://taotoken.net/api 即可。第二个是 local proxy failed。这个报错通常出现在 Client 连 Server 的阶段不是模型侧的问题。检查 Server 是否真的在 8443 监听netstat -ano | findstr 8443看一下。如果端口被占改 appsettings.json 里的端口同时把 Client 的 Location 同步改掉。另外 SSE 要求 HTTPS用 http 连会直接失败。第三个是 reading choices 相关报错比如 “error reading choices field”。这是模型返回体解析失败常见原因是模型 ID 填错或者请求里 tools 字段格式不符合预期。先确认 TAOTOKEN_MODEL_ID 是有效值再把 tools 数组里每个元素的 type 固定为 functionfunction 下必须有 name。如果返回体里 choices 为空多半是模型没被正确路由回模型对话页确认该模型可用。第四个是 OAuth 相关提示。有些客户端会尝试走 OAuth 流程但统一 Key 模式用的是 Bearer 鉴权不需要 OAuth。遇到就检查客户端配置里有没有多余的 auth 类型声明删掉只保留 Key。提示排障时把 Client 的日志级别调高把请求 URL 和返回状态码打出来比盲猜快得多。接入细节可以对照文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_csdk_docutm_campaignrewrite6. 把工具链用起来Key 管理与长期编码场景链路跑通只是起点。真正要长期用得把 Key 和模型选择管起来。我自己的做法是开发机用环境变量CI 或服务器用密钥管理服务注入绝不写进仓库。团队里每个人用自己的 Key配额和调用记录分开出问题好定位。如果你打算把这条工具链用在日常编码或 Agent 场景比如让模型自动调你的构建脚本、查日志、跑测试那调用量会上来按量计费不如包月划算。这种长期编码场景可以看下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentmcp_csdk_codingplanutm_campaignrewrite 它更适合高频工具调用。另外工具类会越加越多。WithToolsFromAssembly 的好处是新增工具只要放进 Tools 目录并实现 ITool重启即生效不用改 Program.cs。我建议给每个工具写清楚 Description因为模型是靠这个描述来决定调不调的描述含糊会导致该调不调、不该调乱调。工具参数也尽量用强类型减少模型填错格式的概率。最后一个小技巧Client 里把 ListToolsAsync 的结果缓存一下别每次请求都拉一遍工具列表工具集稳定时能省不少往返。等你要接更多模型或换模型时只改 TAOTOKEN_MODEL_ID 一个变量Server 和 Client 代码都不用动这就是统一 Key 通道带来的好处。