{"record":{"id":"a0f1c5fa168d2270","repo":"koala73/worldmonitor","slug":"validation","errorCode":"validation","errorMessage":"${error.message}","messagePattern":"\\$\\{error\\.message\\}","errorType":"error_code","errorClass":"SafeWebMcpError","httpStatus":null,"severity":"error","filePath":"src/services/webmcp.ts","lineNumber":794,"sourceCode":"    input: Record<string, unknown>,\n    extra?: { signal?: AbortSignal },\n  ) => Promise<unknown> | unknown,\n  trackEvent: WebMcpAnalytics,\n  hooks: WebMcpInvocationHooks = {},\n): DashboardWebMcpTool['execute'] {\n  return async (args, extra?: WebMcpToolExecutionContext) => {\n    const signal = isAbortSignal(extra?.signal) ? extra.signal : undefined;\n    const execution = signal ? { signal } : undefined;\n    markLcpDebug('wm:webmcp:tool-start', {\n      tool: name,\n      targetCancellationSupported: Boolean(signal),\n    });\n    try {\n      throwIfWebMcpAborted(signal);\n      const preflightResult = await hooks.preflight?.(args, execution);\n      throwIfWebMcpAborted(signal);\n      let result: unknown;\n      if (preflightResult !== undefined) {\n        result = preflightResult;\n      } else if (CANCELLATION_REQUIRED_WEBMCP_TOOLS.has(name) && !signal) {\n        // This tool declared that a phantom completion would be unsafe, and\n        // the host cannot deliver the target-side signal. Return a structured\n        // denial because some hosts erase the name and message of errors\n        // raised by the page callback.\n        result = unsupportedCancellationResult();\n      } else {\n        result = await fn(args, execution);\n        // Browser cancellation rejects executeTool independently of this\n        // callback. Re-check here so late work cannot publish success telemetry\n        // after the host has already cancelled the invocation.\n        throwIfWebMcpAborted(signal);\n      }\n      enforceOutputBudget(result);\n      const invocation = classifyInvocationResult(result);\n      reportWebMcpEvent(trackEvent, 'webmcp-tool-invoked', {\n        tool: name,","sourceCodeStart":776,"sourceCodeEnd":812,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/src/services/webmcp.ts#L776-L812","documentation":"withInvocationLogging maps DashboardPanelCatalogError and MissionPresetCatalogError to a SafeWebMcpError with code 'validation', reusing the catalog error's own message verbatim. These catalog errors mean the requested panel/mission-preset catalog query or identifier was rejected — the arguments failed catalog-level validation before any dashboard action ran.","triggerScenarios":"list_dashboard_panels with an invalid variant/category filter value; open_dashboard_panel / set_panel_enabled with an unknown panel id; list_mission_presets / apply_mission_preset with an unknown preset id or malformed query — i.e. any call that makes the panel or mission-preset catalog throw.","commonSituations":"Hard-coded panel ids that were renamed or removed in a newer dashboard version; category/variant strings that don't match the catalog enum; stale tool integrations written against an older panel catalog; typos in preset names.","solutions":["Use list_dashboard_panels / list_mission_presets first and pass ids/values straight from that response instead of hard-coding them.","Match the variant/category against the catalog's accepted enum values exactly (case-sensitive).","Refresh your integration against the current dashboard version if a previously working id stopped resolving.","Catch this error and surface the embedded message — it already describes the exact catalog validation problem."],"exampleFix":"// before\nawait tools.call('open_dashboard_panel', { id: 'panel-cpu-temp' });\n// after\nconst { panels } = await tools.call('list_dashboard_panels', {});\nconst panel = panels.find((p) => p.id === 'panel-cpu-temp');\nif (!panel) throw new Error('panel-cpu-temp no longer exists in catalog');\nawait tools.call('open_dashboard_panel', { id: panel.id });","handlingStrategy":"validation","validationCode":"async function panelExists(tools, id) {\n  const { panels } = await tools.call('list_dashboard_panels', {});\n  return panels.some((p) => p.id === id);\n}","typeGuard":"function isCatalogValidationError(e) {\n  return e instanceof Error && e.name === 'WebMcpToolError' && typeof e.message === 'string';\n}","tryCatchPattern":"try {\n  await tools.call('open_dashboard_panel', { id });\n} catch (e) {\n  if (/catalog|panel/i.test(e.message)) {\n    const { panels } = await tools.call('list_dashboard_panels', {});\n    const match = panels.find((p) => p.id === id);\n    if (match) return tools.call('open_dashboard_panel', { id: match.id });\n  }\n  throw e;\n}","preventionTips":["Never hard-code panel or preset ids; resolve them via the list tools at runtime.","Keep a cached catalog and invalidate it on dashboard version changes.","Match enum values (variant/category) exactly and case-sensitively.","Re-verify ids after any WorldMonitor upgrade."],"tags":["webmcp","validation","catalog","invalid-arguments"],"backgroundTag":"catalog-validation-failed","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}