{"record":{"id":"948310ee7cc274d4","repo":"koala73/worldmonitor","slug":"dashboard-panel-catalog-exceeded-the-safe-output-l","errorCode":null,"errorMessage":"Dashboard panel catalog exceeded the safe output limit.","messagePattern":"Dashboard panel catalog exceeded the safe output limit\\.","errorType":"error_code","errorClass":"SafeWebMcpError","httpStatus":null,"severity":"error","filePath":"src/services/webmcp.ts","lineNumber":1000,"sourceCode":"    return bounded;\n  });\n  const bounded: DashboardPanelCatalogPage = {\n    variant: boundedText(result.variant, 32),\n    total: Math.max(0, Math.floor(boundedNumber(result.total))),\n    hasMore: result.hasMore === true,\n    nextCursor: result.nextCursor ? boundedText(result.nextCursor, DASHBOARD_PANEL_ID_MAX_CHARS) : null,\n    panels,\n  };\n  while (\n    JSON.stringify(bounded).length > DASHBOARD_PANEL_CATALOG_OUTPUT_TARGET_CHARS\n    && bounded.panels.length > 1\n  ) {\n    bounded.panels.pop();\n    bounded.hasMore = true;\n    bounded.nextCursor = bounded.panels[bounded.panels.length - 1]?.id ?? null;\n  }\n  if (JSON.stringify(bounded).length > MAX_OUTPUT_CHARS) {\n    throw new SafeWebMcpError('Dashboard panel catalog exceeded the safe output limit.');\n  }\n  return bounded;\n}\n\nfunction boundDashboardViewState(\n  value: DashboardActionViewState | undefined,\n): DashboardActionViewState | undefined {\n  if (!value || typeof value !== 'object') return undefined;\n  const bounded: DashboardActionViewState = {};\n  if (typeof value.timeRange === 'string' && value.timeRange) {\n    bounded.timeRange = boundedText(value.timeRange, 32);\n  }\n  if (typeof value.iso2 === 'string' && value.iso2) {\n    bounded.iso2 = boundedText(value.iso2, 2);\n  }\n  if (typeof value.mode === 'string' && value.mode) {\n    bounded.mode = boundedText(value.mode, 8);\n  }","sourceCodeStart":982,"sourceCodeEnd":1018,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/src/services/webmcp.ts#L982-L1018","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Request smaller pages by passing a lower `limit` to list_dashboard_panels and paginate via the returned nextCursor.","Narrow the query with `variant`/`category`/`enabled`/`available` filters so fewer, smaller entries are returned.","Shorten panel catalog metadata (titles/descriptions) at registration time if you control the panels.","If legitimately unavoidable, this is a bug-report condition: the bounder should always be able to produce a page under budget."],"exampleFix":"// before\nconst all = await tools.call('list_dashboard_panels', {});\n// after\nlet cursor;\nconst panels = [];\ndo {\n  const page = await tools.call('list_dashboard_panels', { limit: 10, cursor });\n  panels.push(...page.panels);\n  cursor = page.nextCursor;\n} while (cursor);","handlingStrategy":"validation","validationCode":"const ALLOWED = ['variant', 'category', 'enabled', 'available', 'cursor', 'limit'];\nfunction validPanelQuery(args) {\n  return Object.keys(args).every((k) => ALLOWED.includes(k))\n    && (args.limit === undefined || (Number.isInteger(args.limit) && args.limit > 0 && args.limit <= 50));\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await tools.call('list_dashboard_panels', { limit: 20, cursor });\n} catch (e) {\n  if (e.message.includes('exceeded the safe output limit')) {\n    return tools.call('list_dashboard_panels', { limit: 5, cursor, category: narrowCategory });\n  }\n  throw e;\n}","preventionTips":["Always paginate with a modest limit and follow nextCursor.","Filter by variant/category/enabled/available to shrink result payloads.","Keep panel registration metadata (titles/descriptions) short.","Cap your own limit parameter well below the channel budget."],"tags":["webmcp","output-budget","pagination","catalog"],"backgroundTag":"response-size-limit-exceeded","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}