{"record":{"id":"ebb30a311e0869cd","repo":"koala73/worldmonitor","slug":"32001","errorCode":"-32001","errorMessage":"MCP error %d: %s%s","messagePattern":"MCP error (.+?): (.+?)(.+?)","errorType":"exception","errorClass":"MCPError","httpStatus":null,"severity":"error","filePath":"sdk/python/src/worldmonitor_sdk/__init__.py","lineNumber":289,"sourceCode":"        if params is not None:\n            rpc[\"params\"] = params\n        headers = self._headers(accept=\"application/json, text/event-stream\")\n        headers[\"content-type\"] = \"application/json\"\n        status, content_type, text = self._transport(\n            {\n                \"url\": self.mcp_url,\n                \"method\": \"POST\",\n                \"headers\": headers,\n                \"body\": json.dumps(rpc).encode(\"utf-8\"),\n            },\n            self.timeout,\n        )\n        value = parse_body(text, content_type)\n        # A JSON-RPC error object wins over the HTTP status (the server pairs\n        # auth errors with a 200 on some transports).\n        if isinstance(value, dict) and isinstance(value.get(\"error\"), dict):\n            err = value[\"error\"]\n            raise MCPError(err.get(\"code\", 0), err.get(\"message\", \"\"), err.get(\"data\"))\n        if status < 200 or status >= 300:\n            raise APIError(status, value)\n        if isinstance(value, dict) and \"result\" in value:\n            return value[\"result\"]\n        return value\n\n\ndef _stringify(value):\n    if value is True:\n        return \"true\"\n    if value is False:\n        return \"false\"\n    return str(value)\n\n\n__all__ = [\n    \"API_KEY_HEADER\",\n    \"AUTH_HINT\",","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/sdk/python/src/worldmonitor_sdk/__init__.py#L271-L307","documentation":"Raised as MCPError inside _rpc() when the MCP server's JSON-RPC response contains an 'error' object (line 287-289); the JSON-RPC error wins over the HTTP status because some transports pair auth failures with HTTP 200. The metadata code -32001 equals MCP_AUTH_ERROR_CODE (line 42), the server's signal that the tools/call invocation needs a user API key that was absent, invalid, expired, or tier-insufficient. The message appends AUTH_HINT when code == -32001 (line 72). MCPError exposes .code and .data.","triggerScenarios":"Calling any curated helper that maps to tools/call — world_brief(), country_risk('IR'), market_data(), conflict_events(), call_tool('get_market_data', ...) — without setting api_key or WORLDMONITOR_API_KEY/WM_API_KEY. Also fires when a previously valid key is revoked, when a sandbox key is used against production, or when a free-tier key calls a Pro-gated tool. Public methods list_tools()/list_prompts()/list_resources() do NOT trigger it (they are unauthenticated).","commonSituations":"Local script runs fine but CI lacks the WORLDMONITOR_API_KEY env var. Key value has trailing whitespace or surrounding quotes from a .env loader. Staging key pointed at the production MCP endpoint. A notebook created the Client before os.environ was populated.","solutions":["Set the key explicitly: Client(api_key='wm_...') or export WORLDMONITOR_API_KEY; verify with client.health() first (health is public, so a separate failure isolates connectivity).","Inspect e.code: only -32001 is auth — other codes indicate a malformed tool call or server-side tool error; for those, fix the arguments instead of the key.","Confirm the key is active and tier-appropriate at https://worldmonitor.app/pro.","Ensure no leading/trailing whitespace or quote characters in the env value."],"exampleFix":"# before\nclient = worldmonitor_sdk.Client()  # no key\nbrief = client.world_brief()  # raises MCPError(-32001)\n# after\nimport os\nclient = worldmonitor_sdk.Client(api_key=os.environ['WORLDMONITOR_API_KEY'])\nbrief = client.world_brief()","handlingStrategy":"validation","validationCode":"# Validate the key is present BEFORE any tools/call to get a clear local error.\nimport os\nfrom worldmonitor_sdk import MCP_AUTH_ERROR_CODE\n\napi_key = os.environ.get('WORLDMONITOR_API_KEY') or os.environ.get('WM_API_KEY')\nif not api_key:\n    raise SystemExit('WORLDMONITOR_API_KEY is required for tools/call; get one at https://worldmonitor.app/pro')\nif api_key != api_key.strip():\n    raise SystemExit('WORLDMONITOR_API_KEY has surrounding whitespace — strip it')\n\nclient = worldmonitor_sdk.Client(api_key=api_key.strip())","typeGuard":"from worldmonitor_sdk import MCPError, MCP_AUTH_ERROR_CODE\n\ndef is_auth_error(exc: Exception) -> bool:\n    return isinstance(exc, MCPError) and exc.code == MCP_AUTH_ERROR_CODE","tryCatchPattern":"from worldmonitor_sdk import MCPError, MCP_AUTH_ERROR_CODE, WorldMonitorError\n\ntry:\n    brief = client.world_brief()\nexcept MCPError as e:\n    if e.code == MCP_AUTH_ERROR_CODE:\n        raise SystemExit('Missing/invalid API key for tools/call. Set WORLDMONITOR_API_KEY.') from e\n    raise  # non-auth JSON-RPC error — do not retry blindly\nexcept WorldMonitorError:\n    raise","preventionTips":["Set WORLDMONITOR_API_KEY (or pass api_key=) in every environment before the first tools/call.","Call client.health() right after constructing the Client to fail fast on connectivity before a tools/call.","Strip whitespace and quotes when loading the key from a .env file.","Branch on e.code: only -32001 is auth — retrying other codes without changing arguments is futile."],"tags":["python","mcp","auth","json-rpc","sdk"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}