opcua-mcp
Enables LLMs to read, write, browse, search, and subscribe to live data on OPC-UA industrial automation servers, with caching, discovery index, and support for stdio or HTTP transports.
README
OPC-UA MCP Server
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
Quick start
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:
git clone https://github.com/mwieczorkiewicz/opcua-mcp.git
cd opcua-mcp
make compose-up # starts a test OPC-UA server, opcua-mcp, and a public HTTPS tunnel
make connector-url # prints a URL like https://xyz.trycloudflare.com/mcp
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.
Building and running locally
go build -o opcua-mcp ./cmd/opcua-mcp.go
# stdio (default) - no OPC-UA connection until the client calls opcua_connect
./opcua-mcp
# HTTP - connects eagerly at startup
SERVER_TRANSPORT=http OPCUA_ENDPOINT=opc.tcp://localhost:4840 ./opcua-mcp
Requires Go 1.26+ and, optionally, Docker for the test server / containerized deployment.
What it does
- Read / write node values, with type validation on writes so a mismatched value is rejected before it reaches the device.
- Browse the address space one level at a time or recursively, and look nodes up by name instead of by node ID.
- Subscribe to push-based live updates - subscriptions persist across restarts and are automatically re-established on reconnect.
- Cache reads, browse results, and type info on disk (bbolt), so repeat lookups don't round-trip to the device; writes invalidate the relevant entry automatically.
- Discover and search the address space in the background, indexed with Bleve for fuzzy/partial browse-name lookups.
- Anonymous, username/password, or certificate auth, with configurable OPC-UA security policy and mode.
See docs/architecture.md for how the caching layer, subscription manager, and discovery index fit together.
MCP tools
| 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. |
MCP resources
| 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
Configuration is loaded (via viper) from three sources, in ascending order of precedence:
- Built-in defaults (shown in the tables below).
- An optional config file - TOML, YAML, JSON, or any other format viper
supports. By default
./config.{yaml,yml,toml,json,...}is read if present; point at an explicit path withCONFIG_FILE=/path/to/config.toml. A config file is entirely optional - env vars alone are still enough. - Environment variables (
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:
server:
http_port: "8080"
Server
| 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 |
OPC-UA connection
| 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 |
MCP
| 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 |
Discovery and search
| 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 |
Persistent store
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.
Telemetry
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 |
Docker
docker build -t opcua-mcp .
docker run -p 8080:8080 -e SERVER_TRANSPORT=http -e OPCUA_ENDPOINT=opc.tcp://your-server:4840 opcua-mcp
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.
Development
make start-opcua-server # Microsoft OPC-UA test server in Docker
make run-with-test-server # run the app against it (auto start/stop)
go test ./... # unit tests
go test -race ./...
make test-integration # real Subscribe/reconnect/cache behavior via testcontainers-go (needs Docker)
VS Code launch configs are in .vscode/launch.example.json - copy to
.vscode/launch.json to get stdio/HTTP/auth debug targets that start and
stop the test server automatically. make help lists every available
target.
Tests are table-driven and mock the OPC-UA client at the opcuaClient
interface seam (internal/opcua/mock_client_test.go) rather than against a
live/simulated server - see docs/architecture.md for
how the pieces being tested fit together, and
docs/COMMIT_CONVENTION.md for this repo's
commit message format.
Contributing
Fork it, make your changes, open a PR - see CONTRIBUTING.md.
License
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。