koala73/worldmonitor · error
get-country-intel-brief HTTP
Error message
get-country-intel-brief HTTP ${res.status}${code ? `: ${code}` : ''} What it means
Plain Error thrown in get_country_intel_brief (api/mcp/registry/rpc-tools.ts:1152): the country intel brief endpoint returned non-ok. Before throwing, the tool parses the body for an 'error' field (usually a string like 'invalid_internal_mcp_signature'; non-string shapes are JSON-stringified) and bounds it to 120 chars — non-JSON bodies are HTML-stripped, whitespace-collapsed, and sliced — so the message carries a safe, Sentry-friendly code without body bloat. Note the documented METHOD DRIFT: this tool POSTs while OpenAPI declares only GET; the gateway routes by path so it works, but proxies enforcing method parity can break it.
Solutions
- Read the trailing : <code> — 'invalid_internal_mcp_signature' means the auth signature mismatch; verify method (POST), path, and body match what was signed
- Validate country_code as ISO 3166-1 alpha-2 before calling
- For 5xx, retry with backoff; for 405, check whether an intermediary enforces the OpenAPI-declared GET on this path
- Reproduce with curl POST to the path with the same auth headers to isolate signature vs handler failure
Defensive patterns
Strategy: try-catch
Validate before calling
// Validate the country param client-side before the call
if (!/^[A-Za-z]{2}$/.test(countryCode ?? '')) throw new Error('country_code must be ISO 3166-1 alpha-2'); Type guard
function isCountryBriefHttpError(e) {
return e instanceof Error && /^get-country-intel-brief HTTP \d+/.test(e.message);
}
function briefStatusOf(e) { return Number(e.message.match(/HTTP (\d+)/)?.[1] ?? 0); }
function briefCodeOf(e) { return e.message.match(/: (.+)$/)?.[1] ?? ''; } Try / catch
try {
const brief = await client.callTool('get_country_intel_brief', { country: 'DE' });
} catch (e) {
if (isCountryBriefHttpError(e)) {
const s = briefStatusOf(e), code = briefCodeOf(e);
if (s >= 500) return retryWithBackoff(call, 3);
if (code === 'invalid_internal_mcp_signature') throw new Error('Signature bug: sign the POST method+path+body', { cause: e });
throw e;
}
throw e;
} Prevention
- Sign POST requests with the POST method and the exact request body — GET-shape signatures cause the 401 here
- Validate country_code format before calling
- Be aware of the documented GET/POST method drift on this path when behind method-enforcing proxies
When it happens
Trigger: Calling get_country_intel_brief when the internal MCP signature validation fails (401 with 'invalid_internal_mcp_signature' — signature computed over the wrong method/path/body), an invalid country code produces a 4xx, the handler 5xxs, or a method-enforcing intermediary returns 405.
Common situations: buildAuthHeaders signing GET-shape while the request POSTs (or omitting the body from the signature). Country code not ISO alpha-2. OpenAPI/registry method drift tripping strict gateways. Upstream brief-generation dependency outage.
Related errors
- get-vessel-snapshot HTTP
- body-too-large
- Could not resolve to a country.
- country must be an ISO 3166-1 alpha-2 country code, e.g…
- get-china-decision-signals returned an invalid canonical…
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-08-21).
Data as JSON: /api/errors/4ca722721e64ed87.
Report an issue: GitHub.
Appendix: source
Thrown at api/mcp/registry/rpc-tools.ts:1630
body: briefBody,
signal: AbortSignal.timeout(22_000),
}, execution);
if (!res.ok) {
throwIfBillingDenial(res, 'get-country-intel-brief');
// Surface the gateway's error code in the thrown message so Sentry
// groups the failure by root cause, not just status. Body reads are
// best-effort; a read failure must not mask the HTTP status.
const detail = await res.text().catch(() => '');
let code = '';
// `error` is usually a string (for example,
// `invalid_internal_mcp_signature`), but stringify non-string shapes so
// object envelopes remain readable. Bound both paths so Sentry titles
// cannot bloat on a long body.
try {
const error = (JSON.parse(detail) as { error?: unknown }).error ?? '';
code = (typeof error === 'string' ? error : JSON.stringify(error)).slice(0, 120);
} catch {
code = detail.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 120);
}
throw new Error(`get-country-intel-brief HTTP ${res.status}${code ? `: ${code}` : ''}`);
}
const result = await res.json() as Record<string, unknown>;
const resultSources = collectMcpBriefSources(Array.isArray(result.sources) ? result.sources as DigestItemForBrief[] : [], 6);
// groundingStories stays [] when the 2 s digest fetch failed above, which
// is the honest signal: the brief was written without that grounding.
return {
...result,
sources: resultSources.length > 0 ? resultSources : sources,
groundingStories,
...(digestCoverage ? { digestCoverage } : {}),
};
},
// METHOD DRIFT: _execute POSTs above but OpenAPI declares only GET on this
// path (verified against docs/api/IntelligenceService.openapi.json). The
// gateway routes by path, not method, so POST works at runtime. We declare
// GET here because OpenAPI is the parity test's source-of-truth — fixingView on GitHub (pinned to e586b8b4b8)