
1. 从 Function Calling 的静态困局说起MCP 协议到底解决了什么如果你写过 Function Calling大概率经历过这种别扭工具函数在编译期就写死在代码里想加一个新工具就得改代码、重新编译、重新部署。更麻烦的是每个 AI 应用都要把同一套工具逻辑再实现一遍——数据库查询、文件读写、HTTP 调用重复劳动堆成山。我试过在一个小项目里塞了七八个工具函数结果每次调整参数描述都要重新跑一遍构建调试链路长得让人抓狂。这种「工具跟着应用走」的模式在工具数量少的时候还能忍一旦想接入外部服务立刻变成维护噩梦。MCPModel Context Protocol模型上下文协议要解决的就是这件事。它由 Anthropic 提出是一套开放标准定义了 AI 模型与外部工具之间的通信规范。核心思路可以概括成一句话工具提供方实现一次 MCP Server任何支持 MCP 的 AI 应用都能直接接入。这里有几个概念需要先理清楚不然后面配置会晕MCP Client你的 AI 应用负责发现工具、调用工具。MCP Server独立的工具服务进程对外暴露工具列表和执行能力。TransportClient 与 Server 之间的通信方式常见的有 Stdio本地进程和 HTTP远程服务。ToolsServer 暴露的具体工具包含名称、描述、参数 Schema。和 Function Calling 对比一下差异非常直观维度Function CallingMCP工具来源编译时硬编码运行时动态发现执行位置应用进程内独立进程或远程服务工具更新改代码 重新编译只更新 ServerClient 零改动工具数量受代码维护能力限制可接入任意多个 Server协议标准OpenAI Tool 协议MCP 开放标准跨语言函数必须在应用内实现Server 可以是任何语言打个比方Function Calling 像是你雇了几个全职员工岗位写死在组织架构里MCP 像是接入了一个人才市场按需发现和调用各种工具服务。前者适合小型应用、少量工具后者适合 Agent 平台、工具生态集成。这篇文章的目标不是只教你用别人的 MCP Server而是从零构建一个自己的 MCP Server再用 TaoToken 的统一 Key 和 API 通道把整条工具调用链路跑通。你会看到可复制的服务端配置、工具注册示例以及一次完整的 JSON-RPC 调用验证。适合已经了解 Function Calling、想进一步把工具生态串起来的开发者。2. TaoToken 前置准备统一 Key 与 API 通道怎么配在动手写 MCP Server 之前先把模型侧的入口准备好。MCP 负责工具发现和调用但真正决定「调不调工具、调哪个工具」的还是大模型。所以你需要一个能稳定访问模型 API 的通道这里用 TaoToken 来做统一入口。TaoToken 的作用是把模型访问收敛到一个 Base URL 和一把 Key 上这样你的 MCP Client 在注入工具之后调用模型时不用关心底层换了哪个供应商。对做工具生态的项目来说这一点很关键——工具是动态发现的模型入口如果也是统一可切换的整个链路的可维护性会高很多。先拿到 API Key。打开控制台页面https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_console在 API Keys 页面创建一个新的 Key复制出来备用。注意 Key 只在创建时完整显示一次丢了就得重建。https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_apikeys接下来确认 API 入口地址。TaoToken 的 API Base URL 是https://taotoken.net/api这个地址不加任何查询参数直接作为 OpenAI 兼容客户端的 Endpoint 使用。也就是说你在代码里初始化模型客户端时把Endpoint指向它把 Key 填进去就能走通模型调用。如果你用的是 Claude Code 这类编码工具或者想先验证模型通道是否正常可以走对应的 deep linkhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_codingplanhttps://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_doc想先在对话界面里确认模型能正常响应可以用模型对话入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_chat这里有个容易踩的坑很多人把 Base URL 写成带/v1的路径结果客户端又自动拼了一次变成/v1/v1/chat/completions直接 404。TaoToken 的 API 地址就是https://taotoken.net/api具体路径由客户端 SDK 自己拼接你不要手动加。准备好 Key 和 Base URL 之后模型侧就绪。接下来进入正题构建一个自定义 MCP Server。3. 可复制配置从零构建 MCP Server 并注册工具这一节是全文的技术核心。我们用一个 C# 项目来演示借助官方的ModelContextProtocol.AspNetCore包把 MCP Server 搭起来。项目结构很简单AIHttpMcpServer/ ├── Program.cs # 服务入口配置 MCP Server ├── TestMcpTool.cs # 自定义工具定义 └── AIHttpMcpServer.csproj先装依赖。在.csproj里加上包引用PackageReference IncludeModelContextProtocol.AspNetCore Version1.4.0 /然后在TestMcpTool.cs里定义两个用户管理工具using ModelContextProtocol.Server; using System.ComponentModel; [McpServerToolType] public class TestMcpTool { [McpServerTool] [Description(Search for a user by their username.)] public string SearchUser(string userName) { return $Searching for user: {userName}; } [McpServerTool] [Description(Add a new user by their username.)] public string AddUser(string userName) { return $Adding user: {userName}; } }这里的[Description]和 Function Calling 里的作用一样是给模型理解工具用途的关键信息。区别在于标注方式Function Calling 用[Description]标方法MCP Server 额外需要[McpServerToolType]标类、[McpServerTool]标方法。框架会自动扫描带这两个特性的类和方法把它们注册成 MCP 工具。接着配置Program.csvar builder WebApplication.CreateBuilder(args); builder.Services.AddMcpServer(options { options.ServerInfo new ModelContextProtocol.Protocol.Implementation { Name AIHttpMcpServer, Description A simple AI HTTP MCP server., Version 1.0.0 }; }) .WithHttpTransport() // 启用 HTTP 传输 .WithStdioServerTransport() // 同时支持 Stdio 传输 .WithToolsFromAssembly(); // 自动扫描程序集中的工具 var app builder.Build(); app.MapGet(/, () Hello World!); app.MapMcp(/mcp); // 将 MCP 端点映射到 /mcp 路径 app.Run();逐行解读一下关键配置AddMcpServer注册 MCP Server 服务配置名称、描述、版本等元信息。WithHttpTransport()启用 HTTP 传输允许远程客户端通过网络连接。WithStdioServerTransport()同时支持 Stdio 传输方便本地进程间通信。WithToolsFromAssembly()是最关键的一行自动扫描当前程序集中所有带[McpServerToolType]特性的类注册其中的工具方法。MapMcp(/mcp)把 MCP 协议的 HTTP 端点映射到/mcp路径。启动服务cd AIHttpMcpServer dotnet run服务启动后会在控制台显示监听地址默认是http://localhost:5144/mcp。此时 MCP Server 已经就绪等待 Client 连接。如果你更习惯用配置文件管理 MCP Server很多客户端支持 JSON 或 TOML 格式的配置。以常见的 MCP 客户端配置为例一个 HTTP 传输的 Server 配置片段长这样{ mcpServers: { ai-http-mcp-server: { url: http://localhost:5144/mcp, transport: http } } }如果是 Stdio 传输配置会变成命令加参数的形式{ mcpServers: { ai-stdio-mcp-server: { command: dotnet, args: [run, --project, ./AIHttpMcpServer], transport: stdio } } }这两种配置的差别在于HTTP 适合远程服务、跨网络部署Stdio 适合本地进程通信简单可靠。你可以根据实际场景选。到这里MCP Server 侧就完成了。工具注册的核心步骤始终只有三步用[McpServerTool][Description]定义工具用WithToolsFromAssembly()自动注册用MapMcp(/mcp)暴露端点。记住这三步换任何工具逻辑都是同样的套路。4. 验证请求一次完整的 JSON-RPC 工具调用链路Server 跑起来之后我们需要一个 Client 去连接它、发现工具、把工具注入模型对话最后验证整条链路。这一节把每一步都跑一遍。先创建 MCP Client 并连接 Server。这里用 HTTP 传输using ModelContextProtocol.Client; var config new HttpClientTransport(new HttpClientTransportOptions { Endpoint new Uri(http://localhost:5144/mcp), TransportMode HttpTransportMode.AutoDetect, }); var mcpClient await McpClient.CreateAsync(config); var tools await mcpClient.ListToolsAsync(); Console.WriteLine( 可用工具列表 ); foreach (var tool in tools) { Console.WriteLine($ {tool.Name}: {tool.Description}); }HttpTransportMode.AutoDetect让客户端自动检测 Server 支持的传输模式SSE 或 Streamable HTTP不用手动指定。运行后会看到 可用工具列表 SearchUser: Search for a user by their username. AddUser: Add a new user by their username.这就是 MCP 的核心价值——运行时动态发现工具。你不需要事先知道有哪些工具Client 会自动获取 Server 暴露的全部工具列表包括名称、描述和参数 Schema。接下来把 MCP 工具接入 AI 对话。先创建模型客户端这里用 TaoToken 的统一入口IChatClient client new OpenAI.Chat.ChatClient( model, new ApiKeyCredential(apiKey), new OpenAI.OpenAIClientOptions { Endpoint new Uri(https://taotoken.net/api) }) .AsIChatClient(); using var functionCallingChatClient new ChatClientBuilder(client) .UseFunctionInvocation() .Build();注意Endpoint填的是https://taotoken.net/apiapiKey就是前面在控制台创建的那把 Keymodel填你要用的模型 ID。这三件套——Base URL、Key、Model ID——缺一不可后面排障会反复用到。然后进入对话循环关键改动只有一行while (true) { Console.Write(Prompt: ); ListChatMessage messages []; messages.Add(new(ChatRole.User, Console.ReadLine())); await foreach (ChatResponseUpdate update in functionCallingChatClient .GetStreamingResponseAsync(messages, new() { Tools [.. tools] })) { foreach (var item in update.Contents) { if (item is TextContent text) { Console.Write(text.Text); } } } Console.WriteLine(); }对比一下 Function Calling 和 MCP 在注入工具时的差别// Function Calling手动注册本地函数 new ChatOptions { Tools [AIFunctionFactory.Create(GetWeatherInfo)] } // MCP直接传入动态发现的工具列表 new ChatOptions { Tools [.. tools] }[.. tools]是 C# 的 spread 语法把IListMcpTool展开成工具数组。现在你可以对模型说「帮我搜索用户 Alice」或「添加一个新用户 Bob」模型会自动调用 MCP Server 里的SearchUser和AddUser。为了确认底层真的走了 JSON-RPC我们可以观察一次完整交互。MCP 底层用的是 JSON-RPC 2.0 协议分三个阶段。初始化握手// Client → Server {jsonrpc:2.0,method:initialize,params:{protocolVersion:2024-11-05}} // Server → Client {jsonrpc:2.0,result:{protocolVersion:2024-11-05,capabilities:{}}}工具发现// Client → Server {jsonrpc:2.0,method:tools/list} // Server → Client { result: { tools: [ { name: SearchUser, description: Search for a user by their username., inputSchema: { type: object, properties: { userName: { type: string } } } } ] } }工具调用// Client → Server { jsonrpc: 2.0, method: tools/call, params: { name: SearchUser, arguments: { userName: Alice } } } // Server → Client { result: { content: [ { type: text, text: Searching for user: Alice } ] } }完整链路是这样的用户说「帮我搜索用户 Alice」模型判断需要调用SearchUserMCP Client 通过 JSON-RPC over HTTP 把请求发到localhost:5144Server 执行SearchUser(Alice)返回结果Client 把结果回传给模型模型基于结果生成回答。整条闭环跑通说明 MCP 工具调用生效了。5. 本篇常见错排查401、local proxy failed、reading choices 怎么解链路跑通之前报错是常态。这一节把几个高频错误对照着讲清楚每个都给出定位思路和修复动作。401 Unauthorized。这个最常见基本是 Key 的问题。检查三件事Key 是否复制完整有没有首尾空格、Key 是否已过期或被删除、请求头里的Authorization格式是不是Bearer 你的Key。如果你用的是 TaoToken 的统一入口确认 Base URL 是https://taotoken.net/api不要手动加/v1。三件套里 Base URL、Key、Model ID 任何一个填错都可能表现为 401 或 404。local proxy failed。这个报错通常出现在客户端配置了本地代理但代理进程没起来或者端口不对。排查顺序先确认代理服务是否在运行再确认配置里的端口和实际监听端口一致。如果你没有用代理检查环境变量里有没有残留的HTTP_PROXY、HTTPS_PROXY设置它们会干扰客户端直连。清掉这些变量再试。reading choices 相关报错。这类错误一般出现在解析模型响应时比如reading choices或cannot read choices of undefined。根因通常是响应体不是预期的 OpenAI 格式可能是 Base URL 拼错导致返回了 HTML 错误页也可能是模型 ID 不存在导致返回了错误 JSON。定位方法把原始响应打印出来看确认返回的是不是标准的choices数组结构。修复动作是核对 Base URL 和 Model ID。OAuth 相关报错。如果你在 MCP Client 配置里启用了 OAuth 认证但 Server 端没配对应的认证流程会卡在授权环节。对于本地开发的 MCP Server先关掉 OAuth用最简单的无认证模式跑通链路再逐步加认证。别一上来就把认证拉满排障成本会翻倍。工具列表为空。Client 连上了 Server但ListToolsAsync()返回空。检查WithToolsFromAssembly()是否真的扫描到了工具类——工具类必须在当前程序集里且带[McpServerToolType]特性方法带[McpServerTool]。如果工具类在另一个项目里需要显式注册或调整程序集扫描范围。CC Switch / Cline MCP / Codex auth.json 场景。如果你是在这些工具里配置 MCP记住三件套要写全Base URL 填https://taotoken.net/apiKey 填控制台创建的 KeyModel ID 填你要用的模型。以 Codex 的auth.json为例配置结构大致是{ base_url: https://taotoken.net/api, api_key: 你的Key, model: 你的ModelID }Cline 的 MCP 配置则是在mcpServers里加一个条目指向你的 MCP Server 地址。CC Switch 切换配置时确认切换后的 Base URL 和 Key 是配套的别出现 Key 是 A 通道的、URL 是 B 通道的混搭。排障的通用思路是先确认模型通道Base URL Key Model ID没问题再确认 MCP Server 能独立响应tools/list最后确认 Client 到 Server 的网络连通。分层定位比一股脑改配置高效得多。6. 语义一致 CTA把工具生态接到你的项目里MCP 的价值不在于单个工具而在于它把「工具提供方」和「工具使用方」解耦了。你实现一次 MCP Server任何支持 MCP 的 AI 应用都能接入反过来你的 AI 应用也能动态发现别人提供的工具服务。这种生态效应是 Function Calling 那种静态绑定做不到的。回到实际落地构建 MCP Server 的核心步骤始终是三步定义工具、注册工具、暴露端点。工具逻辑可以是用户管理、数据库查询、文件操作、GitHub 集成、运维监控套路完全一样。安全上要守住几条线工具白名单、参数校验、最小权限、结果脱敏。MCP 工具是真正执行操作的别把危险能力直接暴露出去。如果你想把这条链路接到自己的项目里建议按这个顺序推进先用 TaoToken 的统一 Key 和 API 通道把模型调用跑通确认 Base URL、Key、Model ID 三件套无误再构建一个最小的 MCP Server只放一两个安全工具然后用 Client 连接、发现工具、注入对话验证 JSON-RPC 调用闭环最后逐步扩展工具集每加一个工具都单独验证一次。模型通道和接入文档在这里https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_apikeyshttps://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_doc想先验证模型响应是否正常用模型对话入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_chat如果你在做长期编码或 Agent 类项目需要稳定的模型通道支撑工具调用可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmcp_codingplan最后留一个实操建议调试 MCP 链路时先把 Server 单独跑起来用curl或 Postman 直接打tools/list端点确认 JSON-RPC 响应正常再接入 Client。这样能把「Server 问题」和「Client 问题」分开排障效率会高很多。工具生态的搭建是个渐进过程先把一条最小闭环跑通再往上叠工具比一上来就铺大摊子稳得多。