OPC UA MCP 上手实测:AI 读 PLC 数据、浏览节点、写设定值
装好之后,AI 能自己浏览节点树、批量读点位、改设定值——我们记下了完整的配置、真实返回和该守的规矩。
一句话结论:kukapay/opcua-mcp 和 kukapay/modbus-mcp 这两个 MIT 许可的 MCP Server,可以让你在 Claude Desktop 或 Cursor 里直接用自然语言读 PLC 变量、浏览节点树、改设定值。我们在本地起了 OPC UA 和 Modbus 两台模拟设备,把 11 个工具全部跑通,握手 1.3–1.9 秒、单次调用 0.01–0.03 秒。下面是完整配置、真实返回,以及上线前该守的几条规矩。
披露(先说口径):本文所有数字来自 Windows 10 + Python 3.10.6 的干净 venv,被控对象是本地模拟器(OPC UA 用 asyncua,Modbus 用 pymodbus),没有接真实 PLC。所以本文回答的是"工具怎么装、怎么配、跑起来什么样",不回答"在你那台设备上延迟多少"。
一、这两个 MCP Server 能干什么
| 能力 | opcua-mcp | modbus-mcp |
|---|---|---|
| 读单个变量 | read_opcua_node |
read_register / read_input_registers |
| 批量读 | read_multiple_opcua_nodes |
read_multiple_holding_registers |
| 写单个值 | write_opcua_node |
write_register / write_coil |
| 批量写 | write_multiple_opcua_nodes |
— |
| 浏览地址空间 | browse_opcua_node_children |
—(Modbus 只有寄存器号) |
一句话区分:OPC UA 这条路带语义、跨厂商,能看能改;Modbus 这条路快而直接,只认寄存器号。 前者适合"让 AI 看懂现场",后者适合"让 AI 读一块固定的点位表"。
二、安装:三条命令
python -m venv venv
venv/Scripts/pip install "mcp[cli]<2" asyncua "pymodbus<3.10" python-dotenv "opcua>=0.98.13" "cryptography>=45"
关于版本:这两个仓库写于 2025 年,mcp 与 pymodbus 之后都发过不兼容的大版本。所以安装时把上界钉住(mcp<2、pymodbus<3.10),装完就能直接用;如果你习惯用 uv,uv sync 会按仓库自带的锁文件走,同样没问题。
最终实测版本:
| 包 | 版本 |
|---|---|
mcp |
1.30.0 |
pymodbus |
3.9.2 |
asyncua(当模拟器) |
2.1.0 |
opcua(python-opcua,客户端库) |
0.98.13 |
三、起一台模拟设备(10 秒)
OPC UA 侧,用 asyncua 起一台只有 4 个变量的"虚拟产线",方便先跑通再换真设备:
import asyncio
from asyncua import Server, ua
async def main():
server = Server()
await server.init()
server.set_endpoint("opc.tcp://127.0.0.1:4840/freeopcua/server/")
idx = await server.register_namespace("http://hubwiz.com/demo")
line1 = await (await server.nodes.objects.add_object(idx, "Demo")).add_object(idx, "Line1")
await line1.add_variable(idx, "Temperature", 36.5, ua.VariantType.Double)
sp = await line1.add_variable(idx, "Setpoint", 40.0, ua.VariantType.Double)
run = await line1.add_variable(idx, "Run", True, ua.VariantType.Boolean)
for n in (sp, run):
await n.set_writable()
async with server:
await asyncio.sleep(3600)
asyncio.run(main())
跑起来会打印 4 个 node id(本文测试里是 ns=2;i=3~ns=2;i=6)。Modbus 侧同理,用 pymodbus 起一台 TCP 从站(从站号 1、保持寄存器 0–9 = 10–19、输入寄存器 = 100–109)即可,modbus-mcp 的 README 里有完整参数。
四、接到 Claude Desktop / Cursor
{
"mcpServers": {
"opcua-mcp": {
"command": "python",
"args": ["C:/path/to/opcua-mcp/main.py"],
"env": { "OPCUA_SERVER_URL": "opc.tcp://127.0.0.1:4840/freeopcua/server/" }
},
"modbus-mcp": {
"command": "python",
"args": ["C:/path/to/modbus-mcp/src/modbus_mcp/cli.py"],
"env": { "MODBUS_TYPE": "tcp", "MODBUS_HOST": "127.0.0.1", "MODBUS_PORT": "502" }
}
}
}
重启客户端,工具列表里就会出现 read_opcua_node 这一组。之后你要做的只是开口问:
- "读 ns=2;i=3 的值"
- "把 ns=2;i=4 设成 42.5,然后读回来确认"
- "浏览 Objects 下面有哪些节点"
- "读 ns=2;i=3、i=4、i=5 三个值,告诉我哪个超了 40"
五、实测:5 个 OPC UA 工具的真实返回
| 阶段 | 耗时 |
|---|---|
首次 initialize()(起进程 + 连 OPC UA) |
1.25–1.86 s |
list_tools |
0.01 s |
| 单次读 / 写 / 浏览 | 0.01–0.03 s |
原样摘录:
read_opcua_node(ns=2;i=3) -> Node ns=2;i=3 value: 36.5
read_multiple_opcua_nodes(3 个) -> {'ns=2;i=3': 36.5, 'ns=2;i=4': 40.0, 'ns=2;i=5': True}
browse_opcua_node_children(ns=0;i=85) ->
[{'node_id': 'i=31915', 'browse_name': '0:Locations'},
{'node_id': 'i=2253', 'browse_name': '0:Server'},
{'node_id': 'ns=2;i=1', 'browse_name': '2:Demo'}]
write_opcua_node(ns=2;i=4, "42.5") -> Successfully wrote 42.5 to node ns=2;i=4
读回来验证过:Setpoint 从 40.0 变成 42.5,Run 从 True 写成 False 再写回 True,都按预期生效。
三个使用习惯,能让它更好用:
- 先 browse,再 read。 工具返回的是给人看的字符串(
browse甚至是 Pythonrepr风格),先让模型看清有哪些节点、节点 ID 长什么样,再让它读值,比直接丢一个 node id 稳。 - 批量读优先。 要一次看十几个点位,用
read_multiple_opcua_nodes一次拿完,比逐点问快,也让模型的上下文干净。 - 写完让它读回来。 工业场景里"写成功"和"值真的变了"是两件事,养成回读习惯,报告里也要求它贴回读值。
一个真实用法:每日巡检
装好之后最先能落地的是"每天早上一份巡检报告"。提示词可以固定成这样:
读ns=2;i=3(温度)、ns=2;i=4(设定值)、ns=2;i=5(运行状态)三个值, 温度超过设定值 5 度就标红,并列出可能原因;运行状态是 False 时单独提示。
模型会自己决定调一次 read_multiple_opcua_nodes 拿三个值,再按你给的规则判断。相比写一个定时脚本,好处是规则用自然语言改——下周想把阈值从 5 度改成 3 度,改一句话就行,不用改代码、不用重新部署。
六、Modbus 侧:6 个工具,同样一次跑通
| 调用 | 返回(原样) |
|---|---|
read_register(5) |
Slave 1, Register 5 Value: 15 |
write_register(5, 777) |
Successfully wrote 777 to register 5 on slave 1 |
read_register(5) |
Slave 1, Register 5 Value: 777 ✅ |
read_input_registers(0, count=3) |
Slave 1, Input Registers 0 to 2: [100, 101, 102] |
read_multiple_holding_registers(0, count=4) |
Slave 1, Holding Registers 0 to 3: [10, 11, 12, 13] |
write_coil(2, True) |
Successfully wrote True to coil 2 on slave 1 |
read_coils(0, count=4) |
Slave 1, Coils 0 to 3: [False, False, True, False] ✅ |
延迟同样是 0.01 s 量级(本机回环)。Modbus 没有地址空间,AI 无法"自己探索有什么",所以用之前把点位表写进提示词(或者做成一张对照表挂在项目里),比让它猜要靠谱得多。
读错了地址时,返回会是一个标准的 Modbus 异常:
Error reading register 999 from slave 1:
ExceptionResponse(dev_id=1, function_code=131, exception_code=2)
exception_code 记住两个就够用:2 = 非法数据地址(点位表对不上),4 = 从站故障 / 网关目标设备无响应(线或从站的问题)。在提示词里把这两句教给模型,它排查时会准确很多。
点位表怎么给模型
Modbus 没有地址空间,模型只能靠你给的对照表。最省事的做法是在项目说明(或 Claude 的 Project、Cursor 的 rules 文件)里放一张表:
| 地址 | 类型 | 含义 | 单位 | 可否写 |
|---|---|---|---|---|
| HR 0 | uint16 | 产线速度设定 | rpm | ✅ |
| HR 5 | uint16 | 当前产量 | 件 | ❌ |
| IR 0 | uint16 | 电机温度 | 0.1 ℃ | ❌ |
| Coil 2 | bool | 启动允许 | — | ✅ |
有了这张表,模型问"HR 5 是多少"时会直接映射到 read_register(5),不会再瞎猜地址。OPC UA 侧虽然能 browse,但把节点 ID 和业务含义对上同样值得写一张表——browse 只能告诉你节点叫什么,告诉不了你它代表什么。
七、多这一层值不值
直说:如果只是自己跑一次脚本,别用 MCP,直接调库更省事。
from opcua import Client
c = Client("opc.tcp://127.0.0.1:4840/freeopcua/server/")
c.connect()
print(c.get_node("ns=2;i=3").get_value())
那 MCP 这层买到了什么?
| 买到的 | 具体是什么 |
|---|---|
| 给不写代码的人用 | 工程师在 Claude Desktop / Cursor 里说"读一下 3 号节点的温度",不用打开 IDE |
| 给 agent 自主编排 | 工具名 + 参数描述就是模型的说明书,它能自己决定先 browse 再 read、还是批量读 |
| 跨客户端复用 | 一份 mcpServers 配置,Claude / Cursor / 自研 agent 都能挂 |
判断标准很简单:"读产线"会不会变成 agent 的日常动作? 会,这层值得,把下面那几条规矩补上;只是偶尔查一次,写脚本。
八、那什么时候用 OPC UA,什么时候用 Openness?
| 你想让 AI 做的事 | 该走哪条路 | 为什么 |
|---|---|---|
| 看当前值、看趋势、看报警文本 | OPC UA | 标准协议、跨厂商,变量自带语义与类型;只读时风险最低 |
| 一次采集几十个点位做分析 | OPC UA(批量读) | 一次调用拿一批值,比逐点问快 |
| 改一个设定值 / 置位一个线圈 | OPC UA 或 Modbus(配白名单) | 能做,但要管住范围并回读校验 |
| 改程序:建块、写 SCL、编译、下载 | Openness(MCP 网关) | OPC UA 不碰组态和程序,这件事只有 TIA Portal 的 Openness 接口能做 |
| 把老项目的 LAD 迁成 SCL | Openness | 要读工程结构、生成并编译代码,属于"改工程"而不是"改数据" |
一句话:OPC UA 管"看和改数据",Openness 管"改工程"。 两条路不是替代关系,很多现场最后两个都要:运行期用 OPC UA 观测,工程期用 Openness 变更。
这条路上我们自己的产品是 TiaLink:把 TIA Portal 的 Openness 能力包成 MCP 接口,AI 可以建块、写 SCL、编译、下载到 PLCSIM 再读回核对(支持 V19 / V20,不支持 V21)。和本文测的 opcua-mcp 正好互补——一个动数据,一个动工程。只需要"让 AI 看见产线在发生什么",读完本文直接用 opcua-mcp 就够了。
九、加一层写白名单:约 20 行,实测有效
默认情况下这两个 Server 允许写任意节点。上线前最划算的一步改造,是给写操作加一张白名单——改 20 行,收益很大。以 opcua-mcp 为例:
ALLOWED_WRITE = {"ns=2;i=4", "ns=2;i=5"} # 只允许改设定值和运行位
def _deny(node_id: str) -> str:
return f"已拒绝:节点 {node_id} 不在写入白名单内(ALLOWED_WRITE)"
单节点写在取 client 之前先判一次:
if node_id not in ALLOWED_WRITE:
return _deny(node_id)
批量写要先整体校验再动手,否则会出现"前两个写进去、第三个被拒"的半成品状态:
denied = [i.get("node_id") for i in nodes_to_write if i.get("node_id") not in ALLOWED_WRITE]
if denied:
return f"已拒绝:以下节点不在写入白名单内:{denied}"
我们按这个改法跑了一遍(补丁在 distribution/hubwiz-seo/opcua-mcp-test/main_guard.py),实测结果:
| 调用 | 返回 |
|---|---|
写白名单内的 ns=2;i=4 = 41.5 |
Successfully wrote 41.5 to node ns=2;i=4 ✅ |
写白名单外的 ns=2;i=6 = 99 |
已拒绝:节点 ns=2;i=6 不在写入白名单内(ALLOWED_WRITE) ✅ |
批量写 [i=4→43.0, i=6→7] |
已拒绝:以下节点不在写入白名单内:['ns=2;i=6'],回读 i=4 仍是 41.5 ✅ 没有产生部分写入 |
同一套思路可以直接搬到 modbus-mcp:把允许写的寄存器号做成集合,写工具开头判一次即可。
十、接进每日巡检(可选)
MCP 客户端本身不负责定时,定时交给系统:每天早上 7:00 让 agent 跑一次上面那段提示词,把结果写进一个文件或发到群里。做法有两种:
- 有 CLI 的客户端:用
claude -p "(提示词)" --mcp-config mcp.json之类的一次性调用,套cron/ 计划任务。 - 自研 agent:用
mcp官方 Python SDK 起一个 stdio 会话,脚本里call_tool就行(本文的测试探针mcp_probe.py就是这么写的,可以直接当模板)。
两种都建议把"昨天同一时刻的值"一起读出来做对比,否则单点数值对工程师没有信息量——趋势比快照有用。
十一、上线前的五条规矩
这五条是工业现场的通用做法,换任何一套 AI 接 PLC 的方案都适用:
- 只给只读账户:能只读就别给写权限;OPC UA 侧用受限角色,别用匿名。
- 节点白名单:把允许写入的
node_id列成清单,清单外的拒绝。控制位、安全位永远不进清单。 - 写后回读:每次写成功都读一次比对,不一致就报错给模型,别让它自己宣布完成。
- 审计落盘:谁、什么时候、对哪个节点、写了什么、回读是什么,四行日志。
- 先在仿真器上跑:本文的模拟器脚本就够用,别拿真设备当第一次实验对象。
十二、四个常见问题
Q1:能同时接多台 PLC 吗? 可以,方式是一个设备一个 MCP 进程:opcua-mcp 的一个实例读一个 OPCUA_SERVER_URL,所以在 mcpServers 里配两个条目(opcua-line1 / opcua-line2)即可,Claude 会看到两套工具。如果嫌工具名重复,把 main.py 里的 FastMCP("OPCUA-Control") 改成不同名字更清楚。
Q2:中文变量名、结构体、DB 块怎么办? 结构体在 OPC UA 里就是一棵子树:browse 到父节点,再逐个读成员即可,不用特殊处理。中文变量名同理——返回值是字符串,中文能正常显示。建议先在仿真器上把你们真实的节点树导一遍,确认命名空间索引(ns=2 还是 ns=3)和成员层级,再换真设备。
Q3:能不能只订阅变化,不轮询? 这两个工具是请求-响应式的,没有订阅接口。需要"值变了就通知"的场景,用 OPC UA 原生的 subscription(Python 里 create_subscription + subscribe_data_change),或者退一步用定时轮询 + 变化检测。
Q4:安全上要注意什么? 这两个 Server 建的是匿名连接(Client(url) 不设用户/证书)。所以它们的合理位置是内网、仿真环境、或者你信任的工程机;生产网段建议加一层:设备侧启用用户/证书鉴权、网络侧走 VPN 或跳板,再把 MCP 进程限制在固定网段。
十三、来源与许可
kukapay/opcua-mcp:MIT,Python,31 stars,最后提交 2025-10-29kukapay/modbus-mcp:MIT,Python,25 stars,最后提交 2025-05-12- 协议与规范:OPC Foundation(OPC UA 即 IEC 62541)、Modbus Organization
- 模拟器:
asyncua(OPC UA)、pymodbus(Modbus TCP) - 本文所有返回值均为本机原样摘录;未接真实 PLC,如与你的设备行为不同,以设备为准
免责:写操作会真的改变现场状态,上产线前请走完整的变更审批与回滚演练。
想把 AI 从"读数据"推到"改工程",可以看 PLC 编程 AI 软件:7 个必备的 MCP Server 和 博途 AI 助手:让 AI 直接操作 TIA Portal(TiaLink);本专题的其它文章在 AI × 工业 / PLC。
这是「AI × 工业 / PLC」专题的一部分。完整地图见:AI × 工业 / PLC 完全指南