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

  1. 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.
  2. Wait for the submarine-cable seeder to run and verify seed-meta:infrastructure:submarine-cables exists in GET /api/health.
  3. 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.
  4. 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

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


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/ef548453824486d2. Report an issue: GitHub.