Skip to content

fix: MCP stress findings: explore fan-out, auth check, output size, quality mapping - #5

Merged
erossignon merged 1 commit into
mainfrom
fix/mcp-stress-findings
Oct 26, 2026
Merged

erossignon merged 1 commit into
mainfrom
fix/mcp-stress-findings

Conversation

@erossignon

Copy link
Copy Markdown
Member

Findings from stress-testing @node-i3x/mcp over stdio against a portal instance (about 450 calls). Before → after on the same instance:

Finding Before After
5 parallel explore {maxDepth:3} all failed: i3X Gateway request timed out after 15000ms for /v1/objects/related about 1.6 s each
i3x_connect with a wrong token connected, then every call 401 auth_failed, previous connection kept
telemetry with 200 copies of one id 5.8 MB 29 KB
inspect with a 5000-char id 10.9 s, blocking the event loop 108 ms
history of an Object whose variables keep no history success, 126 empty series, count 0 per-component 501 … not supported plus a note
monitor on a healthy asset unused alarm sub-states (UncertainInitialValue) reported as Bad reported as GoodNoData (needs the server fix deployed)

Causes and fixes:

  • Explore: it sent one /objects/related request per node through an unbounded Promise.all, and one timeout aborted the whole walk. It now walks breadth-first with one bulk request per batch of each level, under a node budget (maxNodes, default 500), and records failures on the node. maxDepth: 0 now means unlimited, as in the value tools; it used to silently become 2.
  • Connect: it verified against /v1/info, which is public. It now also reads /v1/namespaces, and keeps the current token when re-connecting to the same URL without one.
  • Size: elementIds are deduplicated and capped at 100. Tool results are capped at 100 000 characters (I3X_MCP_MAX_RESULT_CHARS) with a notice saying how to narrow the query. Suggestions skip fuzzy matching for queries over 128 characters.
  • Core, history: _readComponentsHistory turned a component read failure into { values: [] }. The component now carries responseDetail (501), an additive field.
  • Core, quality: any non-zero OPC UA status became Bad. Quality now follows severity: Good subcodes stay Good, Uncertain becomes Uncertain (or GoodNoData when the value is null, per the spec's null rules). The pseudo-session connector no longer emits status names such as UncertainInitialValue as the quality.

Tests: packages/mcp/tests/contract-stress.test.ts (real rest-server through inject()), plus core mapping and history, pseudo-session history quality and monitor summary tests. All 19 new tests fail on main's sources.

Caveats:

  • Quality changes server-wide: Good subcodes that were reported Bad are now Good, and Uncertain statuses are now Uncertain/GoodNoData. One existing test asserted that status 1 (Good severity, info bits only) is Bad; it was changed to 0x80000000.
  • Component history failures are still all reported as 501 "not supported", whatever the underlying error, as element-level failures already were.
  • The before/after numbers were measured against a portal running the previous server. The two core fixes are covered by tests only until that server is redeployed.
  • The pre-push hook's demo-embedded smoke test is still flaky under load: it failed the first two pushes of this branch, then passed.

…uality

Explore walks breadth-first with one bulk /objects/related call per level
(was one call per node, unbounded; one timeout aborted the walk), with a
node budget and per-node errors; maxDepth 0 now means unlimited. Connect
verifies the token on /v1/namespaces (/v1/info is public) and keeps the
current token on re-connect. elementIds are deduplicated (max 100), tool
results are capped with a narrowing hint, over-long suggestion queries skip
fuzzy matching.

Server side: a component history read failure now carries a 501
responseDetail instead of an empty series, and OPC UA status maps to quality
by severity, so UncertainInitialValue (unused alarm sub-states) is no longer
Bad and pseudo-session no longer leaks status names as quality.
@erossignon
erossignon merged commit a0da7af into main Oct 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant