1. 为什么要抓 Claude 的包从「黑盒对话」到「看得见的请求链路」很多人用 Claude Code 或者 Claude API 的时候心里其实没底我发一句话它到底往服务器发了什么为什么有时候回答到一半断了为什么明明本地有历史记录翻出来却只有对话内容看不到模型和工具之间来回调用的细节我自己最开始也是这个状态。.claude/project目录里确实有日志看起来像传统 log但仔细一看里面只有用户和助手的对话文本真正关键的「模型决策 → 调用工具 → 工具返回 → 再喂给模型」这条循环链路全被[REDACTED]盖住了。日志里能看到类似这样的行2026-08-10T07:09:18.990Z [DEBUG] autocompact: tokens[REDACTED] levelok effectiveWindow180000token 数被脱敏工具调用的中间态也没暴露。也就是说光靠客户端日志你没法确认 Claude 是不是真的按你设想的「组装系统提示 用户指令 工具执行记录 文件内容 → 发 API → 收流式响应 → 再循环」在跑。这时候抓包就是最直接的手段。Claude 的 API 走的是 HTTPS请求体是 JSON响应是 SSEServer-Sent Events流式分片。用 mitmproxy 做中间人就能把请求头、请求体、每一个 chunk 都摊开看。这篇笔记聚焦的就是这条链路可视化在 TaoToken 统一 Key / API 通道下用 mitmproxy 抓取 Claude 请求与 SSE 流式响应逐帧解析请求头、body 与 chunk 结构并给出可复制的脚本、证书配置和过滤规则。适合谁看适合已经在用 Claude Code 或 Claude API、想搞清楚「它到底做了什么」的人也适合想验证流式分片完整性、排查 401 / 代理失败 / 响应截断这类问题的同学。不需要你懂密码学但需要你能在终端里跑命令、改环境变量。核心检索词先摆出来Claude 抓包、mitmproxy 流式响应解析、Claude API 请求全貌。这三个词贯穿全文你按这个思路往下看就行。抓包的目的不是「偷看」而是验证和排障。验证的是一次请求 用户问题 系统提示 工具/技能清单一起发给 LLM排障的是当流式响应中断、当代理配置不生效、当返回 401 时你能定位到是请求头没带对还是证书没信任还是代理变量被覆盖。下面从环境准备开始一步步来。2. TaoToken 统一 Key 前置把 Base URL、Key、Model ID 三件套先理顺抓包之前得先有一个稳定的 API 通道否则你抓到的可能是一堆连接失败。这里我用 TaoToken 的统一 Key 通道来演示原因是它把 Base URL 和 Key 的管理收敛到一处抓包时请求头里的鉴权字段清晰便于对照。先把三件套说清楚这是后面所有配置的基础项目值说明Base URLhttps://taotoken.net/apiAPI 请求根地址不带 UTMAPI Key在控制台生成形如sk-...请求头里用Model ID例如claude-sonnet-4-5等按你实际开通的模型填控制台入口在这里https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Key 生成页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite如果你还没决定用哪个模型可以先在模型对话页试一下https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite长期做编码或 Agent 任务的话Coding Plan 更合适https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteClaude Code 专用接入说明https://taotoken.net/claudecode?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite为什么强调「统一 Key」因为抓包时你会看到请求头里带着鉴权信息。如果 Key 来源混乱你分不清是哪个通道在发请求排查 401 时就会绕圈。统一到一个 Key请求头里的Authorization或x-api-key就是唯一标识对照起来干净。这里要提醒一句抓包环境里不要把生产环境的 Key 直接暴露在共享终端里。mitmproxy 会把请求头完整记录下来如果你把抓包文件发给别人Key 就泄露了。建议单独生成一个测试用 Key抓完就删。配置 Claude Code 走 TaoToken 通道核心是设置ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY或对应的鉴权变量。不同版本变量名可能略有差异以接入文档为准。下面给一个通用的环境变量写法export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-你的测试Key export ANTHROPIC_MODELclaude-sonnet-4-5如果你用的是 Claude Code 的 settings 文件可以写成 JSON。路径通常在~/.claude/settings.json或项目级.claude/settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的测试Key, ANTHROPIC_MODEL: claude-sonnet-4-5 } }注意这个 JSON 片段里的路径和字段名要和你本地实际文件一致不要照抄字段名到不存在的文件里。改完用cat ~/.claude/settings.json确认一下。三件套理顺之后再启动 mitmproxy抓到的请求才有意义。否则你抓到的可能只是「连接被拒绝」或者「证书错误」看不到真正的请求体。3. 可复制配置mitmproxy 安装、证书信任与 Claude 走代理这一节是全文最需要动手的部分。我按「安装 → 启动 → 证书 → 代理 → 过滤」的顺序来每一步都给可复制的命令。3.1 安装 mitmproxyUbuntu 22.04 可以直接用 apt也可以用 pip 装最新版。pip 版本通常更新更快sudo apt update sudo apt install -y mitmproxy # 或者用 pip 安装最新版推荐 pip3 install mitmproxy装完有三个命令行工具用途不同mitmproxy终端 TUI 界面交互式查看流量适合键盘操作。mitmweb浏览器 Web 界面默认在http://127.0.0.1:8081更直观。mitmdump纯命令行输出适合重定向到文件或写脚本。我平时用mitmweb看结构用mitmdump配合脚本做过滤和落盘。3.2 启动 mitmweb# 终端 1启动代理服务器默认监听 8080 端口 mitmweb --listen-port 8080启动后浏览器会自动打开http://127.0.0.1:8081这是流量查看面板暂时是空的。3.3 安装 mitmproxy CA 证书HTTPS 流量是加密的mitmproxy 必须作为中间人解密。第一次使用需要安装它的根证书# 先跑一下让证书生成 mitmdump sleep 2 kill %1 # 将 mitmproxy CA 证书添加到系统信任 sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy.crt sudo update-ca-certificates如果你用的是 Node 系工具Claude Code 就是 Node 写的系统证书信任还不够Node 有自己的证书链。抓包时可以临时跳过证书验证export NODE_TLS_REJECT_UNAUTHORIZED0注意这个变量只建议在抓包调试时用抓完就取消。长期开着会降低安全性。3.4 让 Claude Code 走代理先装 Claude Code 命令行版本# 初始化 package.json如果还没有 npm init -y # 本地安装 Claude Code不加 -g npm install anthropic-ai/claude-code # 安装完成后命令行入口在 ./node_modules/.bin/claude然后配置代理。这里有个坑系统或终端里可能已经有旧的代理变量会覆盖你新设的。所以先彻底清除再重新设置大小写都设一遍# 1. 彻底清除所有旧代理变量 unset http_proxy HTTP_PROXY https_proxy HTTPS_PROXY all_proxy ALL_PROXY no_proxy NO_PROXY # 2. 重新正确设置小写大写都设防止混淆 export http_proxyhttp://127.0.0.1:8080 export https_proxyhttp://127.0.0.1:8080 export HTTP_PROXYhttp://127.0.0.1:8080 export HTTPS_PROXYhttp://127.0.0.1:8080 # 3. 清除 no_proxy否则某些域名会绕过代理 unset no_proxy NO_PROXY # 4. 验证 echo https_proxy$https_proxy echo HTTPS_PROXY$HTTPS_PROXY # 5. 测试 curl -v -k https://www.baidu.com 21 | head -30如果curl能通说明代理链路是活的。然后启动 Claude./node_modules/.bin/claude3.5 过滤规则只看 Claude 相关流量mitmproxy 默认抓所有流量噪音很大。用过滤表达式只看目标域名。在mitmweb的 Filter 输入框里填~u taotoken\.net或者用mitmdump启动时直接带过滤mitmdump --listen-port 8080 -f ~u taotoken.net如果你想把请求体和响应体落盘写一个简单的 addon 脚本save_claude.pyfrom mitmproxy import http import json import time def response(flow: http.HTTPFlow) - None: if taotoken.net not in flow.request.pretty_host: return ts time.strftime(%Y%m%d-%H%M%S) req_path fclaude-req-{ts}.json resp_path fclaude-resp-{ts}.txt with open(req_path, w, encodingutf-8) as f: f.write(flow.request.text or ) with open(resp_path, w, encodingutf-8) as f: f.write(flow.response.text or ) print(fsaved {req_path} / {resp_path})启动mitmdump --listen-port 8080 -s save_claude.py这样每次 Claude 发请求你都会得到一份请求 JSON 和一份响应文本方便逐帧分析。3.6 一个可复制的 settings 片段如果你用 Claude Code 的 settings 文件把代理和通道配置写在一起路径以你本地为准{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的测试Key, ANTHROPIC_MODEL: claude-sonnet-4-5, HTTP_PROXY: http://127.0.0.1:8080, HTTPS_PROXY: http://127.0.0.1:8080, NODE_TLS_REJECT_UNAUTHORIZED: 0 } }改完重启 Claude触发一次对话回到mitmweb面板应该就能看到请求了。4. 验证请求与流式响应逐帧拆解 request body 和 SSE chunk抓包成功后最关键的是看懂两样东西request 里的 JSON和 response 里的 SSE 分片。4.1 request 里到底装了什么在mitmweb里点开一条POST /v1/messages请求看 Request Body。你会看到类似这样的结构篇幅原因只贴关键部分{ model: claude-sonnet-4-5, messages: [ { role: user, content: [ { type: text, text: system-reminder\nAs you answer the users questions, you can use the following context:\n# currentDate\nTodays date is 2026-08-17.\n/system-reminder\n\n你是谁守则是什么 } ] } ], tools: [ { name: Bash, description: Run a shell command, input_schema: { type: object, properties: { command: { type: string } } } } ], stream: true }这段 JSON 说明了几件事第一用户原始问题「你是谁守则是什么」和系统注入的上下文当前日期、Agent 类型说明、可用 Skills 说明被打包在同一个messages数组里。系统提示不是单独字段而是以system-reminder这种文本形式混在用户消息里。第二tools字段是工具定义的 JSON Schema 列表。这就是「把问题和工具都提供给 LLM」的含义——模型不是凭空知道有哪些工具而是每次请求都带着工具清单。第三stream: true表示要流式返回。这就是为什么响应是 SSE 分片。核心结论一次请求 用户问题 系统提示 工具/技能清单一起发给 LLM。抓包之前你可能只是「感觉」是这样抓包之后是「看见」了。4.2 response 的 SSE 分片结构响应体是text/event-stream每个事件由event:和data:两行组成。典型序列如下event: message_start data: {type:message_start,message:{id:msg_claude_188,type:message,role:assistant,content:[],model:claude-sonnet-4-5,stop_reason:null,usage:{input_tokens:0,output_tokens:0}}} event: content_block_start data: {type:content_block_start,index:0,content_block:{type:text,text:}} event: content_block_delta data: {type:content_block_delta,index:0,delta:{type:text_delta,text:我是}} event: content_block_delta data: {type:content_block_delta,index:0,delta:{type:text_delta,text: Claude}} event: content_block_delta data: {type:content_block_delta,index:0,delta:{type:text_delta,text: Code}} event: content_block_stop data: {type:content_block_stop,index:0} event: message_delta data: {type:message_delta,delta:{stop_reason:end_turn},usage:{output_tokens:42}} event: message_stop data: {type:message_stop}逐帧看message_start响应开始带消息元数据和初始 usage此时 token 数还是 0。content_block_start文本块开始index标识块序号。content_block_delta一个个 token 增量返回。注意「我是」「 Claude」「 Code」是分开的这就是打字机效果的来源。content_block_stop文本块结束。message_delta带stop_reason和最终 usage。message_stop整个消息结束。4.3 验证流式分片完整性的具体动作怎么确认分片没丢我一般做三件事第一数content_block_delta的条数和最终output_tokens对照。虽然 token 数和 delta 条数不是严格 1:1一个 delta 可能含多个 token但数量级应该对得上。如果 delta 只有几条而 output_tokens 是几百说明中间被截断了。第二把每个text_delta的text字段按顺序拼接看是否等于最终完整回答。写个小脚本import json deltas [] with open(claude-resp-20260817-120000.txt, encodingutf-8) as f: for line in f: line line.strip() if line.startswith(data: ): payload line[6:] try: obj json.loads(payload) except json.JSONDecodeError: continue if obj.get(type) content_block_delta: deltas.append(obj[delta].get(text, )) full .join(deltas) print(fdelta count: {len(deltas)}) print(ffull text length: {len(full)}) print(full[:200])第三检查是否有message_stop事件。如果响应在content_block_delta中途就断了没有message_stop那基本可以判定是网络或代理层截断而不是模型主动结束。4.4 用 mitmproxy 脚本实时统计分片如果你想在抓包时实时看分片数可以扩展前面的 addonfrom mitmproxy import http def response(flow: http.HTTPFlow) - None: if taotoken.net not in flow.request.pretty_host: return body flow.response.text or delta_count body.count(content_block_delta) has_stop message_stop in body print(f[claude] deltas{delta_count} has_stop{has_stop} status{flow.response.status_code})跑起来后每完成一次请求终端就会打印分片数和是否正常结束。这个动作对排查「回答到一半没了」特别有用。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth抓包过程中最容易卡在几个固定报错上。我按真实遇到的顺序列出来对照着查。5.1 401 Unauthorized现象请求发出去了但响应是 401body 里提示鉴权失败。排查顺序先看请求头里有没有带 Key。在mitmweb里点开请求看 Headers找Authorization或x-api-key。如果为空说明环境变量没生效。再确认 Key 有没有多余空格或换行。从控制台复制时容易带上换行。用echo -n $ANTHROPIC_API_KEY | wc -c看长度是否符合预期。最后确认 Base URL 和 Key 是配套的。如果你用 TaoToken 的 KeyBase URL 必须是https://taotoken.net/api不能混用其他地址。三件套再贴一次方便对照Base URLhttps://taotoken.net/apiKey控制台生成sk-开头Model ID按实际开通填5.2 local proxy failed / 代理连接失败现象Claude 启动时报代理连接失败或者curl测试不通。原因通常是代理变量被覆盖或者 mitmproxy 没在监听。先确认 mitmproxy 在跑ss -lntp | grep 8080应该能看到监听。再确认变量env | grep -i proxy看大小写是否都设了有没有残留的no_proxy。如果之前设过系统级代理终端里的unset只影响当前会话。新开终端要重新设。5.3 reading choices / 响应解析失败现象客户端报reading choices之类的错误通常是 OpenAI 格式和 Anthropic 格式混用导致的。Claude 的 API 响应是 SSE事件类型是message_start/content_block_delta不是 OpenAI 的choices数组。如果你用了一个按 OpenAI 格式解析的客户端去接 Claude 通道就会报这个错。解决确认客户端走的是 Anthropic Messages API 格式Base URL 指向https://taotoken.net/api不要指向 OpenAI 兼容端点除非文档明确说明。5.4 OAuth / 登录态冲突现象Claude Code 提示 OAuth 相关错误或者登录态和 API Key 冲突。Claude Code 支持 OAuth 登录和 API Key 两种模式。如果你同时配了 OAuth 和ANTHROPIC_API_KEY可能冲突。抓包场景下建议只用 API Key 模式把 OAuth 相关配置清掉。检查~/.claude/下有没有残留的凭据文件必要时备份后移除重新用 Key 登录。5.5 证书错误现象SELF_SIGNED_CERT_IN_CHAIN或unable to verify the first certificate。这是 Node 不信任 mitmproxy 证书。临时方案是NODE_TLS_REJECT_UNAUTHORIZED0长期方案是把 mitmproxy CA 证书导入 Node 的信任链。抓包调试用临时方案就够了。5.6 抓不到包如果mitmweb面板一直是空的按这个顺序查Claude 进程有没有继承代理变量在启动 Claude 的同一个终端里echo $https_proxy确认。过滤规则是不是写错了先去掉过滤看有没有任何流量。请求是不是走了别的域名在面板里搜taotoken或anthropic。是不是用了 HTTP/2 或 QUICmitmproxy 对部分协议支持有限可以在客户端强制 HTTP/1.1 试试。排查完这些基本能覆盖 90% 的抓包失败场景。6. 把抓包变成日常习惯从「看清一次」到「随时可查」抓包这件事做一次是好奇做成习惯才是能力。我现在遇到 Claude 行为异常第一反应不是猜而是开 mitmproxy 抓一段。几个实用技巧都是踩过坑之后留下的第一把抓包脚本和过滤规则存成文件别每次手敲。我放在~/claude-debug/下save_claude.py和start.sh各一份需要时bash start.sh就起来。第二抓包文件按时间命名方便回溯。前面脚本里的claude-req-{ts}.json就是这个思路。抓完一批用grep -l 401 claude-resp-*.txt就能快速定位鉴权失败的请求。第三Key 用完就换。抓包文件里含完整请求头测试 Key 抓完就删别留在磁盘上。第四流式分片完整性检查脚本可以做成定时任务。如果你在跑长任务隔一段时间检查一次有没有message_stop能提前发现截断。第五把抓包结论写进项目笔记。比如「本次请求 tools 字段包含 Bash、Read、Edit 三个工具」下次行为异常时对照能快速判断是不是工具清单变了。最后给一个日常排查的入口组合排障和接入看 API Keys 和接入文档验证模型行为去模型对话页长期编码和 Agent 任务用 Coding Plan。链接都在前面第二节按需取用。抓包不是终点看清链路之后你对 Claude 的每一次调用都会更有把握。