Files
Marc Schier be50f02700 Add additional companion spec MCP tool profiles (#4731)
# Description

Expose existing stack APIs through **337 additional domain MCP tools**:
114 industrial-companion tools, 35 WoT/xRegistry/document tools, and 188
higher-level stack-workflow tools. The fully enabled catalogue contains
**492 tools**; profile selection is not an execution grant.

The packages now follow the reviewed structure:
**`Opc.Ua.Mcp.Workflows`** contains roles/users, files, alarms and
history; **`Opc.Ua.Mcp.WotCon`** contains management/documents and
independently registered live WoT execution. GDS and schema tools remain
separate packages. The executable composes **18 libraries**.

| Workflow | Added domain tools | Profiles |
| --- | ---: | --- |
| GDS directory, certificates, reference-only credentials/tokens and
Part 21 onboarding | 46 | `gds`, `certificates`, `credentials`,
`onboarding` |
| Roles/users, regular and temporary files, alarms/conditions and
historical access | 74 | `roles`, `users`, `files`, `alarms`, `history`
|
| Bounded runtime types, XSD/BSD/UA JSON, PubSub and REST OpenAPI
artifacts | 6 | `schemas` |
| Named PubSub transports, remote configuration, declarative
adapters/bridges and SKS | 52 | `pubsub-runtime`,
`pubsub-configuration`, `pubsub-bridge`, `pubsub-security` |
| Live HTTP(S), MQTT, Modbus TCP and OPC UA WoT forms | 10 |
`wot-runtime` |

- Reuse typed clients, generated proxies, SDK transports, schema
providers and injected services rather than introducing another session
manager or protocol engine.
- Require host enablement plus exact caller/session/secret/network
grants. Protected generic Call/Write/node-management/history/file paths
enforce the same policy.
- Bind classifications, dispatches, file handles and historical cursors
to actual UA generations. Recovery cannot retarget an old handle or
authorized mutation. Preserve acknowledged results after late
cancellation; do not imply atomicity or retry business commands.
- Use opaque secret-store references, explicit writable-store
registration, issuer/resource/caller binding and reserved capacity
before issuance. Report remote success/local persistence failure as
partial completion; redact provider-controlled diagnostic text.
- Bound new named runtimes to eight per host, two per caller,
five-minute idle expiry and a thirty-minute absolute lease by default.
Pending/closing resources consume quota; failed cleanup remains visible.
Credential invalidation and session recovery close dependent runtimes.
- Keep document planning separate from execution, artifacts
root-confined and real JSON inputs typed. Preserve old profile ordinals
while widening the set to 64 bits. CI grades fully enabled privileged
catalogues rather than connection-only views.

**Limitations:** live Kafka remains explicitly unavailable because the
backend cannot enforce policy on broker-discovered connections;
configuration/schema/capability inspection stays available. The latest
public Dekaf v1.23.5 was also checked and its DNS injection remains
internal. No private-API workaround or bootstrap-only security claim was
added. Full legacy Core registration retains its documented
JSON-reflection limitation: native tests isolate its four connection
descriptors and validate the expanded production registrations, not
whole-host NativeAOT support.

## Related Issues

- Fixes #4730.

## Checklist

_Put an `x` in the boxes that apply. You can complete these step by step
after opening the PR._

- [ ] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [x] I have addressed **all** PR feedback received.
2026-10-14 00:14:05 +02:00

5.3 KiB

OPC UA MCP schema workflows

Embeddable schemas profile for runtime type discovery and bounded XSD, BSD, UA JSON, PubSub and REST OpenAPI schema artifacts.

The module reuses the stack's definition resolvers and schema generators. It does not compile supplied code, use Reflection.Emit, or start a UA server. Generated artifacts use the host's configured transfer root and size limits.

Embedding

services.AddOpcUaMcpCore(new OpcUaMcpOptions
{
    TransferRoot = artifactDirectory,
    MaxTransferBytes = 16 * 1024 * 1024
});
services.AddOpcUaMcpSchema(new McpSchemaOptions { MaxTypes = 256 });
services.AddMcpServer().WithOpcUaSchemaTools(McpToolProfile.Schemas);

WithOpcUaSchemaTools also accepts a composed McpToolProfileSet. Repeated registration does not duplicate the six schema tools or the four shared connection tools. Connection tools are available for explicit Session operations; registered/standard local schemas and REST OpenAPI do not require a connection.

Hosts can inject IServiceMessageContext, IDataTypeDefinitionResolver (the default is DataTypeDefinitionRegistry) and IUaSchemaGenerator implementations. SchemaTools also has a direct constructor. Per-operation generation always receives a separate finite registry, never the potentially unlimited host resolver.

Tools

Tool Purpose
schema_list_types A live, bounded local namespace or remote HasSubtype page.
schema_inspect_type Actual structure/enum definition, identity, ranks and remote attribute quality.
schema_load_types Explicit client-side registration through the SDK's AOT-safe default complex-type factory.
schema_generate Xsd, Bsd, JsonCompact or JsonVerbose; Type closure or Namespace.
schema_generate_pubsub Payload, DataSetMessage, NetworkMessage or MetaData JSON schema.
schema_generate_openapi Existing REST OpenAPI generator, with optional bounded component schemas.

Scalar settings are grouped under options; identifiers and actual arrays remain typed top-level arguments. For example:

{
  "typeId": "i=296",
  "outputPath": "argument.schema.json",
  "options": {
    "source": "Registered",
    "format": "JsonCompact",
    "scope": "Type"
  }
}

Use options.source: "Session" plus sessionName for live server definitions. schema_load_types takes the non-empty typeIds string array and optional sessionName; it does not accept assembly paths, code, or a dictionary-loading fallback. Loading affects the current client session's factory, not the server. Per-root results can report partial client-side registration; successful roots and dependencies remain registered.

PubSub input uses either a typed fields array or metadataNodeId with options.source: "Session". Each inline field contains name, dataTypeId and optional valueRank. All three numeric options.fieldContentMask, options.messageContentMask and options.networkContentMask are required. Unused masks must be zero, unknown/unsupported bits are refused, and RawData cannot be combined with DataValue quality bits. MetaData describes the SDK's metadata-message envelope; it does not specialize its generic metadata body. Remote metadata's built-in hints are preserved; custom namespace tables must match the selected context. Use inline fields with context-local identifiers when metadata uses a different namespace table. Custom PubSub type names are limited to letters, digits and underscores, and distinct types cannot share a schema name, because the current SDK provider otherwise aliases their references. serverUrl in OpenAPI options is document metadata and is never fetched.

Bounds and fidelity

The default and maximum ceilings are 512 definitions, 1024 fields per type, 32 dependency/array levels, 16 MiB per artifact and 30 seconds per operation. The host may lower these limits. The shared transfer byte ceiling also applies. Registered-factory discovery additionally caps its identifier/encoding-alias scan at 4096 entries before activating further definitions. Output destinations are preflighted before remote reads, must have existing parent directories beneath TransferRoot, and are published atomically without overwriting existing files. Failures and cancellation leave no partial artifact.

Namespace expansion must fit a complete bounded enumeration; a large remote type hierarchy is an error, not a silently incomplete namespace schema. Choose Type scope for an explicitly bounded root closure on such a server. List offsets refer to a live enumeration, not a durable server snapshot.

The tools preserve DataType identifier namespaces independently of BrowseName namespaces. Unknown definitions, ambiguous schema names and unsupported representations fail explicitly. In particular, the SDK BSD generator cannot preserve multidimensional arrays or OptionSet wire forms, and the current generators cannot fully describe unconstrained-dimensional or subtyped-field envelopes. Integer-backed OptionSets are not registered as signed enumerations by the bounded loader. No fallback invents a permissive successful schema. XML/BSD namespace imports remain references; dependency artifacts are not silently generated or fetched.

See the repository's docs/McpServer.md and docs/SchemaGeneration.md for configuration and the underlying schema contracts.