{"record":{"id":"1ef436d39b53978a","repo":"koala73/worldmonitor","slug":"country-must-be-an-iso-3166-1-alpha-2-country-code","errorCode":null,"errorMessage":"country must be an ISO 3166-1 alpha-2 country code, e.g. \"UA\" for Ukraine or \"US\" for the United States — not a country name or a three-letter code.","messagePattern":"country must be an ISO 3166-1 alpha-2 country code, e\\.g\\. \"UA\" for Ukraine or \"US\" for the United States — not a country name or a three-letter code\\.","errorType":"validation","errorClass":"RpcValidationError","httpStatus":400,"severity":"warning","filePath":"api/mcp/registry/rpc-tools.ts","lineNumber":609,"sourceCode":" * flattened into the generic `-32603 \"Internal error: data fetch failed\"` —\n * strictly worse than not checking at all, since the un-guarded path at least\n * relays the handler's own `-32602 Invalid params` with `error.data.violations`.\n * It also keeps the `<label> HTTP 400` message shape dispatch's client-4xx\n * severity downgrade matches on, so a caller-input rejection reports at\n * `warning` rather than `error`.\n *\n * Why it matters: an agent reads -32603 as transient and retries.\n * WORLDMONITOR-10R recorded 13 `search_intel_history` calls from one IP inside\n * 40 seconds (2026-08-27T01:43:19Z → 01:43:58Z) against a deterministic\n * validation failure.\n *\n * The sibling scope guard in `get_intel_timeline` (unscoped read) gets the same\n * treatment in #7182 — deliberately left alone here so the two changes do not\n * collide on one block.\n */\nfunction assertIntelHistoryCountry(label: string, country: string): void {\n  if (country && !INTEL_HISTORY_COUNTRY_PATTERN.test(country)) {\n    throw new RpcValidationError(label, [{\n      field: 'country',\n      description: 'must be an ISO 3166-1 alpha-2 country code, e.g. \"UA\" for Ukraine or \"US\" for the United States — not a country name or a three-letter code.',\n    }]);\n  }\n}\n\nfunction procurementPageSize(value: unknown): number {\n  return Number.isInteger(value) && (value as number) > 0\n    ? Math.min(PROCUREMENT_TOOL_MAX_PAGE_SIZE, value as number)\n    : PROCUREMENT_TOOL_DEFAULT_PAGE_SIZE;\n}\n\n/**\n * The MCP tool preserves the canonical relevance-filter semantics:\n * malformed/non-positive values disable the filter; values above 100 are\n * deliberately passed through so the route remains the sole authority that\n * clamps its documented upper bound.\n */","sourceCodeStart":591,"sourceCodeEnd":627,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/api/mcp/registry/rpc-tools.ts#L591-L627","documentation":"This RpcValidationError is thrown by the MCP tool registry's local pre-flight guard `assertIntelHistoryCountry` in api/mcp/registry/rpc-tools.ts:509. Before the tool call reaches the HTTP handler, the registry validates the `country` filter against `^[A-Z]{2}$` and rejects anything that is not an uppercase two-letter ISO 3166-1 alpha-2 code. It exists so callers get an actionable JSON-RPC -32602 with `error.data.violations` instead of a generic -32603 internal error, and so agents do not retry a deterministic validation failure (the WORLDMONITOR-10R incident recorded 13 pointless retries in 40 seconds).","triggerScenarios":"Calling an MCP intel-history tool (e.g. `search_intel_history` or `get_intel_timeline`) with `country` set to a country name (\"Ukraine\"), a lowercase code (\"ua\"), a three-letter code (\"UKR\"), or any non-two-uppercase-letter string.","commonSituations":"LLM agents passing natural-language country names from user prompts; callers using ISO 3166-1 alpha-3 or numeric codes; passing lowercase codes; copying country labels from UI datasets instead of code tables.","solutions":["Set `country` to an uppercase ISO 3166-1 alpha-2 code, e.g. \"UA\" or \"US\"","If the caller only has a country name, map it to its alpha-2 code before calling the tool","Omit `country` entirely when no country filter is wanted (it is optional; only the shape is validated)"],"exampleFix":"// before\ntools.search_intel_history({ country: 'Ukraine' })\n// after\ntools.search_intel_history({ country: 'UA' })","handlingStrategy":"validation","validationCode":"const ISO_ALPHA2 = /^[A-Z]{2}$/;\nconst country = 'UA'; // candidate\nif (country && !ISO_ALPHA2.test(country)) {\n  // map from a name or alpha-3 code before calling the tool\n  throw new Error(`Invalid country filter: ${country}`);\n}\nawait tools.search_intel_history({ country });","typeGuard":"function isIsoAlpha2Country(v: unknown): v is string {\n  return typeof v === 'string' && /^[A-Z]{2}$/.test(v);\n}","tryCatchPattern":"try {\n  await callTool();\n} catch (e) {\n  if (e?.code === -32602 && e?.data?.violations?.some((v) => v.field === 'country')) {\n    // fix the country param; do NOT retry unchanged\n  } else throw e;\n}","preventionTips":["Normalize country inputs to ISO 3166-1 alpha-2 uppercase before any MCP intel-history call","Treat -32602 violations as deterministic: never retry the same params"],"tags":["mcp","validation","iso-3166","country-code","rpc"],"backgroundTag":"invalid-parameter-validation","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-08-27T19:53:08.521Z","contentChangedAt":"2026-08-27T19:53:08.521Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}