koala73/worldmonitor · error · SafeWebMcpError
list_dashboard_panels accepts only variant, category, enable
Error message
list_dashboard_panels accepts only variant, category, enabled, available, cursor, and limit.
What it means
The WebMCP tool list_dashboard_panels is registered with a strict input schema (additionalProperties: false). Before executing the tool, the handler checks the exact own-key set of the arguments object via hasOnlyOwnKeys and throws SafeWebMcpError when any unrecognized key is present, keeping the tool surface minimal and predictable for MCP clients.
Solutions
- Remove all arguments except variant, category, enabled, available, cursor, and limit from the tool call.
- Check the tool's inputSchema (properties block above) for exact names and types of the six allowed keys.
- Refresh the MCP client's tool listing so the schema it uses matches the server's current registration.
Example fix
// before
const res = await mcp.callTool('list_dashboard_panels', { category: 'military', search: 'radar' });
// after
const res = await mcp.callTool('list_dashboard_panels', { category: 'military', limit: 20 }); Defensive patterns
Strategy: validation
Validate before calling
const ALLOWED = ['variant','category','enabled','available','cursor','limit'];
const extra = Object.keys(args ?? {}).filter(k => !ALLOWED.includes(k));
if (extra.length) throw new Error(`Unknown list_dashboard_panels args: ${extra.join(', ')}`); Type guard
const isPanelQuery = (a: unknown): a is Record<string, string|boolean|number> => a !== null && typeof a === 'object' && Object.keys(a).every(k => ['variant','category','enabled','available','cursor','limit'].includes(k));
Try / catch
try {
const res = await mcp.callTool('list_dashboard_panels', query);
} catch (e) {
if (e instanceof Error && e.message.includes('accepts only')) {
console.error('Bad arguments, allowed keys: variant, category, enabled, available, cursor, limit', e.message);
} else throw e;
} Prevention
- Build arguments from the tool's inputSchema properties instead of by hand.
- Keep a shared constant of allowed keys in client code.
- Re-list tools after server upgrades to pick up schema changes.
- Type the args object in TypeScript with a strict keyof union.
When it happens
Trigger: Calling list_dashboard_panels with any property outside variant, category, enabled, available, cursor, limit — e.g. misspelled names like variants, category, startCursor, page, or extra keys such as search or id.
Common situations: Developers guessing parameter names instead of reading the tool's JSON schema; clients auto-generating arguments from stale tool metadata; copying query params from other catalog tools (e.g. pagination keys named differently); SDK version drift where an older schema allowed more fields.
Understand the failure class
Background: "Invalid query parameter" / "Failed to parse value of ...": fixing bad query string parameters across APIs — this error's family across 36 libraries.
Related errors
- list_mission_presets accepts only available.
- ${label} HTTP 400
- Could not resolve ${JSON.stringify(echoCountryInput(raw))} t
- get_intel_timeline requires at least one of domain ("conflic
- invalid-user-id
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/806da0c9c6e53deb.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/webmcp.ts:2032
description: 'Catalog cursor from the previous page nextCursor.',
minLength: 1,
maxLength: DASHBOARD_PANEL_ID_MAX_CHARS,
pattern: DASHBOARD_PANEL_ID_PATTERN,
},
limit: {
type: 'integer',
description: 'Maximum panels in this page, from 1 to 8.',
minimum: 1,
maximum: DASHBOARD_PANEL_CATALOG_MAX_LIMIT,
default: DASHBOARD_PANEL_CATALOG_DEFAULT_LIMIT,
},
},
additionalProperties: false,
},
annotations: { readOnlyHint: true },
execute: withBindings(WEBMCP_SPA_TOOL.listDashboardPanels, async (args, extra) => {
if (!hasOnlyOwnKeys(args, ['variant', 'category', 'enabled', 'available', 'cursor', 'limit'])) {
throw new SafeWebMcpError(
'list_dashboard_panels accepts only variant, category, enabled, available, cursor, and limit.',
'validation',
);
}
const query: DashboardPanelCatalogQuery = {};
if (args.variant !== undefined) query.variant = args.variant as string;
if (args.category !== undefined) query.category = args.category as string;
if (args.enabled !== undefined) query.enabled = args.enabled as boolean;
if (args.available !== undefined) query.available = args.available as boolean;
if (args.cursor !== undefined) query.cursor = args.cursor as string;
if (args.limit !== undefined) query.limit = args.limit as number;
return boundDashboardPanelCatalog(await app.listDashboardPanels(query, extra));
}, trackEvent, {
successMetadata: (_args, value) => {
const result = value as DashboardPanelCatalogPage;
return {
resultCount: result.panels.length,
hasMore: result.hasMore === true,View on GitHub (pinned to 7d06c8633d)