koala73/worldmonitor · error · SafeWebMcpError
validation
validation
Error message
${error.message} What it means
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.
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.
Example fix
// before
await tools.call('open_dashboard_panel', { id: 'panel-cpu-temp' });
// after
const { panels } = await tools.call('list_dashboard_panels', {});
const panel = panels.find((p) => p.id === 'panel-cpu-temp');
if (!panel) throw new Error('panel-cpu-temp no longer exists in catalog');
await tools.call('open_dashboard_panel', { id: panel.id }); Defensive patterns
Strategy: validation
Validate before calling
async function panelExists(tools, id) {
const { panels } = await tools.call('list_dashboard_panels', {});
return panels.some((p) => p.id === id);
} Type guard
function isCatalogValidationError(e) {
return e instanceof Error && e.name === 'WebMcpToolError' && typeof e.message === 'string';
} Try / catch
try {
await tools.call('open_dashboard_panel', { id });
} catch (e) {
if (/catalog|panel/i.test(e.message)) {
const { panels } = await tools.call('list_dashboard_panels', {});
const match = panels.find((p) => p.id === id);
if (match) return tools.call('open_dashboard_panel', { id: match.id });
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Dashboard panel catalog exceeded the safe output limit.
- invalid_variant
- malformed_arguments
- switch_monitor input preflight did not run.
- a pull_request payload is required
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-01).
Data as JSON: /api/errors/a0f1c5fa168d2270.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/webmcp.ts:794
input: Record<string, unknown>,
extra?: { signal?: AbortSignal },
) => Promise<unknown> | unknown,
trackEvent: WebMcpAnalytics,
hooks: WebMcpInvocationHooks = {},
): DashboardWebMcpTool['execute'] {
return async (args, extra?: WebMcpToolExecutionContext) => {
const signal = isAbortSignal(extra?.signal) ? extra.signal : undefined;
const execution = signal ? { signal } : undefined;
markLcpDebug('wm:webmcp:tool-start', {
tool: name,
targetCancellationSupported: Boolean(signal),
});
try {
throwIfWebMcpAborted(signal);
const preflightResult = await hooks.preflight?.(args, execution);
throwIfWebMcpAborted(signal);
let result: unknown;
if (preflightResult !== undefined) {
result = preflightResult;
} else if (CANCELLATION_REQUIRED_WEBMCP_TOOLS.has(name) && !signal) {
// This tool declared that a phantom completion would be unsafe, and
// the host cannot deliver the target-side signal. Return a structured
// denial because some hosts erase the name and message of errors
// raised by the page callback.
result = unsupportedCancellationResult();
} else {
result = await fn(args, execution);
// Browser cancellation rejects executeTool independently of this
// callback. Re-check here so late work cannot publish success telemetry
// after the host has already cancelled the invocation.
throwIfWebMcpAborted(signal);
}
enforceOutputBudget(result);
const invocation = classifyInvocationResult(result);
reportWebMcpEvent(trackEvent, 'webmcp-tool-invoked', {
tool: name,View on GitHub (pinned to e586b8b4b8)