Two fixes bundled together, both diagnosed from a live BC MCP failure in
a Jernpladsen (Project Management 365) session:
- 2026-08-05 (previously drafted, never committed): BC rejects a
non-initialize request with a stale/absent Mcp-Session-Id with "A new
session can only be created by an initialize request." The prior retry
just cleared sessionId and resent the same message with no header,
reproducing the identical error. Now replays the client's cached
initialize message first to obtain a fresh session, then retries.
This also fixes dispatchCreateComment's per-chunk fresh-session reset,
which was hitting exactly this failure on every chunk of a >250-char
Task Comment.
- 2026-08-07: the reactive fix above only self-heals after BC has
already rejected a request. A client that opens a fresh session per
call (e.g. a Power Automate flow) never hits staleness at all, which
is why that pattern reads as more reliable than our long-lived
interactive bridge process. Added a proactive refresh: if the cached
session has been idle past a heuristic threshold, re-initialize
before attempting the call instead of waiting for the rejection.
Neither fetch() call had a timeout, and the BC session (Mcp-Session-Id)
plus the underlying HTTP connection were reused for the bridge process's
entire lifetime with no proactive refresh. A long-running Claude Code
session with gaps between calls can leave either stale.
Observed today: a 30-minute hang on one call (no timeout meant it never
failed on its own), and separately an "Internal_CompanyNotFound" on two
machines that likely wasn't a real company problem but a dead session --
Michael noticed the pattern independently: Summatim's frequent quick calls
never have this issue, longer sessions with gaps do.
Added REQUEST_TIMEOUT_MS (30s) via AbortSignal.timeout on both fetch
calls, and wrapped forward() to retry once with sessionId cleared on any
failure -- self-heals a stale session/connection without the developer
noticing. Tested end to end: full initialize + tools/call succeeds
normally (no retry needed), and a forced-failure path correctly retries
once then surfaces the real error if it persists.
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>