{"record":{"id":"15a63896f0cb6d4f","repo":"ChatGPTNextWeb/NextChat","slug":"failed-to-load-preset-servers","errorCode":null,"errorMessage":"Failed to load preset servers","messagePattern":"Failed to load preset servers","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"app/components/mcp-market.tsx","lineNumber":99,"sourceCode":"    };\n\n    // 立即执行一次\n    updateStatuses();\n    // 每 1000ms 轮询一次\n    const timer = setInterval(updateStatuses, 1000);\n\n    return () => clearInterval(timer);\n  }, [mcpEnabled, config]);\n\n  // 加载预设服务器\n  useEffect(() => {\n    const loadPresetServers = async () => {\n      if (!mcpEnabled) return;\n      try {\n        setLoadingPresets(true);\n        const response = await fetch(\"https://nextchat.club/mcp/list\");\n        if (!response.ok) {\n          throw new Error(\"Failed to load preset servers\");\n        }\n        const data = await response.json();\n        setPresetServers(data?.data ?? []);\n      } catch (error) {\n        console.error(\"Failed to load preset servers:\", error);\n        showToast(\"Failed to load preset servers\");\n      } finally {\n        setLoadingPresets(false);\n      }\n    };\n    loadPresetServers();\n  }, [mcpEnabled]);\n\n  // 加载初始状态\n  useEffect(() => {\n    const loadInitialState = async () => {\n      if (!mcpEnabled) return;\n      try {","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/ChatGPTNextWeb/NextChat/blob/defdcdb55d850cd12c4c657eb83729fd66e215c0/app/components/mcp-market.tsx#L81-L117","documentation":"Thrown at app/components/mcp-market.tsx:99 inside the loadPresetServers useEffect when fetch('https://nextchat.club/mcp/list') resolves with response.ok === false. The preset (marketplace) server list is fetched from a remote host on first mount when MCP is enabled; any non-2xx from nextchat.club triggers this throw (which is then caught and shown as a toast).","triggerScenarios":"The nextchat.club service is down or returns 5xx; the endpoint path changed (404); a non-2xx is returned due to maintenance, auth, or rate limiting; the client is behind a proxy/firewall that intercepts the request with a non-2xx; an offline/air-gapped deployment that cannot reach nextchat.club.","commonSituations":"Self-hosted NextChat in an air-gapped or corporate network with no outbound access to nextchat.club; the marketplace service is temporarily unavailable; CDN/WAF in front of nextchat.club blocks the request; user has DNS that resolves but the TLS/network layer returns a non-2xx error page.","solutions":["Confirm outbound HTTPS to https://nextchat.club/mcp/list works from the host (curl it).","Treat preset load failure as non-fatal (already caught): the component already shows a toast and keeps presetServers empty, so just ensure the marketplace UI degrades to 'unavailable' gracefully.","If deploying offline, host the preset list locally and make the URL configurable instead of hard-coded.","Add retry/backoff for transient failures and cache the last good list in localStorage."],"exampleFix":"// before\nconst response = await fetch(\"https://nextchat.club/mcp/list\");\nif (!response.ok) {\n  throw new Error(\"Failed to load preset servers\");\n}\n\n// after\nconst PRESET_URL = process.env.NEXTCHAT_PRESET_URL ?? \"https://nextchat.club/mcp/list\";\nlet response: Response;\ntry {\n  response = await fetch(PRESET_URL);\n} catch (e) {\n  setPresetServers([]);\n  showToast(\"Marketplace unreachable\");\n  return;\n}\nif (!response.ok) {\n  setPresetServers([]);\n  showToast(`Marketplace returned ${response.status}`);\n  return;\n}","handlingStrategy":"fallback","validationCode":"async function presetListReachable(): Promise<boolean> {\n  try {\n    const res = await fetch(\"https://nextchat.club/mcp/list\", { method: \"HEAD\" });\n    return res.ok;\n  } catch {\n    return false;\n  }\n}\n\nif (!(await presetListReachable())) {\n  setPresetServers([]); // degrade gracefully\n}","typeGuard":"function isPresetListResponse(\n  data: unknown,\n): data is { data: unknown[] } {\n  return typeof data === \"object\" && data !== null && Array.isArray((data as any).data);\n}","tryCatchPattern":"// already handled in-component; ensure the catch keeps the UI usable:\ntry {\n  const response = await fetch(\"https://nextchat.club/mcp/list\");\n  if (!response.ok) throw new Error(\"Failed to load preset servers\");\n  setPresetServers((await response.json())?.data ?? []);\n} catch {\n  setPresetServers([]);\n  showToast(\"Marketplace unavailable\");\n} finally {\n  setLoadingPresets(false);\n}","preventionTips":["Make the marketplace URL configurable for offline/air-gapped deployments.","Cache the last successful preset list in localStorage and serve it when the fetch fails.","Treat marketplace unavailability as non-fatal — it must not block core MCP features."],"tags":["mcp","marketplace","network","nextchat-club","frontend"],"backgroundTag":null,"analyzedSha":"defdcdb55d850cd12c4c657eb83729fd66e215c0","analyzedAt":"2026-08-12T09:57:26.489Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}