{"record":{"id":"a17146242e2781a3","repo":"different-ai/openwork","slug":"invalid-payload","errorCode":"invalid_payload","errorMessage":"config is required","messagePattern":"config is required","errorType":"validation","errorClass":"ApiError","httpStatus":400,"severity":"error","filePath":"apps/server/src/cloud-mcp-health.ts","lineNumber":739,"sourceCode":"  // the caller keeps the richer stage-tagged validation error.\n  if (!normalized) return true;\n  let url: URL;\n  try {\n    url = new URL(normalized);\n  } catch {\n    return true;\n  }\n  if (isLoopbackHostname(url.hostname)) return true;\n  if (url.protocol !== \"https:\") return false;\n  if (BUILT_IN_CLOUD_MCP_ORIGINS.has(url.origin)) return true;\n  const { readActivatedEnterpriseDenOrigin } = await import(\"./enterprise-den-origin.js\");\n  const enterpriseOrigin = await readActivatedEnterpriseDenOrigin();\n  return enterpriseOrigin !== null && url.origin === enterpriseOrigin;\n}\n\nfunction normalizeCloudMcpConfig(input: unknown): Record<string, unknown> {\n  if (!isRecord(input)) {\n    throw new ApiError(400, \"invalid_payload\", \"config is required\");\n  }\n  const type = input.type ?? \"remote\";\n  const url = readString(input.url);\n  const output: Record<string, unknown> = { type };\n  if (url) output.url = normalizeCloudEndpointUrl(url) ?? url;\n  const enabled = readBoolean(input.enabled);\n  if (enabled !== undefined) output.enabled = enabled;\n  const headers = normalizeStringRecord(input.headers);\n  if (headers) output.headers = headers;\n  if (input.oauth === false) output.oauth = false;\n  else if (input.oauth === true) output.oauth = {};\n  else if (isRecord(input.oauth)) output.oauth = input.oauth;\n  const timeout = readNumber(input.timeout);\n  if (timeout !== undefined) output.timeout = timeout;\n  return output;\n}\n\nfunction strictCloudMcpDesiredConfigProblem(config: Record<string, unknown>, metadata: CloudMcpDesiredMetadata): CloudMcpValidationProblem | null {","sourceCodeStart":721,"sourceCodeEnd":757,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/cloud-mcp-health.ts#L721-L757","documentation":"normalizeCloudMcpConfig validates the request body's config field: if it is not a plain object (isRecord fails), it throws this 400 invalid_payload ApiError. The endpoint requires a structured MCP config object (type defaults to \"remote\", url/enabled normalized when present).","triggerScenarios":"POSTing to the cloud MCP config endpoint with a missing config field, a JSON string instead of an object, an array, null, or a top-level payload where config was nested one level deeper than expected.","commonSituations":"Client sending form-encoded or stringified body without the server parsing JSON; forgetting the `config` wrapper key; sending `config: \"...\"` after double-serializing; SDK version mismatch sending the old payload shape.","solutions":["Send the body as JSON with a top-level `config` object, e.g. {\"config\": {\"type\": \"remote\", \"url\": \"https://...\", \"enabled\": true}}.","Ensure the request Content-Type is application/json so the body parses into an object rather than a string.","Inspect the payload: remove double-serialization (config must be an object, not a JSON string).","Check the API/SDK version for the expected payload shape."],"exampleFix":"// before\nfetch(url, { body: JSON.stringify({ config: JSON.stringify(cfg) }) })\n// after\nfetch(url, { headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ config: cfg }) })","handlingStrategy":"validation","validationCode":"function assertConfig(body: unknown): asserts body is { config: Record<string, unknown> } {\n  if (typeof body !== \"object\" || body === null ||\n      typeof (body as Record<string, unknown>).config !== \"object\" ||\n      (body as Record<string, unknown>).config === null) {\n    throw new Error(\"body must contain a config object\");\n  }\n}","typeGuard":"function isRecord(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  await api.updateCloudMcp(id, payload);\n} catch (e) {\n  if (isApiError(e) && e.code === \"invalid_payload\" && e.message === \"config is required\") {\n    // fix request body shape: { config: {...} }\n  }\n}","preventionTips":["Always send { config: { type, url, enabled } } as a JSON object.","Set Content-Type: application/json; never send stringified config strings.","Validate payloads with Zod client-side before sending.","Keep client payload shapes in sync with the API schema."],"tags":["http","validation","payload","api"],"backgroundTag":"invalid-request-payload","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}