LearnNewsExamplesServices
Frontmatter
id16185
titleDistinguish local MCP bridge configuration errors from transport failures
stateClosed
labels
bugdeveloper-experienceaitesting
assigneesneo-gpt-emmy
createdAtJul 30, 2026, 8:56 PM
updatedAtJul 30, 2026, 9:53 PM
githubUrlhttps://github.com/neomjs/neo/issues/16185
authorneo-gpt-emmy
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 30, 2026, 9:53 PM

Distinguish local MCP bridge configuration errors from transport failures

Closed Backlog/active-chunk-11 bugdeveloper-experienceaitesting
neo-gpt-emmy
neo-gpt-emmy commented on Jul 30, 2026, 8:56 PM

Context

PR #16182 replaced the external mcp-remote dependency with Neo's owned stdio-to-Streamable-HTTP bridge. Its cycle-3 review identified one operator friction point: every direct CLI startup failure currently ends as:

Neo MCP bridge failed to start.

A source-level falsifier narrows which failures can become actionable:

  • parseArgs() and the missing-bearer guard own bounded local configuration failures;
  • bridgeTransports() rethrows both remote and local transport-start failures into the same top-level catch;
  • Commander's default error writer prints rejected raw argv before throwing;
  • a remote-start error may contain a response body or reflected authorization material.

Arbitrary startup-error rendering would therefore undo #16181's credential containment.

Fresh duplicate sweep on 2026-07-30:

  • live issue search found no equivalent bridge-diagnostics ticket;
  • #16181 / PR #16182 own the bridge and intentionally retain generic transport failure replacement;
  • Memory Core and the exact-head review recover this as a bounded residual;
  • current source and focused tests establish ai/mcp/client/stdioToStreamableHttp.mjs as the owner.

The Problem

The bridge correctly discards remote-influenced transport errors, but applies the same opacity to locally decidable failures:

  • malformed endpoint URL;
  • unsupported endpoint protocol;
  • invalid environment-slot name; and
  • an absent or empty inherited bearer slot.

Printing error.message indiscriminately is not acceptable. The top-level catch also receives transport-start errors, and Commander writes rejected raw argv by default.

The original ticket premise treated a syntactically valid environment-slot name as safe to echo. An adversarial executable probe falsified that premise: token-shaped strings are valid environment identifiers. No argv-supplied value, including the slot identifier, may cross stderr.

The Architectural Reality

  • The CLI accepts an endpoint plus an environment slot name. It never accepts a bearer value as a supported argv option.
  • Syntax validation does not make raw argv safe to reflect.
  • parseArgs() owns endpoint and slot-name validation before transport construction.
  • The resolved slot value is secret.
  • startBridge() and bridgeTransports() are programmatic APIs. Their unknown or transport-originated failures must remain opaque at the executable boundary.
  • Runtime transport failures already discard the upstream error object and emit one fixed local line. That stronger containment remains unchanged.
  • The bridge is one small owner. A generic diagnostics framework would be negative ROI.

The Fix

  1. Introduce a private bridge-configuration error marker inside stdioToStreamableHttp.mjs.
  2. Convert only fixed, Neo-authored pre-transport validation failures into that closed type:
    • invalid URL, without echoing the supplied URL;
    • non-HTTP(S) protocol;
    • invalid environment-slot identifier;
    • missing or empty bearer slot, without interpolating the supplied slot.
  3. At the executable boundary, render specific text only for the closed local marker.
  4. Suppress Commander's raw error writer in parseArgs() so unknown options, missing required options, and missing values produce one Neo-owned generic line.
  5. Continue replacing every transport-start, remote-influenced, and runtime failure with bounded local vocabulary. Help remains exit 0.
  6. Add subprocess fixtures proving both actionable local diagnostics and hostile raw-input replacement.

Contract Ledger

Surface Source of Authority Required behavior Failure boundary Evidence
Endpoint/slot CLI config parseArgs() bounded actionable local message; never echo raw argv private local marker parser + subprocess matrix
Missing inherited slot main() + startBridge() fixed missing-slot diagnostic; never interpolate identifier or value exit 1 before transport start subprocess + zero-start unit
Commander grammar failure Commander + parseArgs() suppress raw writer; emit one generic startup line executable catch subprocess matrix
Transport startup bridgeTransports() discard arbitrary upstream message/body and emit fixed generic startup vocabulary close both transports, exit 1 hostile-start fixture
Runtime transport bridgeTransports().fail() existing one-line replacement, dedupe, nonzero exit close without session DELETE existing reflected-bearer fixture
CLI help Commander grammar deterministic help, exit 0, no bridge start commander.helpDisplayed only existing help/capability probe

Acceptance Criteria

  • Invalid URL, unsupported protocol, invalid slot identifier, and missing/empty inherited slot produce distinct fixed local diagnostics.
  • No diagnostic echoes the raw endpoint string, environment-slot argument, bearer value, remote response body, or arbitrary upstream error message.
  • The missing-slot diagnostic exits 1 before either transport starts.
  • Only a private locally-authored configuration-error marker can cross the executable boundary verbatim.
  • Unknown option, missing required option/value, local-transport, remote-transport, and remote-influenced startup failures retain one fixed generic startup message and exit 1.
  • Commander emits no second raw-argv diagnostic before the Neo-owned line.
  • Runtime reflected-bearer containment, deduplication, session-close ordering, and initialize-marker behavior remain unchanged.
  • --help remains deterministic and exits 0 without starting a bridge.
  • Focused unit and real subprocess fixtures prove both actionable configuration output and hostile-start replacement.

Decision Record impact

none.

Out of Scope

  • A repository-wide error taxonomy or diagnostics framework.
  • Logging transport error objects, HTTP response bodies, request headers, endpoint query strings, environment-slot arguments, or bearer values.
  • Changing MCP protocol/session semantics, authentication modes, Fleet target selection, or generated harness grammars.
  • Revisiting #16181's deferred live container cutover witness.

Avoided Traps

  • Print every startup error.message: transport startup shares the catch and may carry remote-controlled secret-bearing text.
  • Trust Commander errors: its default writer reflects rejected raw argv before exitOverride() throws.
  • Sanitize arbitrary upstream text: replacement is stronger than an encoding-sensitive scrub.
  • Echo a validated slot identifier: grammar validity is not a confidentiality classification.
  • Generic shared abstraction: this bridge has one closed, tiny error boundary.

Evolution

The initial contract named the validated environment-slot identifier in the missing-slot diagnostic. An executable probe using a token-shaped but grammar-valid identifier proved that this could reflect secret material. The contract now forbids every raw argv reflection and preserves actionable output through fixed local vocabulary.

Related

Bridge #16181 / merged PR #16182 · reviewer finding PRR_kwDODSospM8AAAABH2z2ow · Fleet vocabulary follow-up #16184.

Origin Session ID: b1ebc46a-5a83-496c-aa8b-385af785e9cb

Retrieval Hint: stdioToStreamableHttp startup diagnostics Commander raw argv configuration error bearer replacement

tobiu referenced in commit b43e662 - "fix(mcp): bound bridge startup diagnostics (#16185) (#16186)" on Jul 30, 2026, 9:53 PM
tobiu closed this issue on Jul 30, 2026, 9:53 PM