1. 从 echo_upper 到文件级 Skill为什么最小 MCP 工具不够用如果你已经跟着上一篇把 C 版 MCP 服务跑起来手里大概率有一个能用的echo_upper客户端发一段文本服务端把 ASCII 小写字母转成大写再返回。这个工具很适合理解 JSON-RPC 消息流但一旦把它放进真实开发流程问题立刻暴露出来。用户不会说“把 hello 变成大写”用户会说“把这个 txt 文件里的英文字母都转成大写生成一个新文件非字母不要动”。这两句话的差距就是 MCP 工具和 Skill 之间的差距。MCP 工具解决的是“服务端能做什么、参数怎么传、结果怎么返回”Skill 解决的是“什么场景触发、按什么步骤做、边界条件是什么、成功后回报什么”。前者是能力接口后者是流程编排。我这次要做的是在原有 C MCP 服务上新增一个真正适合文件处理的工具uppercase_txt_file再配一个最小 Skilltxt-uppercase-file最后通过 TaoToken 的统一 Key/API 通道把整条链路跑通。整篇文章会给出可复制的 CMake 工程结构、skill 注册配置、端到端调用验证动作以及我实际踩过的几个报错。适合谁看已经写过或跑通过一个最小 MCP 服务、想进一步理解 Skill 与 MCP 分工的 C 开发者正在用 Codex CLI 或类似客户端做 AI 开发流程、想把自研能力接进统一通道的人以及被std::filesystem链接错误、local proxy failed、reading choices这类报错卡住的同学。核心检索词先明确C 实现简易 skill 并接入 TaoToken本质是把一个文件级 MCP 工具包装成 SKILL.md 描述的可复用流程再通过统一 API 通道调用。下面从工程结构开始一步步落地。2. TaoToken 前置准备统一 Key 与 API 通道配置在写 Skill 之前先把调用通道准备好。TaoToken 在这里扮演的角色是统一入口你不需要为每个模型或每个工具单独维护一套鉴权和地址而是用同一个 Key 走同一个 API 前缀。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个地址不加 UTM 参数直接用于配置。你需要准备三样东西我把它称为“三件套”后面无论配 Codex、Cline 还是 Claude Code都绕不开配置项值说明Base URLhttps://taotoken.net/api所有请求的统一前缀API Key在控制台创建形如sk-...只显示一次Model ID按需选择例如claude-sonnet-4-5等获取 Key 的路径是控制台里的 API Keys 页面创建后立刻复制保存页面刷新后不再完整显示。如果你还没决定用哪个模型可以先去模型对话页面做一次最小验证确认 Key 和通道都正常再回来配本地工程。这里要强调一个容易踩的坑Base URL 和具体路径的拼接方式。很多客户端要求你填的是“到/api为止”的基址然后由客户端自己拼/v1/messages或/v1/chat/completions也有客户端要求你填完整路径。TaoToken 的 API 基址是https://taotoken.net/api在 Codex 的config.toml里通常写成base_url https://taotoken.net/api由客户端补全后续路径。如果你填成https://taotoken.net/api/v1很可能出现 404 或路径重复。另外Key 不要硬编码进源码或提交到 Git。本地测试用环境变量工程里用配置文件这是基本纪律。我见过有人把 Key 写进mcp_cpp_demo.cpp里图省事结果一提交就泄露只能作废重建。准备阶段做完你应该能回答三个问题Base URL 是什么、Key 存在哪、用哪个 Model ID。这三个答案会在第 3 节的配置片段里反复出现。如果你更偏向长期编码和 Agent 场景可以顺带了解 Coding Plan它适合把这类自研 Skill 纳入日常开发流如果只是先验证模型通不通模型对话页面就够了。3. 可复制配置CMake 工程结构、MCP 注册与 SKILL.md这一节是全文的技术核心给出可以直接抄的工程结构、配置片段和 SKILL.md。先看目录布局我把它整理成 CMake 友好的形式而不是依赖 VS 工程文件mcp_example/ ├── CMakeLists.txt ├── mcp_cpp_demo/ │ ├── CMakeLists.txt │ └── mcp_cpp_demo.cpp ├── skill_examples/ │ └── txt-uppercase-file/ │ └── SKILL.md └── sample.txt顶层CMakeLists.txt只做工程声明和子目录引入cmake_minimum_required(VERSION 3.16) project(mcp_example LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_subdirectory(mcp_cpp_demo)子目录mcp_cpp_demo/CMakeLists.txt负责可执行文件和依赖。这里用FetchContent拉取nlohmann/json避免手动拷贝头文件include(FetchContent) FetchContent_Declare( nlohmann_json GIT_REPOSITORY https://github.com/nlohmann/json.git GIT_TAG v3.11.3 ) FetchContent_MakeAvailable(nlohmann_json) add_executable(mcp_cpp_demo mcp_cpp_demo.cpp) target_link_libraries(mcp_cpp_demo PRIVATE nlohmann_json::nlohmann_json)注意CMAKE_CXX_STANDARD 17这一行std::filesystem需要 C17。如果你沿用 VS 工程对应的是在.vcxproj里加LanguageStandardstdcpp17/LanguageStandard否则会报std::filesystem未定义或链接错误。源码部分核心改动是在原有echo_upper之外新增uppercase_txt_file。工具声明放在tools/list的返回里参数 schema 如下{ {name, uppercase_txt_file}, {description, Read a UTF-8 txt file, convert ASCII lowercase letters to uppercase, keep non-letters unchanged, and write the result to a txt file}, {inputSchema, { {type, object}, {properties, { {inputPath, {{type, string}, {description, Path to the source .txt file}}}, {outputPath, {{type, string}, {description, Optional output .txt path. Defaults to input_upper.txt}}}, {overwrite, {{type, boolean}, {description, Whether to overwrite an existing output file}, {default, false}}} }}, {required, json::array({inputPath})} }} }处理逻辑的关键点只处理 ASCII 的a-z不要用全局 locale 的大小写转换否则会误伤 UTF-8 中文字节。转换函数保持逐字节判断static std::string to_upper_copy_ascii(std::string s) { for (char ch : s) { if (ch a ch z) { ch static_castchar(ch - a A); } } return s; }handle_uppercase_txt_file负责校验参数、校验.txt扩展名、读取输入、转换、写入输出、返回摘要。默认输出路径用make_default_output_path生成原文件名_upper.txt已存在且未允许覆盖时直接报错。接下来是 Skill 侧。SKILL.md放在skill_examples/txt-uppercase-file/下内容如下--- name: txt-uppercase-file description: Convert the contents of a UTF-8 .txt file so that ASCII lowercase letters become uppercase while non-letters stay unchanged. Use when the user wants to uppercase a txt file, batch-convert simple text content, or write the transformed result to a new txt file by calling the MCP tool uppercase_txt_file. --- # TXT 文件大写转换 接收用户传入的 .txt 文件路径。 优先调用 MCP 工具 uppercase_txt_file参数如下 - inputPath输入文件路径 - outputPath用户指定输出路径时填写 - overwrite仅当用户明确允许覆盖现有文件时填写 遵循规则 - 将文件按 UTF-8 文本处理 - 仅将 ASCII 小写字母 a-z 转为大写 A-Z - 汉字、数字、空格、标点符号保持不变 - 默认创建新文件不覆盖源文件 如果输出路径已存在且未请求覆盖停止执行并清晰提示文件冲突。 工具执行成功后返回信息 - 源文件路径 - 输出文件路径 - 原文件是否被保留这份 SKILL.md 没有重新实现任何转换逻辑它只做三件事声明触发场景、指定优先调用的 MCP 工具、约束边界条件。这就是 Skill 与 MCP 的标准分工。最后是 Codex CLI 的 MCP 注册配置写在~/.codex/config.tomlWindows 下是C:\Users\你\.codex\config.toml[mcp_servers.cpp_demo] type stdio command D:\\mcp_example\\build\\mcp_cpp_demo\\Debug\\mcp_cpp_demo.exe cwd D:\\mcp_example startup_timeout_sec 120如果你同时要接 TaoToken 的模型通道在同一个config.toml里补上模型配置三件套齐全model claude-sonnet-4-5 model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEYenv_key指向环境变量名Key 本身放在系统环境变量里不写进文件。Skill 文件放到 Codex 可发现的位置例如C:\Users\你\.codex\skills\txt-uppercase-file\SKILL.md。配置完成后输入/skills应该能看到txt-uppercase-file出现在列表里。4. 验证请求与成功结果从编译到端到端调用配置写完必须验证否则你永远不知道是工具没注册、Skill 没被发现还是通道没通。我按“编译 → 手动 MCP 调用 → Skill 触发”三步走。第一步编译。用 CMake 的话cmake -S . -B build -DCMAKE_BUILD_TYPEDebug cmake --build build --config DebugWindows 上如果生成的是 VS 工程也可以用 MSBuild D:\Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe D:\mcp_example\build\mcp_cpp_demo.sln /t:Build /p:ConfigurationDebug /p:Platformx64编译成功后可执行文件在build/mcp_cpp_demo/Debug/mcp_cpp_demo.exe或你 VS 工程对应的x64\Debug\下。第二步手动发 MCP 消息验证工具注册。启动程序后先发初始化{jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2024-11-05,capabilities:{},clientInfo:{name:manual-test,version:0.1.0}}}再查工具列表{jsonrpc:2.0,id:2,method:tools/list}返回里应该同时出现echo_upper和uppercase_txt_file。如果只有前者说明tools/list的数组没改对回去检查 JSON 结构。准备一个测试文件D:\mcp_example\sample.txt内容写成hello, World! 123 今天abc然后调用文件工具{jsonrpc:2.0,id:3,method:tools/call,params:{name:uppercase_txt_file,arguments:{inputPath:D:\\mcp_example\\sample.txt}}}成功时返回类似{ jsonrpc: 2.0, id: 3, result: { content: [ { type: text, text: Converted file to uppercase.\nInput: D:\\mcp_example\\sample.txt\nOutput: D:\\mcp_example\\sample_upper.txt } ], isError: false } }同时sample_upper.txt生成内容应为HELLO, WORLD! 123 今天ABC。注意中文“今天”没变数字和标点也没变只有 ASCII 小写字母被转换这说明逐字节判断生效了。第三步Skill 触发。在 Codex CLI 里输入/skills确认txt-uppercase-file在列表然后用自然语言触发用 txt-uppercase-file skill 处理 D:\mcp_example\sample.txt把这个 txt 文件转成大写不要覆盖原文件预期行为是模型优先调用uppercase_txt_file传入inputPath不传overwrite返回源文件路径、输出文件路径和原文件保留状态。如果模型绕开工具自己读文件再写回说明 SKILL.md 的 description 不够明确或者 Skill 没被正确加载。到这里整条链路就通了C MCP 工具提供能力SKILL.md 提供流程约束TaoToken 提供统一模型通道。三者各司其职没有互相替代。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节对照真实报错给出定位思路。这些错我在不同阶段都遇到过按出现频率排序。401 Unauthorized。最常见的原因是 Key 没生效或 Base URL 拼错。先确认环境变量TAOTOKEN_API_KEY在当前终端可见Windows 下改完环境变量要重开终端。再确认base_url是https://taotoken.net/api没有多写/v1。如果 Key 是从控制台复制的检查有没有带多余空格或换行。401 基本是鉴权层问题和 MCP 工具本身无关。local proxy failed。这个报错通常出现在客户端尝试走本地代理但代理没启动或端口不对。检查你的客户端配置里有没有残留的代理设置把代理相关字段清掉直连https://taotoken.net/api。如果你在config.toml里同时配了model_provider和旧的代理项两者会冲突删掉不用的那个。reading choices 相关报错。这类错误一般出现在响应解析阶段客户端期望的返回结构和实际返回不匹配。常见诱因是 Base URL 指向了错误的路径导致返回的是 HTML 错误页而不是 JSON。用curl直接打一次接口确认返回体是 JSONcurl -s https://taotoken.net/api/v1/messages \ -H x-api-key: $TAOTOKEN_API_KEY \ -H content-type: application/json \ -d {model:claude-sonnet-4-5,max_tokens:64,messages:[{role:user,content:ping}]}如果返回 HTML 或 404说明路径不对如果返回 JSON 但客户端仍报 reading choices检查客户端版本是否支持当前响应格式。OAuth 相关报错。有些客户端默认走 OAuth 流程而 TaoToken 用的是 API Key 鉴权。如果你看到 OAuth token 获取失败说明客户端在尝试它自己的登录流程而不是用你配的 Key。检查配置里是否误开了 OAuth 模式改成 API Key 模式把env_key指向正确的环境变量。MCP 工具调用返回 -32602。这是参数校验失败常见于inputPath传了非字符串、overwrite传了非布尔值或者输入文件不是.txt。对照handle_uppercase_txt_file里的校验顺序逐条排查先看扩展名再看文件是否存在再看是否普通文件最后看输出是否冲突。Skill 不触发。先确认/skills列表里有它再确认 SKILL.md 的 frontmatter 格式正确---包裹name和description都在。description 里要包含触发关键词比如“uppercase a txt file”否则模型匹配不到。排障时如果怀疑是通道问题去 API Keys 页面重新确认 Key 状态或对照接入文档检查路径拼接。如果只是想验证模型本身通不通用模型对话页面发一句话最快。长期做编码和 Agent 的话Coding Plan 能省掉不少重复配置。6. 把自研 Skill 接进统一通道下一步怎么走走到这里你已经有了一个可被 AI 开发流程调用的最小 SkillC 写的 MCP 工具负责文件级大写转换SKILL.md 负责触发条件和边界约束TaoToken 负责统一模型通道。这套结构可以复制到任何“读文件 → 处理 → 写文件”的场景比如批量重命名、格式转换、日志提取。下一步我建议做两件事。第一把uppercase_txt_file的参数再扩一层比如支持encoding或dryRun让工具边界更清晰Skill 的 description 也能写得更具体。第二把 Skill 目录纳入版本管理和 MCP 源码放在同一个仓库这样工具和流程说明不会脱节。如果你还没拿到 Key先去控制台创建如果通道已经通了直接去接入文档对照一遍路径拼接确认没有多余后缀。需要长期跑编码和 Agent 任务的话Coding Plan 比按次调用更省心。整条链路的关键不是某个单点配置而是“能力用 MCP 暴露、流程用 Skill 约束、通道用统一 Key 收口”这个分工记住这一点后面接什么工具都不会乱。