{"record":{"id":"e1a16d372998691f","repo":"ChatGPTNextWeb/NextChat","slug":"failed-to-load-tools","errorCode":null,"errorMessage":"Failed to load tools","messagePattern":"Failed to load tools","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"app/components/mcp-market.tsx","lineNumber":235,"sourceCode":"      setConfig(newConfig);\n      showToast(\"Server configuration updated successfully\");\n    } catch (error) {\n      showToast(\n        error instanceof Error ? error.message : \"Failed to save configuration\",\n      );\n    } finally {\n      updateLoadingState(savingServerId, null);\n    }\n  };\n\n  // 获取服务器支持的 Tools\n  const loadTools = async (id: string) => {\n    try {\n      const result = await getClientTools(id);\n      if (result) {\n        setTools(result);\n      } else {\n        throw new Error(\"Failed to load tools\");\n      }\n    } catch (error) {\n      showToast(\"Failed to load tools\");\n      console.error(error);\n      setTools(null);\n    }\n  };\n\n  // 更新加载状态的辅助函数\n  const updateLoadingState = (id: string, message: string | null) => {\n    setLoadingStates((prev) => {\n      if (message === null) {\n        const { [id]: _, ...rest } = prev;\n        return rest;\n      }\n      return { ...prev, [id]: message };\n    });\n  };","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/ChatGPTNextWeb/NextChat/blob/defdcdb55d850cd12c4c657eb83729fd66e215c0/app/components/mcp-market.tsx#L217-L253","documentation":"Thrown at app/components/mcp-market.tsx:235 inside loadTools when getClientTools(id) returns null. getClientTools (app/mcp/actions.ts:78) returns clientsMap.get(clientId)?.tools ?? null, so null means either the clientId is absent from the in-memory clientsMap, or the entry exists but its .tools is null (init failed, paused, or still initializing).","triggerScenarios":"User opens the 'tools' view for a server that is paused, in 'error' status, still initializing (tools not yet populated), or not in clientsMap at all (never added / removed / map cleared by restartAllClients).","commonSituations":"Clicking 'view tools' right after adding a server before async init finishes; server failed to start (bad command/path, missing env); server was paused; restartAllClients ran and the map is mid-rebuild; the id passed is stale.","solutions":["Check getClientsStatus()[id].status === 'active' before calling loadTools; if paused, call resumeMcpServer first.","If status is 'initializing', poll or await completion before loading tools.","If status is 'error', surface errorMsg instead of the generic 'Failed to load tools'.","Differentiate 'no tools' from 'client missing' so the user knows whether to resume or re-add."],"exampleFix":"// before\nconst result = await getClientTools(id);\nif (result) {\n  setTools(result);\n} else {\n  throw new Error(\"Failed to load tools\");\n}\n\n// after\nconst status = (await getClientsStatus())[id];\nif (!status) {\n  showToast(`Server ${id} not found`);\n  setTools(null);\n  return;\n}\nif (status.status === \"paused\") {\n  showToast(`Resume ${id} first`);\n  setTools(null);\n  return;\n}\nif (status.status === \"error\") {\n  showToast(`Server error: ${status.errorMsg ?? \"unknown\"}`);\n  setTools(null);\n  return;\n}\nconst result = await getClientTools(id);\nsetTools(result ?? []);","handlingStrategy":"validation","validationCode":"import { getClientsStatus } from \"@/app/mcp/actions\";\n\nasync function canLoadTools(id: string): Promise<boolean> {\n  const statuses = await getClientsStatus();\n  return statuses[id]?.status === \"active\";\n}\n\nif (await canLoadTools(id)) {\n  await loadTools(id);\n} else {\n  showToast(`Server ${id} is not active; cannot list tools`);\n}","typeGuard":"function hasTools(tools: unknown): tools is NonNullable<ReturnType<typeof getClientTools>> {\n  return tools != null && Array.isArray(tools);\n}","tryCatchPattern":"try {\n  const result = await getClientTools(id);\n  if (!result) throw new Error(\"Failed to load tools\");\n  setTools(result);\n} catch {\n  const status = (await getClientsStatus())[id];\n  showToast(\n    status?.status === \"paused\"\n      ? `Resume ${id} first`\n      : `Server ${id} error: ${status?.errorMsg ?? \"unknown\"}`,\n  );\n  setTools(null);\n}","preventionTips":["Only offer 'view tools' when getClientsStatus reports 'active'.","Distinguish 'paused' and 'error' states so the user gets an actionable message.","Await initialization completion before enabling tool-list actions."],"tags":["mcp","tools","client","lifecycle","frontend"],"backgroundTag":null,"analyzedSha":"defdcdb55d850cd12c4c657eb83729fd66e215c0","analyzedAt":"2026-08-12T09:57:26.489Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}