koala73/worldmonitor · error · SafeWebMcpError

Dashboard panel catalog exceeded the safe output limit.

Error message

Dashboard panel catalog exceeded the safe output limit.

What it means

boundDashboardPanelsResult caps the panel catalog response (pagination, popping panels, setting hasMore/nextCursor) and then verifies the final JSON fits MAX_OUTPUT_CHARS. If even the bounded payload exceeds the safe output limit, it throws this SafeWebMcpError rather than emitting an oversized MCP response. It protects the WebMCP channel from unbounded catalog data.

Source

Thrown at src/services/webmcp.ts:1000

    return bounded;
  });
  const bounded: DashboardPanelCatalogPage = {
    variant: boundedText(result.variant, 32),
    total: Math.max(0, Math.floor(boundedNumber(result.total))),
    hasMore: result.hasMore === true,
    nextCursor: result.nextCursor ? boundedText(result.nextCursor, DASHBOARD_PANEL_ID_MAX_CHARS) : null,
    panels,
  };
  while (
    JSON.stringify(bounded).length > DASHBOARD_PANEL_CATALOG_OUTPUT_TARGET_CHARS
    && bounded.panels.length > 1
  ) {
    bounded.panels.pop();
    bounded.hasMore = true;
    bounded.nextCursor = bounded.panels[bounded.panels.length - 1]?.id ?? null;
  }
  if (JSON.stringify(bounded).length > MAX_OUTPUT_CHARS) {
    throw new SafeWebMcpError('Dashboard panel catalog exceeded the safe output limit.');
  }
  return bounded;
}

function boundDashboardViewState(
  value: DashboardActionViewState | undefined,
): DashboardActionViewState | undefined {
  if (!value || typeof value !== 'object') return undefined;
  const bounded: DashboardActionViewState = {};
  if (typeof value.timeRange === 'string' && value.timeRange) {
    bounded.timeRange = boundedText(value.timeRange, 32);
  }
  if (typeof value.iso2 === 'string' && value.iso2) {
    bounded.iso2 = boundedText(value.iso2, 2);
  }
  if (typeof value.mode === 'string' && value.mode) {
    bounded.mode = boundedText(value.mode, 8);
  }

View on GitHub (pinned to 9361220cc0)

Solutions

  1. Request smaller pages by passing a lower `limit` to list_dashboard_panels and paginate via the returned nextCursor.
  2. Narrow the query with `variant`/`category`/`enabled`/`available` filters so fewer, smaller entries are returned.
  3. Shorten panel catalog metadata (titles/descriptions) at registration time if you control the panels.
  4. If legitimately unavoidable, this is a bug-report condition: the bounder should always be able to produce a page under budget.

Example fix

// before
const all = await tools.call('list_dashboard_panels', {});
// after
let cursor;
const panels = [];
do {
  const page = await tools.call('list_dashboard_panels', { limit: 10, cursor });
  panels.push(...page.panels);
  cursor = page.nextCursor;
} while (cursor);
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED = ['variant', 'category', 'enabled', 'available', 'cursor', 'limit'];
function validPanelQuery(args) {
  return Object.keys(args).every((k) => ALLOWED.includes(k))
    && (args.limit === undefined || (Number.isInteger(args.limit) && args.limit > 0 && args.limit <= 50));
}

Try / catch

try {
  return await tools.call('list_dashboard_panels', { limit: 20, cursor });
} catch (e) {
  if (e.message.includes('exceeded the safe output limit')) {
    return tools.call('list_dashboard_panels', { limit: 5, cursor, category: narrowCategory });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling list_dashboard_panels when the serialized (even fully paginated) panel catalog still exceeds MAX_OUTPUT_CHARS — e.g. an enormous number of panels with very long descriptions/names such that a single page cannot fit under the budget.

Common situations: Environments with many custom/registered panels with verbose metadata; a limit parameter that yields a page too large for the remaining budget; catalog entries with unusually long localized strings.

Related errors


AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01). Data as JSON: /api/errors/948310ee7cc274d4. Report an issue: GitHub.