Treat zero work_item_iid as create in save_work_item MCP tool

What does this MR do and why?

Fixes the save_work_item MCP tool's create/update dispatch for clients that zero-fill their request payloads. Some agent executors (Go-style serializers) send every schema field with its zero value, including work_item_iid: 0 on create requests. Rails presence treats the integer 0 as present, so the tool routed these create requests down the update path, which then failed opaquely against a work item that cannot exist (iids start at 1). In the reported incident, the agent fell back to the legacy create work item / update work item tools instead.

Closes #621661 (closed) (GA must-have, type::bug).

The dispatch check now treats work_item_iid as an update signal only when it is a positive integer. 0 is unambiguous here: iids start at 1, so a zero value can only mean the client left the field unset.

The tool description and the work_item_iid parameter description now warn agents to send only the fields they intend to set, and to never send placeholder zero values (0, empty strings, empty arrays). doc/user/model_context_protocol/mcp_server_tools.md is updated accordingly.

Design notes

  1. The framework already treats nil and empty-string arguments as omitted: Mcp::Tools::Base::BaseService#reject_omitted_arguments strips them before dispatch. This change extends the same "omitted value" policy to the one remaining unambiguous zero value, integer 0 for work_item_iid.
  2. Deliberately not scrubbing empty arrays or booleans: on the update path, assignee_ids: [] legitimately means "clear all assignees," and false is a real boolean value, not a placeholder. Presence-based semantics have to be preserved for those types even though they're relaxed for work_item_iid.
  3. A fully zero-filled request that gets routed to the create path can still fail if it carries update-only fields with non-zero values (for example state: "opened"). It now fails with the create path's self-correcting message ("state can only be used when updating (provide work_item_iid)") instead of an opaque not-found from the update path, so agents can drop the offending field and retry successfully.

How to validate locally

  1. Check out the branch and restart GDK rails (gdk restart rails-web).
  2. Create a personal access token with api and mcp scopes.
  3. Call tools/call on save_work_item with work_item_iid: 0 plus clean create fields (project_id, title, type_name) — a work item is created.
  4. Replay the incident-shaped zero-filled payload — the call fails on the create path with the self-correcting message about update-only fields, instead of the update path's opaque failure.
  5. Call with a positive work_item_iid — the work item is updated (regression check).

The following is the transcript of the end-to-end run against GDK used to validate this change:

1a. description has zero-value warning: true
1b. iid description: "Positive internal ID of the work item to update. Omit to create a new work item."
2. incident shape (expect create-path self-correcting error): isError=true "Validation error: state can only be used when updating (provide work_item_iid)"
3. zero iid clean create: isError=false {"id"=>"gid://gitlab/WorkItem/806", "iid"=>"27", "type"=>"Issue", "title"=>"E2E 621661 clean create", "state"=>"OPEN", ...}
   db: created iid=27 title="E2E 621661 clean create"
4. positive iid update: isError=false {"id"=>"gid://gitlab/WorkItem/806", "iid"=>"27", "type"=>"Issue", "title"=>"E2E 621661 updated", ...}
   db title now: "E2E 621661 updated"

Merge request reports

Loading
Loading