{"record":{"id":"7ac360bbfdab904d","repo":"BloopAI/vibe-kanban","slug":"failed-to-save-mcp-servers","errorCode":null,"errorMessage":"Failed to save MCP servers","messagePattern":"Failed to save MCP servers","errorType":"http","errorClass":"ApiError","httpStatus":null,"severity":"error","filePath":"packages/web-core/src/shared/lib/api.ts","lineNumber":1095,"sourceCode":"    const params = new URLSearchParams(query);\n    // params.set('profile', profile);\n    const response = await makeHostAwareRequest(\n      `/api/mcp-config?${params.toString()}`,\n      hostId,\n      {\n        method: 'POST',\n        body: JSON.stringify(data),\n      }\n    );\n    if (!response.ok) {\n      const errorData = await response.json();\n      console.error('[API Error] Failed to save MCP servers', {\n        message: errorData.message,\n        status: response.status,\n        response,\n        timestamp: new Date().toISOString(),\n      });\n      throw new ApiError(\n        errorData.message || 'Failed to save MCP servers',\n        response.status,\n        response\n      );\n    }\n  },\n};\n\n// Profiles API\nexport const profilesApi = {\n  load: async (\n    hostId?: string | null\n  ): Promise<{ content: string; path: string }> => {\n    const response = await makeHostAwareRequest('/api/profiles', hostId);\n    return handleApiResponse<{ content: string; path: string }>(response);\n  },\n  save: async (content: string, hostId?: string | null): Promise<string> => {\n    const response = await makeHostAwareRequest('/api/profiles', hostId, {","sourceCodeStart":1077,"sourceCodeEnd":1113,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/packages/web-core/src/shared/lib/api.ts#L1077-L1113","documentation":"mcpApi.save POSTs the MCP server configuration to /api/mcp-config; on a non-OK response it parses the JSON body and throws ApiError with errorData.message when the backend supplied one, otherwise this fallback string 'Failed to save MCP servers'. It means the server refused or failed to persist the MCP servers config for the project/profile.","triggerScenarios":"Saving MCP server settings when the POST /api/mcp-config responds 4xx/5xx: invalid config payload failing backend validation (bad command, missing required fields), malformed JSON body, the target project/profile doesn't exist, or the backend errors writing the config file (permissions, disk). Response body is not valid JSON itself, response.json() will throw before this message is produced.","commonSituations":"Entering an MCP server command that doesn't exist or invalid env var syntax; editing config for a remote host that is offline (proxy returns 502/504); backend config file owned by another user or read-only filesystem; stale hostId pointing to a removed host.","solutions":["Check the response.status and any server message logged by the console.error('[API Error]...') output for the precise backend reason","Validate the MCP server entries (command exists, args/env well-formed) before saving","Confirm the backend host is reachable and the project/profile still exists; refresh the host list if using a remote host","Check backend logs and filesystem permissions where the MCP config is written"],"exampleFix":"// before\nif (!response.ok) {\n  const errorData = await response.json();\n  ...\n  throw new ApiError(errorData.message || 'Failed to save MCP servers', response.status, response);\n}\n// after\nif (!response.ok) {\n  const errorData = await response.json().catch(() => ({}));\n  throw new ApiError(\n    errorData.message || `Failed to save MCP servers (HTTP ${response.status})`,\n    response.status,\n    response\n  );\n}","handlingStrategy":"validation","validationCode":"function validateMcpServers(data: UpdateMcpServersBody): string | null {\n  for (const s of data.servers ?? []) {\n    if (!s.command?.trim()) return 'Each MCP server needs a command';\n  }\n  return null;\n}\nconst err = validateMcpServers(body);\nif (err) return showToast(err); // don't call the API","typeGuard":"function isApiError(e: unknown): e is ApiError {\n  return e instanceof ApiError;\n}","tryCatchPattern":"try {\n  await mcpApi.save(query, body, hostId);\n} catch (e) {\n  if (isApiError(e)) showToast(`MCP save failed (${e.status}): ${e.message}`);\n  else showToast('MCP save failed unexpectedly');\n}","preventionTips":["Validate MCP server entries (command, args, env) client-side before saving","Confirm the target host is online before saving remote MCP config","Catch ApiError specifically to read e.status and e.message for the backend reason","Check backend write permissions on the MCP config file if saves persistently fail"],"tags":["mcp","api-error","config-save","http"],"backgroundTag":"api-request-failed","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}