{"record":{"id":"94d6b3fe8ff1c7cb","repo":"koala73/worldmonitor","slug":"could-not-resolve-json-stringify-echocountryinput-raw-to-a","errorCode":null,"errorMessage":"Could not resolve ${JSON.stringify(echoCountryInput(raw))} to a country. Pass an ISO 3166-1 alpha-2 code (e.g. \"IQ\"), an alpha-3 code (\"IRQ\"), or an English country name (\"Iraq\").","messagePattern":"Could not resolve (.+?) to a country\\. Pass an ISO 3166-1 alpha-2 code \\(e\\.g\\. \"IQ\"\\), an alpha-3 code \\(\"IRQ\"\\), or an English country name \\(\"Iraq\"\\)\\.","errorType":"validation","errorClass":"RpcValidationError","httpStatus":null,"severity":"warning","filePath":"api/mcp/_country-args.ts","lineNumber":26,"sourceCode":"  // Never stringify a non-string. `String(x)` runs the value's own toString /\n  // valueOf, and `{\"toString\":\"x\"}` is legal JSON a caller can send: the\n  // shadowed, non-callable toString makes String() throw\n  // `TypeError: Cannot convert object to primitive value`. That turns this\n  // guard — whose whole job is to produce a clean 400 — into a 500. Describing\n  // the type is also more useful to the caller than `[object Object]`.\n  const text = typeof raw === 'string' ? raw.trim() : `<non-string ${typeof raw}>`;\n  return text.length > MAX_ECHOED_COUNTRY_INPUT\n    ? `${text.slice(0, MAX_ECHOED_COUNTRY_INPUT)}…`\n    : text;\n}\n\nexport const COUNTRY_ARG_HINT =\n  'Pass an ISO 3166-1 alpha-2 code (e.g. \"IQ\"), an alpha-3 code (\"IRQ\"), or an English country name (\"Iraq\").';\n\nexport function requireCountryCode(raw: unknown, operation: string, field = 'country_code'): string {\n  const resolved = resolveCountryCode(raw);\n  if (resolved) return resolved;\n  throw new RpcValidationError(operation, [{\n    field,\n    description: `Could not resolve ${JSON.stringify(echoCountryInput(raw))} to a country. ${COUNTRY_ARG_HINT}`,\n  }]);\n}\n\n/** Omitted optional filters retain the unfiltered result; invalid entries do not. */\nexport function resolveCountryFilter(raw: unknown, field: string): string[] {\n  if (raw == null || (typeof raw === 'string' && !raw.trim())) return [];\n  const values = Array.isArray(raw) ? raw : [raw];\n  return values.map((value) => requireCountryCode(value, 'country-filter', field).toLowerCase());\n}\n","sourceCodeStart":8,"sourceCodeEnd":38,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/api/mcp/_country-args.ts#L8-L38","documentation":"requireCountryCode validates MCP tool arguments against resolveCountryCode, which accepts ISO 3166-1 alpha-2, alpha-3, or English country names. If the input cannot be resolved, it throws an RpcValidationError whose description embeds the echoed (sanitized) input plus the COUNTRY_ARG_HINT, telling the caller exactly which formats are accepted.","triggerScenarios":"Calling any MCP tool that funnels country arguments through requireCountryCode (via resolveCountryFilter/countryCode/code) with an unrecognized value: full ISO alpha-2 is expected, but things like 'uk', lowercase full names with typos, numeric codes, empty-ish strings that fail normalization, or localized names will not resolve.","commonSituations":"LLM tool callers emitting 'UK' style or full locale names ('United Kingdom of Great Britain...'); clients sending numeric ISO 3166-1 numeric codes; older integrations sending alpha-2 with whitespace/newlines that survive trim but fail matching.","solutions":["Send a valid ISO 3166-1 alpha-2 code (e.g. \"IQ\") — the most reliable form","Or send an alpha-3 code (\"IRQ\") or the exact English country name (\"Iraq\")","Trim/canonicalize the value client-side before sending","Check the error description: it echoes the offending input so you can see what actually arrived (encoding/quoting bugs show up here)"],"exampleFix":"// before\ncallTool('get-country-brief', { country_code: 'UK' }); // RpcValidationError\n// after\ncallTool('get-country-brief', { country_code: 'GB' }); // resolves","handlingStrategy":"validation","validationCode":"const ISO2 = new Set(['IQ','GB','US']); // or full ISO list\nfunction looksLikeCountryArg(v) { return typeof v === 'string' && /^[A-Za-z]{2,3}$/.test(v.trim()) || typeof v === 'string' && v.trim().length > 3; }","typeGuard":"function isPlausibleCountryCode(v) { return typeof v === 'string' && v.trim().length >= 2 && v.trim().length <= 56; }","tryCatchPattern":"try { return await tool.call(params); } catch (e) { if (e instanceof RpcValidationError) return { error: 'invalid_country', hint: e.violations[0]?.description }; throw e; }","preventionTips":["Canonicalize country inputs (trim, uppercase for alpha-2) before sending","Prefer alpha-2 codes over names in programmatic clients","Constrain LLM tool schemas to a 2-letter pattern for country_code","Check the echoed input in the error description when debugging"],"tags":["validation","mcp","country-code","arguments"],"backgroundTag":"invalid-argument-value","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}