ChatGPTNextWeb/NextChat · warning · Error

Failed to load tools

Error message

Failed to load tools

What it means

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).

Source

Thrown at app/components/mcp-market.tsx:235

      setConfig(newConfig);
      showToast("Server configuration updated successfully");
    } catch (error) {
      showToast(
        error instanceof Error ? error.message : "Failed to save configuration",
      );
    } finally {
      updateLoadingState(savingServerId, null);
    }
  };

  // 获取服务器支持的 Tools
  const loadTools = async (id: string) => {
    try {
      const result = await getClientTools(id);
      if (result) {
        setTools(result);
      } else {
        throw new Error("Failed to load tools");
      }
    } catch (error) {
      showToast("Failed to load tools");
      console.error(error);
      setTools(null);
    }
  };

  // 更新加载状态的辅助函数
  const updateLoadingState = (id: string, message: string | null) => {
    setLoadingStates((prev) => {
      if (message === null) {
        const { [id]: _, ...rest } = prev;
        return rest;
      }
      return { ...prev, [id]: message };
    });
  };

View on GitHub (pinned to defdcdb55d)

Solutions

  1. Check getClientsStatus()[id].status === 'active' before calling loadTools; if paused, call resumeMcpServer first.
  2. If status is 'initializing', poll or await completion before loading tools.
  3. If status is 'error', surface errorMsg instead of the generic 'Failed to load tools'.
  4. Differentiate 'no tools' from 'client missing' so the user knows whether to resume or re-add.

Example fix

// before
const result = await getClientTools(id);
if (result) {
  setTools(result);
} else {
  throw new Error("Failed to load tools");
}

// after
const status = (await getClientsStatus())[id];
if (!status) {
  showToast(`Server ${id} not found`);
  setTools(null);
  return;
}
if (status.status === "paused") {
  showToast(`Resume ${id} first`);
  setTools(null);
  return;
}
if (status.status === "error") {
  showToast(`Server error: ${status.errorMsg ?? "unknown"}`);
  setTools(null);
  return;
}
const result = await getClientTools(id);
setTools(result ?? []);
Defensive patterns

Strategy: validation

Validate before calling

import { getClientsStatus } from "@/app/mcp/actions";

async function canLoadTools(id: string): Promise<boolean> {
  const statuses = await getClientsStatus();
  return statuses[id]?.status === "active";
}

if (await canLoadTools(id)) {
  await loadTools(id);
} else {
  showToast(`Server ${id} is not active; cannot list tools`);
}

Type guard

function hasTools(tools: unknown): tools is NonNullable<ReturnType<typeof getClientTools>> {
  return tools != null && Array.isArray(tools);
}

Try / catch

try {
  const result = await getClientTools(id);
  if (!result) throw new Error("Failed to load tools");
  setTools(result);
} catch {
  const status = (await getClientsStatus())[id];
  showToast(
    status?.status === "paused"
      ? `Resume ${id} first`
      : `Server ${id} error: ${status?.errorMsg ?? "unknown"}`,
  );
  setTools(null);
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of ChatGPTNextWeb/NextChat@defdcdb55d (2026-08-12). Data as JSON: /api/errors/e1a16d372998691f. Report an issue: GitHub.