news 2026/9/28 7:30:57

C#实现自己的MCP Client:从零构建可配置的TaoToken接入骨架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C#实现自己的MCP Client:从零构建可配置的TaoToken接入骨架

1. 为什么我要自己写一个 C# 版 MCP Client

市面上现成的 MCP Client 不少,Claude Desktop、Cursor、各种开源 GUI 都能连 MCP Server,日常查资料、跑工具确实够用。但真到了企业项目里,问题就来了:客户端要嵌进自己的 .NET 服务里,要统一走公司内部的 API 通道,要能读 appsettings.json 动态切换模型和端点,还要把工具调用结果写进自己的日志和审计链路。这些需求,免费客户端一个都满足不了。

所以这篇不聊怎么装现成软件,而是用 C# 从零搭一个可配置的 MCP Client 骨架,把模型调用统一收敛到 TaoToken 的 API 通道上。你跟着做完,会得到一个能跑通 MCP 握手、能列出工具、能发一次真实模型请求的最小可用客户端。适合有 .NET 基础、想把自研客户端接进统一 API 通道的开发者。全程 .NET 8 + 官方 ModelContextProtocol SDK,配置全部外置,不写死任何 Key。

先说清楚 MCP 是什么:Model Context Protocol,一套让客户端和工具服务端对话的协议。Client 负责发起连接、列工具、调工具;Server 负责暴露工具。传输层常见两种,SSE 走 HTTP 长连接,Stdio 走本地进程管道。这篇先用 SSE 把链路跑通,Stdio 留到自定义 Server 那篇再展开。

2. 前置准备:TaoToken 统一 Key 与项目初始化

2.1 为什么模型通道要单独抽出来

MCP Client 本身只管协议握手和工具调度,它不负责“用哪个模型”。真正干活的是背后的 LLM。如果每个项目各自填一家厂商的 Key、各自处理不同的请求格式,维护成本会爆炸。TaoToken 在这里的角色就是统一入口:一个 Key、一个 BaseUrl,兼容主流模型调用格式,客户端侧只认这一套配置。这样你换模型、加模型,改的是配置文件,不是代码。

注册和拿 Key 的入口在这里,先拿到再往下走:

控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite

API 根地址统一用https://taotoken.net/api,注意这个不带任何跟踪参数,代码里就写这个。

2.2 新建控制台项目并装 SDK

打开终端,建一个 .NET 8 控制台项目:

dotnet new console -n McpClientDemo cd McpClientDemo dotnet add package ModelContextProtocol --prerelease

--prerelease必须加,因为官方 C# SDK 目前还是预览版(0.1.0-preview.x 系列)。装完可以在 csproj 里确认一下版本号。这个包提供了McpClientFactory、McpClient以及传输层相关类型,是后面所有代码的基础。

顺手把配置读取需要的包也加上,.NET 8 自带Microsoft.Extensions.Configuration系列,但 JSON 提供程序要显式引:

dotnet add package Microsoft.Extensions.Configuration.Json dotnet add package Microsoft.Extensions.Configuration.Binder

3. 可复制的 appsettings.json 配置骨架

3.1 配置文件长什么样

在项目根目录建appsettings.json,内容如下。这份骨架把 MCP Server 端点、TaoToken 通道、模型名全部外置,代码里不出现任何硬编码:

{ "Mcp": { "Transport": "Sse", "Endpoint": "https://your-mcp-server.example.com/sse", "ClientName": "McpClientDemo", "ClientVersion": "1.0.0" }, "TaoToken": { "BaseUrl": "https://taotoken.net/api", "ApiKey": "sk-替换成你自己的Key", "Model": "claude-3-5-sonnet", "TimeoutSeconds": 60 } }

几个字段说明一下。Mcp.Endpoint换成你实际要连的 MCP Server 地址,SSE 协议一般以/sse结尾。TaoToken.ApiKey从上面 API Keys 页面拿。Model填你要用的模型标识,具体支持哪些可以在模型对话页面试:

模型对话:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite

3.2 让配置文件参与编译输出

控制台项目默认不会把 appsettings.json 复制到输出目录,得在 csproj 里加一段:

<ItemGroup> <None Update="appsettings.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>

不加这段,运行时ConfigurationBuilder找不到文件,会直接抛异常。这是新手最容易踩的第一个坑。

3.3 定义强类型配置类

建一个AppConfig.cs,把配置映射成对象,避免到处GetSection字符串:

public class McpOptions { public string Transport { get; set; } = "Sse"; public string Endpoint { get; set; } = string.Empty; public string ClientName { get; set; } = "McpClientDemo"; public string ClientVersion { get; set; } = "1.0.0"; } public class TaoTokenOptions { public string BaseUrl { get; set; } = "https://taotoken.net/api"; public string ApiKey { get; set; } = string.Empty; public string Model { get; set; } = string.Empty; public int TimeoutSeconds { get; set; } = 60; }

这样后面注入的时候类型清晰,改配置也不会漏字段。

4. 从零实现 MCP Client 核心代码

4.1 加载配置并创建传输层

在Program.cs里先把配置读进来:

using Microsoft.Extensions.Configuration; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol.Transport; var config = new ConfigurationBuilder() .SetBasePath(AppContext.BaseDirectory) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .Build(); var mcpOptions = config.GetSection("Mcp").Get<McpOptions>()!; var taoOptions = config.GetSection("TaoToken").Get<TaoTokenOptions>()!; Console.WriteLine($"准备连接 MCP Server: {mcpOptions.Endpoint}");

注意SetBasePath(AppContext.BaseDirectory),指向输出目录,和前面 csproj 的复制设置对应。用reloadOnChange: true是为了后面改配置不用重启。

接着创建 SSE 传输实例:

var transport = new SseClientTransport(new SseClientTransportOptions { Endpoint = new Uri(mcpOptions.Endpoint), Name = mcpOptions.ClientName });

SseClientTransportOptions里Endpoint是必填的,Name会作为客户端标识发给 Server,方便服务端做区分。

4.2 建立连接并列出工具

用工厂方法创建客户端,这一步就是 MCP 握手:

var client = await McpClientFactory.CreateAsync(transport); Console.WriteLine("MCP 握手完成,连接已建立"); var tools = await client.ListToolsAsync(); Console.WriteLine($"发现 {tools.Count} 个工具:"); foreach (var tool in tools) { Console.WriteLine($" - {tool.Name}: {tool.Description}"); }

McpClientFactory.CreateAsync内部会完成协议版本协商、能力交换。如果这一步卡住或抛异常,八成是 Endpoint 不通或协议不匹配,排查方法见第 6 节。

4.3 把工具调用接到 TaoToken 通道

MCP Client 拿到工具列表后,真正的推理请求要发给模型。这里用 HttpClient 走 TaoToken 的统一通道,把工具描述作为上下文传进去:

using System.Net.Http.Headers; using System.Text; using System.Text.Json; var http = new HttpClient { BaseAddress = new Uri(taoOptions.BaseUrl), Timeout = TimeSpan.FromSeconds(taoOptions.TimeoutSeconds) }; http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", taoOptions.ApiKey); var toolSummary = string.Join("\n", tools.Select(t => $"- {t.Name}: {t.Description}")); var payload = new { model = taoOptions.Model, messages = new[] { new { role = "user", content = $"当前可用工具如下:\n{toolSummary}\n请用一句话说明你能帮我做什么。" } } }; var json = JsonSerializer.Serialize(payload); var response = await http.PostAsync( "/v1/chat/completions", new StringContent(json, Encoding.UTF8, "application/json")); response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); Console.WriteLine("模型返回:"); Console.WriteLine(body);

这段代码把 MCP 的工具发现结果和模型调用串起来了。BaseUrl用https://taotoken.net/api,路径拼/v1/chat/completions,认证走 Bearer。Key 从配置读,不写死在代码里。

5. 验证请求:一次完整的握手与调用

5.1 运行前检查清单

跑之前确认三件事:appsettings.json 里的Mcp.Endpoint是真实可达的 MCP Server;TaoToken.ApiKey已替换;csproj 里配置复制那段加上了。然后:

dotnet run

5.2 预期输出

正常的话,控制台会依次打印:

准备连接 MCP Server: https://your-mcp-server.example.com/sse MCP 握手完成,连接已建立 发现 3 个工具: - fetch: 抓取指定网页内容 - search: 执行关键词搜索 - calc: 执行数学计算 模型返回: {"id":"...","choices":[{"message":{"role":"assistant","content":"我可以帮你抓取网页、搜索信息并做计算。"}}]}

看到“MCP 握手完成”这行,说明协议层通了;看到模型返回内容,说明 TaoToken 通道也通了。这两步都过,最小可用客户端就算跑通。

5.3 用模型对话页面交叉验证

如果模型返回异常,先去模型对话页面用同样的模型名发一条消息,确认 Key 和模型本身没问题:

模型对话:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite

页面能正常回,说明问题在客户端代码;页面也报错,说明是 Key 或模型配置的问题。这个二分法能省很多排查时间。

6. 本篇常见错误排查

6.1 握手阶段超时或连接被拒

最常见的是Mcp.Endpoint写错。SSE 端点必须以/sse结尾,写成根路径会 404。另外确认网络能直连该地址,企业内网可能需要走内部网关。如果 Server 用的是 Stdio 协议,用 SSE 传输去连必然失败,得换StdioClientTransport。

6.2 配置文件读不到

报FileNotFoundException或配置全为默认值,检查两点:csproj 里有没有CopyToOutputDirectory;SetBasePath是不是指向了AppContext.BaseDirectory。用相对路径Directory.GetCurrentDirectory()在dotnet run和直接跑 exe 时行为不一致,容易出问题。

6.3 模型请求返回 401 或 403

Key 没替换、Key 前后有空格、或者Authorization头拼错都会导致。确认格式是Bearer sk-xxx,中间一个空格。另外 BaseUrl 别写成带路径的形式,https://taotoken.net/api后面代码里再拼/v1/chat/completions,两处别重复。

6.4 工具列表为空

握手成功但ListToolsAsync返回 0 个工具,说明 Server 端没注册工具,或者当前客户端权限不够。换一个已知有工具的 Server 端点验证,排除是客户端问题还是服务端问题。

6.5 长期编码场景的配置建议

如果你打算把这个骨架用在日常编码或 Agent 长任务里,频繁手动填 Key 不现实。可以了解下 Coding Plan,它面向的就是长期编码和 Agent 场景的稳定通道:

Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite

接入细节和参数说明都在文档里,遇到协议层问题先翻文档:

接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

6.6 一个容易忽略的坑:HttpClient 复用

上面示例里每次运行 new 一个 HttpClient,在控制台 demo 里没问题,但搬到常驻服务里必须用IHttpClientFactory或单例复用。频繁创建 HttpClient 会导致端口耗尽,这个坑在压测时才会暴露,提前注意。

7. 把骨架接进你的项目

到这里,一个可配置的 C# MCP Client 骨架就成型了:配置外置在 appsettings.json,传输层用 SSE,工具发现走官方 SDK,模型调用统一收敛到 TaoToken 通道。你可以在此基础上加工具调用循环、加日志、加重试,把它变成真正能用的客户端。

下一步建议先把 Stdio 传输也补上,这样本地工具和远程工具都能覆盖。自定义 MCP Server 的部分,等传输层两种都跑通后再动手会顺很多。代码里所有 Key 都从配置读,别图省事写死,这是能长期维护的前提。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 7:30:45

LITESTAR 4D室内篮球场照明设计:从照度标准到均匀度优化全流程

最近接了一个社区文体中心的室内篮球场照明方案。场地标准比赛尺寸25m15m&#xff0c;净高12m&#xff0c;业主要求不高&#xff0c;原话是“够亮就行”。但等我把第一版灯位排出来&#xff0c;用LITESTAR 4D一算&#xff0c;水平平均照度倒是轻轻松松超过400lx&#xff0c;均匀…

作者头像 李华
网站建设 2026/9/28 7:30:33

Django与Flask实战:驾校预约管理系统与考试组卷系统设计全解

我印象很深的是去一家驾校调研时看到的场景&#xff1a;前台小姑娘面前摆着一张写满备注的排班表&#xff0c;手机微信一直弹消息&#xff0c;电话、现场预约、短信三套渠道的信息全要靠手记&#xff0c;稍不留神就出现两个学员约了同一个教练同一个时段的情况。隔壁办公室里&a…

作者头像 李华
网站建设 2026/9/28 7:30:03

孤岛微电网分布式二次控制:从下垂控制到动态事件触发

去年做孤岛微电网仿真时&#xff0c;我被一台突然投切的负载折腾到半夜。光伏和储能组成的小型孤岛电网&#xff0c;五台分布式电源并联运行&#xff0c;负载从10kW跳到30kW&#xff0c;频率直接掉到49.7Hz附近&#xff0c;下垂控制拼命在出力分配上找平衡&#xff0c;但系统频…

作者头像 李华
网站建设 2026/9/28 7:29:28

Pytest回归测试实战:fixture与参数化构建高效防线

从"测试是负担"到"回归是防线"&#xff0c;中间差的不是工具&#xff0c;而是一套能把用例组织得明明白白、跑得又快又稳的实践方法。Pytest 恰好是这套方法里最顺手的载体。这篇文章不聊抽象的概念&#xff0c;直接讲我怎么用 Pytest 把回归测试从"定…

作者头像 李华
网站建设 2026/9/28 7:28:53

从零搭建金融数据服务:架构设计、数据采集与清洗存储实战

1. 金融数据服务从零搭建的完整思路1.1 为什么我要自己搭一套金融数据服务最早接触金融数据这块&#xff0c;是因为我需要一套能稳定拉取行情、财报、宏观指标的接口层。市面上的商业数据终端一年动辄几万块&#xff0c;对于个人开发者或者小团队来说成本太高&#xff1b;而免费…

作者头像 李华
网站建设 2026/9/28 7:28:47

电力系统PMU最优配置:二进制粒子群算法与Matlab实现详解

做电力系统规划的同学对PMU&#xff08;同步相量测量单元&#xff09;应该不陌生。单台PMU价格不低&#xff0c;安装位置又决定广域测量系统&#xff08;WAMS&#xff09;的“视野”边界&#xff0c;所以PMU站址选在哪里&#xff0c;和选多少台一样重要。最佳PMU位置配置&#…

作者头像 李华