bcquality/custom/knowledge/mcp/bc-mcp-bridge-must-surface-non-2xx-responses-before-sse-parsing.md
Michael Dieringer 9e66377fa6 Ret bc-mcp: config-skabelon matcher broen + fejl svaelges ikke laengere
Live incident i aften: businesscentral MCP fejlede med et generisk
30-sekunders "connection timed out", ingen brugbar fejl. To reelle, adskilte
fejl fundet ved at teste direkte mod BC's endpoint:

1. ~/.bc-mcp.config.json havde "company": "CURABIS ApS" (Vist navn), men BC's
   faktiske Navn-felt er "Curabis ApS". BC svarede korrekt og hurtigt (400,
   under 200ms) - problemet var aldrig BC.

2. bc-mcp-bridge.js svaelgede det svar stille: en fejl-krop formateret som
   almindelig JSON, men markeret content-type text/event-stream, blev sendt
   til parseSSE() som kun leder efter "data:"-linjer - fandt ingen, returnerede
   en tom liste. Broen skrev derfor INGENTING, hverken stdout eller stderr, og
   Claude Code ventede blot sin egen 30-sekunders timeout ud.

Rettet:
- forward() tjekker nu !r.ok FOER content-type-forgrening, ubetinget - en
  fejlrespons naar aldrig parseSSE, uanset hvad serveren paastaar om sin
  egen content-type. Testet direkte mod det reproducerede scenarie: fejlen
  vises nu med det samme (5s test-vindue, ikke 30s timeout), med det fulde
  BC-fejlsvar synligt i baade stdout (JSON-RPC error) og stderr.
- bc-mcp.config.template.json matchede slet ikke broens faktiske felter
  (tenantId/baseUrl vs. broens tenant/company/configurationName) - enhver ny
  udvikler der udfyldte skabelonen efter dens egne feltnavne ville faa en
  config der intet virkede med. Rettet til de rigtige feltnavne, plus en
  eksplicit advarsel om Navn vs. Vist navn i company-feltet.
- Mode A's opsaetningsbesked (Step 3b) opdateret til at naevne alle
  placeholder-felter, ikke kun secret'en.
- To nye BCQuality-videnfiler dokumenterer begge fejl til fremtidig
  fejlsoegning.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 00:18:37 +02:00

61 lines
2.5 KiB
Markdown

---
bc-version: [all]
domain: mcp
keywords: [businesscentral, mcp, bc-mcp-bridge, error-handling, sse, timeout, troubleshooting]
technologies: [al, mcp]
countries: [w1]
application-area: [all]
---
# bc-mcp-bridge.js Must Check `!r.ok` Before Any SSE Parsing, Unconditionally
## Description
`bc-mcp-bridge.js`'s `forward()` function decides how to parse the BC MCP
endpoint's response body based on its `content-type` header. Before
2026-07-31 it only treated a response as an error when `!r.ok && !text` —
i.e. only when there was no body at all. A non-2xx response WITH a body
(the common case — BC returns structured JSON error objects) fell through
to the content-type branch instead.
## Incident (2026-07-31)
BC returned a 400 with a plain JSON error body (`{"Error": {"Message":
"..."}}`) while still labelling the response `content-type:
text/event-stream`. `parseSSE()` only extracts lines starting with `data:` —
a plain JSON body has none, so it returned `[]` silently. The stdin loop's
`for (const out of responses) process.stdout.write(...)` then had nothing to
iterate, so the bridge produced **zero output** — not even to stderr — for a
request that BC had already answered in under 200ms. Claude Code had no
signal to work with and waited out its own 30-second client-side timeout,
which was the only thing the developer actually saw.
## Rule
Check `!r.ok` before considering content-type at all, and throw
unconditionally (with the body text included) when it's true. Never let a
non-2xx response reach `parseSSE` — a server is free to mislabel an error
body's content-type, and the client must not depend on that label being
honest.
## Anti-Pattern
const ct = r.headers.get("content-type") || "";
const text = await r.text();
if (!r.ok && !text) throw new Error(`HTTP ${r.status}`);
return ct.includes("text/event-stream") ? parseSSE(text) : [text.trim()];
// A 400 WITH a body silently falls through to parseSSE and returns [].
## Compliant
const ct = r.headers.get("content-type") || "";
const text = await r.text();
if (!r.ok) throw new Error(`HTTP ${r.status}: ${text || "(empty body)"}`);
return ct.includes("text/event-stream") ? parseSSE(text) : [text.trim()];
## Scope
`bc-mcp-bridge.js` specifically, but the underlying principle generalizes to
any stdio MCP bridge that branches parsing logic on a server-supplied
content-type header: validate the HTTP status first, independent of what
the header claims the body's shape is.