{"record":{"id":"97d6da23a1872663","repo":"unslothai/unsloth","slug":"no-fields-to-update","errorCode":null,"errorMessage":"No fields to update","messagePattern":"No fields to update","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"studio/backend/routes/mcp_servers.py","lineNumber":216,"sourceCode":"    # stdio is OAuth-less: drop a stale OAuth flag when switching to a command.\n    if \"url\" in changes and is_stdio(changes[\"url\"]):\n        changes[\"use_oauth\"] = False\n    return changes\n\n\n@router.put(\"/{server_id}\", response_model = McpServerResponse)\nasync def update_mcp_server(\n    server_id: str,\n    payload: McpServerUpdate,\n    current_subject: str = Depends(get_current_subject),\n    via_api_key: ViaApiKey = False,\n):\n    old = mcp_servers_db.get_server(server_id)\n    if not old:\n        raise HTTPException(status_code = 404, detail = \"MCP server not found\")\n    changes = _changes_from_payload(payload)\n    if not changes:\n        raise HTTPException(status_code = 400, detail = \"No fields to update\")\n    # Both directions, so an API key can neither repoint an http row at a command\n    # nor edit a stdio row's env/name/enabled flag. Before every side effect, so a\n    # refusal leaves the row, its OAuth tokens, cache and sessions untouched.\n    if is_stdio(old[\"url\"]) or is_stdio(changes.get(\"url\", old[\"url\"])):\n        require_ui_session_for_local_commands(via_api_key)\n    # headers == HTTP headers (remote) or env vars (stdio). On a transport-type\n    # switch with no new headers, drop the old ones so env secrets aren't\n    # re-sent as HTTP headers (or vice versa).\n    if (\n        \"url\" in changes\n        and is_stdio(changes[\"url\"]) != is_stdio(old[\"url\"])\n        and \"headers_json\" not in changes\n    ):\n        changes[\"headers_json\"] = None\n    # Clear persisted OAuth tokens when the URL changes or OAuth is disabled;\n    # fastmcp keys tokens by URL and would otherwise let a re-pointed server\n    # silently inherit the old account's credentials.\n    if bool(old.get(\"use_oauth\")) and (","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/routes/mcp_servers.py#L198-L234","documentation":"400 from the update endpoint when _changes_from_payload returns an empty dict — the PUT body contained none of the recognized mutable fields (display_name, url, headers, is_enabled, use_oauth) or contained them only as absent keys. The route treats a no-op update as a client error rather than writing nothing and returning 200.","triggerScenarios":"PUT /{server_id} with an empty JSON object {}, with only unknown/typo'd field names like {\"displayName\": ...}, or with a fully-undefined payload after client-side key stripping.","commonSituations":"Field-name mismatches between client and API (camelCase vs snake_case), PATCH semantics expected but the endpoint requires at least one field, or forms that strip unchanged values and end up sending {}.","solutions":["Include at least one valid field: display_name, url, headers, is_enabled, or use_oauth.","Check for field-name typos — the API uses snake_case exactly as documented.","If you only wanted to verify the row, use GET instead of an empty PUT."],"exampleFix":"// before\nawait api.put(`/api/mcp-servers/${id}`, {}); // 400 'No fields to update'\n\n// after\nawait api.put(`/api/mcp-servers/${id}`, {is_enabled: row.is_enabled}); // real field\n// or just read: await api.get(`/api/mcp-servers/${id}`);","handlingStrategy":"validation","validationCode":"const EDITABLE = new Set(['display_name','url','headers','is_enabled','use_oauth']);\nconst keys = Object.keys(body).filter(k => EDITABLE.has(k));\nif (keys.length === 0) throw new Error('PUT needs at least one editable field');","typeGuard":"function hasEditableField(body) { return ['display_name','url','headers','is_enabled','use_oauth'].some(k => k in body); }","tryCatchPattern":null,"preventionTips":["Use exactly the snake_case field names the API documents.","Use GET when you only want to read; PUT requires a real change."],"tags":["mcp","validation","http-400","empty-payload"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}