Skip to content

fix(mcp): unwrap i3X envelopes, surface errors, read components, real in-process routes - #3

Merged
erossignon merged 3 commits into
mainfrom
fix/mcp-envelope-unwrap
Oct 26, 2026
Merged

erossignon merged 3 commits into
mainfrom
fix/mcp-envelope-unwrap

Conversation

@erossignon

@erossignon erossignon commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Symptom: against a real node-i3x server every @node-i3x/mcp tool returned empty without error: i3x_explore_plant_hierarchy reported 0 roots (server has 84), i3x_inspect_asset said "not found" for existing assets, history and types came back empty. A base URL ending in /v1 gave /v1/v1/... 404s.

Cause: the REST client read bare keys (res.objects, res.values, ...) while the server wraps every payload in {success, result} / {success, results: [...]}, and it swallowed every error into null/[]. The in-process binding re-implemented the routes and diverged (parentId undefined, hardcoded info).

Fix:

  • Unwrap both envelopes. success:false raises an error carrying its responseDetail; only a per-item 404 means "not found".
  • Values and history come back per element ({elementId, result | error}).
  • Strip a trailing /v1 from the base URL. /objects/related now sends elementIds and keeps sourceRelationship.
  • maxDepth (default 2) on inspect, telemetry and monitor: an Object's readings are its components.
  • Suggestions also match on displayName (cached for 60 s).
  • History stats are computed per series; invalid times are an error.
  • InProcessConnectorBinding now runs the real rest-server routes through inject(). Version comes from package.json.

Tests: tests/rest-binding.test.ts (fetch stub: envelopes, error envelopes, both URL forms) and tests/contract.test.ts (real rest-server through inject(), no port). Run against main's sources, 24 of them fail.

Checked manually over stdio against a portal instance: 84 roots, and CoffeeMachineA returns its sub-components and component values.

Caveats:

  • Breaking changes to the exported I3XClientBinding interface (related, values, history and suggestion return shapes). InProcessConnectorBinding now takes RestServerDeps instead of I3xStack, and is no longer exported from the package root.
  • InProcessConnectorBinding (and injectFetch) moved to the @node-i3x/mcp/in-process subpath export; @node-i3x/rest-server is an optional peer dependency, so the REST-only CLI installs no Fastify. createApp gained an optional { logger } argument, because Fastify logs to stdout, which is the MCP stdio channel.
  • i3x_monitor_asset_events keeps its name but is now an honest one-shot snapshot with quality alerts. It never subscribed; real streaming over /v1/subscriptions is not implemented.

The client read bare keys (res.objects, res.values, ...) while node-i3x
answers { success, result } and bulk { success, results: [...] }, so every
tool returned empty lists without error. Unwrap both envelopes; a
success:false envelope or a failed bulk item now throws with its
responseDetail, and getObjectById returns null only for a 404 item.

Also: strip a trailing /v1 from the base URL (was /v1/v1), send elementIds
to /objects/related and read .object, keep the parent out of inspect's
subComponents, read inspect values with maxDepth 2 (an Object's readings
are its components), and take the advertised version from package.json.
…ess routes

Values/history now come back per element ({elementId, result|error}), so
history reports keep their own elementId after a failure; stats are per
series including components; GoodNoData is not bad; invalid or inverted
times are errors. maxDepth (default 2) is exposed on inspect, telemetry and
monitor, since an Object's readings are its components. Related results keep
sourceRelationship; explore follows children by parentId. Suggestions match
displayName and reuse the object list for 60 s. monitor_asset_events is a
one-shot snapshot with quality alerts: it no longer claims a subscription.

InProcessConnectorBinding now runs the real rest-server routes via inject()
(createApp gains a logger option: Fastify logs to stdout, the MCP channel).
Contract tests drive those routes; they fail on the previous sources.
…shared children

- monitor_asset_events alerts on an element read that is Bad with value null
  (how a failed read looks); the value guard skipped exactly those.
- InProcessConnectorBinding moves to the mcp package's in-process subpath export,
  and the rest-server package becomes an optional peer dependency, so the
  REST-only CLI no longer installs Fastify and its plugins.
- explore/inspect drop reverse relationships instead of filtering on parentId,
  which names a single parent: a shared component now shows under each parent.
- A successful bulk item without result becomes a per-element error instead
  of an entry with neither result nor error.
@erossignon
erossignon merged commit 4fb86e9 into main Oct 26, 2026
2 checks passed
@erossignon
erossignon deleted the fix/mcp-envelope-unwrap branch October 26, 2026 08:16
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