Read, write, browse, search, and subscribe to live data on an OPC-UA industrial automation server.
Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β or use 1-click editor setup below.
π‘ Paste the JSON block into your client's configuration file under mcpServers, then restart the application.
An MCP server that lets an LLM read, write, browse, search, and subscribe to live data on an OPC-UA industrial automation server - over stdio or HTTP, with a persistent cache and a searchable index of the address space built in.
https://github.com/user-attachments/assets/0b676e6e-17ce-42f5-918f-9a615e939008
The fastest way to see it working end-to-end, using the bundled Microsoft OPC-UA test server and a public tunnel Claude can reach:
Paste that URL into Claude at Settings β Connectors β Add custom
connector, then ask it to browse the server or read a value. Stop with
make compose-down when you're done - see
docs/deployment.md for what that tunnel exposes and
how to run against your own OPC-UA server instead.
Requires Go 1.26+ and, optionally, Docker for the test server / containerized deployment.
See docs/architecture.md for how the caching layer, subscription manager, and discovery index fit together.
| Tool | Description |
|---|---|
opcua_read | Read one or more node values. Subscribed nodes are served from the live cache; others go live unless max_age_ms allows a cached value. |
opcua_write | Write a value to a node. Validates the value's type against the node before writing. |
opcua_get_value | Read a single node's value - a convenience wrapper over opcua_read. |
opcua_get_value_by_name | Read a value by browse name instead of node ID, via the discovery index. |
opcua_browse | List a node's immediate children. |
opcua_browse_nodes | Recursively browse from a node up to a depth limit, nesting children under their parent. |
opcua_node_info | Get a node's metadata (data type, access level, etc.). |
opcua_find_similar_nodes | Fuzzy-match browse names against the discovery index. |
opcua_subscribe | Start push-based updates for one or more nodes at a given interval. |
opcua_unsubscribe | Cancel a subscription, by ID or by naming one of its nodes. |
opcua_list_subscriptions | List active subscriptions. |
opcua_connect / opcua_disconnect | Manage the connection explicitly (mainly relevant in stdio mode). |
opcua_server_info | Get OPC-UA server metadata. |
opcua_discovery_stats | Stats on the background discovery cache (node count, depth distribution, enabled flags). |
opcua_force_discovery | Trigger an immediate discovery refresh instead of waiting for the next cycle. |
opcua_debug_search / opcua_ensure_server_nodes | Diagnostics for troubleshooting why a node isn't showing up in search. |
| Resource | Description |
|---|---|
opcua://node/{node_id} | Node data, e.g. opcua://node/ns=2;i=1. Accepts a comma-separated list for multiple nodes. |
opcua://server | OPC-UA server information. |
Configuration is loaded (via viper) from three sources, in ascending order of precedence:
./config.{yaml,yml,toml,json,...} is read if
present; point at an explicit path with CONFIG_FILE=/path/to/config.toml.
A config file is entirely optional - env vars alone are still enough.SERVER_*, OPCUA_*, MCP_*, SEARCH_*, STORE_*) - always win over the config file, so existing env-var-only deployments keep working unchanged.A config file mirrors the env var names, lowercased and nested under each
prefix, e.g. SERVER_HTTP_PORT becomes:
| Variable | Default | Description |
|---|---|---|
SERVER_TRANSPORT | stdio | stdio or http |
SERVER_HTTP_PORT | 8080 | Port for HTTP transport |
SERVER_LOG_LEVEL | info | debug, info, warn, error |
SERVER_LOG_FORMAT | json | json or text |
SERVER_LOG_OUTPUT | stdout | stdout, stderr, or file (forced to stderr in stdio mode, since stdout carries the MCP stream) |
SERVER_LOG_FILE | - | Log file path, required if SERVER_LOG_OUTPUT=file |
SERVER_LOG_ADD_SOURCE | false | Add source file/line to log entries |
| Variable | Default | Description |
|---|---|---|
OPCUA_ENDPOINT | opc.tcp://localhost:4840 | Server endpoint |
OPCUA_AUTH_MODE | anonymous | anonymous, username, or certificate |
OPCUA_USERNAME / OPCUA_PASSWORD | - | Required if AUTH_MODE=username |
OPCUA_CERT_FILE / OPCUA_KEY_FILE | - | Required if AUTH_MODE=certificate |
OPCUA_SERVER_CERT | - | Server certificate file path |
OPCUA_SECURITY_POLICY | None | None, Basic128Rsa15, Basic256, Basic256Sha256, Aes128_Sha256_RsaOaep |
OPCUA_SECURITY_MODE | None | None, Sign, SignAndEncrypt |
OPCUA_REQUEST_TIMEOUT | 30s | Per-request timeout |
OPCUA_SESSION_TIMEOUT | 60s | Session timeout |
OPCUA_MAX_RETRIES | 3 | Connection retry attempts |
OPCUA_RETRY_DELAY | 1s | Delay between retries |
| Variable | Default | Description |
|---|---|---|
MCP_NAME | OPC-UA MCP Server | Server name reported to clients |
MCP_VERSION | 1.0.0 | Server version reported to clients |
MCP_ENABLE_TOOLS | true | Enable tools |
MCP_ENABLE_RESOURCES | true | Enable resources |
MCP_ENABLE_PROMPTS | false | Enable prompts |
MCP_HTTP_PATH | /mcp | HTTP endpoint path |
| Variable | Default | Description |
|---|---|---|
SEARCH_ENABLE_DISCOVERY | true | Enable background node discovery |
SEARCH_DISCOVERY_INTERVAL | 30s | How often to re-crawl the address space |
SEARCH_DISCOVERY_ROOT_NODE | i=85 | Root node to crawl from (Objects folder) |
SEARCH_MAX_DISCOVERY_DEPTH | 10 | Maximum crawl depth |
SEARCH_MAX_NODES_PER_BROWSE | 10000 | Cap on nodes returned per browse call |
SEARCH_ENABLE_SEARCH | true | Enable the Bleve search index |
SEARCH_INDEX_PATH | ./search_index | Search index directory |
SEARCH_MAX_RESULTS | 100 | Max results per search |
SEARCH_MIN_SCORE | 0.1 | Minimum match score |
SEARCH_ENABLE_CACHE | true | Master switch for read-through caching. false makes every opcua_read/opcua_write/opcua_browse_nodes call go live, matching pre-cache behavior exactly |
Backs read-through caching and subscription persistence with an on-disk bbolt database.
| Variable | Default | Description |
|---|---|---|
STORE_DB_PATH | mcp_opcua_store.db | Database file path |
STORE_OPEN_TIMEOUT | 5s | How long to wait for the file lock on open |
STORE_TYPEINFO_TTL | 24h | Freshness window for cached type info |
STORE_BROWSE_TTL | 5m | Freshness window for cached browse results |
STORE_BATCH_WINDOW | 25ms | How often subscription notifications flush to the store |
STORE_BATCH_MAX_ITEMS | 250 | Max notifications flushed per batch |
STORE_NOTIFY_CHAN_BUFFER | 1024 | Buffer size for incoming subscription notifications |
If the store fails to open (e.g. a stale lock from a prior ungraceful shutdown, or a read-only filesystem), the server logs a warning and keeps running with caching forced off and subscription tools returning an error - every other tool is unaffected.
opcua-mcp collects anonymous, aggregate usage telemetry (which tools get used, cache hit rate, error categories - never node IDs, endpoint URLs, node values, or credentials) to help prioritize maintenance of this open-source project. It's on by default; see docs/telemetry.md for exactly what is and isn't collected.
Opt out with either:
| Variable | Effect |
|---|---|
DO_NOT_TRACK=1 | The cross-project community convention (consoledonottrack.com) |
OPCUA_MCP_TELEMETRY=false | This project's own switch |
Multi-stage build on Chainguard's minimal Go image, running from scratch -
no shell, small attack surface. Mount ./search_index and
./mcp_opcua_store.db as volumes to persist discovery/cache/subscription
state across restarts. Full auth-mode examples, the Compose dev stack, and
the Claude-connector tunnel setup are in
docs/deployment.md.
No reviews yet β be the first to share how this listing worked for you.
Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.
[](https://allmcps.com/mcp/opc-ua-mcp-server)<a href="https://allmcps.com/mcp/opc-ua-mcp-server"><img src="https://allmcps.com/api/badge/opc-ua-mcp-server?style=directory" alt="OPC UA MCP Server on AllMCPs" /></a>