OPC UA MCP Server
π OPC UA MCP Server
Let Claude, Codex, Gemini and any other MCP agent read sensors, browse the plant and β only when you allow it β control equipment on any OPC UA server.
Quick start Β· Connect your agent Β· Tools Β· Production Β· Docs

This project is a bridge between an AI agent and an OPC UA server. OPC UA is the protocol used to expose industrial equipment and its data; MCP (Model Context Protocol) lets an agent call tools to work with that data. The bridge runs on your computer and connects to an existing OPC UA server, or to a local simulator for a first test.
Your MCP client starts the bridge as a local stdio process: it exchanges MCP messages over standard input and output. One 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 control profile and an authenticated server (pinned certificate or CA trust store). The
operatorprofile also requires allowlisted targets, and every control call is audited.π Python or Node β two first-class runtimes with the same tool contract. Use whichever you have, accounting for the documented runtime differences.
π¦ One-file Claude Desktop install β a
.mcpbbundle 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)"]Related MCP server: OT-AIops
Quick start
Follow these steps in order: choose an installation, start or identify an OPC UA server, configure your MCP client, then check the connection. You need an MCP client such as one of the agents listed below.
1. Choose an installation
Choose one runtime; you do not need both. The package routes fetch the server on demand, so you do not need to clone this repository to use an existing OPC UA server.
Route | Prerequisites | Command your MCP client launches |
Node package | Node.js 22.13+, including npm and npx |
|
Python package | Python 3.10+ and uv (which supplies uvx) |
|
Claude Desktop bundle | Claude Desktop; no separate Node or Python installation | Install the |
Single-file executable | Download for your platform; no Node or Python installation | Use its absolute path as the client command; see installation instructions |
For a package route, check that it can run from your terminal. Run the command for your chosen runtime:
# Node
npx -y opcua-mcp-server --version
# Or Python
uvx opcua-mcp-server --versionThese commands print a version and exit. In normal use, your MCP client launches
the server without --version; you do not also start the bridge in a separate
terminal. A bridge started by itself waits for MCP messages, rather than showing
an interactive prompt or a web page.
2. Choose an OPC UA endpoint
For an existing server, get its endpoint URL, including any path, from its
administrator (for example opc.tcp://192.168.0.10:4840). The bridge reads it from
OPCUA_SERVER_URL, which you set in your MCP client's configuration in step 3.
If unset, the default is opc.tcp://localhost:4840.
The default profile is observe (read-only), and the default OPC UA connection is
unencrypted and anonymous. Use those defaults for the local mock. For real
equipment, prepare the security configuration before
connecting.
Try it without a plant
If you do not have an OPC UA server, start the bundled simulated plant. This optional route needs Git, Python 3.10+ and uv, even if you chose the Node bridge. Run these commands in a terminal:
git clone https://github.com/IndustriAgents/OPCUA-MCP.git
cd OPCUA-MCP
uv sync --all-packages
uv run --no-sync opcua-mock-serverWait for Server is running and ready for connections, then leave this terminal
running. The mock listens on port 4840; use
opc.tcp://localhost:4840/freeopcua/server/ as your endpoint. Stop it with
Ctrl-C when finished. If the port is busy, choose another port with
uv run --no-sync opcua-mock-server --endpoint opc.tcp://127.0.0.1:14840/freeopcua/server/
and use that URL in your client configuration.
The plant supports browsing, sensor reads, data-change subscriptions, raw value history, plain events and event history. It also has writable actuators and methods, subject to the bridge's control policy. It does not implement processed aggregates or Alarms & Conditions: its plain alarm events cannot be listed as retained conditions, acknowledged or acted on with alarm-action tools. Use the separate mocks for those operations:
Mock | Endpoint | Coverage |
| Sensors, actuators, methods, raw history, subscriptions and plain events | |
| Processed history, such as average buckets | |
| Active conditions and acknowledgement |
The separate mocks need Node and npm; their READMEs give installation and startup commands. The MCP Inspector walkthrough has more examples, and the compatibility matrix records tested coverage.
Connect your agent
3. Configure the client and launch the bridge
Pick one client below. The examples use the local plant endpoint from step 2;
replace opc.tcp://localhost:4840/freeopcua/server/ with your endpoint if needed.
For a secure connection, add the settings from Going to production
to the same env block (or the bundle's settings fields).
To use Python in a JSON configuration, set "command": "uvx" and
"args": ["opcua-mcp-server"]. In a CLI registration command, replace
npx -y opcua-mcp-server with uvx opcua-mcp-server. In TOML, use
command = "uvx" and args = ["opcua-mcp-server"].
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://localhost:4840/freeopcua/server/ --dry-runIt 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. Check the preview, then repeat the command without --dry-run to write
the configuration and restart Claude Desktop. Every flag and safety rule, editing
claude_desktop_config.json by hand, and fixing a server that fails to start:
docs/install.md.
claude mcp add opcua -e OPCUA_SERVER_URL=opc.tcp://localhost:4840/freeopcua/server/ -- npx -y opcua-mcp-serverAdd --scope project to share it with your team through .mcp.json.
codex mcp add opcua --env OPCUA_SERVER_URL=opc.tcp://localhost:4840/freeopcua/server/ -- npx -y opcua-mcp-serverOr add it to ~/.codex/config.toml:
[mcp_servers.opcua]
command = "npx"
args = ["-y", "opcua-mcp-server"]
env = { OPCUA_SERVER_URL = "opc.tcp://localhost:4840/freeopcua/server/" }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/freeopcua/server/ --dry-run.
This installer command requires a permanent package installation first:
npm install -g opcua-mcp-server or uv tool install opcua-mcp-server.
Check the preview and repeat without --dry-run to write it.
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/freeopcua/server/" }
}
}
}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/freeopcua/server/" }
}
}
}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/freeopcua/server/" }
}
}
}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/freeopcua/server/" }
}
}
}Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"opcua": {
"command": "npx",
"args": ["-y", "opcua-mcp-server"],
"env": { "OPCUA_SERVER_URL": "opc.tcp://localhost:4840/freeopcua/server/" }
}
}
}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/freeopcua/server/" }
}
}
}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).
4. Check the connection
Restart your desktop client or start a new CLI client session after configuring
it, then enable or approve the opcua server if your client asks. The client
starts the bridge automatically.
Ask: "Use get_server_status to check the OPC UA connection." A working
connection reports connected: true. Then ask: "Browse the OPC UA variables
and read the current temperature." Against the plant mock, this works with the
default read-only profile.
If it fails, check that the OPC UA server is still running and that
OPCUA_SERVER_URL matches its endpoint. If the bridge cannot launch, check its
logs and the client command's path; desktop clients may need absolute paths.
The installation guide explains
client command paths, and testing troubleshooting
covers startup, connection and tool errors.
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
operatorprofile"Start production on line 1 at 100 units/hour." β needs the
operatorprofile
These prompts depend on the connected server's nodes and capabilities. Server-computed
averages need processed aggregate support, and active alarms need Alarms &
Conditions; see the mock coverage above. Writes and
method calls also need allowlisted targets and the security settings
below, in addition to 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.
Tools
15 tools, identical on both runtimes and defined once in contract/tools.json. Arguments, results and limits: docs/tools.md.
Access | Offered under | Tools |
read | every profile, including the default |
|
monitor | every profile, including the default |
|
alarm-action |
|
|
control |
|
|
Control and alarm tools also need a verified server (a secured channel and a pinned OPCUA_SERVER_CERT or an OPCUA_SERVER_TRUST_STORE) unless a lab override is set. β Works only on a server that advertises the feature it needs, such as historical access.
Going to production
Out of the box the agent can only read, but the OPC UA channel isunencrypted 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 settings to your MCP
client's env block, using paths to your actual certificate files. The following
blocks show environment names and values; in JSON, enter them as quoted key/value
pairs. Generate certificates and arrange trust with the OPC UA administrator as
described in docs/certificates.md.
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 reachChoose one user authentication method alongside those channel settings. For username/password authentication, provide both:
OPCUA_USERNAME=mcp-operator
OPCUA_PASSWORD=<your-password>Supply the real password through your client's secret storage or environment; the installer never accepts a password flag. See password handling for client-specific setup.
Alternatively, for X.509 user authentication, leave OPCUA_USERNAME and
OPCUA_PASSWORD unset and provide both of these instead:
OPCUA_USER_CERT=/etc/opcua/user.pem
OPCUA_USER_KEY=/etc/opcua/user_key.pemThe user certificate and key identify the user; they are separate from
OPCUA_CLIENT_CERT and OPCUA_CLIENT_KEY, which secure the channel. Keep the
channel settings in either case: X.509 user authentication requires a security
policy other than None.
Instead of pinning with OPCUA_SERVER_CERT, you can use
OPCUA_SERVER_TRUST_STORE together with OPCUA_SERVER_APPLICATION_URI for CA
trust-store verification; see the certificate guide.
2. Decide what the agent may do with OPCUA_PROFILE:
Profile | What the agent can do |
| Read, browse, history, subscriptions, events. No writes, no method calls. |
| The above, plus only the nodes and methods you allowlist in |
| Every tool. Only for a tightly scoped OPC UA account. |
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
Guide | What's inside |
The | |
Every setting, profiles, allowlists, value bounds, secure connections, reconnection | |
Full tool reference, result shapes, request limits, partial results | |
Every tool with real calls and responses against the mock plant | |
What is and is not verified, the audit trail, reporting a vulnerability | |
Generating a client certificate a server will accept | |
Servers tested, and where the Python and Node runtimes differ | |
How it fits together, and why | |
The test suite, MCP Inspector and agent walkthroughs | |
What is next, and what has landed |
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.
Available Tools
11 toolsbrowse_opcua_nodesARead-onlyIdempotent
Explore the OPC UA address space: list a node's children, walk a subtree, resolve a human-readable browse path to a node ID, or search for nodes by name. With the defaults it lists the immediate children of node_id. Raise depth to walk further down, set node_class to 'Variable' to find only readable tags, and include_values to read each one as it is found. browse_path addresses a node by name instead of by ID ('/Objects/Reactor/Temperature'), which is how to start when you do not know the numeric IDs; with depth 0 it simply resolves that path and returns the node it names. name_filter keeps only nodes whose browse name contains the text. Every node also reports its type_definition β what it is, as against what class it belongs to: 'AnalogItemType' for a tag that publishes a unit and a range, a subtype of 'AlarmConditionType' for something act_on_alarm applies to, 'FolderType' for structure. Every traversal is bounded by max_nodes and says in truncated whether it stopped early, so a short list is never mistaken for a complete one.
| Name | Required | Description | Default |
|---|---|---|---|
| depth | No | How many levels below the starting node to walk. 1 (the default) lists its immediate children; 0 returns only the starting node itself, which is how to resolve a `browse_path` to a node ID. Clamped to 64. | |
| node_id | No | Where to start, as a node ID. Defaults to the Objects folder 'ns=0;i=85', the root of the plant's own address space. Ignored when `browse_path` is given. | |
| max_nodes | No | Stop after inspecting this many nodes (default 500, clamped to 5000). A traversal that stops early sets 'truncated' in the result. | |
| node_class | No | Keep only nodes of this OPC UA NodeClass: 'Variable' for readable tags, 'Object' for structure, 'Method' for callable operations. Every class is returned when unset. Filtering does not prune the walk β an Object is still descended into when `depth` allows. | |
| browse_path | No | Where to start, as a slash-separated browse path resolved through the server ('/Objects/Reactor/Temperature', or 'Reactor/Temperature' relative to `node_id`). Use this when you know what a thing is called but not its node ID. A path the server cannot resolve is an error, not an empty result. | |
| name_filter | No | Keep only nodes whose browse name contains this text, case-insensitively. Searching for 'temp' below the Objects folder is how to find a tag whose exact name you do not know. | |
| include_values | No | Read each Variable's current value as it is found (default false). Costs one extra read per variable, so leave it off when mapping a large address space and read what you need with read_opcua_nodes afterwards. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes | |
| completeness | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, and the description adds context beyond them: traversals are bounded by max_nodes, truncation is reported via 'truncated' so a short list isn't mistaken for complete, include_values costs one extra read per variable, node_class filtering does not prune the walk, and an unresolvable browse_path is an error rather than an empty result. These are exactly the non-obvious behaviors an agent needs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is front-loaded with the four modes before drilling into each parameter, and every sentence carries operational information. It is on the long side as a single undivided paragraph, which slightly hurts scannability, but there is no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
A 7-parameter traversal tool with an output schema is well covered: the description supplies the semantics the schema can't (type_definition, truncation, filter-vs-prune, path resolution errors) and doesn't waste space re-documenting return shapes the output schema already defines. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description earns a bump by adding semantics the schema does not: the depth-0 path-resolution idiom, the fact that node_class filtering is applied post-walk rather than pruning, and the explanation that type_definition describes what a node *is* versus its NodeClass. Minor gap: max_nodes' interaction with depth beyond the truncation note is only lightly treated.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource ('Explore the OPC UA address space') and then enumerates the four concrete modes: list children, walk a subtree, resolve a browse path, search by name. It is clearly distinguishable from read_opcua_nodes (referenced explicitly as the way to fetch values afterwards) and from act_on_alarm (mentioned via 'AlarmConditionType').
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states the default behavior ('with the defaults it lists the immediate children of node_id') and then gives conditional routing: use browse_path when you don't know numeric IDs, set depth 0 to resolve a path, use name_filter for unknown names, and leave include_values off when mapping a large address space, reading with read_opcua_nodes instead. That is explicit when-to-use, when-not-to-use, and named alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_server_statusARead-onlyIdempotent
Report whether this MCP server is connected to the OPC UA server and what that server says about itself: its endpoint and connection security, its ServerStatus (state, current time, start time, build info) and its NamespaceArray as index -> URI. The one tool that answers while the connection is down β it reports 'connected': false and why, rather than failing β so it is both the health check and the first thing to call when another tool cannot reach the server. The namespace array is also how to tell which 'ns=' index belongs to which namespace URI on this server. 'diagnostics' is the server's own ServerDiagnosticsSummary when it publishes one β session and subscription counts, and how many sessions and requests it has rejected β which is what separates 'the server is slow' from 'the server is refusing us'. OPC UA leaves diagnostics optional, so null there means this server does not say, not zero. 'capabilities' says which optional features β value history, event history, aggregates and their names β the server was found to offer, on which session and when: the answer to why read_opcua_history or read_event_history was refused.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint/idempotentHint annotations, it discloses the key non-obvious behavior: it does not fail when the connection is down, it returns 'connected': false with a reason. It also warns that null diagnostics means the server does not publish them, not zero. That null-versus-zero distinction is genuinely important, though the description leans on detailed return-field semantics that the output schema already carries.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in the first sentence, which is good, but the description runs long for a parameterless status tool and devotes several clauses to output fields the output schema already documents. Those clauses are informative but not all of them earn their space here.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter read-only diagnostic with an output schema and full annotation coverage, nothing an agent needs is missing. Between the annotations and the output schema the contract is fully covered, and the description fills the remaining gaps around failure behavior and diagnostic semantics.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so there is nothing to disambiguate; the baseline applies. The description correctly spends no space on arguments and instead explains what the no-argument result means.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names the specific verb and resource β report whether the MCP server is connected to the OPC UA server, plus endpoint, security, ServerStatus and NamespaceArray. The second sentence explicitly separates it from the rest of the sibling set: it is the health check and the one tool that answers while the connection is down.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Guidance is explicit and conditional: call it first when another tool cannot reach the server, and consult 'capabilities' when read_opcua_history or read_event_history was refused. It also names the concrete use case of mapping 'ns=' indexes to namespace URIs, which no sibling covers.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_active_alarmsARead-onlyIdempotent
List the alarm and condition instances the server is currently retaining β those that are active, unacknowledged, or both. Asks the server for them directly (the ConditionRefresh method), so the answer is the current state of the plant rather than whatever this MCP server happened to be listening for. Use this to answer 'what is wrong right now'; use read_events to see what has happened since you last looked.
| Name | Required | Description | Default |
|---|---|---|---|
| node_id | No | The notifier node whose conditions to list. Defaults to the Server object 'ns=0;i=2253'. | |
| timeout_seconds | No | How long to wait for the server to finish sending its retained conditions (default 5). Running out is an error, not an empty list: a partial answer cannot be told apart from 'no alarms'. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, but the description adds real value beyond them: it discloses that the data comes from a direct ConditionRefresh round-trip rather than passively collected subscription data, which changes how the agent should interpret completeness. The one remaining gap is return shape and volume, which the output schema covers.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each earning its place: identity/scope first, mechanism second, routing guidance third. No filler or repetition of the title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With a rich output schema, full parameter documentation, and annotations covering the safety profile, the description supplies everything an agent needs: what it returns, how it is fetched, and when to prefer it over the history sibling.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema already documents both node_id and the timeout's error-vs-empty-list semantics. The description adds no parameter-level detail beyond what is in the schema, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (list) and resource (alarm and condition instances the server is currently retaining), then defines the scope precisely as 'active, unacknowledged, or both'. An agent can distinguish this from read_events without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit decision rule: use this for 'what is wrong right now', use read_events for what has happened since you last looked. It names the sibling alternative and the condition that selects it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_subscriptionsARead-onlyIdempotent
List the active OPC UA data-change subscriptions, each with the value changes buffered for it since it was created. This is how to collect what subscribe_opcua_nodes has been watching; the same document is also readable as the 'opcua://subscriptions' resource.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes | |
| completeness | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the safety profile is covered; the description adds real behavioral context by explaining that results include changes buffered since subscription creation. The notable omission is whether listing drains or leaves the buffer intact, which matters for repeated calls. Strong but not fully complete on mutation-side effects of a read.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, both load-bearing: the first defines the payload and its buffered content, the second routes the agent from the subscribe sibling and offers the resource alternative. Nothing is repeated from the name or annotations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description needn't explain return structure, and it correctly focuses on provenance of the data and the alternate access path. The only meaningful gap is buffer-consumption semantics, which an agent calling this repeatedly would want to know.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to disambiguate and the baseline of 4 applies. The description does not need to explain any inputs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb and resource ('List the active OPC UA data-change subscriptions') and adds scope that distinguishes it: each subscription comes with the value changes buffered for it since creation. An agent can tell this apart from subscribe_opcua_nodes, read_opcua_nodes, and list_active_alarms without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states explicitly when to reach for this tool ('This is how to collect what subscribe_opcua_nodes has been watching') and names an equivalent alternative path (the 'opcua://subscriptions' resource). Both the use condition and the alternate access route are spelled out rather than inferred.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_event_historyARead-onlyIdempotent
Read events the OPC UA server stored, for a time range that has already passed. subscribe_events only sees what arrives after you subscribe, so it cannot answer 'what happened overnight' or 'what fired in the ten minutes before the line stopped' β the events are gone by the time anyone asks. This reads them back out of the server's own event archive instead. Same records as read_events and the same fields, so an alarm looks identical whether it was watched live or recovered afterwards. Needs a server that reports AccessHistoryEventsCapability β storing values and storing events are separate features and many servers do one without the other β and against one that does not, a call is refused with capability_not_supported and what to use instead. Point node_id at the notifier that emits the events β the Server object by default, which is where events are collected on most servers. The structured result's completeness says whether the range held more events than came back.
| Name | Required | Description | Default |
|---|---|---|---|
| node_id | No | The notifier node whose stored events to read. Defaults to the Server object 'ns=0;i=2253', which is where most servers collect events. This is the same node subscribe_events takes. | |
| end_time | No | End of the time range, ISO-8601 with a timezone. Defaults to now. | |
| num_values | No | Maximum number of events to return, capped at 5000. 0 (the default) means as many as that cap allows, not every event in the range β an alarm burst can be tens of thousands of events, and asking for all of them is a request that never returns. | |
| start_time | No | Start of the time range, ISO-8601 with a timezone: '2026-09-09T12:00:00Z' or '2026-09-09T14:00:00+02:00'. A timestamp with no timezone is refused rather than guessed. Defaults to one hour before `end_time`. | |
| severity_min | No | Keep only events of at least this OPC UA severity (1-1000). Default 0, which keeps every event. Filtered here rather than by the server, exactly as subscribe_events does, so a server that mishandles a filter cannot silently drop events. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes | |
| completeness | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnly/idempotent/non-destructive; the description adds substantially beyond them: a server capability prerequisite, the specific failure code capability_not_supported, the 5000-event cap, the risk of unbounded bursts, and that filtering happens client-side to avoid server misbehavior. It also explains that the `completeness` field reports truncation, which is real behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is front-loaded with the purpose and the sibling distinction before prerequisites and parameters, and no sentence is filler. It runs long and restates a couple of schema facts (defaults, node id value), which keeps it from a 5.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a five-parameter historical-read tool it covers the prerequisite, the failure mode, defaults, cap behavior, and truncation signal; an output schema exists so return-shape explanation is not owed. Nothing needed to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds cross-tool meaning: node_id is the notifier and the same node subscribe_events takes, and the result's `completeness` ties back to range size. Most other parameter detail (ISO-8601 formats, defaults, cap) duplicates the schema rather than extending it.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence names a specific verb and resource ('Read events the OPC UA server stored') and immediately scopes the time range to the past. It explicitly distinguishes itself from both subscribe_events (live only) and read_events (identical records, different path), so an agent can pick it without inspecting schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives a concrete when-to-use framing ('what happened overnight', 'what fired in the ten minutes before the line stopped') plus the exclusion that subscribe_events cannot answer retrospective questions. It also names when the call will be refused (no AccessHistoryEventsCapability) and what that refusal returns.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_eventsA
Read the events buffered by subscribe_events, oldest first. The events returned are removed from the buffer, so a second call returns only what has arrived since. If the buffer overflowed in the meantime, the result says how many were dropped rather than quietly skipping them β in the structured result's completeness, which also says whether more are still buffered.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of events to return (default 50). | |
| node_id | No | The subscribed notifier node to read from, as passed to subscribe_events. Defaults to the Server object 'ns=0;i=2253'. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes | |
| completeness | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=false and idempotentHint=false, and the description explains exactly why: returned events are removed from the buffer, making the call non-idempotent. It further discloses overflow behavior and points at the `completeness` field in the structured result, adding real behavioral context beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each carrying distinct information: source and ordering, drain semantics, and overflow reporting. Front-loaded with the core action and no redundant restatement of the tool name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-optional-parameter read tool with an output schema, the description covers the essential non-obvious behavior: buffer draining, ordering, and where overflow/drop counts appear. Nothing an agent needs in order to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so `limit` and `node_id` are already documented in the schema. The description adds no additional meaning about parameter behavior or interactions, which is acceptable given the schema does the heavy lifting, but it does not exceed the baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Read the events buffered by subscribe_events') plus ordering semantics ('oldest first'), which cleanly separates this from history-oriented siblings like read_event_history. An agent can tell what it returns and where the data comes from without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description makes the prerequisite explicit by tying the tool to subscribe_events, and clarifies the calling pattern ('a second call returns only what has arrived since'). It does not explicitly contrast with read_event_history or state when to prefer one over the other, so it is clear context without full exclusion guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_opcua_historyARead-onlyIdempotent
Read what a node's value has been over time. Without aggregate_function this returns the raw stored readings, with ReturnBounds=false: no preceding/following boundary values or BadBoundNotFound placeholders. With it, the server computes one summary value per processing_interval over the range instead β 'Average' for a trend, 'Maximum'/'Minimum' for excursions, 'Count' for how often something was recorded β which is how to ask about a week of data without transferring a week of data. Needs a server that keeps history (AccessHistoryDataCapability) or offers aggregates, and aggregate_function needs the aggregates; a call the connected server cannot serve is refused with capability_not_supported and a suggestion, before anything is sent. get_server_status reports which of the two the server offers, and its aggregate function names. Aggregate reads follow native continuation points within the same call, preserving the original interval anchor; they return the completed range or an explicit error within the 5000-value bound. The structured result's completeness says whether the range held more than came back and how to continue; never infer that from the number of records.
| Name | Required | Description | Default |
|---|---|---|---|
| node_id | Yes | The OPC UA node ID to read history for. Example: 'ns=2;i=2'. | |
| end_time | No | End of the time range, ISO-8601 with a timezone. Defaults to now. | |
| num_values | No | Maximum number of raw readings to return, capped at 5000. 0 (the default) means as many as that cap allows, not every reading in the range β a raw read of a year of fast data is a request that never returns. When the cap is reached the result says so, and the way to see the whole range is `aggregate_function` rather than a bigger number. Ignored when `aggregate_function` is given, where the number of results is decided by `processing_interval`. | |
| start_time | No | Start of the time range, ISO-8601 with a timezone: '2026-09-09T12:00:00Z' or '2026-09-09T14:00:00+02:00'. A timestamp with no timezone is refused rather than guessed. Required when `aggregate_function` is given; for a raw read, defaults to one hour before `end_time`. | |
| aggregate_function | No | Summarise each interval with this OPC UA aggregate instead of returning raw readings, by its OPC UA name: 'Average', 'Minimum', 'Maximum', 'Count', 'Total', 'Interpolative', ... Which ones the connected server offers is its own choice, listed by get_server_status under capabilities.aggregate_functions; a name it does not offer is refused with the list it does. | |
| processing_interval | No | Width in milliseconds of each interval the aggregate is computed over. 0 (the default) makes the whole range a single interval, giving one result. Only meaningful with `aggregate_function`. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes | |
| completeness | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and destructiveHint=false, so the safety profile is covered. The description adds genuinely useful behavior beyond that: ReturnBounds=false with no boundary values or BadBoundNotFound placeholders, a pre-flight capability_not_supported refusal with a suggestion before anything is sent, continuation-point handling that preserves the interval anchor, and the 5000-value bound. It stops short of describing the shape of the returned records, leaving that to the output schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and the raw-versus-aggregate fork before any caveats, which is the right order. It is a dense single block, though, and a few phrases ("which is how to ask about a week of data without transferring a week of data") spend words on rhetoric rather than new information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers what an agent needs to call this correctly on a complex 6-parameter tool: mode selection, capability preconditions, error behavior, size limits, and an explicit instruction to read the structured result's completeness field rather than counting records. Return-value detail is delegated to the output schema, which exists.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the parameter docs are already thorough, including the num_values cap semantics, the timezone requirement on start_time, and the interaction between aggregate_function and processing_interval. The description mostly restates those rules in prose, adding only the reasoning that aggregate reads preserve the original interval anchor, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource ("Read what a node's value has been over time") and immediately splits the tool into its two distinct modes: raw stored readings versus aggregate summaries. The capability gating and the pointer to get_server_status make it distinguishable from read_opcua_nodes and the other history siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use each mode: omit aggregate_function for raw readings, supply it to ask about a week of data without transferring a week of data, and use it rather than raising num_values when the cap is hit. It also names prerequisites (AccessHistoryDataCapability or aggregate support) and routes the agent to get_server_status to discover which applies.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_opcua_nodesARead-onlyIdempotent
Read the current value of one or more OPC UA nodes in a single request. Pass every node you need in one call: the OPC UA server answers them together, so reading fifty nodes costs one round trip rather than fifty. Each node comes back as its own record with the value, its data type, the OPC UA status code, and the source and server timestamps β the quality and the age are what decide whether a value can be acted on, and a bare number carries neither. A node the server rejects is one 'Badβ¦' status among the results, not a failure of the call.
| Name | Required | Description | Default |
|---|---|---|---|
| node_ids | Yes | The OPC UA node IDs to read, in the format 'ns=<namespace>;i=<identifier>'. Example: ['ns=2;i=2', 'ns=2;i=3']. A single node is a one-element list. At most 500 per call. | |
| include_write_access | No | Also report, per node, whether it can be written and what a write must look like (write_access): its type, whether it is an array, its enumeration states and labels, and the bounds a value must stay within. Set it before writing a node you have not written before. Costs one extra read of the nodes' attributes the first time a node is asked about in a session. Default false. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnly/idempotent annotations: it explains the single-round-trip batching behavior, that each node returns its own record with value, data type, status code and both timestamps, and critically that a rejected node surfaces as a per-node 'Badβ¦' status rather than failing the whole call. That partial-failure semantics is exactly the kind of detail an agent cannot infer from structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four sentences, front-loaded with the action and the batching rule, then the return shape, then partial-failure semantics. The aside about quality and age ('a bare number carries neither') is rhetorical padding but does justify the returned metadata, so the cost is small.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers everything an agent needs before calling: what it reads, how to batch, what comes back per node, and how failures are represented. With an output schema present and annotations declaring the safety profile, the remaining details are already structured.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the node ID format, the 500-item cap and the include_write_access cost model are already fully documented in the schema. The description reinforces the batching intent of node_ids but adds no format or constraint detail beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Read the current value of one or more OPC UA nodes in a single request') and scopes it as a single batched read, which clearly separates it from siblings like read_opcua_history, browse_opcua_nodes, and subscribe_opcua_nodes.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a concrete usage rule β 'Pass every node you need in one call' β and quantifies the benefit (fifty nodes, one round trip), which tells the agent to batch rather than loop. It never explicitly names an alternative tool or states when not to use this one (e.g. for historical values or continuous monitoring), so it stops short of full guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
subscribe_eventsA
Start buffering OPC UA events (alarms, condition changes, plain events) from a notifier node. Returns at once; the events themselves are collected in the background and read with read_events. Calling it again for the same node replaces the subscription and discards what it had buffered. Use this to watch for things happening; use list_active_alarms for what is wrong right now.
| Name | Required | Description | Default |
|---|---|---|---|
| node_id | No | The notifier node to subscribe to. Defaults to the Server object 'ns=0;i=2253', which is where most servers publish every event they raise. | |
| buffer_size | No | How many events to hold before the oldest are dropped (default 100, clamped to 10000). read_events reports how many were dropped. | |
| severity_min | No | Buffer only events of at least this OPC UA severity (1-1000). Default 0, which keeps every event. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations, it discloses important behavior: the call returns immediately, events buffer in the background, repeated calls replace the subscription, and buffered events are discarded on replacement. These details go well beyond the readOnly/idempotent/destructive annotations and help the agent avoid accidental buffer loss.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with purpose, then covers asynchronous return, replacement semantics, and alternatives in four efficient sentences. Every sentence provides actionable information with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the annotations, full schema descriptions, and output schema, the description is complete for correct invocation. It explains the asynchronous subscription model, replacement behavior, buffer discard effect, and the key sibling alternative.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents node_id, buffer_size, and severity_min, including defaults and clamping. The description does not add meaningful parameter-level detail beyond referring generally to a notifier node, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb, resource, and scope: 'Start buffering OPC UA events (alarms, condition changes, plain events) from a notifier node.' It distinguishes the tool from siblings by naming read_events for retrieval and list_active_alarms for current problems.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly says to use this tool 'to watch for things happening' and to use list_active_alarms 'for what is wrong right now.' It also directs the agent to read_events for collected events, giving clear routing between subscription, retrieval, and current-state tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
subscribe_opcua_nodesA
Watch one or more OPC UA nodes for value changes instead of polling them. The OPC UA server pushes each new value into a buffer this MCP server keeps; collect them with list_subscriptions or the 'opcua://subscriptions' resource. This is the right way to answer 'tell me when this changes' or 'what has this been doing' over a short window β repeatedly calling read_opcua_nodes both misses changes between calls and costs a round trip each time. Each node gets its own subscription record and its own ID; cancel them with unsubscribe_opcua_nodes. Subscriptions are re-established automatically if the connection drops.
| Name | Required | Description | Default |
|---|---|---|---|
| node_ids | Yes | The OPC UA node IDs to monitor. Example: ['ns=2;i=3']. A single node is a one-element list. Each gets its own OPC UA subscription, and this server holds at most 200 at a time. | |
| buffer_size | No | How many of the most recent changes to retain per subscription; older ones are discarded. Default 20, clamped to 1..1000. | |
| deadband_type | No | Filter out changes too small to be worth reporting (OPC UA Part 4 Β§7.22). 'none' (the default) reports every change the server samples, which on a noisy analogue tag fills the buffer with sensor jitter in about a second. 'absolute' compares against `deadband_value` in the node's own engineering units. 'percent' reads `deadband_value` as a percentage of the node's EURange, and needs the node to publish one. | |
| deadband_value | No | How far the value must move to be reported, in engineering units for 'absolute' and in percent of EURange for 'percent'. Ignored when `deadband_type` is 'none' or unset; required when it is not. | |
| sampling_interval | No | How often (ms) the OPC UA server samples each node. 0 (the default) means sample at `publishing_interval`; a shorter interval queues several readings per publish. | |
| data_change_trigger | No | What counts as a change. 'statusValue' (the default) reports a change of value or of OPC UA status. 'status' reports only status transitions β useful for watching a tag go bad without caring what it reads. 'statusValueTimestamp' also reports a re-sample that changed nothing but the timestamp, which is how to tell 'the value is steady' from 'the value stopped updating'. | |
| publishing_interval | No | How often (ms) the OPC UA server publishes queued changes. Default 1000. Clamped to at least 50. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes | |
| completeness | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint=false, idempotentHint=false, destructiveHint=false, which says little about lifecycle. The description adds real behavior: the server buffers pushed values, each node gets its own subscription record and ID, subscriptions are re-established automatically if the connection drops, and results are retrieved through a separate tool or resource rather than returned here.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core verb and the anti-pattern it replaces, and every subsequent sentence carries distinct information (collection path, per-node records, cancellation, reconnect behavior). It runs four sentences, slightly longer than strictly necessary, but no sentence is filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a stateful, non-idempotent subscription tool with 7 parameters, the description covers the full lifecycle an agent needs: creation, retrieval of buffered values, cancellation, capacity context, and automatic re-establishment after disconnects. An output schema exists, so return-value detail is correctly left out.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the per-parameter docs are unusually rich (deadband semantics, trigger modes, clamping), so the schema does the heavy lifting. The description only adds that subscriptions are per-node and separately identified, which lightly informs node_ids but adds nothing about buffer_size, deadband, or intervals. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence gives a specific verb ('Watch') and resource ('one or more OPC UA nodes for value changes') and immediately frames it against polling. Later sentences distinguish it from read_opcua_nodes (polling) and point to list_subscriptions/unsubscribe_opcua_nodes, so an agent can separate it from every relevant sibling without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use framing ('tell me when this changes' or 'what has this been doing' over a short window) plus an explicit when-not alternative: repeatedly calling read_opcua_nodes misses changes between calls and costs a round trip. It also names the follow-up tools (list_subscriptions, the opcua://subscriptions resource) and the cancel path (unsubscribe_opcua_nodes).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
unsubscribe_opcua_nodesA
Cancel one or more active data-change subscriptions and discard what they had buffered. Returns the subscriptions as they were at the moment they were cancelled, so anything still buffered can be read one last time. An ID that is not active is an error naming it, and no subscription is cancelled.
| Name | Required | Description | Default |
|---|---|---|---|
| subscription_ids | Yes | The subscription IDs returned by subscribe_opcua_nodes, as listed by list_subscriptions. Example: ['sub-1']. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes | |
| completeness | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=false and destructiveHint=false, and the description adds real behavioral detail beyond them: buffered data is discarded but returned so it can be read one last time, and an unknown ID causes an error with no cancellation happening at all (all-or-nothing failure). It doesn't cover authorization requirements or whether cancellation is awaited/bounded by a timeout, but the failure semantics are unusually well disclosed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with the action, then return content, then failure behavior. No filler or repetition of the name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return values need not be explained, and the description still covers the key mutation semantics (discard + last-read return, atomic failure on invalid IDs). For a one-parameter mutation tool this is close to complete; only auth/permission and concurrency caveats are absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single parameter is documented there, including the ID format and origin (subscribe_opcua_nodes, list_subscriptions). The description only echoes 'one or more active', which the schema already enforces via minItems 1; no additional semantics are added.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Cancel one or more active data-change subscriptions'), plus the exact consequence ('discard what they had buffered'). That is clearly distinguishable from subscribe_opcua_nodes, list_subscriptions, and the read_* siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The context is clear: this is for tearing down active subscriptions, and the ID source is implied by 'active'. However, it never names an alternative or an exclusion (e.g., when to prefer list_subscriptions to check state first, or what to do for a non-existent ID besides expecting an error).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
11 tool updates
v0.1.0- First observed
browse_opcua_nodes - First observed
get_server_status - First observed
list_active_alarms - First observed
list_subscriptions - First observed
read_event_history - First observed
read_events - First observed
read_opcua_history - First observed
read_opcua_nodes - First observed
subscribe_events - First observed
subscribe_opcua_nodes - First observed
unsubscribe_opcua_nodes
TDQS
Scored across 11 tools
Most tools target clearly different operations: browsing vs current reads vs history vs live data-change subscriptions vs live events vs alarm listing. The closest pairs are read_events/read_event_history and subscribe_events/subscribe_opcua_nodes, whose names are similar but whose descriptions clearly separate live buffering from archived retrieval and event streams from data-change streams. Slight ambiguity remains from those parallel names, but the descriptions prevent likely misselection.
All names follow a snake_case verb_noun pattern, which is readable and predictable. The main inconsistency is the optional 'opcua' qualifier: read_opcua_nodes, browse_opcua_nodes, read_opcua_history, subscribe_opcua_nodes, and unsubscribe_opcua_nodes use it, while read_event_history, subscribe_events, read_events, list_subscriptions, and list_active_alarms do not. This is a minor deviation rather than a chaotic naming scheme.
At 11 tools, the set is well within a reasonable range for an OPC UA monitoring and browsing server. Each tool covers a distinct capability: status, browse, current read, historical read, data-change subscription lifecycle, event subscription/read, event history, active alarms, and subscription listing. No tool appears redundant or purely filler.
The read/monitoring surface is fairly complete: browse, current values, historical values, live data changes, event buffering, event history, alarms, and server capability discovery are all present. However, core OPC UA write operations such as writing node values or calling methods are absent, and alarm acknowledgement is not exposed even though alarm listing is. There is also a reference to an 'act_on_alarm' tool in the browse description that is not present in the tool set, indicating a notable surface gap.
Maintenance
Related MCP Connectors
Connect ChatGPT, Claude, Cursor or VS Code to your Windows PC: files, terminal, browser, apps.
Persistent memory, hybrid search and a goal graph for AI agents, over stdio or remote HTTP.
Connect AI assistants to your approved computer for files, commands, browser and desktop tasks.
- DNS-DATA RemoteDeprecatedcom.dns-data
Connect AI assistants to authorized Windows and macOS devices for files, commands, and management.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceConnects AI agents to OPC UA-enabled industrial systems for real-time monitoring and control of operational data. It enables users to read, write, and browse industrial device nodes through natural language interactions.MIT
- AlicenseBqualityAmaintenanceProvides AI agents with safe, governed read access to industrial control systems (OPC-UA, Modbus, S7, Mitsubishi, MTConnect, MQTT/Sparkplug) plus cross-protocol diagnostics for troubleshooting data breaks, alarm floods, and unhealthy tags.1569MIT
- AlicenseNot gradedqualityBmaintenanceEnables 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.5MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to discover, browse, read, search, and safely write OPC UA industrial device data within controlled edge networks, with guarded two-phase writes and evidence-based root-cause diagnostics.1Apache 2.0