Add update action to rename pipelines in save_pipeline MCP tool
What does this MR do and why?
This MR adds an update action to the save_pipeline MCP server tool, allowing a pipeline to be renamed. The action accepts pipeline_id plus a new name parameter, which is required when action: "update" is used. The behavior matches the REST endpoint PUT /projects/:id/pipelines/:pipeline_id/metadata.
This closes #619469 (closed), a GA must-have for the tool.
Documentation is updated in doc/user/model_context_protocol/mcp_server_tools.md: the save_pipeline section now lists update in the action enum, documents the new name parameter, includes a rename example, and adds a history bullet. The existing cross-reference to manage_pipeline is narrowed to cover only pipeline deletion, since renaming is now handled directly by save_pipeline.
Design notes
- No GraphQL mutation exists for pipeline name/metadata (
app/graphql/mutations/ci/pipeline/only has cancel, create, destroy, and retry). Because of this,SavePipelineService#perform_v0_1_0branches in Ruby foraction: "update"and callsCi::Pipelines::UpdateMetadataServicedirectly — the same service used by the REST metadata endpoint — before the arguments reachSavePipelineTool, whose GraphQL operation map has noupdateentry. This mirrors the existing precedent insave_merge_request_review, which dispatches itspost_duo_reviewaction the same way. - Error handling follows the uniform not-found convention introduced in !249875 (merged). A missing pipeline, a pipeline the caller cannot read, and a pipeline the caller can read but not rename (the service's internal
:update_pipelinepermission check returns reason:forbiddenwith a nil payload) all resolve to the same message:Pipeline not found or inaccessible.This avoids leaking whether a resource exists when the caller lacks access. - A missing
pipeline_idornameraises anArgumentErrorwith a self-correcting message ("Provide pipeline_id to rename a pipeline" / "Provide name to rename the pipeline"), consistent with the tool's existing validation style. - The aggregated
manage_pipelinetool also exposes rename via the REST route, so there is some overlap.save_pipelineis the GA-canonical tool per the tool consolidation conventions, which is why theupdateaction was added here, with the docs cross-reference narrowed accordingly.
How to validate locally
- Check out the branch and restart GDK rails (
gdk restart rails-web). - Create a personal access token with
apiandmcpscopes. - Call
tools/listvia a JSON-RPC POST to/api/v4/mcpand confirm thesave_pipelinetool'sactionenum is["retry", "cancel", "update"]and that anameproperty is present. - Call
tools/callwith{name: "save_pipeline", arguments: {project_id: "<id>", pipeline_id: <id>, action: "update", name: "New name"}}and confirm the pipeline is renamed (verify in the database or UI). Note thatpipeline_idis an integer in the schema, matching the pre-existing convention. - Confirm that omitting
nameorpipeline_idreturns the self-correcting validation errors, and that an unknownpipeline_idreturnsPipeline not found or inaccessible.
The following is the transcript of an end-to-end run against GDK exercising the steps above:
1a. action enum: ["retry", "cancel", "update"]
1b. name prop: {"type"=>"string", "description"=>"New pipeline name. Required for action: \"update\"."}
target pipeline: id=5316 current name=nil
2. rename: isError=false {"action"=>"update", "id"=>5316, "name"=>"E2E renamed 619469", "status"=>"failed", "ref"=>"duo/fix/24-config-loader-crash-on-malformed-json", "web_url"=>"https://gdk.test:3443/gitlab-duo/mobile-consumer/-/pipelines/5316"}
db name now: "E2E renamed 619469"
3. rename again: isError=false {"action"=>"update", "id"=>5316, "name"=>"E2E renamed twice", ...}
db name now: "E2E renamed twice"
4. missing name: isError=true "Validation error: Provide name to rename the pipeline"
5. no pipeline_id:isError=true "Validation error: Provide pipeline_id to rename a pipeline"
6. bogus id: isError=true "Pipeline not found or inaccessible."