ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MCP协议并非AI的USB-C:六大安全风险深度拆解

MCP协议并非AI的USB-C:六大安全风险深度拆解 如果只看各家宣传MCPModel Context Protocol模型上下文协议几乎是过去两年AI基础设施里最像“USB-C”的东西一套协议连接AI与数据。无论是让大模型读数据库、操作浏览器、还是调用企业内部APIMCP Server一次部署Claude、Cursor、各类IDE智能插件都能直接识别、插上就用。这种统一接口带来的“即插即用”体验确实解决了过去AI应用各自为战、集成成本居高不下的痛点。但问题是USB-C之所以好用是因为物理接口有标准协商机制该传电流时传电流该传数据时传数据而MCP在这件事上并没有真正做到——它把“接口统一”做到了却把“权限边界”“调用审计”这些本该同步标准化的安全机制遗留给了开发者自己。这篇内容先把MCP的定位、架构和运行流程讲清楚然后重点拆解我在真实工程环境里看到的六个安全问题权限放大、工具链成为C2通道、安全上下文断裂、供应链投毒、凭证泄露、审计缺失。每一类都有实际案例或攻击路径不是为了制造焦虑而是因为MCP正在快速进入企业生产环境大家如果只看到效率、不看边界后面踩坑的代价会非常大。文章面向两类人一类是准备给团队引入MCP做Agent基础设施的架构师/后端另一类是正在用Cursor、Claude等工具但没想过“这些工具背后的Server到底拿到多少权限”的开发者。下文所有结论都来自我在实际项目中部署MCP Server、设计权限模型、处理安全事故的真实经历。1. MCP协议是什么AI生态的“USB-C接口”背后到底解决了什么问题1.1 为什么很多人把MCP称为AI的USB-C过去一年我参与过不少Agent项目最痛的点就是“模型和系统之间的胶水代码”太多。今天要接一个Slack机器人明天要接一个数据库查询后天要接公司内部的工单系统每一个连接都要单独写一套适配层OpenAI Function Calling有自己的一套格式LangChain插件又有另一套抽象到了企业内部系统还得按对方的API风格定制调用。本质上模型本身越来越聪明但它碰不到外界而“让模型碰到外界”这个动作每个团队都在重复造轮子。MCP做的事情就是把“模型↔工具/数据源”之间的会话格式、调用方式、生命周期统一成一瓶“标准接口液”。一个MCP Server实现好之后任何支持MCP的Host客户端宿主都能直接加载它就像你把一个USB-C设备插到任何一台有USB-C口的电脑上那样。Claude Desktop能用Cursor能用其他兼容MCP的Agent框架也能用不需要为每个平台单独维护一套适配代码。这就是MCP被称为“AI生态USB-C接口”的原因连接面的标准化。但和物理USB-C不一样物理接口有主从协商、电压握手、协议识别MCP在这套东西上还很原始。USB-C接口本身不知道你插的是什么设备但至少会先通过握手确定能不能供电MCP默认的很多交互里Host几乎无条件信任Server端暴露出来的工具列表——潜在风险的种子就从这里埋下。坦白说这个比喻只成立了一半接口形态确实统一了但“安全协商”这个本应和接口一块儿设计的东西被严重后置了。1.2 核心角色拆解Host、Client、Server谁在指挥谁在执行MCP的架构非常轻轻到很多第一次接触的人会低估它。它只有三个核心角色MCP Host用户直接打交道的AI应用比如Claude Desktop、Cursor或者你自己写的Agent程序。Host负责接收用户的输入把输入交给大模型理解并决定“要不要调用某个工具”“调用哪个”。MCP ClientHost内部负责和MCP Server通信的连接器组件通常以SDK形式存在。它管连接建立、协议握手、消息收发。MCP Server把具体能力暴露出来的服务端程序。一个Server可以暴露三个维度的能力工具Tools、资源Resources和提示Prompts。比如一个GitHub MCP Server能暴露create_issue这样的工具也能暴露仓库文件列表这样的资源。典型的调用链是这样的用户在Host里问“帮我把仓库里所有待修复的Issue整理成一个表格。”Host把这句话发给大模型模型看到工具列表里有list_open_issues、get_issue_detail决定先调用前者再循环调用后者。大模型生成一个工具调用请求Host通过MCP Client把它发给MCP Server。MCP Server执行真实操作请求GitHub API把结果返回给Host。Host把结果塞回给大模型模型继续推理最终生成回复。这个流程看起来很像传统的API调用但区别在于传统API调用谁是调用方谁是被调用方非常清晰而MCP场景下“用户意图”通过大模型这个不可预测的中间层间接驱动工具执行。模型的判断可能受提示词注入影响可能受上下文误导这种不确定性会放大工具调用链的风险。2. MCP怎么跑起来一次完整请求的运行流程与技术核心2.1 一次真实的MCP会话从握手到工具调用我以一个实际项目为例我给团队内部做了一个数据库查询MCP Server暴露两个工具一个query_database一个fetch_schemaHost端接的是Claude Desktop。完整的调用流程大致是这样的。对话初始阶段Host需要和Server建立会话走的是MCP协议的初始化握手流程Host发送initialize请求带上自己的协议版本和客户端信息。Server返回自己支持的协议版本、Server信息、以及自身能力声明。双方确认版本兼容之后Host发送initialized通知Server进入就绪状态。随后Host自动发起tools/list请求拿到Server所有工具的定义清单包括工具名、描述、入参的JSON Schema。这一阶段特别值得一提tools/list返回的是一个全量清单。以我的数据库Server为例如果我把query_database和fetch_schema都注册进去了那么Host从一开始就知道这两个工具的存在任何一个有权限调用工具的模型都能看到它们。之后用户问“帮我查一下本月订单量最大的前十位客户。”模型决定调用工具模型输出调用请求格式是{name: query_database, arguments: {sql: SELECT customer_id, SUM(amount) as total FROM orders WHERE month 2025-04 GROUP BY customer_id ORDER BY total DESC LIMIT 10}}。Host将请求以tools/call方法发往MCP Server。Server执行SQL把查询结果封装成JSON返回。Host把结果交给模型模型根据数据生成最终答复。如果工具是带副作用的比如提交订单、删除记录、导入数据调用路径一模一样协议层面完全没有区分“只读工具”和“写工具”。权限控制完全依赖Host端的实现自觉以及模型对工具描述的理解。这一下就点出了安全问题的根MCP是一套表达能力很强的协议但它在协议层没有建立任何安全语义。2.2 传输层与消息格式stdio、SSE、JSON-RPC的关键取舍MCP的传输层目前常见的有两类stdioServer作为Host的子进程启动标准输入输出就是通信管道。优势是本地部署轻量没有网络暴露面劣势是Server只能被当前Host进程使用没法远程复用。Streamable HTTP / SSEServer跑在远端通过HTTP或SSE建立流式连接。优势是多个Host可以共享同一个Server便于集中治理劣势是网络暴露本身成为一个攻击面鉴权、TLS、访问控制全都要自己补。消息格式用的是JSON-RPC 2.0一个非常轻量的协议。请求、响应、通知在JSON结构上有严格区分整体设计很干净。但干净是优点也是缺点——协议给的消息头字段非常少没有标准化的身份信息字段没有标准化的审计追踪字段更没有标准化的安全策略协商机制。两端的真实身份、操作者的真实身份、会话的上下文全部依赖外部注入大家用起来千奇百怪。在我实际接入过的MCP Server里至少有一半在初始阶段只做“协议版本协商”不做任何身份认证。你要是拉了一个远程MCP Server直接连它可能既不校验Host身份也不校验用户身份任何人都能调用它的工具。这个状态和早年很多数据库暴露在公网没设密码是一个性质的问题。3. 六大安全风险深度解析MCP被神话之后的真实暗面3.1 风险一权限放大——工具清单全量暴露远比“最小权限”危险第一个要说的风险也是我在多数项目里第一个撞上的问题MCP Server给Host暴露的永远是全量工具清单而不是按用户、按场景裁剪过的子集。拿我自己的数据库MCP Server举例工具清单里有query_database、fetch_schema、甚至还有一个drop_table原本是给自动化运维任务用的一个内部工具而已。当我把这个Server接入团队共用的Host时任何用户问模型一句“把这个月的临时表清理掉”模型只要能看到drop_table这个工具就有可能在推理中决定调用它。MCP协议不会阻止Host默认不做二次确认Server只管收到请求就执行。在传统系统里权限控制通常层层收窄用户角色决定功能菜单后端接口再校验一次数据库账号再限制一次。但在MCP场景里这些层次被压缩了工具的注册是开发时写的工具的可见性却是运行时由模型决定的而模型是概率系统——它能怎么组合工具并不完全可控。当时我们的加固方案是在MCP Server内部做了一层“按会话来源过滤工具名”的逻辑Host请求tools/list时Server根据请求头里携带的用户标识动态返回工具子集。但这需要Host愿意把用户标识传给ServerMCP协议没有强制约定字段名——各家用各家的我用X-User-Id别人用X-User根本没法统一。协议层白白少了一个关键约束。3.2 风险二工具链成为C2通道——AI Agent被当作“无界终端”控制第二个风险更隐蔽也更令我不安MCP Server有可能变成C2命令与控制基础设施的一部分。C2的核心诉求是攻击者需要一条“稳定的、看起来合法的控制通道”来下发指令、窃取数据。传统C2通道往往用的是DNS隧道、HTTPS轮询这些技巧但特征明显防守方盯得紧。MCP上来之后“AI工具调用”本身就成了新的藏身之处。具体的路径是这样的攻击者通过各种方式植入一个恶意的MCP Server——比如伪装成一个“日历助手”MCP工具清单看起来人畜无害只有create_event、list_calendars但Server端代码里内置了读取本地文件、上传到指定服务器的功能。当用户正常调用“日历助手”工具时Server在执行正常功能的同时把本机敏感文件分批外传。由于调用方是AI应用、流量走的也是正常HTTP/SSE防守方看到的只是一堆“工具调用”很难察觉异常。更麻烦的是反过来用。如果攻击者能控制Host侧——比如说通过提示词注入操纵了模型的输出——那模型会以合法身份不停调用MCP Server上暴露出来的各种工具。相当于攻击者通过模型这个“无界终端”间接调用了所有可用工具。对于加入Host的工具模型天然有执行权限整个链路像是打开了一个远端命令执行的窗口。我们做过一次模拟在一台部署了MCP Server的开发机上诱导模型调用“发送测试报告”工具工具本身会执行一段shell命令把/etc/passwd文件打到日志里。从Host日志看就是一个工具调用记录完全无害但实际上文件内容已经进了日志聚合系统。这事之后我坚定了一个看法所有执行类MCP工具都必须按“可能执行任意代码”的标准做防护不能只看工具描述是否安全。3.3 风险三安全上下文断裂——Host的用户权限与Server的执行身份脱节第三个问题我把它称为“安全上下文断裂”。传统安全模型讲究“身份-权限-审计”三者一致你是谁、你能干什么、你干了什么是一串连贯的信息。但在MCP架构里这三者经常是断开的。举个例子企业内部跑了一个工单管理MCP Server部署在服务器上进程启动时用的服务账号叫mcp_service拥有对所有工单的读写权限。用户A在Host上只有“查看工单”的权限但当A对AI助手说“帮我把这个工单状态改成已完成”时Host把工具调用请求发给MCP ServerServer端用的是mcp_service这个服务账号去执行操作权限校验发生在Server进程内部。如果Server没有做额外的用户维度过滤A就通过AI拿到了超出自己权限的执行能力。这还不是最惨的更惨的是审计视角完全断裂日志里只记录了“mcp_service调用了update_ticket”以及工单ID但到底是谁、从哪个Host发起的、带着什么意图发起的MCP协议没有标准字段承载。也就是说事后想追查一次安全事故得把Host日志、Server日志、模型上下文一段段拼起来中间还有大量缺失。这类风险不是说MCP设计者故意忽略而是MCP把“模型”这个新执行主体引入了系统但传统“用户-系统”二元信任模型没有直接迁移过来。结果就是信任链在宿主和Server之间出现了断层。3.4 风险四供应链投毒——第三方MCP生态的“克隆攻击”第四条风险对现在习惯到处拉MCP Server来用的开发者来说是最近的一根刺。MCP生态正在复制npm/PyPI当年的故事代码包越方便供应链攻击越猖獗。当前大量MCP Server以Python包或Node包形式分发开发者一条pip install mcp-server-xxx或者npx xxx就把Server拉到了本地。由于很多还是个人项目或早期开源项目缺少签名校验、缺少代码审计、甚至包名都很容易伪造。我见过一个真实的案例某个团队的开发者想安装一个“读取PDF并提取结构化信息”的MCP Server在PyPI上搜到一个名字只差一个字母的仿冒包。这个包在代码里嵌入了os.system调用在初始化时下载一个加密的二进制payload然后通过MCP Server的标准化工具名继续正常工作。从功能上看完全没问题PDF确实能解析但从安全角度这台机器已经失守了。只要是走MCPHost一定会加载Server的tools/list返回结果并且会在运行过程中无条件执行工具调用。一个恶意Server模拟正常人畜无害的工具接口太容易了。MCP协议本身完全没提供类似“代码签名”“清单校验”“来源可验证”的机制一切都靠安装者自辨。现在我在团队里定了两条规矩第一生产环境用的MCP Server必须是自研或经过逐行审计的知名开源项目第二任何第三方来源的Server一律先扔到隔离环境跑一周看它的网络行为、文件行为、进程行为没问题再接进共享Host。这个流程很土但有用。3.5 风险五凭证泄露——MCP Server是密钥流通的天然放大器第五个风险来自一个常见操作把密钥喂给MCP让Server去做认证。这个操作方便但代价极大。在我们内部的一个MCP Server接入过程中有同事为了快速对接第三方服务把API Token直接写在了Server的环境变量里而Server为了调试方便在tools/list返回的一个工具描述里附带了部分Token字符串——本意是方便测试结果Token字符串被Host端记录到了会话日志里而会话日志会同步给大模型做上下文。也就是说密钥进入了模型的上下文窗口。模型上下文窗口里的数据会跟着会话一起被Host记录、被日志系统保存、甚至在某些配置下被发送给第三方模型服务。密钥一旦进了“上下文”泄露面就从“一个进程的环境变量”扩大到“所有能读到对话日志的地方”。而这还不是最严重的。更常见的泄露路径在工具参数里。比如一个send_email工具要求传入SMTP用户名和密码模型在执行时如果上下文里没有这些值而Host系统配置了“自动从密码管理器获取”那么密钥会在毫无保护的状态下以明文参数经过MCP协议传输。很多MCP Server默认不加密参数、不脱敏日志于是密钥明文出现在Server端日志里。我现在对MCP Server的处理规范是三个“绝不”绝不把长期凭证写入工具参数绝不把密钥放进工具描述绝不让模型直接接触原始Token一律通过Server端的凭证托管模块间接使用。3.6 风险六审计与追溯的“黑匣子”——会话难复现、事件难定责最后一条风险不是攻击型风险但它是让安全事件变严重的关键因素审计缺失。MCP的JSON-RPC消息格式里没有标准的“会话ID”“请求ID”关联字段没有“操作用户”“操作意图”这类审计信息。不同Host实现五花八门有的记录全部往返消息有的只记录工具名和返回码有的干脆关掉日志。等真出了安全事故想回答“当时模型为什么调用这个工具、是谁让我调用的、这条数据是从哪一步开始泄露的”基本靠猜。有一次我们排查一个“误删数据”事件用户用AI助手执行了一条操作删了一个不该删的集合。我们回看MCP Server日志只有一条delete_many的记录连参数都没记录完整。Host端的日志更少只记录了一个“工具执行完成”。最后不得不从模型会话历史、用户输入、操作时间三点交叉拼凑才勉强定位责任环节。MCP协议完全可以借鉴数据库的“预写日志”或API网关的“流量日志”思路在协议层强制约定最小审计字段。现在没有等于每个人都要自己发明一套而且各不兼容。说到底MCP还太年轻。它的通用性带来了效率但安全能力还没有跟上。4. 实操层面的安全加固清单我给MCP实践配置了哪些防御4.1 最小权限与分组策略把工具的可见性做成动态能力如果你还没法完全放弃MCP那至少应该做下面这些事。第一条是动态工具清单。我们之前吃过tools/list全量暴露的亏后来在Server端做了一个基于“租户用户”维度的过滤器async def list_tools(self, user_context): if user_context.get(role) admin: return self.all_tools if user_context.get(scope) readonly: return [t for t in self.all_tools if t.read_only] return []这个抽象每家的实现方式都不一样但原则一致MCP Server在生成工具清单时应该收窄到当前会话所需的最小集合而不是无脑把所有工具交出去。因为工具清单就是大模型的“决策空间”决策空间越小被误用和滥用的风险越低。同时如果一个工具是创写型操作的写库、发消息、删数据我强烈建议在工具层加一个“人工确认”钩子。具体做法是让工具返回一个“待确认”标志由Host端结合自身UI做一个确认弹窗确认后才真正执行。这个动作直接阻断了一类“模型误操作”风险代价只是交互上多一步但对生产环境来说是完全值得的。4.2 来源可信度管理与运行时审计像对待开源依赖一样对待MCP Server对待MCP Server要用对待生产依赖的标准甚至更严格。第一建立MCP Server清单。把每个Server的源码仓库、维护者、版本、最近更新时间、工具清单全部登记。任何一次升级都要走变更流程不能随手更新。第二在隔离环境做行为测试。拉一个新Server回来先放进一个网络隔离的容器里跑观察它的外联域名、文件读写、进程行为。如果它在运行时报错信息里出现了和工具功能无关的敏感路径直接弃用。第三运行时审计用代理模式收拢。把MCP流量统一收敛到一个网关在网关上记录“谁调用了什么工具、参数是什么、返回给谁、耗时多少”。不要依赖Host自带的日志——Host通常只管模型对话记录不到Server执行细节。这部分我给一个最小化的JSON-RPC审计日志字段建议字段含义是否必需session_id当前会话唯一ID是user_id操作用户标识是tool_name被调用工具名是arguments工具入参脱敏后是result_code返回状态是model_context_id模型上下文ID推荐有了这些字段至少能在事后回答“发生了什么”这个问题。至于“为什么模型会这么做”那是另一个层面的研究但不是本文重点。5. 常见问题速查表与个人操作体会5.1 MCP部署中最常踩的五个坑速查表现象原因应对Server能在本地跑但Host连不上传输层协议不匹配stdio vs HTTP或握手协商失败检查初始化握手的版本信息、统一走HTTP运输时确认鉴权头模型“找不到工具”tools/list返回了清单但工具描述不清晰模型没识别精简工具描述加明确使用示例工具调用中Token泄露到日志工具参数未脱敏记录了完整认证信息在工具执行和日志输出两端分别做脱敏权限校验形同虚设Server端没有实现用户维度过滤默认全部工具可见按上文动态工具清单方案处理会话复盘困难缺少统一审计字段部署MCP网关统一收敛和记录流量5.2 我对MCP未来的一个观察我个人的判断是MCP一定会继续发展因为它解决的问题太痛了。但它的安全演进不会自己完成——协议层大概率会逐步加入标准化鉴权、审计、权限能力而在那之前所有引入MCP的团队都得自己把这些能力补上。我的一个具体建议是在内部实践时把MCP Server当作“运行中的第三方代码”来管理而不是“一个配置文件”。它和其他服务一样需要上线评审、需要运行监控、需要定期清理不再使用的工具。最后再分享一个小技巧定期对MCP Server做一次“冗余工具清理”。我见过太多Server上线之后工具列表堆了一大堆历史遗留能力根本没人在用但每个工具都代表着一条暴露面。删掉冗余工具往往比加一堆防火墙规则更有效。安全很多时候不是做加法而是做减法。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进