Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | 41x 32x 38x 38x 38x 25x 25x 25x 25x | /**
* @module scripts/fetch-calendar/mcp/errors
* @description Typed transport-error class and HTML-error detector for the
* MCP calendar transport.
*
* Per `Threat_Modeling.md` (trust-boundary rule for external HTML), an HTML
* response from a JSON-RPC endpoint is treated as a hostile / error response
* — never parsed as JSON — and the orchestrator falls back to the web
* scraper instead.
*
* @author Hack23 AB
* @license Apache-2.0
*/
// HTML detection: common HTML document / fragment leading tags.
export const HTML_PREFIX_RE =
/^\s*(?:<!doctype(?=[\s>])|<html(?=[\s>/])|<head(?=[\s>/])|<body(?=[\s>/])|<title(?=[\s>/])|<meta(?=[\s>/]))/i;
/**
* Returns true when `text` looks like an HTML document rather than JSON.
* Used to detect when the MCP endpoint returns an error page instead of JSON.
*/
export function isHtmlErrorResponse(text: string): boolean {
return HTML_PREFIX_RE.test(text);
}
/**
* Detect the riksdag-regering **degraded-kalender sentinel** payload.
*
* When the upstream `data.riksdagen.se/kalender/` endpoint serves an HTML
* error page instead of JSON, the MCP server does not surface a JSON-RPC
* error — it returns a *successful* tool result whose inner content is a
* sentinel envelope such as:
*
* ```json
* { "count": 0, "events": [], "rawHtml": "<script…",
* "error": "Riksdagens kalender-API returnerade HTML istället för JSON.",
* "notice": "API:et fungerar inte korrekt för närvarande.",
* "suggestions": [ … ] }
* ```
*
* The empty `events: []` array would otherwise be read as a legitimate
* zero-event window, masking the outage and suppressing the web-scraper
* fallback. Treat the presence of a non-empty `error` string or a `rawHtml`
* field as a degraded signal so the orchestrator falls straight back to the
* public-page scraper.
*/
export function isDegradedKalenderSentinel(inner: Record<string, unknown>): boolean {
const hasErrorString = typeof inner['error'] === 'string' && inner['error'].trim().length > 0;
const hasRawHtml = typeof inner['rawHtml'] === 'string' && inner['rawHtml'].trim().length > 0;
return hasErrorString || hasRawHtml;
}
/** Typed error for MCP transport / protocol failures. */
export class CalendarMcpError extends Error {
/** Error category. */
readonly kind: 'html' | 'http' | 'network' | 'json' | 'tool';
/** Raw response body (only present for `html` / `http` kinds). */
readonly responseText?: string;
constructor(message: string, kind: CalendarMcpError['kind'], responseText?: string) {
super(message);
this.name = 'CalendarMcpError';
this.kind = kind;
this.responseText = responseText;
}
}
|