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.

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@e586b8b4b8 (2026-09-01). Data as JSON: /api/errors/948310ee7cc274d4. Report an issue: GitHub.

Appendix: source

Thrown at src/services/webmcp.ts:1008

    },
  };

  const collections = [enabled, mounted, enabledLayers];
  while (JSON.stringify(result).length > maxChars) {
    const candidate = collections
      .filter((collection) => collection.length > 0)
      .sort((left, right) => (
        right.reduce((sum, item) => sum + item.length, 0)
        - left.reduce((sum, item) => sum + item.length, 0)
      ))[0];
    if (!candidate) break;
    candidate.pop();
  }

  result.map.layersTruncated = enabledLayers.length < result.map.enabledLayerCount;
  result.panels.mountedTruncated = mounted.length < result.panels.mountedCount;
  result.panels.enabledTruncated = enabled.length < result.panels.enabledCount;
  return result;
}

function boundUnavailableReason(value: unknown): DashboardPanelUnavailableReason | undefined {
  if (value === 'panel_not_entitled' || value === 'panel_disabled' || value === 'panel_not_live') {
    return value;
  }
  return undefined;
}

function boundDashboardPanelCatalog(result: DashboardPanelCatalogPage): DashboardPanelCatalogPage {
  const panels = (Array.isArray(result.panels) ? result.panels : []).map((panel) => {
    const available = panel?.available === true;
    const unavailableReason = available ? undefined : boundUnavailableReason(panel?.unavailableReason);
    const bounded: DashboardPanelCatalogItem = {
      id: boundedText(panel?.id, DASHBOARD_PANEL_ID_MAX_CHARS),
      label: boundedText(panel?.label, DASHBOARD_PANEL_LABEL_MAX_CHARS),
      category: boundedText(panel?.category, DASHBOARD_PANEL_CATEGORY_MAX_CHARS),
      variants: Array.isArray(panel?.variants)

View on GitHub (pinned to e586b8b4b8)