bytedance/deer-flow · error · MCPConfigRequestError

Failed to update MCP configuration

Error message

Failed to update MCP configuration

What it means

Thrown when PUT /api/mcp/config (full replacement of the MCP server configuration) returns non-2xx. The Gateway re-validates and atomically writes extensions_config.json; a 422 means the submitted MCPConfig failed schema validation (bad command/args/env types), while 500 usually means the file could not be written.

Source

Thrown at frontend/src/core/mcp/api.ts:48

  if (!response.ok) {
    throw new MCPConfigRequestError(
      response.status,
      await readErrorDetail(response, "Failed to load MCP configuration"),
    );
  }
  return response.json() as Promise<MCPConfig>;
}

export async function updateMCPConfig(config: MCPConfig) {
  const response = await fetch(`${getBackendBaseURL()}/api/mcp/config`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(config),
  });
  if (!response.ok) {
    throw new MCPConfigRequestError(
      response.status,
      await readErrorDetail(response, "Failed to update MCP configuration"),
    );
  }
  return response.json();
}

export async function updateMCPServerState(
  serverName: string,
  enabled: boolean,
) {
  const response = await fetch(`${getBackendBaseURL()}/api/mcp/config`, {
    method: "PATCH",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      server_name: serverName,

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Read the 422 'detail' — FastAPI lists the exact failing JSON path and expected type
  2. Fix the offending server entry in the editor and re-PUT
  3. Ensure the process running the Gateway can write extensions_config.json (check mount flags in docker-compose)
  4. Reload GET /api/mcp/config to see the server's current canonical shape and diff your payload against it

Example fix

// before
await updateMCPConfig(editedConfig);

// after
try {
  await updateMCPConfig(editedConfig);
} catch (e) {
  if (e instanceof MCPConfigRequestError && e.status === 422) {
    setFormErrors(e.message); // FastAPI detail names the invalid field
    return;
  }
  throw e;
}
Defensive patterns

Strategy: validation

Validate before calling

function validateMCPConfig(config: MCPConfig): string[] {
  const errs: string[] = [];
  for (const [name, srv] of Object.entries(config.mcpServers ?? {})) {
    if ('command' in srv && !srv.command) errs.push(`${name}: command required for stdio`);
    if ('url' in srv && !/^https?:\/\//.test(srv.url)) errs.push(`${name}: url must be http(s)`);
    if (srv.env !== undefined && typeof srv.env !== 'object') errs.push(`${name}: env must be an object`);
  }
  return errs;
}

Type guard

export function isMCPConfigRequestError(e: unknown): e is MCPConfigRequestError {
  return e instanceof MCPConfigRequestError;
}

Try / catch

try {
  await updateMCPConfig(config);
} catch (e) {
  if (isMCPConfigRequestError(e) && e.status === 422) {
    setEditorErrors(e.message);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Saving the MCP editor with a server entry missing required fields (e.g. command empty for a stdio server), env not an object, or url malformed for an SSE/streamable server; concurrent edits where two tabs PUT different configs; filesystem permission loss on the repo root.

Common situations: Editing the JSON textarea directly and leaving a trailing comma or quoting a number; switching a server between stdio and SSE types without clearing stale fields; running the Gateway in Docker where extensions_config.json is mounted read-only.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/b746b33f4b62d4b4. Report an issue: GitHub.