MCP C# SDK 的 Client 调 LLM,Endpoint 指向 TaoToken

发布时间:2026/9/19 4:00:07
MCP C# SDK 的 Client 调 LLM,Endpoint 指向 TaoToken 在 EDT.McpServer.Client 里用 Microsoft.Extensions.AI 的 OpenAIClient 接 MCP Tools 时Endpoint、Key、ModelId 三项配置最容易出错。本文用 TaoToken官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 提供 Key 和 Base URL把 MCP C# SDK 的 Client 调 LLM 的 Endpoint 指向 TaoToken验证请求是否成功。真正容易翻车的不是 TimeTool而是 LLM:EndPoint、LLM:ApiKey、LLM:ModelIdEndpoint 如果写成 OpenAI 官方地址或者误加 /v1就会出现 404、401 或模型不存在而 MCP C# SDK 的 ListToolsAsync 本身能列出一堆 Tool却因为模型通道没接通导致 GetCurrentTime 永远不被 LLM 选中。下面回到 EDT.McpServer.Client把 aiClientOptions.Endpoint 填成 https://taotoken.net/api输入 what time is it in Chengdu看控制台是否由 LLM 自主调用 GetCurrentTime 并打印 It is xx:xx in Chengdu.。这篇按验证用量的视角把跑通请求、看调用是否成功的步骤拆开。TaoToken 只提供 Key 和 Base URL不参与 MCP 的 Tool 逻辑。一、MCP C# SDK 的 Client 调 LLM场景与痛点这个示例里有两个控制台项目EDT.McpServer.ConsoleHost 和 EDT.McpServer.Client。前者用 ModelContextProtocol 0.1.0-preview.4 把 TimeTool.GetCurrentTime 标记成 MCP Tool后者通过 StdIo 连上 Server用 ListToolsAsync 列出 Tool再把 Tool 交给 Microsoft.Extensions.AI 的 OpenAIClient让 LLM 通过 Function Invocation 决定是否调用。TimeTool 的关键代码大致如下using ModelContextProtocol.Server; using System.ComponentModel; namespace EDT.McpServer.Tools.ConsoleHost; [McpServerToolType] public static class TimeTool { [McpServerTool, Description(Get the current time for a city)] public static string GetCurrentTime(string city) $It is {DateTime.Now.Hour}:{DateTime.Now.Minute} in {city}.; }EDT.McpServer.ConsoleHost 启动 MCP Server 时重点是 WithToolsFromAssemblyvar builder Host.CreateEmptyApplicationBuilder(settings: null); builder.Services .AddMcpServer() .WithStdioServerTransport() .WithToolsFromAssembly(); await builder.Build().RunAsync();EDT.McpServer.Client 端先创建 MCP Client再 ListToolsAsyncawait using var mcpClient await McpClientFactory.CreateAsync(new() { Id time, Name Time MCP Server, TransportType TransportTypes.StdIo, TransportOptions new() { [command] ..\..\..\..\EDT.McpServer\bin\Debug\net8.0\EDT.McpServer.exe } }); var tools await mcpClient.ListToolsAsync(); foreach (var tool in tools) { Console.WriteLine(${tool.Name} ({tool.Description})); }真正需要改的是 LLM 客户端配置var apiKeyCredential new ApiKeyCredential(config[LLM:ApiKey]); var aiClientOptions new OpenAIClientOptions(); aiClientOptions.Endpoint new Uri(config[LLM:EndPoint]); var aiClient new OpenAIClient(apiKeyCredential, aiClientOptions) .AsChatClient(config[LLM:ModelId]); var chatClient new ChatClientBuilder(aiClient) .UseFunctionInvocation() .Build(); var mcpTools await mcpClient.ListToolsAsync(); var chatOptions new ChatOptions() { Tools [.. mcpTools] };这里的痛点很直接Endpoint 和 Key 从哪来自己搭一套模型通道配置、鉴权、模型切换、用量核对都要自己管既慢又容易乱。本文的目标不是重写 MCP 逻辑而是把 LLM 请求稳定地指向 TaoToken然后验证 MCP Client 的列 Tool、选 Tool、调 Tool 链路是否跑通。二、TaoToken 前置创建 Key确认 Base URL先打开 TaoToken 官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册然后进入控制台创建一把 API Key保存为 YOUR_API_KEY。TaoToken 在这里只提供 Key 和 Base URL不参与 MCP 的 Tool 逻辑。也就是说TimeTool 怎么注册、ListToolsAsync 返回什么、LLM 是否选择 GetCurrentTime仍然由 MCP C# SDK 和模型决定TaoToken 只负责模型请求的鉴权和通道。LLM 客户端要填的 Base URL 是https://taotoken.net/api注意三点不要加 /v1不要加 UTM 参数ModelId 按 TaoToken 通道可选模型填写不要照抄示例里的模型字符串。创建 Key 和查看接入方式可以走这两个入口API Keyshttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc三、可复制配置EDT.McpServer.Client 的 appsettings.json 与 OpenAIClient在 EDT.McpServer.Client 项目里把 appsettings.json 配置成下面这样{ LLM: { ApiKey: YOUR_API_KEY, EndPoint: https://taotoken.net/api, ModelId: 按 TaoToken 通道可选模型填写 } }这里保持LLM:EndPoint的大小写与代码一致。Endpoint 只写基础地址不要写https://taotoken.net/api/v1也不要把网页链接里的utm_source等参数带进去。OpenAIClientOptions.Endpoint 需要的是 API 基础地址不是浏览器地址栏链接。Program.cs 里读取配置并构建 chatClientvar config new ConfigurationBuilder() .AddJsonFile(appsettings.json, optional: false) .Build(); var apiKeyCredential new ApiKeyCredential(config[LLM:ApiKey]!); var aiClientOptions new OpenAIClientOptions { Endpoint new Uri(config[LLM:EndPoint]!) }; var aiClient new OpenAIClient(apiKeyCredential, aiClientOptions) .AsChatClient(config[LLM:ModelId]!); var chatClient new ChatClientBuilder(aiClient) .UseFunctionInvocation() .Build();MCP Client 仍然按原来的方式连接 EDT.McpServer.ConsoleHostawait using var mcpClient await McpClientFactory.CreateAsync(new() { Id time, Name Time MCP Server, TransportType TransportTypes.StdIo, TransportOptions new() { [command] ..\..\..\..\EDT.McpServer\bin\Debug\net8.0\EDT.McpServer.exe } });把 MCP Tools 交给 LLMvar mcpTools await mcpClient.ListToolsAsync(); foreach (var tool in mcpTools) { Console.WriteLine(${tool.Name} ({tool.Description})); } var chatOptions new ChatOptions { Tools [.. mcpTools] };最后进入对话循环IListChatMessage chatHistory [ new(ChatRole.System, You are a helpful assistant delivering time in one sentence in a short format. ) ]; while (true) { Console.Write(User ); var question Console.ReadLine(); if (string.IsNullOrWhiteSpace(question) || question.ToUpper() EXIT) break; chatHistory.Add(new ChatMessage(ChatRole.User, question)); var response await chatClient.GetResponseAsync(chatHistory, chatOptions); var content response.ToString(); Console.WriteLine($Assistant {content}); chatHistory.Add(new ChatMessage(ChatRole.Assistant, content)); }项目引用方面至少确认 ModelContextProtocol、Microsoft.Extensions.AI、OpenAIClient 所在包以及 Microsoft.Extensions.Configuration.Json 能正常恢复。版本以 ModelContextProtocol 0.1.0-preview.4 为参考实际以项目 restore 结果为准。四、验证请求dotnet run 后输入 what time is it in Chengdu进入 EDT.McpServer.Client 目录重新运行dotnet run如果 MCP Server exe 路径正确启动后应该先看到 Tool 列表类似GetCurrentTime (Get the current time for a city) Assistant How can I assist you today? User然后输入what time is it in Chengdu期望看到Assistant It is xx:xx in Chengdu.如果控制台能打印出这句话说明下面这条链路已经跑通MCP Client 通过 StdIo 连上了 EDT.McpServer.ConsoleHostListToolsAsync 成功列出 GetCurrentTimechatOptions.Tools 把 MCP Tool 定义交给了 LLMUseFunctionInvocation 触发 Function InvocationLLM 选择 GetCurrentTimeMCP Client 调用 Tool并把结果回填给 LLM模型请求从 TaoToken 的 https://taotoken.net/api 出去。从“验证用量”的视角看重点不是只看控制台有没有回复而是确认这次请求确实经过了 TaoToken 通道。控制台出现It is xx:xx in Chengdu.说明 LLM 自主调用了 GetCurrentTimeTool 返回值也回到了模型侧。之后可以回到 TaoToken 侧核对这次调用是否成功、发生时间是否符合预期、用量是否正常。TaoToken 不展示 MCP Tool 内部执行明细所以 Tool 是否被选中、是否被调用仍然以 EDT.McpServer.Client 的控制台输出和 MCP Server 日志为准。想先用模型对话验证 Key 和 ModelId可以走模型对话https://taotoken.net/console/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel-chat五、本篇常见错排查401、404、Tool 未被调用1. 401 Unauthorized 或 invalid api key现象chatClient.GetResponseAsync直接抛异常控制台没有 Assistant 回复。原因config[LLM:ApiKey]还是YOUR_API_KEY或者复制 Key 时带了空格、换行。处理到 API Keys 页面重新创建一把 Key填入 appsettings.json再运行 EDT.McpServer.Client。2. 404 Not Found 或 model not found现象请求发出后返回 404或者提示模型不存在。原因Endpoint 写成了https://taotoken.net/api/v1或者把 UTM 参数拼进了 API 地址。处理改回https://taotoken.net/apiModelId 按 TaoToken 通道可选模型填写。API 地址不要带/v1不要带网页 UTM。3. 控制台能列 Tools但输入问题后不调用 GetCurrentTime现象启动后能看到 GetCurrentTime但输入what time is it in Chengdu后模型只闲聊。原因没有把mcpTools赋给chatOptions.Tools或者没有调用UseFunctionInvocation()。处理检查 EDT.McpServer.Client 的 Program.cs确认var mcpTools await mcpClient.ListToolsAsync();后设置了Tools [.. mcpTools]并且 chatClient 由ChatClientBuilder(...).UseFunctionInvocation().Build()构建。4. TimeTool 没被注册ListToolsAsync 返回空现象MCP Client 启动后没有打印任何 Tool。原因EDT.McpServer.ConsoleHost 没有WithToolsFromAssembly()或者 TimeTool 缺少[McpServerToolType]。处理检查 Server 端AddMcpServer().WithStdioServerTransport().WithToolsFromAssembly()并确认 TimeTool 类和方法上的特性都存在。5. MCP Server exe 路径错现象McpClientFactory.CreateAsync抛出进程启动失败。原因TransportOptions[command]指向的EDT.McpServer.exe路径不存在。处理先 build EDT.McpServer.ConsoleHost再按bin/Debug/net8.0/EDT.McpServer.exe的实际路径填写。工作目录不同会影响相对路径必要时改成绝对路径排查。6. appsettings.json 没被复制到输出目录现象config[LLM:EndPoint]为 null或启动时报 Uri 为空。原因appsettings.json 没有设置 CopyToOutputDirectory。处理在项目文件中把 appsettings.json 设置为复制到输出目录或改用环境变量、用户机密读取。继续保持LLM:EndPoint大小写一致。7. 只打印 Tool 列表没有 Assistant 回复现象输入问题后卡住、超时或者只看到 User 后没有下文。原因Endpoint、Key、ModelId 三者不匹配或者模型通道本身没通。处理先用模型对话验证 Key 和 ModelId再回到 MCP Client。不要先怀疑 MCP C# SDK先把 LLM 客户端配置调通。8. 把 Tool 逻辑问题归到 TaoTokenTaoToken 只提供 Key 和 Base URL不参与 MCP 的 Tool 逻辑。如果 GetCurrentTime 本身没返回先查 MCP Server如果模型没选 Tool先查 ChatOptions 和 Function Invocation如果请求根本没出去再查 Endpoint、Key、ModelId。六、下一步把 MCP Client 的模型通道固定下来这次改造没有动 MCP C# SDK 的 Tool 注册和调用链只是把 Microsoft.Extensions.AI 的 OpenAIClient Endpoint 指向 TaoToken用一把 Key 管模型请求。重新运行 EDT.McpServer.Client 后输入what time is it in Chengdu如果控制台出现It is xx:xx in Chengdu.就说明 MCP Client 的列 Tool、选 Tool、调 Tool 链路已经跑通模型请求也从https://taotoken.net/api出去之后可以在 TaoToken 侧核对调用是否成功。如果继续做接入和排障建议把 API Keys 和接入文档放在手边API Keyshttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc如果你要把这套 MCP Client 用到长期编码或 Agent 工具链可以看 Coding PlanCoding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding-plan最后再固定一次配置LLM:EndPoint用https://taotoken.net/api不加/v1、不加 UTMLLM:ApiKey用YOUR_API_KEYLLM:ModelId按 TaoToken 通道可选模型填。改完重新dotnet run EDT.McpServer.Client输入what time is it in Chengdu观察 LLM 是否自主调用 GetCurrentTime。能稳定打印时间就说明 MCP C# SDK 的 Client 调 LLM 已经接到 TaoToken 通道上了。