unslothai/unsloth · warning · HTTPException

No fields to update

Error message

No fields to update

What it means

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.

Source

Thrown at studio/backend/routes/mcp_servers.py:216

    # stdio is OAuth-less: drop a stale OAuth flag when switching to a command.
    if "url" in changes and is_stdio(changes["url"]):
        changes["use_oauth"] = False
    return changes


@router.put("/{server_id}", response_model = McpServerResponse)
async def update_mcp_server(
    server_id: str,
    payload: McpServerUpdate,
    current_subject: str = Depends(get_current_subject),
    via_api_key: ViaApiKey = False,
):
    old = mcp_servers_db.get_server(server_id)
    if not old:
        raise HTTPException(status_code = 404, detail = "MCP server not found")
    changes = _changes_from_payload(payload)
    if not changes:
        raise HTTPException(status_code = 400, detail = "No fields to update")
    # Both directions, so an API key can neither repoint an http row at a command
    # nor edit a stdio row's env/name/enabled flag. Before every side effect, so a
    # refusal leaves the row, its OAuth tokens, cache and sessions untouched.
    if is_stdio(old["url"]) or is_stdio(changes.get("url", old["url"])):
        require_ui_session_for_local_commands(via_api_key)
    # headers == HTTP headers (remote) or env vars (stdio). On a transport-type
    # switch with no new headers, drop the old ones so env secrets aren't
    # re-sent as HTTP headers (or vice versa).
    if (
        "url" in changes
        and is_stdio(changes["url"]) != is_stdio(old["url"])
        and "headers_json" not in changes
    ):
        changes["headers_json"] = None
    # Clear persisted OAuth tokens when the URL changes or OAuth is disabled;
    # fastmcp keys tokens by URL and would otherwise let a re-pointed server
    # silently inherit the old account's credentials.
    if bool(old.get("use_oauth")) and (

View on GitHub (pinned to 203007d190)

Solutions

  1. Include at least one valid field: display_name, url, headers, is_enabled, or use_oauth.
  2. Check for field-name typos — the API uses snake_case exactly as documented.
  3. If you only wanted to verify the row, use GET instead of an empty PUT.

Example fix

// before
await api.put(`/api/mcp-servers/${id}`, {}); // 400 'No fields to update'

// after
await api.put(`/api/mcp-servers/${id}`, {is_enabled: row.is_enabled}); // real field
// or just read: await api.get(`/api/mcp-servers/${id}`);
Defensive patterns

Strategy: validation

Validate before calling

const EDITABLE = new Set(['display_name','url','headers','is_enabled','use_oauth']);
const keys = Object.keys(body).filter(k => EDITABLE.has(k));
if (keys.length === 0) throw new Error('PUT needs at least one editable field');

Type guard

function hasEditableField(body) { return ['display_name','url','headers','is_enabled','use_oauth'].some(k => k in body); }

Prevention

When it happens

Trigger: 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.

Common situations: 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 {}.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/97d6da23a1872663. Report an issue: GitHub.