koala73/worldmonitor · error · McpSourceUnavailableError
The submarine-cable catalog is unavailable
Error message
The submarine-cable catalog is unavailable
What it means
Thrown by the simulate_infrastructure_cascade MCP tool when the user passes a source_id starting with 'cable:' but the submarine-cable catalog cache (infrastructure:submarine-cables:v1) returned null — meaning the graph has no cable nodes, so the requested cable id cannot exist. The tool distinguishes this from a genuinely unknown id: if the cable catalog WERE loaded but the id was wrong, it returns a helpful error envelope with known_id_sample instead of throwing.
Source
Thrown at api/mcp/registry/analysis-tools.ts:502
checks,
);
const cables = submarineCablesToCableInputs(cablesPayload);
const graph = buildDependencyGraph({ cables, waterways: MCP_CASCADE_WATERWAYS });
const stats = getGraphStats(graph);
const sourceId = typeof params.source_id === 'string' ? params.source_id.trim() : '';
if (!sourceId) {
const catalog: Record<string, Array<{ id: string; name: string }>> = {};
for (const node of graph.nodes.values()) {
if (node.type === 'country') continue;
(catalog[node.type] ??= []).push({ id: node.id, name: node.name });
}
return { ...freshness, data: { catalog, cascade: null, stats } };
}
if (!graph.nodes.has(sourceId)) {
if (cablesPayload === null && sourceId.startsWith('cable:')) {
throw new McpSourceUnavailableError(
'The submarine-cable catalog is unavailable',
freshness.unavailable_inputs,
freshness.failed_inputs,
);
}
const sample = [...graph.nodes.keys()].filter((id) => !id.startsWith('country-')).slice(0, 12);
return {
...freshness,
data: { catalog: null, cascade: null, stats },
error: `unknown source_id "${sourceId}" — call without source_id for the full catalog`,
known_id_sample: sample,
};
}
const rawLevel = Number(params.disruption_level ?? 1);
const disruptionLevel = Math.min(1, Math.max(0.1, Number.isFinite(rawLevel) ? rawLevel : 1));
const cascade = calculateCascade(graph, sourceId, disruptionLevel);
return { ...freshness, data: { catalog: null, cascade, stats } };View on GitHub (pinned to ffec79ac33)
Solutions
- Call simulate_infrastructure_cascade with NO source_id first — it returns the full catalog built from whatever data IS present, confirming whether the cable cache is populated.
- Wait for the submarine-cable seeder to run and verify seed-meta:infrastructure:submarine-cables exists in GET /api/health.
- If you need a non-cable node (chokepoint, pipeline, port), use its correct prefix — those come from curated registries (MCP_CASCADE_WATERWAYS) that do not depend on the cable cache.
- Retry after the seeder cycle; the cable table changes slowly so a transient null resolves on the next seed.
Example fix
// before — requests a cable node while the catalog is unseeded
tool: 'simulate_infrastructure_cascade', params: { source_id: 'cable:sea-me-we-6' }
// after — fetch the catalog first to confirm availability
tool: 'simulate_infrastructure_cascade', params: {} Defensive patterns
Strategy: validation
Validate before calling
// Before requesting a cable source_id, verify the catalog is populated
const catalogResult = await callMcpTool('simulate_infrastructure_cascade', {});
if (!catalogResult.data?.catalog || Object.keys(catalogResult.data.catalog).length === 0) {
// Cable cache is empty — do not request a cable: source_id
throw new Error('Submarine-cable catalog not yet seeded');
}
const cableExists = catalogResult.data.catalog.cable?.some(c => c.id === desiredCableId);
if (!cableExists) throw new Error(`Cable ${desiredCableId} not in catalog`); Type guard
function isMcpSourceUnavailableError(e: unknown): e is { unavailableInputs: string[]; failedInputs: string[] } & Error {
return e instanceof Error && (e as any).name === 'McpSourceUnavailableError';
} Try / catch
try {
const result = await callMcpTool('simulate_infrastructure_cascade', { source_id: 'cable:sea-me-we-6' });
} catch (e) {
if (isMcpSourceUnavailableError(e) && e.message.includes('submarine-cable')) {
// Catalog not seeded — call without source_id to get the catalog once it loads
const catalog = await callMcpTool('simulate_infrastructure_cascade', {});
} else throw e;
} Prevention
- Always call simulate_infrastructure_cascade without source_id first to get the catalog and confirm the cable cache is live.
- Verify the requested source_id exists in the returned catalog before requesting a cascade.
- Use non-cable node types (chokepoint, pipeline, port) which come from curated registries, not the seeded cable table.
When it happens
Trigger: Calling simulate_infrastructure_cascade with source_id='cable:<anything>' when the infrastructure:submarine-cables:v1 Redis key is null — before the cable seeder has run, after a Redis flush, or during a TeleGeography fetch outage. The cable-prefix check fires because the tool infers the user intended a cable node that the empty graph cannot contain.
Common situations: First call after deploy before the submarine-cable seeder (long 25200-minute staleness budget, so it seeds less frequently) has written its key; a TeleGeography source outage that left the cache empty; Redis eviction of the cable key under memory pressure.
Related errors
- No event feeds are available for exposure enrichment
- No digest input feeds are available
- No hotspot-escalation input feeds are available
- Feed digest unavailable for ${variant}/en
- Seeded world brief unavailable (${result.reason})
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/ef548453824486d2.
Report an issue: GitHub.