koala73/worldmonitor · error · McpSourceUnavailableError

Feed digest unavailable for

Error message

Feed digest unavailable for ${variant}/en

What it means

McpSourceUnavailableError thrown in list-feed-digest (api/mcp/registry/nlp-tools.ts:195): the digest endpoint fetch itself succeeded (assertToolFetchOk passed) but the response body has zero categories AND zero feedStatuses — structurally an empty shell. The error names the exact cache key ('news:digest:v1:<variant>:en') in unavailable_inputs so the caller knows which producer is missing, with an empty failed_inputs list (this is a genuine miss, not a Redis read error).

Solutions

  1. Retry later — the digest producer runs on a cadence and the shell becomes populated after the next run
  2. Check /api/health for the news:digest:v1:<variant>:en key freshness
  3. Hit the underlying digest API endpoint directly to confirm it also returns the empty shell (isolates MCP from the producer)
  4. If the variant should already be live, run the news digest seed script or restart its producer
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check the digest key for the variant via health before calling
const health = await fetch('https://<host>/api/health').then(r => r.json());
if (!health?.datasets?.[`news:digest:v1:${variant}:en`]?.fresh) defer('digest not published yet');

Type guard

function isSourceUnavailable(e) {
  return e?.name === 'McpSourceUnavailableError'
    || (typeof e?.message === 'string' && e.message.startsWith('Feed digest unavailable for '));
}

Try / catch

try {
  return await client.callTool('list-feed-digest', { variant });
} catch (e) {
  if (isSourceUnavailable(e)) {
    // unavailable_inputs names the exact key: news:digest:v1:<variant>:en
    return retryAfter(digestProducerCadenceMs);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling list-feed-digest for a variant whose digest producer has not published yet — e.g. a newly rolled-out language variant where the news digest cron has not run, or a deployment where the digest seed was skipped. The endpoint returns 200 with an empty object.

Common situations: New variant deployment ordering (MCP tool shipped before the variant's digest producer). Digest generation job failed silently upstream. Digest key evicted/TTL-expired so the endpoint serves an empty shell. Local dev against prod API without the local digest seeded.

Related errors


AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-08-27). Data as JSON: /api/errors/81c552757e785cd3. Report an issue: GitHub.

Appendix: source

Thrown at api/mcp/registry/nlp-tools.ts:321

        .slice(0, 12),
      stale: cov.state === 'stale',
      // #7084: complete the stale disclosure. The flag alone says the
      // evidence is old without saying how old or why — an agent deciding
      // whether stale evidence is usable needs the age.
      staleAgeSeconds: cov.state === 'stale'
        ? nlpClampInt(cov.staleAgeSeconds ?? 0, 0, Number.MAX_SAFE_INTEGER, 0)
        : 0,
      staleReason: cov.state === 'stale'
        ? nlpTruncateUtf8(cov.staleReason ?? '', NLP_DIGEST_METADATA_MAX_BYTES)
        : '',
    }
    : undefined;
  if (
    Object.keys(categories).length === 0
    && Object.keys(body.feedStatuses ?? {}).length === 0
    && digestCoverage?.state !== 'unavailable'
  ) {
    throw new McpSourceUnavailableError(
      `Feed digest unavailable for ${variant}/en`,
      [`news:digest:v1:${variant}:en`],
      [],
    );
  }
  const availableCategories = Object.keys(categories).sort();
  let groups: NlpDigestCategoryGroup[];
  let note: string | undefined;

  if (!category) {
    groups = Object.values(categories);
  } else if (Object.prototype.hasOwnProperty.call(categories, category)) {
    groups = [categories[category]!];
  } else if (digestCoverage?.state === 'unavailable') {
    // This is a valid empty response, not an unknown category. Preserve the
    // requested filter and the explicit coverage state without a misleading
    // corrective note.
    groups = [];

View on GitHub (pinned to e586b8b4b8)