{"record":{"id":"d096398216e7036e","repo":"ChatGPTNextWeb/NextChat","slug":"client-clientid-not-found","errorCode":null,"errorMessage":"Client ${clientId} not found","messagePattern":"Client (.+?) not found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/mcp/actions.ts","lineNumber":344,"sourceCode":"    for (const [clientId, serverConfig] of Object.entries(config.mcpServers)) {\n      await initializeSingleClient(clientId, serverConfig);\n    }\n    return config;\n  } catch (error) {\n    logger.error(`Failed to restart clients: ${error}`);\n    throw error;\n  }\n}\n\n// 执行 MCP 请求\nexport async function executeMcpAction(\n  clientId: string,\n  request: McpRequestMessage,\n) {\n  try {\n    const client = clientsMap.get(clientId);\n    if (!client?.client) {\n      throw new Error(`Client ${clientId} not found`);\n    }\n    logger.info(`Executing request for [${clientId}]`);\n    return await executeRequest(client.client, request);\n  } catch (error) {\n    logger.error(`Failed to execute request for [${clientId}]: ${error}`);\n    throw error;\n  }\n}\n\n// 获取 MCP 配置文件\nexport async function getMcpConfigFromFile(): Promise<McpConfigData> {\n  try {\n    const configStr = await fs.readFile(CONFIG_PATH, \"utf-8\");\n    return JSON.parse(configStr);\n  } catch (error) {\n    logger.error(`Failed to load MCP config, using default config: ${error}`);\n    return DEFAULT_MCP_CONFIG;\n  }","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/ChatGPTNextWeb/NextChat/blob/defdcdb55d850cd12c4c657eb83729fd66e215c0/app/mcp/actions.ts#L326-L362","documentation":"Thrown at app/mcp/actions.ts:344 inside executeMcpAction when clientsMap.get(clientId) is undefined OR when the entry exists but client.client is null. Unlike pause/resume (which check the config file), this checks the live in-memory clientsMap, so a server can be in the config but still trigger this if it was never initialized, was paused, or failed to initialize (client set to null with an errorMsg).","triggerScenarios":"Calling executeMcpAction before initializeSingleClient has completed (async init still pending); calling it on a paused server (clientsMap entry was deleted on pause); calling it on a server whose createClient/listTools threw — the entry is {client:null, tools:null, errorMsg}; or on a completely unknown id.","commonSituations":"User invokes a tool immediately after adding a server before the async init resolves; server is in 'error' status; restartAllClients cleared the map; the worklet/client process crashed and clientsMap was not repopulated; calling execute on a paused server.","solutions":["Before executing, call getClientsStatus() and check the entry is 'active' (client present, no errorMsg).","If the server is paused, call resumeMcpServer first and await it.","If the server is in 'error' status, surface errorMsg to the user and offer re-add/resume rather than executing.","Guard with a small retry/await loop keyed on initialization completion, or show a 'still initializing' message."],"exampleFix":"// before\nconst result = await executeMcpAction(id, request);\n\n// after\nconst status = (await getClientsStatus())[id];\nif (!status || status.status !== \"active\") {\n  throw new Error(\n    `Client ${id} not ready (status: ${status?.status ?? \"missing\"}). ${status?.errorMsg ?? \"\"}`,\n  );\n}\nconst result = await executeMcpAction(id, request);","handlingStrategy":"validation","validationCode":"import { getClientsStatus } from \"@/app/mcp/actions\";\n\nasync function isClientReady(clientId: string): Promise<boolean> {\n  const statuses = await getClientsStatus();\n  return statuses[clientId]?.status === \"active\";\n}\n\nif (await isClientReady(id)) {\n  const result = await executeMcpAction(id, request);\n} else {\n  throw new Error(`Client ${id} is not ready; resume or re-add it.`);\n}","typeGuard":"type ClientStatus = { status: \"active\" | \"paused\" | \"error\" | \"initializing\"; errorMsg?: string };\n\nfunction isActiveClient(\n  s: ClientStatus | undefined,\n): s is ClientStatus & { status: \"active\" } {\n  return s?.status === \"active\";\n}","tryCatchPattern":"try {\n  return await executeMcpAction(id, request);\n} catch (e) {\n  if (e instanceof Error && /not found/.test(e.message)) {\n    const status = (await getClientsStatus())[id];\n    throw new Error(\n      `Client ${id} not ready (status: ${status?.status ?? \"missing\"})`,\n    );\n  }\n  throw e;\n}","preventionTips":["Never execute on a client whose status is not 'active'.","Await initialization before exposing tools to the user.","Auto-resume paused servers or hide their tools until resumed."],"tags":["mcp","client","state-mismatch","lifecycle","validation"],"backgroundTag":null,"analyzedSha":"defdcdb55d850cd12c4c657eb83729fd66e215c0","analyzedAt":"2026-08-12T09:57:26.489Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}