尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

我用 C++ 写了个 MCP,让 AI 通过 TaoToken 看懂每帧 GPU 在画什么

我用 C++ 写了个 MCP,让 AI 通过 TaoToken 看懂每帧 GPU 在画什么 1. 图形调试的痛点一帧上千个 Draw Call人眼根本看不过来做图形渲染的同学大概都有这种体验一个画面出问题打开 RenderDoc 抓一帧左边是几百上千个 Draw Call 的列表右边是 Pipeline State、资源绑定、Shader 代码、纹理格式一大堆面板。你知道问题就在这一帧里但具体是哪个 Pass、哪个 Draw、哪个状态配错了只能靠经验一个个点开看。我最近在调一个 D3D11 的场景画面右上角有一块莫名其妙的黑块。按老办法我得先看 Event Browser 里哪些 Draw 覆盖了那个区域再逐个检查 Scissor Rect、Viewport、Blend State、Depth Test运气不好还得反汇编 Shader 看输出。整个过程下来半小时起步。问题的本质不是 RenderDoc 不好用而是它给的信息太多、太底层而人脑擅长的是提问—定位—验证这个循环不擅长在几千条结构化数据里做关联。这恰好是 AI 擅长的部分。所以我想能不能写一个 MCP 服务把 RenderDoc 的 Replay API 包装成 AI 能调用的工具让我直接对 AI 说帮我看看这一帧哪里不对它自己去翻 Draw Call、查 Pipeline、导出对比图。这篇就来讲我怎么用 C 从零写这个 MCP 服务端以及怎么通过 TaoToken 的统一 Key 和 API 通道把它接到 Claude Code 里最后跑通一次抓帧到 AI 问答的完整验证。2. 前置准备TaoToken 统一通道与 MCP 服务端骨架2.1 为什么走 TaoToken 而不是直连各家 API写 MCP 服务端的时候我一开始想的是直接对接某一家模型的 API。但实际做下来发现两个麻烦一是不同模型的接口格式、鉴权方式、流式返回都不一样MCP 服务端里要写一堆适配代码二是调试阶段经常要换模型对比效果每换一次就得改配置、改 Key。TaoToken 提供的是统一的 Key 和 API 通道模型对话、Coding Plan、API Keys 管理都在一个控制台里。对 MCP 服务端来说我只需要认一个 base URL 和一个 Key剩下的模型切换在服务端配置里改个模型名就行。官网在 https://taotoken.net API 入口是 https://taotoken.net/api 接入文档在 https://taotoken.net/doc 。这里要说明一下TaoToken 是合规的 API 聚合通道不是那种灰色中转Key 和调用都在官方控制台里管理用起来心里踏实。2.2 MCP 服务端的目录结构我用 C17 写的CMake 构建整体分三层协议层负责 JSON-RPC 2.0 over stdio业务层封装 RenderDoc Replay API中间用桥接层连接。这样协议层不知道 RenderDoc 的存在业务层不知道 MCP 的存在以后换协议只动一层。目录大概长这样renderdoc-mcp/ ├── CMakeLists.txt ├── config.toml # MCP 服务端配置 ├── src/ │ ├── mcp-proto/ # JSON-RPC 2.0 MCP 规范 │ │ ├── server.cpp │ │ └── tool_registry.cpp │ ├── renderdoc-core/ # Replay API 封装 │ │ ├── session.cpp │ │ ├── drawcall.cpp │ │ └── diff_engine.cpp │ └── bridge/ │ └── tool_bridge.cpp └── third_party/ ├── nlohmann_json/ └── stb_image/依赖就两个nlohmann/json 做序列化stb_image 做图像编码。RenderDoc 的 Replay API 通过它自带的头文件和库链接进来。2.3 config.toml 骨架MCP 服务端的配置我放在 config.toml 里主要是 TaoToken 的接入信息和 RenderDoc 的路径[mcp] name renderdoc-mcp version 0.1.0 protocol_version 2025-03-26 [taotoken] base_url https://taotoken.net/api api_key sk-你的TaoToken密钥 model claude-sonnet-4-20250514 timeout_seconds 120 [renderdoc] # RenderDoc 安装路径Windows 示例 install_path C:/Program Files/RenderDoc # 抓帧文件默认目录 capture_dir ./captures [session] # 主分析 Session 和 Diff Session 独立共存 max_concurrent_sessions 4api_key 那行建议不要硬编码在文件里实际用的时候从环境变量读config.toml 里写个占位符就行。我本地是这么处理的std::string get_api_key(const toml::table cfg) { const char* env std::getenv(TAOTOKEN_API_KEY); if (env *env) return std::string(env); return cfg[taotoken][api_key].value_or(); }3. 可复制配置把 RenderDoc 帧数据转成 AI 可读结构3.1 声明式工具注册MCP 的核心是 Tools。我用了声明式注册每个 Tool 定义名字、描述、参数 schema桥接层自动做参数校验和错误码映射。这样加一个新 Tool 只要写一段声明加一个处理函数不用改协议层。以打开抓帧这个 Tool 为例registry.add_tool({ .name open_capture, .description 打开一个 .rdc 抓帧文件返回帧的基本信息, .params { {path, ParamType::String, rdc 文件路径, true} }, .handler [](const json args) - json { std::string path args[path]; auto session SessionManager::instance().open(path); return { {capture_id, session.id()}, {api, session.graphics_api()}, // D3D11 / D3D12 / Vulkan {draw_calls, session.draw_call_count()}, {passes, session.pass_count()} }; } });3.2 把 Pipeline 状态转成结构化 JSONAI 要能看懂一帧关键是把它关心的信息转成结构化的、语义清晰的 JSON。RenderDoc 的 Replay API 返回的是 C 结构体我写了一层转换把 Pipeline State、资源绑定、Shader 阶段都拍平成 JSON。比如查某个 Draw Call 的完整状态json dump_draw_state(Session s, uint32_t event_id) { auto state s.get_pipeline_state(event_id); json out; out[event_id] event_id; out[shaders] { {vs, state.vs.entry_point}, {ps, state.ps.entry_point} }; out[rasterizer] { {cull_mode, to_string(state.raster.cull_mode)}, {fill_mode, to_string(state.raster.fill_mode)}, {scissor, state.raster.scissor_enable}, {scissor_rects, state.raster.scissor_rects} }; out[output_merger] { {blend_enable, state.om.blend_enable}, {rt_formats, state.om.rt_formats} }; out[render_targets] state.om.render_targets; return out; }这样 AI 拿到的是一个干净的 JSON字段名都是自解释的不需要它去猜 RenderDoc 内部结构。3.3 Diff 引擎两帧对比版本回归检测是图形调试的高频需求。我写了个 Diff 引擎同时加载两个 .rdc用 LCS 算法对齐 Draw Call 序列从 6 个维度对比Draw Call、Pipeline、像素、Pass 结构、资源、统计。DiffResult diff_captures(const std::string a, const std::string b) { auto sa SessionManager::instance().open(a); auto sb SessionManager::instance().open(b); DiffEngine engine(sa, sb); engine.align_draw_calls(); // LCS 对齐 engine.compare_pipeline(); engine.compare_passes(); engine.compare_resources(); engine.compare_stats(); return engine.result(); }对齐之后AI 能直接看到新增了 3 个 Draw Call某个 Pass 的 Blend State 变了某个 RT 格式从 RGBA8 变成了 R11G11B10这种结论而不是让它自己去比对两棵巨大的状态树。3.4 接入 TaoToken 的调用封装MCP 服务端本身不直接调模型它是被 Claude Code 这类客户端调用的。但我在服务端里加了一个可选的自检通道用来验证 TaoToken 通道是否通。封装很简单std::string call_taotoken(const std::string prompt) { auto cfg load_config(config.toml); std::string url cfg[taotoken][base_url].value_or() /v1/messages; std::string key get_api_key(cfg); json body { {model, cfg[taotoken][model]}, {max_tokens, 2048}, {messages, {{{role, user}, {content, prompt}}}} }; auto resp http_post(url, { {x-api-key, key}, {anthropic-version, 2023-06-01}, {content-type, application/json} }, body.dump()); return resp; }注意 base_url 用的是 https://taotoken.net/api 后面拼 /v1/messages 走 Anthropic 兼容格式。如果你用的是 OpenAI 兼容格式拼 /v1/chat/completions 就行TaoToken 两种都支持。4. 验证请求从抓帧到 AI 问答跑通一次4.1 编译与启动 MCP 服务端先编译mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease cmake --build . --config Release -j 8编译产物是 renderdoc-mcp 可执行文件。启动的时候它会从 stdio 读 JSON-RPC 消息这是 MCP 的标准传输方式。4.2 在 Claude Code 里注册 MCP 服务Claude Code 的 MCP 配置在 settings 里加一段{ mcpServers: { renderdoc: { command: /path/to/renderdoc-mcp, args: [--config, /path/to/config.toml], env: { TAOTOKEN_API_KEY: sk-你的TaoToken密钥 } } } }配好之后重启 Claude Code它会自动拉起 MCP 服务端握手、列 Tools。你可以在对话里输入 /mcp 看连接状态正常的话能看到 renderdoc 这个 server 和它暴露的 59 个 Tool。4.3 一次完整的帧捕获到 AI 问答我拿之前那个黑块的场景做验证。先在 RenderDoc 里抓一帧存成 black_issue.rdc然后在 Claude Code 里输入帮我打开 ./captures/black_issue.rdc画面右上角有块黑色区域看看是哪里不对AI 的调用链是这样的第一步调 open_capture 打开抓帧拿到基本信息{ capture_id: sess_001, api: D3D11, draw_calls: 847, passes: 12 }第二步它调 list_draw_calls 按区域筛选找出覆盖右上角的 Draw Call。这里我在 Tool 里加了个 region 参数传归一化坐标服务端用 Viewport 和 Scissor 做过滤{ region: {x: 0.7, y: 0.0, w: 0.3, h: 0.3}, matched: [412, 413, 415, 501] }第三步对每个候选 Draw 调 get_draw_state检查 Pipeline。AI 发现 event 415 的 Scissor Rect 是 (0, 0, 0, 0)等于把整个绘制裁掉了{ event_id: 415, rasterizer: { scissor: true, scissor_rects: [[0, 0, 0, 0]] } }第四步它调 export_render_target 把这一帧的 RT 导成 PNG确认黑块位置和 Scissor 区域吻合。最后 AI 给出的结论是event 415 的 Scissor Rect 被错误地设成了零面积导致该 Pass 的绘制全部被裁掉右上角因此显示为背景色黑色。修复建议是检查设置 Scissor 的那段代码确认是不是用了未初始化的矩形。整个过程我只打了一句话AI 自己调了 6 次 Tool从 847 个 Draw Call 里定位到了问题。这就是我想要的体验。4.4 验证 TaoToken 通道如果你想单独验证 TaoToken 通道是否通可以用服务端自带的自检命令./renderdoc-mcp --selftest --config config.toml它会往 TaoToken 发一条测试消息打印返回。正常的话能看到模型回复。这一步能排除掉是 MCP 的问题还是 API 通道的问题。5. 本篇常见错排查5.1 MCP 服务端启动就退出最常见的原因是 config.toml 路径不对或者 RenderDoc 的 install_path 写错了。MCP 服务端启动时会去加载 RenderDoc 的库路径不对直接崩。检查方法是在终端手动跑一次./renderdoc-mcp --config config.toml --verbose看它打印的日志一般会明确告诉你哪一步失败了。5.2 Claude Code 里看不到 renderdoc 这个 server先确认 command 路径是绝对路径相对路径在 Claude Code 的工作目录下经常找不到。然后确认 env 里的 TAOTOKEN_API_KEY 传进去了。如果还不行看 Claude Code 的 MCP 日志通常在 ~/.claude/logs/ 下面。5.3 调 Tool 返回 capture not found抓帧文件的路径问题。MCP 服务端的工作目录和你在对话里写的相对路径可能不是同一个。建议在 config.toml 里把 capture_dir 设成绝对路径对话里也用绝对路径。5.4 TaoToken 返回 401Key 不对或者没传。检查两点一是环境变量 TAOTOKEN_API_KEY 有没有设二是 config.toml 里的 base_url 是不是 https://taotoken.net/api 别多写或少写路径。如果用的是 OpenAI 兼容格式确认 endpoint 拼的是 /v1/chat/completions。5.5 Diff 引擎报 session limit exceededconfig.toml 里 max_concurrent_sessions 设小了。Diff 要同时开两个 Session如果设成 1 就会失败。改成 4 或更大。5.6 Shader 反汇编返回空RenderDoc 反汇编 Shader 需要对应的后端支持。D3D11 的 HLSL 反汇编一般没问题Vulkan 的 SPIR-V 需要装 spirv-cross。确认你的 RenderDoc 版本带了这些工具路径在 install_path 下面。6. 把 MCP 接到你的工作流写这个 MCP 服务端最大的收获不是 59 个 Tool 本身而是想清楚了一件事图形调试的信息密度太高人脑不适合做第一遍筛选AI 适合。MCP 协议给了标准化的桥接方式TaoToken 给了统一的模型通道两者一接RenderDoc 的专业能力就能被 AI 直接调用。如果你也想自己搭一套建议从最小的 Tool 开始——先做 open_capture 和 list_draw_calls跑通 MCP 握手和一次 Tool 调用再逐步加 Pipeline 查询、Diff、断言。接入配置上API Keys 在 https://taotoken.net/api-keys 管理接入文档在 https://taotoken.net/doc 模型对话调试在 https://taotoken.net/chat 。如果你打算长期在编码和 Agent 场景里用Coding Plan 会更划算入口在 https://taotoken.net/coding-plan 。我踩过的坑基本都写在上面了尤其是 config.toml 路径和 Session 并发这两个第一次配的时候卡了不少时间。先把这两块弄对后面加 Tool 就是体力活了。
返回列表