Treat zero-filled IDs in MCP tool arguments as omitted
What does this MR do and why?
GPT models in the Duo Agent Platform fill every optional MCP tool argument with a default, and some of those defaults
break our tools. id: 0 next to a username makes get_user fail, zero IDs point lookups at records that can't exist,
and empty old_str/new_str turn a plain add_commit create into a failed partial edit. This MR treats those values
as omitted, the way we already treat null and "", and adds the dap_openai_non_strict_tools flag for the AI
gateway change that stops GPT having to fill every argument in the first place
(gitlab-org/modelops/applied-ml/code-suggestions/ai-assist!7436 (merged)).
This is an alternative to !255139 (closed) by @terrichu, for #621520. That MR made optional fields nullable in our MCP tool schemas, which Gemini rejects. Here the fix moves to the AI gateway: it sends tools to OpenAI with strict mode off, so GPT no longer has to fill every argument (gitlab-org/modelops/applied-ml/code-suggestions/ai-assist!7436 (merged)). This MR keeps the parts of !255139 (closed) that work the same for every model.
What changes
BaseService#reject_omitted_argumentsnow also drops zero and negative integers on ID fields (*_id,*_iid, and a bareid/iid), and0on integer fields withminimum: 1, such asfirstandpage. A dropped value falls through to the normal "missing" validation.add_commitonly treats an action as a partial edit whenold_strornew_strhas content. Whitespace still counts, so"\n"is still a partial edit. Anupdatewith neither content nor a partial edit is refused, so it can't empty the file, and an emptycontentno longer blocks a partial edit.dap_openai_non_strict_tools(gitlab_com_derisk, off by default) is pushed to the AI gateway with the other Duo Workflow flags. On its own it does nothing; ai-assist!7436 reads it.- Specs that pinned the old rejection of
first: 0andpage: 0, and the zerowork_item_iidcreate, now expect the value to be stripped.
save_merge_request keeps milestone_id: 0, because the REST API removes the milestone for 0. Tools can name the ID
arguments where 0 is a value (arguments_where_zero_is_a_value); milestone_id is the only one today. With the
gateway change, GPT can leave milestone_id out instead of sending 0. The tool description now says that 0 removes
the milestone on update.
References
Related to #621520. Alternative to !255139 (closed). The gateway half is gitlab-org/modelops/applied-ml/code-suggestions/ai-assist!7436 (merged) (gitlab-org/modelops/applied-ml/code-suggestions/ai-assist#2766 (closed)).
Screenshots or screen recordings
No UI changes. The one user-visible effect, for get_user called the way GPT calls it:
| Before | After |
|---|---|
Validation error: Provide exactly one of username, id, or me |
The user is returned |
How to set up and validate locally
These steps send GitLab's MCP server the arguments a GPT model sends, so you can see each failure on master and the
fix on this branch without an LLM. Run steps 3 to 6 once on master and once on this branch, with
gdk restart rails-web after switching.
-
In a Rails console (
gdk rails c), create an MCP token forroot. MCP needs an OAuth token with themcpscope; personal access tokens can't have it.user = User.find_by_username('root') organization = Organizations::Organization.first app = Authn::OauthApplication.create!(name: 'MCP repro', redirect_uri: 'http://localhost', scopes: 'mcp', owner: user, organization: organization) puts OauthAccessToken.create!(application: app, resource_owner: user, scopes: 'mcp', organization: organization, expires_in: 2.hours).plaintext_token -
In a shell, store the token and add a helper that prints whether the tool call failed and the start of what it returned:
export TOKEN=<token from step 1> mcp() { curl -s http://gdk.test:3000/api/v4/mcp -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":$1}" | jq -c '.result | {isError, text: .content[0].text[0:120]}' } -
Look up a user by username, with the
id: 0GPT sends next to it:mcp '{"name":"get_user","arguments":{"username":"root","id":0,"me":false}}'master:Validation error: Provide exactly one of username, id, or me- This branch: the
rootuser
-
List tags with
first: 0:mcp '{"name":"list_tags","arguments":{"project_id":"flightjs/Flight","first":0}}'master:Validation error: first is invalid- This branch: the tags
-
Create a file with the empty
old_strandnew_strGPT adds to every action:mcp '{"name":"add_commit","arguments":{"project_id":"flightjs/Flight","branch":"mcp-zero-fill-repro","start_branch":"master","commit_message":"MCP zero-fill repro","actions":[{"action":"create","file_path":"mcp-zero-fill-repro.txt","content":"hello","old_str":"","new_str":""}]}}'master:Partial edits are only supported for update actions- This branch: the new commit, on a new
mcp-zero-fill-reprobranch. Delete the branch afterwards.
-
Check that
milestone_id: 0still removes a milestone, which this MR keeps forsave_merge_request. Pick a merge request that has a milestone (or give one a milestone first) and use its project and IID:mcp '{"name":"save_merge_request","arguments":{"project_id":"toolbox/gitlab-smoke-tests","merge_request_iid":1,"milestone_id":0}}'- Both:
Merge request updated successfully via save_merge_request., and the merge request no longer has a milestone.
- Both:
-
Optional, with a GPT model: if your GDK's AI gateway has an OpenAI key, open Agentic Chat in
flightjs/Flight, pick a GPT model, and ask:Use the get_user tool to look up the user root.Use the add_commit tool to create a file called gpt-test.txt with content "hello" on a new branch gpt-test from master, with commit message "GPT test commit".
On
master, Terri's GPT run hit the error from step 5 with theadd_commitprompt. On this branch both calls should succeed;log/api_json.logshows the arguments GPT sent. I haven't run this step myself, because my GDK has no OpenAI key.
Automated checks
spec/services/mcp,ee/spec/services/mcp,ee/spec/services/ee/mcp,spec/requests/api/mcp,ee/spec/requests/api/mcp,ee/spec/lib/api/helpers/duo_workflow_helpers_spec.rbandspec/lib/feature/definition_spec.rbpass.
To try the gateway flag once ai-assist!7436 is deployed:
Feature.enable(:dap_openai_non_strict_tools, User.find_by_username('your-username'))MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.