
前阵子在调一台六自由度机械臂任务说起来很朴素让大模型看一眼桌面照片自己判断先抓哪件、再放哪件。真正做的时候才发现最难的不是模型而是它和这堆外设之间的“沟通方式”。相机模块有自己的一套接口机械臂控制器又有一套位置服务再来一套每个都要写胶水代码每个返回的数据格式都不一样模型上下文被这些东西切得七零八落。后来我把这些能力统一接到MCPModel Context Protocol模型上下文协议上整个任务编排一下子就顺了。这篇主要聊MCP在具身智能场景里的用法包括架构原理、从零封装一个机械臂控制服务的过程以及我在实测中踩过的几个典型的坑。1. 为什么具身智能系统迟早会遇到MCP从工具碎片化说起1.1 具身智能的真实工作流比想象中更依赖工具编排很多人提起具身智能第一反应是“一个很聪明的大脑”但真正做项目的人都知道具身智能的核心闭环是感知、决策、执行三件事来回转。机械臂要完成“抓桌上红色杯子放到指定区域”背后要同时处理视觉图像、点云数据、当前关节角度、夹爪开合状态、任务指令文本中间还要随时查询力控传感器的读数。这些信息的来源五花八门有局域网内的HTTP接口有串口指令有共享内存甚至还有直接读文件系统的。传统做法是给每个数据源写一套对接代码。相机那边封装一个get_frame()机械臂那边封装一个move_to_pose()力传感器再封装一个read_force()。单看任意一个封装都还好但一旦要让大模型来做决策和编排问题就来了大模型本身是一个“对话式”的推理引擎它需要的输入是结构化的文本或对象引用而不是一堆奇奇怪怪的二进制流和私有协议报文。你得写大量样板代码把报文转成模型能理解的描述再把模型输出的动作意图转回底层指令。我在模拟项目X里第一次尝试让大模型操控机械臂时光是“把一圈传感器数据变成模型能看懂的上下文”这一步就占了大半工程量。而且每加一个新器件比如加一台深度相机又要重复一遍这套转换逻辑。最难受的是代码长得很快但没有任何一段是可复用的换一个机器人平台基本全部推倒重来。1.2 “上下文碎片化”与重复造轮子MCP要解决的根因这里有两个很关键的痛点我后来才意识到它们本质上是一件事。第一个是上下文碎片化模型要决策就得在一个上下文窗口里同时看到“任务指令、相机看到的东西、机械臂当前的姿态、力传感器反馈”但传统接口把这些信息拆散在不同系统里格式、更新频率、访问方式全都不一样。大模型被逼着在每次推理时手动拼装这些碎片既费token又容易拼错。第二个是重复造轮子。每个做具身智能的团队都在写自己的“感知模块对接层”“控制模块对接层”但这些代码的逻辑高度相似都是把外部能力暴露给大模型让模型根据任务需求去调用。既然大家都在做同一件事那就应该有统一的接口标准。MCP想解决的就是这个定义一套通用的“模型到工具”通信协议让任何外部能力以统一方式暴露成模型可调用的工具、资源和提示。可以把它类比成USB-C接口。以前电脑接打印机、显示器、U盘要各自准备不同的线缆和驱动USB-C普及以后一个口通吃大部分外设外围设备只要遵守标准协议就能被识别和使用。MCP在这件事里的角色就是把“模型上下文接入”这件事规范化。某一台机械臂控制器暴露成MCP工具之后换一个同样支持MCP的控制服务理论上模型侧的代码几乎不用改。1.3 为什么这个时间点MCP被具身智能圈注意到MCP最早在AI编程助手和智能体框架里火起来很多开发工具都靠它把外部数据源和功能接进模型。我观察到的一个趋势是具身智能团队开始主动把这类主流的模型工具协议往机器人系统上套。原因也不复杂具身智能的技术栈越来越像“一个大模型配上若干工具”模型负责拆解任务、调用合适的技能机械臂、传感器、导航模块都退化成工具。在这样的架构下协议选型就非常关键。如果团队自研一套接口那意味着大模型只能和自家工具对话生态是封闭的如果选一个正在业内快速铺开的标准协议那好处是社区已经有成熟的示例代码、调试工具、开发包而且未来新出的传感器或执行器如果原生支持MCP模型侧可以直接识别使用。我自己就是在这个背景下重新审视MCP的研究之后发现它的分层思想恰好能被具身智能直接借用于是决定在一台机械臂上真实跑一遍看看这协议到底好不好用、坑在哪里。2. MCP的核心架构拆解Host、Client、Server与三类原语2.1 三层角色宿主应用、本地客户端、远端服务端MCP的架构拆开看就三层。最上层是宿主应用Host也就是真正拿着模型做决策的程序比如具身智能任务编排系统、AI编程助手这类东西。中间是客户端Client它作为宿主机和远端服务之间的会话管理器负责建立连接、发送请求、接收响应。最下层是服务端Server它把具体能力暴露出来比如机械臂控制服务、视觉感知服务、位置查询服务每个都是一个独立的MCP Server。我用一个生活中的比方来理解宿主机是总指挥客户端是总指挥手下的联络员服务端是各领域的专家。总指挥不直接和专家对话而是通过联络员传话。这样做的最大好处是解耦总指挥不需要关心某个专家的内部实现是串口协议还是HTTP接口联络员统一负责把指令翻译过去、把结果翻译回来。在模拟项目X里我把任务编排进程当成Host每个MCP Client对应一个外部设备服务。机械臂控制是一个Server深度相机是另一个Server两者互不感知。模型要动机械臂时只对“工具名”和“参数描述”负责完全不需要理睬底层走的是什么物理链路。这种透明性对后续扩展非常友好多加一个灵巧手就多起一个Server宿主侧几乎不动。2.2 三类原语怎么分工Tools、Resources、PromptsMCP协议里最值得琢磨的是它定义了三类原语工具Tools、资源Resources、提示Prompts。很多人刚开始容易把它们混为一谈但它们的分工逻辑非常清晰直接对应模型与外部世界交互的三种基本方式。工具是“模型主动发起的动作”比如移动机械臂到某个关节角度、打开夹爪、执行一次抓取。这类操作的共同点是它们会改变系统状态或者要求外部设备立刻执行某件事。资源是“模型可被动读取的上下文数据”比如相机当前帧、点云文件、关节角度状态、力传感器读数。资源本身不产生动作只是把信息暴露出来供模型查看。提示则是“可复用的任务模板”本质上是预先写好的指令片段或示例帮助模型快速进入某一类任务的处理状态比如“开始标准抓取流程”这样一段带步骤说明的提示词。原语含义具身智能示例触发方式Tools可执行动作移动关节、抓取物体、切换到安全模式模型主动调用Resources可读取数据相机帧引用、点云URI、关节状态模型按需读取Prompts任务模板标准抓取流程、开门流程、巡检流程宿主机预加载为什么协议要把这三类分开而不是一律当成“工具”因为大模型的推理逻辑里动作和数据是两回事。如果把“读取相机帧”和“移动机械臂”都塞进工具列表模型可能会在需要观察场景时误触发了某个动作这在物理世界里是危险的事。分开之后读取数据走资源通道执行动作走工具通道权限和校验都能分别设置安全边界会清晰很多。2.3 传输层与消息格式为什么选中JSON-RPC 2.0MCP的传输层设计也很克制。最初主要是本机进程间通信走标准输入输出stdio这对具身智能机器人特别有用因为很多控制程序本来就是本地进程机械臂驱动、视觉推理都在同一台工控机上跑。stdio方式在流程控制上很直接宿主侧启动一个服务端进程通过标准输入输出收发消息进程退出连接就断掉。具身智能场景里我实际跑下来发现stdio比远程调用稳定不少少了一层网络波动的问题。如果是跨设备部署比如视觉服务跑在另一台机器上那可以用HTTP传输方式MCP定义了基于流式HTTP的远程通信能力客户端通过HTTP Endpoint与服务端对话。对绝大多数具身智能团队来说本机用stdio、跨机用HTTP这个组合已经覆盖了90%的场景。消息格式选JSON-RPC 2.0也是一步妙棋。相比gRPC或自定义二进制协议JSON-RPC的优点首先是通用任何语言都能轻松处理其次是可调试开发期直接打印报文就能看清模型到底调用了哪些工具、传了什么参数。具身智能系统里最多可能有十几个微服务如果每个都用私有二进制协议排查一个工具调用异常会非常痛苦。JSON-RPC让整个调用链路的日志都统一格式出问题时靠日志就能迅速定位。我在做模拟项目X的时候调试机械臂工具调用就主要靠打印JSON-RPC报文比之前用私有协议时在十六进制里翻找数据高效得多。3. 从零封装一个机械臂动作控制MCP服务代码级实操记录3.1 环境与设计把哪些能力暴露成工具动手之前我会先想清楚一个问题哪些操作值得暴露成工具哪些不应该。工具粒度太细比如把“设置某个关节的角度”都拆成单独的微操作模型调用起来会累死粒度太粗比如只暴露一个“执行完整抓取”模型又失去了中间决策的空间。在模拟项目X里我最终把工具粒度定为“原子动作加状态查询”的组合。机械臂侧暴露了这几个核心工具move_to_joint_pose接收一组关节角位移指令move_to_cartesian_pose接收笛卡尔空间目标位姿grasp和release控制夹爪开合get_current_pose查询当前末端位姿。相机侧暴露capture_frame_ref用于获取一帧图像的引用地址。之所以这么设计是因为大模型的强项在于规划而不是高频率的底层控制。给模型“移动到点A、抓取物体、移动到点B”这样的原子动作它就能组合出千变万化的任务策略如果只给它一个“把杯子从这里搬到那里”的宏指令一旦实际场景里出现意料之外的障碍物模型连绕行都做不到因为中间步骤都不在它手里。3.2 服务端实现用社区SDK快速搭建目前社区里已经有现成的MCP开发包不需要从零实现JSON-RPC的编解码逻辑。下面我用mcp_sdk来代指我常用的Python版开发工具包不同实现的API可能略有差异但整体思路是一样的。先看机械臂控制服务端的核心代码from mcp_sdk import Server, Tool, Resource, Prompts # 初始化一个MCP服务这里设置的服务名在宿主机中作为唯一标识 server Server(arm_controller) # 定义一个工具移动到指定关节角度 server.tool() def move_to_joint_pose(joints: list[float]) - dict: 将机械臂移动到指定关节角度。 joints: 六个关节的目标角度度按顺序排列。 返回: 执行结果包含是否成功和当前位姿。 # 实际项目中这里调用底层运动库例如向控制器发送目标角度 # 本段仅为示例返回模拟结果 result control_arm.joint_move(joints) return {ok: result.ok, current_pose: result.pose} # 定义抓取工具 server.tool() def grasp() - dict: 闭合夹爪尝试抓取当前夹爪中心区域的物体。 调用前建议先通过 get_current_pose 确认机械臂已对准目标。 result control_arm.gripper_close() return {ok: result.ok, gripper_width: result.width} # 暴露一个资源当前关节状态和末端位姿 server.resource(status://arm/current) def arm_status() - str: 返回机械臂当前状态包括关节角度、末端坐标、夹爪开合量。 state control_arm.read_state() return state.to_json() # 注册一个可复用的抓取任务提示 server.prompt() def pick_place_template(object_name: str): 在已知目标名称的情况下用标准流程执行一次抓取放置任务。 return f 目标物体{object_name} 请先读取 status://arm/current 获取当前机械臂状态 然后按下列步骤执行 1. 使用 move_to_joint_pose 调整到安全高度 2. 使用 get_current_pose 确定末端对准目标 3. 调用 grasp 抓取物体 4. 移动到目标放置点后调用 release 释放。 # 启动stdio服务宿主机通过标准输入输出与本服务通信 server.run(transportstdio)这段代码里最关键的是工具函数上面的那段描述文本。MCP把描述文本直接送给模型作为模型判断“该不该调用这个工具”的主要依据。描述写得含糊模型就调用得犹豫描述写得精准模型的调用准确率会明显上升。这一点很多第一次写工具的人容易忽略我建议在描述里把参数含义、返回值内容、前置条件都写清楚哪怕看起来啰嗦一点也值得。3.3 宿主侧接入从“任务提示”到“一次夹取循环”服务端就绪之后宿主侧也要做对应接入。宿主机通过MCP Client连接机械臂Server模型先通过读取资源了解状态再根据任务提示决定工具调用顺序。下面是一段宿主侧的核心代码演示了连接服务、读取资源状态信息、调用工具的过程import asyncio from mcp_sdk import Client async def run_task(): # 连接机械臂控制服务 arm_client Client() await arm_client.connect(command[python, arm_server.py]) # 让模型读取机械臂当前状态资源 status_text await arm_client.read_resource(status://arm/current) print(机械臂状态:, status_text) # 把任务指令和可用工具列表交给大模型去规划 task 先移动到安全高度然后抓取桌面上的红色杯子 plan await llm_plan(task, available_toolsawait arm_client.list_tools()) # 按模型规划依次调用工具 for action in plan: result await arm_client.call_tool(action[name], argumentsaction[arguments]) print(f调用 {action[name]} 结果: {result}) await arm_client.close() asyncio.run(run_task())这段代码看起来像是“聊几句天、调几个工具”但放到真实机械臂上跑的时候还牵扯到一个异步和非阻塞的问题。机械臂一个动作要执行一两秒如果宿主侧同步阻塞等待结果那么整个任务编排就会被卡住其他传感器的状态更新也没法及时处理。我建议在真实项目里把工具调用放到异步任务里同时保持一个循环定期读取系统状态确保决策层始终感知最新世界状态而不只是在动作间隙里看一眼。4. 具身智能场景下的四个真实坑传感器、超时、上下文与权限边界4.1 传感器数据太大图像帧别直接塞进工具返回第一个坑是视觉数据太大。我最初的实现里相机Server直接把一帧图像base64编码后作为资源返回给模型。结果一帧1080p图像编码后轻松超过几MB模型要读好几次才能把内容“看完”token消耗迅速飙升而且每次读取都要几秒钟整个任务的节奏全被打乱。后来我把方案改成“返回引用按需取用”资源只返回一个帧ID和本地文件路径模型先拿到引用信息在真正需要看清画面细节时再通过一个专门的工具去获取缩小后的或局部区域的图像。这相当于把“读全文”改成了“读摘要加按需精读”模型处理效率提升非常明显。如果你也要在MCP里暴露图像数据我强烈建议不要直接把大图塞进上下文而是参考这个做法。4.2 机械臂动作耗时与协议超时不要用长轮询第二个坑是动作执行超时。机械臂从点A移动到点B物理过程可能要走两三秒如果走HTTP传输的方式调用工具HTTP请求的默认超时时间往往只有一两秒。我在最初调试时遇到的情况是工具调用发到服务端机械臂才走到一半HTTP客户端已经等不及报超时了请求被中断机械臂悬在半空机器人系统的状态机和模型推理结果彻底对不上。解决办法有两种我在模拟项目X里都验证过。第一种如果动作可以拆成“发起”和“查询”两步那就让工具立即返回一个任务ID再提供状态查询工具让模型轮询第二种本机部署的场景下直接用stdio传输减少网络层超时的影响。实际操作中我采用了“快速返回ID加轮询状态”的方案模型调用move_to_joint_pose后立刻拿到一个task_id再通过get_task_status确认动作是否完成这样既不会中断执行也能让模型保持对进度感知。4.3 上下文污染与状态过期抓取前后世界变了第三个坑来自具身智能特有的问题世界状态会变。模型在0.5秒前读到机械臂在某位置然后推理花了一秒等真正执行的时候机械臂可能已经被人动过或者传送带上的物体已经移动了位置。传统软件系统不关心这种“读到的状态是否过期”但物理世界没有这么温柔。我在排查一次抓取失败时发现模型以为夹爪正在目标物体正上方但实际情况是它读取状态之后系统为了避障自动小幅调整了机械臂位置最后模型仍按旧坐标执行抓取。解决方案是在资源侧给状态信息加时间戳和序号同时在工具调用前增加一次状态新鲜度校验。如果状态数据超过一定时限就要求模型先重新读取资源再决定动作而不是拿旧数据硬上。这一步机制看着简单但对系统的安全运行非常重要因为陈旧状态导致的误动作在真实机器人上很有可能撞到东西或者损坏夹爪。4.4 权限边界危险动作不能交给模型自由发挥第四个坑是关于权限控制的。让大模型自由调用机械臂工具是一件让人紧张的事因为模型可能规划出一个在逻辑上合理但在物理上危险的动作序列。比如在整理任务中模型为了快速移动物体可能直接调用一个高速运动指令或者一个任务里的参数出现异常模型却坚持执行了“夹爪强力闭合”很可能压坏抓取的易碎品。我采取的办法是给工具分权限等级。普通只读状态查询工具完全放开运动控制类工具必须经过参数校验速度上限、加速度上限、目标点位置是否在机械臂工作空间之内、夹爪力度是否超过安全阈值。这些校验全部放在服务端执行而不是指望模型自觉。另外有些高危险动作比如切换到高速模式、执行运动学奇异位姿我会在服务端设置成需要宿主侧二次确认才能执行。在MCP里这可以通过额外的“确认提示”机制实现。说白了允许模型调用工具不等于给模型完全的物理控制权服务端校验和安全兜底绝不能省。5. MCP在具身智能技术栈中的定位再思考接口标准之后还有哪些事5.1 MCP与机器人中间件的边界谁也不替代谁很多刚接触MCP的人会问它和机器人操作系统这类中间件框架是什么关系是不是有了MCP就能不要机器人中间件了我的看法是两者解决的问题完全不同谁也不替代谁。机器人中间件解决的是实时通信、运动规划、驱动抽象这些底层问题它保证机械臂在毫秒级周期内稳定执行轨迹MCP解决的是“大模型如何理解和使用外部能力”这一问题它聚焦在交互协议的标准化上。用一个不太准确但容易理解的比喻机器人中间件像是脊髓和末梢神经负责快速、可靠的反射式控制MCP更像大脑和外界对话时用的那套标准语言。没有前者机械臂根本动不了没有后者大模型面对一堆专用接口就只能干瞪眼。具身智能系统的合理架构应该是“底层实时控制走机器人中间件上层智能决策通过MCP接入工具和数据”两者配合而不是互相排斥。对比维度MCP传统机器人中间件定位模型与外部工具间的接口协议机器人组件间的运行时通信与调度实时性不保证硬实时提供实时通信能力交互对象大模型、智能体传感器、执行器、算法模块典型场景任务规划、工具调用、上下文读取运动控制、状态发布、数据流传输5.2 生态现状与个人选型建议MCP的接口规范本身不算复杂我在模拟项目X里从搭建服务端到跑通第一个工具调用大概花了不到一天。但这个协议能发挥多大价值很大程度取决于生态里有多少工具支持它。就目前的情况看AI编程助手对MCP的支持最完善智能体框架也在快速跟进机器人领域虽然还处于早期但由于具身智能对大模型工具编排的依赖越来越强我判断接口标准的统一迟早会发生。关于选型我给几条个人经验。第一低频的状态查询、任务级动作指令适合优先改造成MCP工具因为改造难度低、收益明显第二高频低延迟的控制回路不要试图走MCP电流环、速度环这类毫秒级控制必须留在机器人中间件或实时系统里MCP的定位是决策层的对话协议不是运动控制总线第三如果团队已经有成熟的私有工具对接层不必急着全部推倒重来可以先用MCP封装几个最常用的工具跑通流程验证收益后再逐步扩大范围。5.3 一点个人观察与实测感触最后聊点我在实测中的切身感受。设计MCP工具时最花心思的不是怎么写代码而是怎么给模型设计“好描述”和“好粒度”。好工具描述能让模型准确决策好粒度能让模型充分发挥组合潜力这两件事做不好协议再标准也没用。另外我建议具身智能团队在引入MCP时一定要配套一套完整的调用日志系统把每次工具调用、参数、耗时、返回结果都记录清楚因为大模型规划的不确定性远超传统规则系统只有靠日志才能追踪到“模型为什么会做出某个动作”排查问题时才能有据可依。我个人能明显感觉到自从把机械臂控制、视觉感知、状态查询这几个模块都接入MCP之后调试大模型行为变得轻松了不止一个量级。之前遇到一次异常抓取要同时翻机械臂日志、视觉日志、调度日志三个格式还都不一样现在所有交互都走同一套JSON-RPC格式一份日志就能还原完整决策链路定位问题的速度大幅提升。这也让我更相信接口标准化这件事对于具身智能这种“强耦合、多设备、高不确定性”的系统来说价值比想象中还要大。