{"record":{"id":"31248b192d1ac704","repo":"koala73/worldmonitor","slug":"mcp-error-code-message","errorCode":null,"errorMessage":"MCP error #{code}: #{message}","messagePattern":"MCP error #(.+?): #(.+?)","errorType":"exception","errorClass":"MCPError","httpStatus":null,"severity":"error","filePath":"sdk/ruby/lib/worldmonitor.rb","lineNumber":244,"sourceCode":"      h[API_KEY_HEADER] = api_key if api_key\n      h\n    end\n\n    def rpc(method, params = nil)\n      body = { \"jsonrpc\" => \"2.0\", \"id\" => 1, \"method\" => method }\n      body[\"params\"] = params if params\n      request_headers = headers(accept: \"application/json, text/event-stream\")\n      request_headers[\"content-type\"] = \"application/json\"\n      status, content_type, text = @transport.call(\n        { url: mcp_url, method: \"POST\", headers: request_headers, body: JSON.generate(body) },\n        timeout\n      )\n      value = self.class.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 value.is_a?(Hash) && value[\"error\"].is_a?(Hash)\n        err = value[\"error\"]\n        raise MCPError.new(err[\"code\"] || 0, err[\"message\"] || \"\", err[\"data\"])\n      end\n      raise APIError.new(status, value) unless (200..299).cover?(status)\n\n      value.is_a?(Hash) && value.key?(\"result\") ? value[\"result\"] : value\n    end\n\n    def http_transport(request, timeout)\n      uri = URI.parse(request[:url])\n      http = Net::HTTP.new(uri.host, uri.port)\n      http.use_ssl = uri.scheme == \"https\"\n      http.open_timeout = timeout\n      http.read_timeout = timeout\n      klass = request[:method] == \"POST\" ? Net::HTTP::Post : Net::HTTP::Get\n      req = klass.new(uri.request_uri)\n      (request[:headers] || {}).each { |k, v| req[k] = v }\n      req.body = request[:body] if request[:body]\n      res = http.request(req)\n      [res.code.to_i, res[\"content-type\"].to_s, res.body.to_s]","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/sdk/ruby/lib/worldmonitor.rb#L226-L262","documentation":"Raised as WorldMonitor::MCPError inside rpc() when the MCP server's JSON-RPC response contains an 'error' hash (line 242-244); the JSON-RPC error takes precedence over the HTTP status because some transports pair auth failures with HTTP 200. The code field carries the JSON-RPC error code: -32001 (MCP_AUTH_ERROR_CODE) means the tools/call needs a valid user API key; other negative codes indicate malformed requests or server-side tool failures. The message appends AUTH_HINT when code == -32001 (line 68). MCPError exposes .code and .data readers.","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 api_key or WORLDMONITOR_API_KEY/WM_API_KEY set (yields code -32001). Also fires for non-auth JSON-RPC errors: calling an unknown tool name, passing wrong/missing arguments, or a server-side tool execution failure (codes other than -32001). Public methods list_tools/list_prompts/list_resources do NOT trigger auth errors.","commonSituations":"Works locally but CI lacks WORLDMONITOR_API_KEY. Key has trailing whitespace from a dotenv loader. Staging key against the production endpoint. A free-tier key calling a Pro-gated tool. Calling a tool name that was renamed in a server update (non-auth error code).","solutions":["Inspect e.code: -32001 is auth — set api_key: or WORLDMONITOR_API_KEY; other codes indicate a bad tool name or arguments — check them against client.list_tools output.","Verify the key is active at https://worldmonitor.app/pro.","Strip whitespace and quotes from the env value before passing it.","Confirm tool names and required arguments with list_tools before calling unfamiliar tools."],"exampleFix":"# before\nclient = WorldMonitor::Client.new  # no key\nbrief = client.world_brief  # raises MCPError(-32001)\n# after\nclient = WorldMonitor::Client.new(api_key: ENV['WORLDMONITOR_API_KEY'])\nbrief = client.world_brief","handlingStrategy":"validation","validationCode":"# Validate the key is present BEFORE any tools/call.\napi_key = ENV['WORLDMONITOR_API_KEY'] || ENV['WM_API_KEY']\nraise 'WORLDMONITOR_API_KEY is required for tools/call' if api_key.nil? || api_key.strip.empty?\nraise 'Key has surrounding whitespace' unless api_key == api_key.strip\n\nclient = WorldMonitor::Client.new(api_key: api_key.strip)","typeGuard":"def auth_error?(exc)\n  exc.is_a?(WorldMonitor::MCPError) && exc.code == WorldMonitor::MCP_AUTH_ERROR_CODE\nend","tryCatchPattern":"begin\n  brief = client.world_brief\nrescue WorldMonitor::MCPError => e\n  if e.code == WorldMonitor::MCP_AUTH_ERROR_CODE\n    raise 'Missing/invalid API key for tools/call. Set WORLDMONITOR_API_KEY.'\n  end\n  raise # non-auth JSON-RPC error — do not retry without fixing the arguments\nrescue WorldMonitor::Error\n  raise\nend","preventionTips":["Pass api_key: or set WORLDMONITOR_API_KEY before the first tools/call in every environment.","Call client.health right after constructing the Client to fail fast on connectivity.","Strip whitespace and quotes when loading the key from a dotenv file.","Branch on e.code: only -32001 is auth — confirm tool names and args with list_tools for other codes."],"tags":["ruby","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"}