Summary
When calling the execute_rest_write tool, the body argument does not appear to be sent as the HTTP request payload. Fields placed in body are missing server-side and the GitLab API responds 400 ... is missing. Passing the same fields via the query argument works.
Environment
- Tool:
execute_rest_write (the arbitrary REST write escape hatch)
- GitLab: self-managed EE, REST API v4
- Client: Claude Code via MCP
Repro
Creating an inline diff discussion on a merge request.
Fails — fields in body:
This also fails when body is passed as a JSON-encoded string instead of an object.
Works — identical fields moved to query (bracketed keys for the nested position):
The same pattern holds for PUT note edits (.../notes/:id and .../discussions/:discussion_id/notes/:id): the body argument is ignored, but query works.
Expected
The body argument (documented as "Request body — JSON-serialized as application/json") should be sent as the request body, so REST writes can use JSON payloads — including nested objects like position — rather than requiring everything to be flattened into query with bracket notation.
Notes
- The dedicated
create_note tool sends its body correctly, so JSON POST works in general; this looks specific to how execute_rest_write handles the body argument.
- The
query workaround is viable but awkward for nested/complex payloads.
Summary
When calling the
execute_rest_writetool, thebodyargument does not appear to be sent as the HTTP request payload. Fields placed inbodyare missing server-side and the GitLab API responds400 ... is missing. Passing the same fields via thequeryargument works.Environment
execute_rest_write(the arbitrary REST write escape hatch)Repro
Creating an inline diff discussion on a merge request.
Fails — fields in
body:execute_rest_write({ method: "POST", path: "/projects/<url-encoded-path>/merge_requests/18/discussions", body: { "body": "comment text", "position": { "base_sha": "df0e5a44...", "head_sha": "53a77fd6...", "start_sha": "df0e5a44...", "position_type": "text", "new_path": "psicore.py", "old_path": "psicore.py", "new_line": 1760 } } }) // -> 400: body is missingThis also fails when
bodyis passed as a JSON-encoded string instead of an object.Works — identical fields moved to
query(bracketed keys for the nestedposition):execute_rest_write({ method: "POST", path: "/projects/<url-encoded-path>/merge_requests/18/discussions", query: { "body": "comment text", "position[base_sha]": "df0e5a44...", "position[head_sha]": "53a77fd6...", "position[start_sha]": "df0e5a44...", "position[position_type]": "text", "position[new_path]": "psicore.py", "position[old_path]": "psicore.py", "position[new_line]": "1760" } }) // -> 201 CreatedThe same pattern holds for
PUTnote edits (.../notes/:idand.../discussions/:discussion_id/notes/:id): thebodyargument is ignored, butqueryworks.Expected
The
bodyargument (documented as "Request body — JSON-serialized as application/json") should be sent as the request body, so REST writes can use JSON payloads — including nested objects likeposition— rather than requiring everything to be flattened intoquerywith bracket notation.Notes
create_notetool sends its body correctly, so JSON POST works in general; this looks specific to howexecute_rest_writehandles thebodyargument.queryworkaround is viable but awkward for nested/complex payloads.