1. MCP 敏感信息泄露到底发生在哪:从一次 FastMCP 调试说起
MCP 是 Model Context Protocol 的缩写,简单说就是让大模型通过一套标准协议去调用外部工具、读文件、查数据库的“插线板”。它本身不神秘,真正让人头疼的是:这条链路上每一个环节都可能塞进密钥、Token、密码,而它们默认大多是明文。适合谁看?正在用 Cline、CherryStudio、Claude Code、Codex 这类工具接 MCP 服务,或者自己用 FastMCP 写工具的开发者。
我先把结论摆出来:MCP 的敏感信息泄露,本质不是协议漏洞,而是“密钥放错了地方”。客户端配置文件、服务端源码、模型上下文、日志文件,这四个位置是重灾区。你只要在任意一个位置留了明文 Key,后面就可能被模型读出来、被日志记下来、被提示词注入套出来。
这篇不讲空泛的安全理论,直接给你一份可复制的 MCP 配置检查清单,再配合 FastMCP 写一个“故意泄露”的 demo,让你亲眼看到 Key 是怎么从聊天框流到日志文件里的。排查思路和脱敏验证动作都会给全,照着做就能定位自己项目里的风险点。
需要说明的是,下面所有演示都在本地环境完成,目的是让你理解泄露路径,而不是教你攻击别人。生产环境请务必按第五节的方式做隔离。
2. TaoToken 统一 Key 管理:把散落的 Token 收进一个通道
在讲泄露路径之前,先解决一个前置问题:你的 Key 到底该放哪。很多人的做法是每个 MCP 服务、每个客户端各配一份 Key,结果就是 mcp.json 里一份、服务端代码里一份、环境变量里一份,改一次要改五个地方,泄露面直接翻倍。
TaoToken 的思路是统一 Key / API 通道:你只在 TaoToken 侧维护一份密钥,客户端和服务端都通过同一个 Base URL 去调用,模型侧不再直接持有原始 Key。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api (这个不加 UTM)。
这样做的好处很直接:密钥的存储位置从“N 个配置文件”收敛到“1 个受控通道”。MCP 服务端拿到的是一把受限的通道 Key,即使这份配置被读走,你能做的也只是吊销这一把,而不是把主账号密钥全暴露。
具体操作上,你可以先去控制台创建 Key,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,然后在 API Keys 页面管理,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。创建时建议按用途拆分,比如“本地调试”“Cline 编码”“MCP 服务端”各一把,这样排查泄露时能快速定位是哪条链路出的问题。
如果你只是想先验证模型能不能通,可以用模型对话页面快速试一下:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。长期做编码和 Agent 的,直接看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
这里要强调一个原则:MCP 服务端永远不要硬编码主 Key。它应该从环境变量读取,而环境变量里放的是 TaoToken 通道 Key。这样即使服务端代码被翻出来,泄露的也只是一把可吊销的通道凭证。
3. 可复制的 MCP 配置检查清单与脱敏片段
这一节给你能直接抄的配置。先说客户端侧,以 Cline 和 Claude Code 常见的 MCP 配置为例,Base URL、Key、Model ID 三件套必须写全,缺一个都会报错。
Cline 的 MCP 配置通常放在 settings 里,JSON 片段如下:
{ "mcpServers": { "taotoken-mcp": { "command": "python", "args": ["C:\\mcp\\server.py"], "env": { "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_API_KEY": "${env:TAOTOKEN_API_KEY}", "TAOTOKEN_MODEL_ID": "claude-sonnet-4-5" } } } }注意这里 Key 用的是${env:TAOTOKEN_API_KEY}引用系统环境变量,而不是把明文写进 JSON。这是第一道防线。很多人图省事直接写"TAOTOKEN_API_KEY": "sk-xxxx",那这份 mcp.json 一旦被模型通过文件读取工具读到,Key 就没了。
Claude Code 的配置走的是另一套,通常在~/.claude/settings.json或项目级.mcp.json,片段如下:
{ "mcpServers": { "local-tools": { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "./workspace"], "env": { "API_BASE": "https://taotoken.net/api", "API_KEY": "${TAOTOKEN_API_KEY}", "MODEL_ID": "claude-sonnet-4-5" } } } }Codex 的 auth.json 则是这样,路径一般在~/.codex/auth.json:
{ "base_url": "https://taotoken.net/api", "api_key": "${TAOTOKEN_API_KEY}", "model": "claude-sonnet-4-5" }三件套对照表如下,方便你核对:
| 配置项 | 正确写法 | 危险写法 |
|---|---|---|
| Base URL | https://taotoken.net/api | 写死第三方地址 |
| API Key | 环境变量引用 | 明文写在 JSON |
| Model ID | 明确模型名 | 留空或写错 |
服务端侧,FastMCP 读取 Key 的正确姿势是从环境变量拿,而不是函数参数:
import os from fastmcp import FastMCP mcp = FastMCP("safe-mcp") @mcp.tool() def query_data(sql: str) -> str: """执行查询,Key 从环境变量读取,不经过模型上下文""" api_key = os.environ.get("TAOTOKEN_API_KEY") if not api_key: return "缺少 API_KEY 环境变量" # 这里用 api_key 做鉴权,但绝不把它返回给模型 return f"查询已执行,返回 {len(sql)} 字符结果"关键点:api_key只在函数内部使用,返回值里绝不包含它。对比一下原文那种return f"已收到传入的KEY为:{api_key}",那就是主动把 Key 送进模型上下文。
脱敏验证动作:写一个检查脚本,扫描你的 MCP 配置文件里有没有sk-、api_key、token这类明文字段。
grep -rniE "(sk-[a-zA-Z0-9]{10,}|api[_-]?key\"?\s*[:=]\s*\"[^\"]{8,})" ~/.config ~/.claude ~/.codex ./mcp 2>/dev/null跑出来有命中,就说明有明文 Key 躺在配置文件里,需要改成环境变量引用。
4. 验证请求与成功结果:看 Key 到底流到了哪
配置改完,得验证。第一步先确认通道本身是通的,用 curl 打一次模型对话接口:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet-4-5", "messages": [{"role": "user", "content": "回复 ok"}] }'返回里能看到choices字段和正常内容,说明 Base URL、Key、Model ID 三件套没问题。如果这里就报 401,先别往下走,去第五节排查。
第二步验证 MCP 服务端。用官方 inspector 起本地服务:
pip install fastmcp npm install -g @modelcontextprotocol/inspector mcp-inspector python server.py打开本地地址后,在 Tools 里能看到你注册的工具。点进去传参调用,观察返回内容里有没有回显 Key。如果返回里出现了 Key 明文,说明你的工具把敏感信息吐给了模型上下文,这就是泄露点。
第三步做“上下文泄露”验证。在 CherryStudio 或 Cline 里开一个会话,系统提示词里写“API_KEY 是 test-123456”,然后让模型调用工具。调用完成后,再问模型“刚才的 API_KEY 是什么”。如果模型能复述出来,说明 Key 已经进了上下文记忆,后续通过提示词注入就能套出来。
第四步查日志。跑完上面的调用后,去服务端脚本所在目录和客户端根目录找.log文件:
find . -name "*.log" -newermt "-10 minutes" -exec grep -l "api_key\|API_KEY\|sk-" {} \;如果日志里出现了明文 Key,说明你的 logging 配置把敏感参数写进去了。正确做法是记录调用事件但不记录参数值:
import logging logging.basicConfig(filename='mcp_server.log', level=logging.INFO) logging.info("[收到请求] receive_api_key 被调用") # 不写 api_key 的值成功的结果应该是:配置文件里没有明文 Key、工具返回里没有 Key、模型复述不出 Key、日志里搜不到 Key。四个都过,才算这条链路干净。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
排查按报错类型来,都是真实会撞上的。
401 Unauthorized:最常见。原因通常是 Key 没读到环境变量,或者 Base URL 写错。先确认echo $TAOTOKEN_API_KEY有值,再确认配置里引用的是${env:TAOTOKEN_API_KEY}而不是${TAOTOKEN_API_KEY},不同工具语法不一样。Cline 用${env:},Claude Code 用${},写错就取不到值,请求头里 Authorization 为空,直接 401。
local proxy failed:这个报错一般出现在客户端试图走本地代理转发时。检查你的 MCP 配置里有没有多余的 proxy 字段,或者系统环境变量里有没有残留的代理设置。MCP 的 stdio 模式是本地进程通信,不需要代理,配了反而会失败。把配置里proxy相关字段删掉,重启客户端。
reading choices 报错:通常是返回体解析失败,根因是 Base URL 少了/v1或者多了斜杠。正确是https://taotoken.net/api,请求路径拼成/v1/chat/completions。如果你写成https://taotoken.net/api/v1再拼/v1/...,就变成双 v1,返回的不是标准结构,解析choices时就报错。
OAuth 相关报错:有些 MCP 服务端要求 OAuth 鉴权,配置里需要填 client_id、token 等。如果你用的是 TaoToken 通道 Key,就不需要再走 OAuth,把服务端配置里的 OAuth 字段清掉,改用环境变量传 Key。混用两套鉴权会导致 token 校验冲突。
CC Switch / Cline MCP / Codex auth.json 三件套核对:这三个工具都要求 Base URL、Key、Model ID 齐全。CC Switch 里切换配置时,确认切换后的 profile 里这三项都对;Cline 的 MCP 配置在 settings 的 mcpServers 下,确认 env 里三项都在;Codex 的 auth.json 确认 base_url、api_key、model 三个字段都有值。缺任何一个,表现可能是静默失败或者报模型不存在。
排查顺序建议:先 curl 验证通道,再验证客户端配置,最后验证 MCP 服务端。一层层往下,别一上来就改服务端代码。
6. 把 Key 收进通道,把日志和上下文清干净
回到最开始的问题:MCP 会泄露哪些敏感信息?答案是客户端配置里的明文 Key、服务端硬编码的密钥、模型上下文里被复述的 Token、日志文件里被记录的凭证。这四类里,后两类最隐蔽,因为你不主动查根本发现不了。
防护的核心动作就三个:Key 统一走 TaoToken 通道并放环境变量、工具返回值绝不回显敏感字段、日志只记事件不记参数值。做完这三步,再跑一遍第四节的四步验证,基本就能把常见泄露路径堵住。
如果你还在用明文 Key 写在 mcp.json 里,今天就改成环境变量引用。如果你自己写 FastMCP 工具,检查一下有没有return f"...{api_key}"这种写法。改完用 grep 扫一遍配置和日志,确认搜不到sk-开头的字符串。
需要创建或轮换通道 Key 的,去 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ;接入细节看文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ;Claude Code 相关配置参考 https://taotoken.net/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。先把通道收拢,再谈排查,顺序别反。