{"record":{"id":"219df6c159e9e7e6","repo":"koala73/worldmonitor","slug":"get-vessel-snapshot-http-res-status-detail","errorCode":null,"errorMessage":"get-vessel-snapshot HTTP ${res.status}${detail ? ` — ${detail}` : ''}","messagePattern":"get-vessel-snapshot HTTP (.+?)(.+?)` : ''\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/mcp/registry/rpc-tools.ts","lineNumber":1478,"sourceCode":"      // nested `location` (the previous snake_case reads matched nothing, so\n      // density_zones was permanently empty).\n      type VesselLoc = { latitude?: number; longitude?: number };\n      type VesselResp = {\n        snapshot?: {\n          snapshotAt?: number;\n          densityZones?: { name?: string; location?: VesselLoc; intensity?: number; shipsPerDay?: number; deltaPct?: number; note?: string }[];\n          disruptions?: { name?: string; type?: string; severity?: string; location?: VesselLoc; darkShips?: number; vesselCount?: number; region?: string; description?: string }[];\n        };\n      };\n\n      const res = await fetch(url, {\n        headers: { ...auth, 'User-Agent': 'worldmonitor-mcp-edge/1.0' },\n        signal: AbortSignal.timeout(8_000),\n      });\n      if (!res.ok) {\n        throwIfBillingDenial(res, 'get-vessel-snapshot');\n        const detail = (await res.text().catch(() => '')).slice(0, 200);\n        throw new Error(`get-vessel-snapshot HTTP ${res.status}${detail ? ` — ${detail}` : ''}`);\n      }\n      const data = await res.json() as VesselResp;\n      const snap = data.snapshot ?? {};\n\n      // 3° pad: maritime zones sit offshore, outside land bboxes (e.g. the\n      // Strait of Hormuz at 26.6N/56.3E vs AE's ne corner at 26.06/56.38).\n      // (0,0) is the handler's default for missing coordinates → exclude.\n      const PAD_DEG = 3;\n      const inCountryBbox = (loc?: VesselLoc): boolean => {\n        const lat = loc?.latitude ?? 0;\n        const lon = loc?.longitude ?? 0;\n        if (lat === 0 && lon === 0) return false;\n        if (lat < sw_lat - PAD_DEG || lat > ne_lat + PAD_DEG) return false;\n        const lo = sw_lon - PAD_DEG;\n        // Source boxes stored wrapped (sw_lon > ne_lon) span the dateline;\n        // unwrap to a monotonic interval before reasoning about the pad.\n        const hi = (sw_lon > ne_lon ? ne_lon + 360 : ne_lon) + PAD_DEG;\n        // Pad widened the interval to the full circle — AQ and RU are stored","sourceCodeStart":1460,"sourceCodeEnd":1496,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/api/mcp/registry/rpc-tools.ts#L1460-L1496","documentation":"Thrown by the get_vessel_snapshot MCP tool when the downstream vessel endpoint returned a non-OK HTTP status. The code first checks for a billing denial (throwIfBillingDenial, re-thrown separately), then extracts up to 200 characters of error detail from the response body and includes it in the message. The 8-second fetch timeout means a slow upstream can also surface here indirectly.","triggerScenarios":"The vessel snapshot handler returned 4xx/5xx — common causes: 401/403 (auth/HMAC failure), 429 (rate limit from the maritime data provider), 400 (invalid country_code or bbox parameters), 500/502/504 (upstream provider outage — MarineTraffic/AIS source), or the 8s AbortSignal.timeout firing. Billing denial (402) is intercepted earlier and never reaches this throw.","commonSituations":"Maritime AIS provider outage or rate limit; an unsupported or non-coastal country_code with no vessel data (though this may return empty 200 rather than error); auth context expiry causing 401/403; a slow provider response exceeding the 8s budget.","solutions":["Read the HTTP status from the error message: 429 — retry with backoff; 5xx — retry after delay; 400 — verify country_code; 401/403 — check auth context.","Retry with exponential backoff for 429 and 5xx (transient provider issues).","Try a different country_code (ideally a major maritime nation) to determine if the failure is country-specific or systemic.","If the detail mentions a timeout, the upstream provider is slow — retry once; persistent timeouts indicate a provider degradation."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Validate country_code and auth before calling get_vessel_snapshot\nif (!/^[A-Z]{2}$/.test(countryCode)) {\n  throw new Error('country_code must be ISO 3166-1 alpha-2');\n}\nif (context.expiresAt && Date.now() > context.expiresAt) {\n  throw new Error('Auth context expired');\n}","typeGuard":"function isVesselSnapshotHttpError(e: unknown): boolean {\n  return e instanceof Error && /get-vessel-snapshot HTTP \\d+/.test(e.message);\n}","tryCatchPattern":"try {\n  const vessels = await callMcpTool('get_vessel_snapshot', { country_code: 'AE' });\n} catch (e) {\n  if (e instanceof Error) {\n    const status = e.message.match(/HTTP (\\d+)/)?.[1];\n    if (status === '429' || (status && Number(status) >= 500)) {\n      await exponentialBackoffRetry();\n    } else if (status === '401' || status === '403') {\n      refreshAuthContext();\n    } else {\n      throw e;\n    }\n  } else throw e;\n}","preventionTips":["Prefer coastal/maritime nations for vessel data — landlocked countries may return empty or error.","Implement exponential backoff for 429/5xx — the maritime AIS provider may be rate-limited.","Keep auth context fresh to avoid 401/403 failures."],"tags":["mcp","http","vessel-snapshot","maritime","upstream-failure"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}