{
  "markdown": "# OPC-UA MCP Server\n\nAn MCP server that lets an LLM read, write, browse, search, and subscribe to\nlive data on an OPC-UA industrial automation server - over stdio or HTTP,\nwith a persistent cache and a searchable index of the address space built in.\n\nhttps://github.com/user-attachments/assets/0b676e6e-17ce-42f5-918f-9a615e939008\n\n## Quick start\n\nThe fastest way to see it working end-to-end, using the bundled Microsoft\nOPC-UA test server and a public tunnel Claude can reach:\n\n```bash\ngit clone https://github.com/mwieczorkiewicz/opcua-mcp.git\ncd opcua-mcp\nmake compose-up        # starts a test OPC-UA server, opcua-mcp, and a public HTTPS tunnel\nmake connector-url      # prints a URL like https://xyz.trycloudflare.com/mcp\n```\n\nPaste that URL into Claude at **Settings → Connectors → Add custom\nconnector**, then ask it to browse the server or read a value. Stop with\n`make compose-down` when you're done - see\n[docs/deployment.md](docs/deployment.md) for what that tunnel exposes and\nhow to run against your own OPC-UA server instead.\n\n### Building and running locally\n\n```bash\ngo build -o opcua-mcp ./cmd/opcua-mcp.go\n\n# stdio (default) - no OPC-UA connection until the client calls opcua_connect\n./opcua-mcp\n\n# HTTP - connects eagerly at startup\nSERVER_TRANSPORT=http OPCUA_ENDPOINT=opc.tcp://localhost:4840 ./opcua-mcp\n```\n\nRequires Go 1.26+ and, optionally, Docker for the test server / containerized\ndeployment.\n\n## What it does\n\n- **Read / write** node values, with type validation on writes so a\n  mismatched value is rejected before it reaches the device.\n- **Browse** the address space one level at a time or recursively, and look\n  nodes up by name instead of by node ID.\n- **Subscribe** to push-based live updates - subscriptions persist across\n  restarts and are automatically re-established on reconnect.\n- **Cache** reads, browse results, and type info on disk (bbolt), so repeat\n  lookups don't round-trip to the device; writes invalidate the relevant\n  entry automatically.\n- **Discover and search** the address space in the background, indexed with\n  Bleve for fuzzy/partial browse-name lookups.\n- **Anonymous, username/password, or certificate auth**, with configurable\n  OPC-UA security policy and mode.\n\nSee [docs/architecture.md](docs/architecture.md) for how the caching layer,\nsubscription manager, and discovery index fit together.\n\n## MCP tools\n\n| Tool | Description |\n|---|---|\n| `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. |\n| `opcua_write` | Write a value to a node. Validates the value's type against the node before writing. |\n| `opcua_get_value` | Read a single node's value - a convenience wrapper over `opcua_read`. |\n| `opcua_get_value_by_name` | Read a value by browse name instead of node ID, via the discovery index. |\n| `opcua_browse` | List a node's immediate children. |\n| `opcua_browse_nodes` | Recursively browse from a node up to a depth limit, nesting children under their parent. |\n| `opcua_node_info` | Get a node's metadata (data type, access level, etc.). |\n| `opcua_find_similar_nodes` | Fuzzy-match browse names against the discovery index. |\n| `opcua_subscribe` | Start push-based updates for one or more nodes at a given interval. |\n| `opcua_unsubscribe` | Cancel a subscription, by ID or by naming one of its nodes. |\n| `opcua_list_subscriptions` | List active subscriptions. |\n| `opcua_connect` / `opcua_disconnect` | Manage the connection explicitly (mainly relevant in stdio mode). |\n| `opcua_server_info` | Get OPC-UA server metadata. |\n| `opcua_discovery_stats` | Stats on the background discovery cache (node count, depth distribution, enabled flags). |\n| `opcua_force_discovery` | Trigger an immediate discovery refresh instead of waiting for the next cycle. |\n| `opcua_debug_search` / `opcua_ensure_server_nodes` | Diagnostics for troubleshooting why a node isn't showing up in search. |\n\n## MCP resources\n\n| Resource | Description |\n|---|---|\n| `opcua://node/{node_id}` | Node data, e.g. `opcua://node/ns=2;i=1`. Accepts a comma-separated list for multiple nodes. |\n| `opcua://server` | OPC-UA server information. |\n\n## Configuration\n\nConfiguration is loaded (via [viper](https://github.com/spf13/viper)) from\nthree sources, in ascending order of precedence:\n\n1. Built-in defaults (shown in the tables below).\n2. An optional config file - TOML, YAML, JSON, or any other format viper\n   supports. By default `./config.{yaml,yml,toml,json,...}` is read if\n   present; point at an explicit path with `CONFIG_FILE=/path/to/config.toml`.\n   A config file is entirely optional - env vars alone are still enough.\n3. Environment variables (`SERVER_*`, `OPCUA_*`, `MCP_*`, `SEARCH_*`, `STORE_*`) - **always win** over the config file, so existing env-var-only deployments keep working unchanged.\n\nA config file mirrors the env var names, lowercased and nested under each\nprefix, e.g. `SERVER_HTTP_PORT` becomes:\n\n```yaml\nserver:\n  http_port: \"8080\"\n```\n\n### Server\n\n| Variable | Default | Description |\n|---|---|---|\n| `SERVER_TRANSPORT` | `stdio` | `stdio` or `http` |\n| `SERVER_HTTP_PORT` | `8080` | Port for HTTP transport |\n| `SERVER_LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error` |\n| `SERVER_LOG_FORMAT` | `json` | `json` or `text` |\n| `SERVER_LOG_OUTPUT` | `stdout` | `stdout`, `stderr`, or `file` (forced to `stderr` in stdio mode, since stdout carries the MCP stream) |\n| `SERVER_LOG_FILE` | - | Log file path, required if `SERVER_LOG_OUTPUT=file` |\n| `SERVER_LOG_ADD_SOURCE` | `false` | Add source file/line to log entries |\n\n### OPC-UA connection\n\n| Variable | Default | Description |\n|---|---|---|\n| `OPCUA_ENDPOINT` | `opc.tcp://localhost:4840` | Server endpoint |\n| `OPCUA_AUTH_MODE` | `anonymous` | `anonymous`, `username`, or `certificate` |\n| `OPCUA_USERNAME` / `OPCUA_PASSWORD` | - | Required if `AUTH_MODE=username` |\n| `OPCUA_CERT_FILE` / `OPCUA_KEY_FILE` | - | Required if `AUTH_MODE=certificate` |\n| `OPCUA_SERVER_CERT` | - | Server certificate file path |\n| `OPCUA_SECURITY_POLICY` | `None` | `None`, `Basic128Rsa15`, `Basic256`, `Basic256Sha256`, `Aes128_Sha256_RsaOaep` |\n| `OPCUA_SECURITY_MODE` | `None` | `None`, `Sign`, `SignAndEncrypt` |\n| `OPCUA_REQUEST_TIMEOUT` | `30s` | Per-request timeout |\n| `OPCUA_SESSION_TIMEOUT` | `60s` | Session timeout |\n| `OPCUA_MAX_RETRIES` | `3` | Connection retry attempts |\n| `OPCUA_RETRY_DELAY` | `1s` | Delay between retries |\n\n### MCP\n\n| Variable | Default | Description |\n|---|---|---|\n| `MCP_NAME` | `OPC-UA MCP Server` | Server name reported to clients |\n| `MCP_VERSION` | `1.0.0` | Server version reported to clients |\n| `MCP_ENABLE_TOOLS` | `true` | Enable tools |\n| `MCP_ENABLE_RESOURCES` | `true` | Enable resources |\n| `MCP_ENABLE_PROMPTS` | `false` | Enable prompts |\n| `MCP_HTTP_PATH` | `/mcp` | HTTP endpoint path |\n\n### Discovery and search\n\n| Variable | Default | Description |\n|---|---|---|\n| `SEARCH_ENABLE_DISCOVERY` | `true` | Enable background node discovery |\n| `SEARCH_DISCOVERY_INTERVAL` | `30s` | How often to re-crawl the address space |\n| `SEARCH_DISCOVERY_ROOT_NODE` | `i=85` | Root node to crawl from (Objects folder) |\n| `SEARCH_MAX_DISCOVERY_DEPTH` | `10` | Maximum crawl depth |\n| `SEARCH_MAX_NODES_PER_BROWSE` | `10000` | Cap on nodes returned per browse call |\n| `SEARCH_ENABLE_SEARCH` | `true` | Enable the Bleve search index |\n| `SEARCH_INDEX_PATH` | `./search_index` | Search index directory |\n| `SEARCH_MAX_RESULTS` | `100` | Max results per search |\n| `SEARCH_MIN_SCORE` | `0.1` | Minimum match score |\n| `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 |\n\n### Persistent store\n\nBacks read-through caching and subscription persistence with an on-disk\nbbolt database.\n\n| Variable | Default | Description |\n|---|---|---|\n| `STORE_DB_PATH` | `mcp_opcua_store.db` | Database file path |\n| `STORE_OPEN_TIMEOUT` | `5s` | How long to wait for the file lock on open |\n| `STORE_TYPEINFO_TTL` | `24h` | Freshness window for cached type info |\n| `STORE_BROWSE_TTL` | `5m` | Freshness window for cached browse results |\n| `STORE_BATCH_WINDOW` | `25ms` | How often subscription notifications flush to the store |\n| `STORE_BATCH_MAX_ITEMS` | `250` | Max notifications flushed per batch |\n| `STORE_NOTIFY_CHAN_BUFFER` | `1024` | Buffer size for incoming subscription notifications |\n\nIf the store fails to open (e.g. a stale lock from a prior ungraceful\nshutdown, or a read-only filesystem), the server logs a warning and keeps\nrunning with caching forced off and subscription tools returning an error -\nevery other tool is unaffected.\n\n## Telemetry\n\nopcua-mcp collects anonymous, aggregate usage telemetry (which tools get\nused, cache hit rate, error categories - never node IDs, endpoint URLs, node\nvalues, or credentials) to help prioritize maintenance of this open-source\nproject. It's on by default; see\n[docs/telemetry.md](docs/telemetry.md) for exactly what is and isn't\ncollected.\n\nOpt out with either:\n\n| Variable | Effect |\n|---|---|\n| `DO_NOT_TRACK=1` | The cross-project community convention ([consoledonottrack.com](https://consoledonottrack.com)) |\n| `OPCUA_MCP_TELEMETRY=false` | This project's own switch |\n\n## Docker\n\n```bash\ndocker build -t opcua-mcp .\ndocker run -p 8080:8080 -e SERVER_TRANSPORT=http -e OPCUA_ENDPOINT=opc.tcp://your-server:4840 opcua-mcp\n```\n\nMulti-stage build on Chainguard's minimal Go image, running from `scratch` -\nno shell, small attack surface. Mount `./search_index` and\n`./mcp_opcua_store.db` as volumes to persist discovery/cache/subscription\nstate across restarts. Full auth-mode examples, the Compose dev stack, and\nthe Claude-connector tunnel setup are in\n[docs/deployment.md](docs/deployment.md).\n\n## Development\n\n```bash\nmake start-opcua-server      # Microsoft OPC-UA test server in Docker\nmake run-with-test-server    # run the app against it (auto start/stop)\n\ngo test ./...                # unit tests\ngo test -race ./...\nmake test-integration        # real Subscribe/reconnect/cache behavior via testcontainers-go (needs Docker)\n```\n\nVS Code launch configs are in `.vscode/launch.example.json` - copy to\n`.vscode/launch.json` to get stdio/HTTP/auth debug targets that start and\nstop the test server automatically. `make help` lists every available\ntarget.\n\nTests are table-driven and mock the OPC-UA client at the `opcuaClient`\ninterface seam (`internal/opcua/mock_client_test.go`) rather than against a\nlive/simulated server - see [docs/architecture.md](docs/architecture.md) for\nhow the pieces being tested fit together, and\n[docs/COMMIT_CONVENTION.md](docs/COMMIT_CONVENTION.md) for this repo's\ncommit message format.\n\n## Contributing\n\nFork it, make your changes, open a PR - see [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\n[MIT](LICENSE)\n",
  "bytes": 10908,
  "sha": "f02cf8ae062f9cb75ad16b36366920f549e45dd77edc9043e74f681cec1975d0",
  "repo_slug": "mwieczorkiewicz/opcua-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_mwieczorkiewicz_opcua_mcp_0dacc23a/readme"
}