MCP到MHS:大模型控制物理设备的安全语义契约

发布时间:2026/9/8 21:57:54
MCP到MHS:大模型控制物理设备的安全语义契约 我最近在一间不算大的实验室里做了一件事把一台倒置荧光显微镜的 MCP server 写了出来然后让 Claude 通过这个 server 自动完成“移动载物台、换物镜、对焦、采图”这一套动作。听着像是科幻但真正跑起来后我发现问题根本不在模型能不能理解“对焦”这件事而在于硬件世界和软件世界对“调用”的定义完全不一样。这篇文章想聊的正是这层差异Anthropic 把 MCP 从软件工具一路推进到了物理设备MCP 生态开始长出一种新的“模型硬件标准”Model Hardware Standard简称 MHS。我先说明一下MHS 并不是凭空发明的一套新协议更像是 MCP 延伸到物理世界时大家发现必须补上的那套语义契约。下面会结合显微镜、机械臂和量子激光实验台把 MHS 的底层逻辑拆开讲清楚。1. 我第一次让模型控制显微镜翻车后才理解 MHS 的出现1.1 表面上是接口问题实际上是语义鸿沟最早我的想法很简单显微镜厂商提供了 Python SDK那我不就是把 SDK 里的几个函数包成 MCP tools 吗这在软件侧明明是已经被验证过无数次的做法——Git 操作可以包成 MCP浏览器可以包成 MCPFigma 也能通过 MCP 被 Claude 调用。显微镜无非就是把move_stage(x100, y100)变成一个 JSON-RPC 调用而已。结果第一轮实测就翻车了。模型很自然地调用了move_stage返回也是{ok: true}但实际载物台纹丝不动。排查半天才发现这台显微镜重启之后必须先做原点回归否则驱动器会拒绝所有绝对定位指令。SDK 函数没有报错只返回了一个“预期会被忽略”的状态码模型当然不知道发生了什么。这个故事特别典型软件 API 里的“调用成功”通常意味着操作已经发生但物理设备里的“调用成功”只代表指令被设备接收了不一定代表动作真正完成了更不代表设备已经进入你预期的状态。这是模型控制物理世界时遇到的第一道坎而 MCP 原生协议并没有专门定义这些语义。1.2 MHS 不是 MCP 的替代品而是一层物理语义标准如果把 MCP 比作快递物流的标准包装箱不管里面装的是什么只要用标准箱封装就能在物流网络中流转。MCP 定义了工具如何被描述、参数如何传递、结果如何返回但它不关心这个工具背后是读数据库还是驱动一个大功率激光器。对于软件工具来说这没问题因为软件函数大多无状态、副作用可控、可重复执行出错最多就是报个异常。物理设备就不一样了。设备有状态动作有惯性连续执行会产生互相干扰更别说还有安全边界。你没办法把“打开激光器”当成一个普通函数反复重试也不能在设备还没停稳时就把下一个动作发进去。MHS 做的事就是把这些物理语义补进了 MCP 的工具描述、资源定义和调用流程里让模型看得懂设备状态、知道哪些动作有风险、明白动作之后需要等待什么反馈。一句话总结MCP 解决“模型怎么调用工具”MHS 解决“模型怎么安全地与物理世界打交道”。理解了这一点再去看 Anthropic 推动的 MCP 生态会发现它其实不是要取代硬件控制层而是想在模型和硬件控制层之间放一个统一、可解释、可审计的接口。设备本身的 PID 控制环、电机伺服、实时逻辑都不归 MHS 管MHS 管的是更上层的行为编排和状态仲裁。2. MHS 到底要约定什么设备说明书、动作字段、实时状态过去几个月我看了不少团队为 MHS 写的草案也自己设计过一套。不同草案细节差别很大但共识出奇一致模型硬件标准需要同时覆盖三个东西——能力描述、动作定义、状态通道。2.1 把“设备说明书”变成模型能读的资源以前我们给 Claude 写 MCP server通常只需要在 tool description 里写几句话比如“移动载物台到指定位置单位是微米”。对软件工具来说这种描述可能够了但硬件设备的信息密度远高于此。一个实际显微镜的 MHS capability 描述通常长这样{ mhs_version: 1, device_id: scope-01, device_class: inverted_microscope, axes: [ { name: x_stage, unit: um, min: -12500, max: 12500, soft_limit: 12000, max_speed: 8000, homing_required: true } ], objectives: [10, 20, 40, 100], laser: { wavelengths_nm: [405, 488, 561], max_power_mw: 50, interlock_required: true }, actions: [ home, move_stage_absolute, switch_objective, autofocus, acquire_image, run_laser_exposure ] }这串 JSON 不是为了给前端展示的而是放在 MCP resourcemhs://device/capabilities下面让模型在开始操作前先读一遍。它相当于把一份设备说明书的关键页直接送进模型的上下文里面包含坐标范围、单位、安全限位、是否需要回归原点这类直接影响决策的信息。我在实现时会把这份能力描述缓存在 server 端并按设备状态动态更新。比如显微镜当前处于BUSY状态时描述里会明确标记available_actions: []模型读到后就不会继续发指令。这个细节很重要让模型直接从能力描述里“看到”当前可执行动作比让它从几十轮历史对话里倒推状态可靠得多。2.2 给动作补上四类物理字段MCP 对工具的描述其实已经很标准了name、description、input schema。但物理设备需要一个动作在“能不能做、做多久、做了之后能不能撤销”这些维度上给出明确标记。我自己归纳了四类必须补充的字段字段含义例子precondition执行前必须成立的条件must_be_homed、chamber_closedduration_ms动作预计持续时长500、3000reversible是否可逆false、truerequire_operator是否需要人类确认false、true比如“开启激光曝光”这个动作MHS 描述里必须写明precondition: light_curtain_closed、reversible: false、require_operator: true。模型不是真能理解这些字段背后的安全意义但这些字段会约束它能构建出的操作序列。另一个容易被忽略的字段是idempotency_key。软件 API 可以容忍重复调用但物理动作不一定。比如“关闭激光快门”重试两次问题不大“移动载物台”如果因为网络超时被重复提交载物台就可能多跑一段距离轻则错过目标视场重则撞坏样本。所以 MHS server 通常会要求客户端每次请求带一个唯一动作 IDserver 端只执行一次后续相同 ID 的请求直接返回已经执行过的结果。这种设计思路是从支付系统学来的在硬件控制里反而比在软件 API 里更重要。2.3 遥测与事件把状态从返回值升级成资源普通 MCP 工具调用是“你问我答”调一次函数拿到一个结果。物理世界不是这样一个动作可能持续几百毫秒设备状态在动作中还会发生变化。如果模型只能靠工具返回值猜状态那等于让一个近视眼通过拍照片判断跑步运动员的实时位置。MHS 的解决办法是把状态流也变成 MCP resource 和 notification。设备会维护一个mhs://device/state资源当前处于什么状态、XYZ 坐标是多少、激光器是否 ready全部实时刷新。动作结束后 server 不是简单地返回一串文本而是主动发一个状态变更通知比如{ event: state_changed, device_id: scope-01, state: IDLE, position_um: {x: 120.5, y: 880.0, z: 0.0}, timestamp: 2025-06-11T10:24:31Z }这样的好处有两个。第一模型收到通知后知道“动作现在真正完成了”第二状态以资源形式存在模型随时可以重新读取不需要靠纯文本日志推断。显微镜实验中模型下一步该做什么往往取决于当前载物台是否已经稳定、焦点质量分数是否够高这些信息必须从状态通道拿而不是从聊天记录里翻。3. 显微镜、机械臂、量子激光三种设备的接入方式完全不同MHS 给了统一语义但不同设备对实时性的要求、对安全模型的依赖、对控制粒度的需求差异极大。我建议不要把 MHS 理解成一个万能抽象层它更像一套公共词汇表不同设备在这套词汇表里填不同的内容。3.1 自动调焦显微镜动作慢状态要等显微镜看起来是三个场景里最温和的但它特别适合用来验证 MHS 的基本流程。它的动作时间尺度是百毫秒到秒级正好落在模型推理的节奏里模型发一个指令等设备响应通过状态变化拿到新状态然后再决定下一步。我第一次做自动对焦时第一版方案是让模型自己不断调用read_focus_score、move_z用类似二分法的方式找焦点。模型确实能写出正确的二分逻辑但每轮推理都要等设备响应整套流程拖得很慢。后来我把“自动对焦”这个动作升级成了 MHS server 端的高层工具模型只需要调autofocusserver 内部执行对焦算法完成后把焦点位置和置信度返回给模型。这个调整让整条链路快了好几倍。这其实是 MHS 设计里很反直觉的一点硬件标准不是让模型掌握所有低层控制而是让模型尽量使用高层、安全、经过验证的设备动作。模型的任务是看懂实验目标、选择正确的工作流、处理异常电机怎么转、对焦算法怎么收敛这些应该留在设备端的控制器里。3.2 机械臂作业坐标、原点、互斥一个都不能少机械臂比显微镜复杂在两点运动学和并发。机械臂的坐标系通常有多个基座坐标系、末端夹具坐标系、视觉标定坐标系。模型如果不懂这些坐标系很容易计算出某个目标位置但在错误的坐标系里执行。MHS 的能力描述里必须明确写上当前动作使用的坐标系、单位、以及是否需要先做归零校准。我在仿真环境里测过一组很经典的抓取任务让模型把一个方块从位置 A 搬到位置 B。模型生成的计划是合理的——先移动到 A 上方、下降、夹爪闭合、抬起、移动到 B、放下。问题出在动作之间的“状态互斥”夹爪没有完全闭合时机械臂不能立刻抬起否则物体会掉。真实机器人控制器通常有内部状态机保证这一点但模型侧的 MHS server 不能假设模型清楚这些状态转换规则。所以机械臂的 MHS 适配器里我还会额外加一层互斥锁逻辑同一时间只允许一个动作在执行队列里前一个动作未收到done通知后一个动作直接返回device_busy。这些规则和模型是否聪明无关纯粹是物理世界的基本约束机器不能同时执行两个需要同一关节的动作。3.3 量子激光实验台模型只做编排实时控制仍归底层听起来最“科幻”的量子激光场景反而最能说明 MHS 的边界。量子光学实验里确实有一些激光器、衰减器、移相器和单光子探测器它们之间有时序要求某些延迟要到纳秒甚至皮秒量级。这种控制不可能经过一个跑在普通服务器上的 MCP tool 再回传一个大语言模型的推理延迟足够让实验失败一万次。所以真正合理的架构是分层控制。底层用 FPGA 或者专门的时序控制器完成高精度闭环上层实验编排系统负责“什么时候开始、参数怎么组合、数据怎么落盘”。MHS 和模型只接在上层编排系统上。Claude 能做的事情是读取实验配置、决定要不要切换功率档位、在预设的操作序列里选一组执行而不是直接去给激光器发实时控制信号。连接量子激光实验台时MHS server 的核心价值是安全边界和流程审计。比如“把激光功率从 1mW 调整到 3mW”这个动作必须在硬件层面上经过安全互锁确认由独立于模型的安全守卫检查光路是否封好、操作者是否在安全区域、功率设定值是否在允许范围内。模型可以提出请求但真正执行按钮必须被一套固定规则锁住。MHS 在这里更像一个“权限代理”而不是智能决策核心。4. 一个最小 MHS 适配器怎么搭从设备类到 MCP Server如果你手上也有一台可以通过 Python SDK 控制的设备我建议按下面这个顺序搭最小适配器。我以显微镜为例但流程对所有设备通用。4.1 先写设备驱动类再暴露成 MCP最容易犯的错是直接拿厂商 SDK 的函数名去开一个mcp.tool()。厂商 SDK 是为写桌面软件的人设计的变量名、单位、错误处理都不是为模型准备的。正确顺序是先写一个干净的 Python 设备类内部统一单位做状态管理class MicroscopeDevice: def __init__(self): self.position_um {x: 0.0, y: 0.0, z: 0.0} self.busy False self.homed False self.objective 10 self.laser_enabled False self.laser_power_mw 0.0 def home(self): self.busy True # 调用厂商SDK执行归零 self.position_um {x: 0.0, y: 0.0, z: 0.0} self.homed True self.busy False return self.get_state() def move_stage_absolute(self, x_um: float, y_um: float): if not self.homed: return {ok: False, reason: must_home_first} if not (-12000 x_um 12000 and -12000 y_um 12000): return {ok: False, reason: target_out_of_soft_limit} # 调用厂商SDK执行运动 self.position_um[x] x_um self.position_um[y] y_um return self.get_state()这个类的好处是让你在一个没有 MCP 干扰的环境里先验证设备的真实行为。你可以在命令行里反复测试move_stage_absolute确认边界条件然后再考虑怎么把动作暴露给模型。4.2 capability 资源和动作实现设备类写好后用 FastMCP 包一层就很直接from fastmcp import FastMCP mcp FastMCP(microscope-mhs) device MicroscopeDevice() mcp.resource(mhs://device/capabilities) def capability() - str: return json.dumps(CAPABILITY_JSON, ensure_asciiFalse) mcp.resource(mhs://device/state) def state() - str: return json.dumps(device.get_state(), ensure_asciiFalse) mcp.tool() def home_and_wait() - dict: result device.home() return result mcp.tool() def move_stage_absolute(x_um: float, y_um: float) - dict: result device.move_stage_absolute(x_um, y_um) return result这里的CAPABILITY_JSON就是前面那段设备说明书。第一版不需要实现完整 MHS 标准只需要做到三件事模型能读能力描述、模型能读实时状态、每个动作执行后返回完整状态。设置好之后用 MCP 客户端连上去Claude 就能根据当前状态决定该调哪个动作。4.3 本地安全守卫必须独立于模型在 MHS 适配器里我认为最不能省略的部分是一个独立于模型调用的安全守卫函数。它的作用是执行硬校验不接收任何模型改写后的逻辑。用代码表达就是class LaserGuard: HARD_MAX_POWER_MW 20.0 staticmethod def can_enable(target_power_mw: float) - bool: return target_power_mw LaserGuard.HARD_MAX_POWER_MW mcp.tool() def enable_laser(power_mw: float) - dict: if not LaserGuard.can_enable(power_mw): return {ok: False, reason: request_exceeds_hard_limit} # 再执行真实硬件控制 ...千万不要把这个校验逻辑放在提示词或者工具描述里让模型“自觉遵守”。大模型可能因为上下文太长而忽略一条限制也可能会在极端情况下生成绕过限制的参数。唯一可靠的做法是在设备驱动之前加一层由普通代码实现的硬校验最好连设备厂商 SDK 本身也再有一道独立物理互锁。安全层设计的原则永远是模型犯错也不至于造成事故。5. 实测中反复遇到的五个坑每个都值得写成排错日志5.1 单位缺失让模型把微米当毫米显微镜载物台的运动距离通常用微米但模型在对话中看到细胞尺寸、样本容器这些信息时很容易自行脑补毫米单位。第一次实测我让模型把载物台从当前视野向右移动“大约两个细胞直径”结果模型直接传了一个 2000 的值看起来像微米其实它想表达的是 2000 微米但它没有意识到这个范围已经接近载物台行程上限。这里不能指望模型“聪明”到会自动换算必须靠 MHS 字段把单位写死。更保险的做法是server 端只接受x_um这种自带单位的参数名并且把范围min/max明确暴露给模型。如果参数超范围直接返回错误并附带当前合法范围模型通常看到错误信息后会自行修正。我们后来在所有动作参数里都加了unit字段这种错误基本绝迹。5.2 动作返回后设备还在运动模型以为万事大吉很多硬件 SDK 的动作调用都是“提交后立刻返回”真实运动在后台持续几百毫秒。第一版 server 在move_stage_absolute里直接返回了 SDK 的提交结果模型立刻发起下一个采图动作结果图拍下来时载物台还没到位。这个问题的修复不是让动作函数强制 sleep而是让设备类维护一个motion_done事件等驱动器确认到位后再返回状态。在 MHS 层面这意味着每个耗时动作必须设置明确的duration_ms和完成事件。模型不需要知道硬件底层怎么判断到位但 server 返回给模型的状态必须保证是动作完成后的真实状态而不是动作提交后的中间态。5.3 “返回 ok”不等于“动作有效”这个坑在显微镜原点回归那次也出现过。SDK 函数返回了一个非零状态码但我当时没有解析直接把它当成成功处理。教训是硬件厂商 SDK 的返回值往往包含警告位、错误码、校准状态不能只判断函数没有抛异常。我后来给 MHS server 加了一个强制约定所有动作返回值必须是结构化状态而不是一串自然语言“success”。如果设备还有未处理的报警状态字段必须带上alarms数组。模型只有看到完整的结构状态才能发现“位置变了但是激光器温度异常”这类跨模块问题。5.4 图像和点云塞进工具返回值会撑爆上下文显微镜采图后如果直接把 base64 图片塞进 MCP tool 的返回文本会让上下文变得非常大也会增加模型解析的负担。MHS 场景里二进制数据应该走资源通道而不是工具返回通道。比如acquire_image动作只返回frame_id真正的图像放在mhs://device/frames/{frame_id}这个 MCP 资源里模型需要时再按 URI 读取。在需要视觉判断的任务里我会再配合 MCP 的图片内容块把压缩后的预览图传给模型让模型可以“看”到当前视野同时保留原始分辨率的 frame_id 供后续算法处理。这个分离在软件 MCP server 里很少见但在物理设备场景里几乎不可避免——显微镜一帧 2000 万像素的原始数据很容易到几十 MB不可能作为普通 tool 返回值。5.5 重复请求是比超时更隐蔽的危险网络请求可能超时客户端为了恢复可能会重试同一个动作。对于读操作毫无问题但对于写操作就可能是灾难。MHS 的解决方式是强制每个动作请求携带action_idserver 端在收到重复 ID 时不重新执行而是把第一次执行的结果原样返回。这要求设备动作都必须有幂等语义。我一开始嫌这个字段多余直到有次模拟弱网环境测试载物台因为同一个移动指令被重发了三次硬生生往一个方向多跑了三倍距离。那次之后我把idempotency_key当成了所有 MHS 动作的必填项就像数据库写操作必须处理重复提交一样。6. MHS 往前走的几个方向以及我现在会怎么选型6.1 设备目录与统一网关会是第一个成熟层现在每个团队接一台设备就写一个 MCP server动作命名五花八门。有的叫moveTo有的叫move_to_abs这种碎片化无法支撑大规模物理 AI 应用。我判断 MHS 下一步会先把“能力描述格式”和“设备目录协议”固定下来类似 USB 的设备描述符设备插入后主机能自动识别它是什么、支持哪些能力。放在 MHS 场景里就是每台设备在接入网络时把自己注册到一个设备网关网关能读取 capability、聚合状态、统一鉴权。模型不需要关心后台是一台显微镜还是一台机械臂只需要问网关“当前可用的物理资源有哪些”网关返回一套标准化能力清单。这个抽象对实验室自动化特别有价值同一个实验流程可以在不同设备组合上跑只要它们都符合 MHS。6.2 物理动作评测会成为训练和验收的硬指标软件 MCP tool 做评测可以靠单元测试和端到端断言物理设备动作却很难评判“这次执行得好不好”。同样是抓取一个零件机械臂可能碰到了边缘但最终还是成功了也可能表面成功了但力控曲线异常。MHS 的结构化状态流天然可以产生大量时序数据动作前状态、动作后状态、中间事件、报警记录。这些数据回收以后能直接用于构建物理 AI 数据工厂。大家现在都在提 data factory blueprint本质就是让真实设备把每一次动作都自动变成带标签的训练样本。我现在会把action_id、状态变化、传感器读数、最终结果全部落盘而不是只记录模型的文本输出。这些数据既是评测集也是后续调优的原料。6.3 如果团队准备尝试 MHS我的建议是从最小闭环开始不要一上来就设计一个覆盖所有设备的完整标准那是标准组织做的事不是做应用的人该做的事。先选一台低风险、可重复、状态容易观测的设备比如一个桌面级的显微镜、一台小型的教学机械臂把前面说的能力描述、状态通道、安全守卫跑通让它连续稳定运行一整天不出问题再考虑扩展到更复杂的设备。我在实验室里最深的体会是物理 AI 的难点从来不在模型不够聪明而在于可靠的设备抽象、可信的状态反馈、可执行的安全边界。MHS 能不能统一成最终标准现在没人能打包票但把设备说明书变成模型能读的 JSON、把动作结果变成结构化状态、把安全校验交给硬代码这些做法无论标准怎么演进都不会白做。等标准真正成熟的那天你已经攒下了大量可迁移的适配经验这才是比协议本身值钱的东西。

相关新闻