
One local MCP stdio process connects one client context to one OPC UA endpoint.
Use a separate process for each endpoint. Remote MCP and shared multi-client
operation are gated by the identity and isolation RFC.
Features
- 🔌 Any OPC UA server — PLC, SCADA gateway or historian. Nothing to install on the plant side.
- 🧰 The whole operator toolkit — read, browse, history and aggregates, subscriptions, events and alarms, writes and method calls.
- 🛡️ Read-only by default — writes and method calls need an explicit profile, an allowlist and a pinned server certificate, and every control call is audited.
- 🐍 Python or Node — two first-class runtimes with the same tools and the same answers. Use whichever you have.
- 📦 One-file Claude Desktop install — a
.mcpb bundle with nothing else to set up.
flowchart LR
A["AI agent<br/>(Claude, Codex, Gemini, Cursor…)"] -->|MCP over stdio| B["OPC UA MCP Server<br/>(Python or Node)"]
B -->|OPC UA| C["OPC UA server<br/>(PLC / SCADA / historian)"]
Quick start
Every MCP client runs the same command, with your endpoint in OPCUA_SERVER_URL:
npx -y opcua-mcp-server # Node 22.13+
uvx opcua-mcp-server # Python 3.10+
- Add it to your agent — pick yours below.
- Point it at a server —
opc.tcp://<host>:4840, or start the bundled mock plant.
- Ask — "What's the current temperature in the reactor vessel?"
Connect your agent
Replace opc.tcp://localhost:4840 with your endpoint. To use the Python runtime,
swap npx -y opcua-mcp-server for uvx opcua-mcp-server.
Claude Desktop
Easiest — the bundle. Download opcua-mcp-server-<version>.mcpb from the
latest release,
drag it into Settings → Extensions, and fill in OPC UA endpoint. No
Node, no Python, no JSON.
Or let the server write the config (needs Node or Python):
npm install -g opcua-mcp-server # or: uv tool install opcua-mcp-server
opcua-mcp-server --install claude-desktop --url opc.tcp://192.168.0.10:4840 --dry-run
It checks the config with the server's own startup validation and writes absolute
paths, because Claude Desktop does not inherit your shell's PATH. The profile
defaults to read-only; encryption, a pinned server certificate, a control profile,
a policy file and an audit file are all flags, and passwords are never taken as
flags. Drop --dry-run to write. Every flag and safety rule, editing
claude_desktop_config.json by hand, and fixing a server that fails to start:
docs/install.md.
Claude Code
claude mcp add opcua -e OPCUA_SERVER_URL=opc.tcp://localhost:4840 -- npx -y opcua-mcp-server
Add --scope project to share it with your team through .mcp.json.
OpenAI Codex
codex mcp add opcua --env OPCUA_SERVER_URL=opc.tcp://localhost:4840 -- npx -y opcua-mcp-server
Or add it to ~/.codex/config.toml:
[mcp_servers.opcua]
command = "npx"
args = ["-y", "opcua-mcp-server"]
env = { OPCUA_SERVER_URL = "opc.tcp://localhost:4840" }
Or let the server write that entry, with the same checks as for Claude Desktop:
opcua-mcp-server --install codex --url opc.tcp://localhost:4840 --dry-run.
Gemini CLI
Add to ~/.gemini/settings.json, or .gemini/settings.json in a project:
{
"mcpServers": {
"opcua": {
"command": "npx",
"args": ["-y", "opcua-mcp-server"],
"env": { "OPCUA_SERVER_URL": "opc.tcp://localhost:4840" }
}
}
}
Google Antigravity
In the agent panel, open ⋯ → MCP Servers → Manage MCP Servers → View raw
config, add the entry below to mcp_config.json, and restart Antigravity:
{
"mcpServers": {
"opcua": {
"command": "npx",
"args": ["-y", "opcua-mcp-server"],
"env": { "OPCUA_SERVER_URL": "opc.tcp://localhost:4840" }
}
}
}
Cursor
Add to ~/.cursor/mcp.json, or .cursor/mcp.json in a project:
{
"mcpServers": {
"opcua": {
"command": "npx",
"args": ["-y", "opcua-mcp-server"],
"env": { "OPCUA_SERVER_URL": "opc.tcp://localhost:4840" }
}
}
}
VS Code (GitHub Copilot)
Add to .vscode/mcp.json — note the top-level key is servers:
{
"servers": {
"opcua": {
"type": "stdio",
"command": "npx",
"args": ["-y", "opcua-mcp-server"],
"env": { "OPCUA_SERVER_URL": "opc.tcp://localhost:4840" }
}
}
}
Windsurf
Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"opcua": {
"command": "npx",
"args": ["-y", "opcua-mcp-server"],
"env": { "OPCUA_SERVER_URL": "opc.tcp://localhost:4840" }
}
}
}
Any other MCP client
Most clients accept this mcpServers entry. The server speaks MCP over stdio.
{
"mcpServers": {
"opcua": {
"command": "npx",
"args": ["-y", "opcua-mcp-server"],
"env": { "OPCUA_SERVER_URL": "opc.tcp://localhost:4840" }
}
}
}
[!TIP]
No Node or Python on the machine? Download a single-file executable from the
latest release
and use its path as the command — see docs/install.md.
Several PLCs? Each entry talks to one endpoint, so add one entry per PLC
(why).
Try it without a plant
The repo ships a simulated industrial plant — sensors, actuators, methods,
history and events — so you can try every tool without touching real equipment:
git clone https://github.com/IndustriAgents/OPCUA-MCP.git && cd OPCUA-MCP
uv sync --all-packages
uv run --no-sync opcua-mock-server # opc.tcp://localhost:4840/freeopcua/server/
Point your agent at that URL. The MCP Inspector walkthrough
has prompts to try, and the compatibility matrix lists
two smaller mocks for aggregates and alarms.
What you can ask
- "Show me all the variables in the system."
- "What was the temperature over the last hour? Give me the hourly average."
- "Watch the tank level and tell me what it does over the next minute."
- "What alarms are active right now?"
- "Set the valve position to 80%." — needs the
operator profile
- "Start production on line 1 at 100 units/hour." — needs the
operator profile
Every reading comes back with its data type, status, timestamps and engineering
units — never a bare number. What a result looks like.
15 tools, identical on both runtimes and defined once in contract/tools.json. Arguments, results and limits: docs/tools.md.
Control and alarm tools also need a verified server (a secured channel and a pinned OPCUA_SERVER_CERT) unless a lab override is set. † Works only on a server that advertises the feature it needs, such as historical access.
Going to production
[!WARNING]
Out of the box the agent can only read, but the OPC UA channel is
unencrypted and anonymous — fine for the mock or a lab, not for real
equipment. Secure the channel before connecting to anything that matters.
1. Secure the channel and pin the server — add these to the env block:
OPCUA_SECURITY_POLICY=Basic256Sha256 # implies SignAndEncrypt
OPCUA_CLIENT_CERT=/etc/opcua/client.pem # this client's identity
OPCUA_CLIENT_KEY=/etc/opcua/client_key.pem
OPCUA_SERVER_CERT=/etc/opcua/server.pem # pin the server you meant to reach
OPCUA_USERNAME=mcp-operator # or OPCUA_USER_CERT for X.509
OPCUA_PASSWORD=…
2. Decide what the agent may do with OPCUA_PROFILE:
3. Keep a record — set OPCUA_AUDIT_FILE for an append-only log of every
control call.
Misconfiguration stops the server at startup with a message naming the variable,
rather than failing later against live equipment. The MCP policy is defence in
depth, not a replacement for OPC UA authorisation: scope the OPC UA account to the
same nodes and methods.
Every setting, value bounds on writes and allowlists that survive a server
restart: docs/configuration.md. Threat model:
SECURITY.md. Certificates: docs/certificates.md.
Documentation
Contributing
Contributions are welcome — see CONTRIBUTING.md for the
project layout, local development and adding a tool to both runtimes.
The most useful thing you can send is a result from a real OPC UA server: open a
compatibility report
saying which server, which version and which tools worked. Test only on equipment
you are authorised to use, and keep writes and method calls to a simulator or lab.
License
MIT — see LICENSE.
Tool inputs and structured results use JSON Schema draft 2020-12 with generated
TypeScript/Python types and CI drift checks. See
contract generation for the authoring workflow.