start_duo_session MCP tool

Note

Before picking up this work: this issue adds an MCP tool. Please follow the Adding a new tool guidance first. That includes creating an MCP Tool Proposal, using verb_object naming (get_ / list_ / save_ / delete_), and the shared resource-identification input base classes.

Note

Shipped as route-backed, not GraphQL. !254979 (merged) implements this via route_setting :mcp on POST /ai/duo_workflows/workflows, wrapped in Mcp::Tools::DuoWorkflows::StartDuoSessionService < Base::AggregatedService — the same pattern as save_merge_request and semantic_search. The GraphQL-mutation plan below (aiDuoWorkflowCreate, TriggerDuoFlowTool < GraphqlTool, EE_GRAPHQL_TOOLS) was superseded early in development and never built; kept below for history. See the route-backed-vs-custom decision in #627559 (closed).

Problem Statement / Use Case

Agents in Duo Agentic Chat need to delegate long-running, multi-step tasks to specialist GitLab flows — such as SAST false positive detection, vulnerability resolution, or secret detection analysis — without blocking the chat session. The existing start_flow catalog tool (id: 93) already exposes this capability inside the Duo Agentic Chat agent, but it is not yet available as an MCP tool that external MCP clients (Claude Code, Cursor, etc.) can call.

start_flow maps to start_duo_session in the MCP naming convention: it starts an asynchronous CI-based Duo flow session and returns a workflow_id immediately. The caller then polls get_session (see companion issue) with that workflow_id to track progress and retrieve the final result.

Naming: the tool starts a single flow session, so it belongs to the add_/action verb class (like add_commit) rather than save_ (CRUD) or get_/list_ (reads).

Scope and Non-Goals

  • In scope: start an asynchronous Duo flow (foundational or custom AI Catalog) in a project; return a workflow_id and a poll_after_seconds hint; register trigger_duo_developer as a backward-compat alias.
  • Non-goals: synchronous agent turns (ask_duo_agent), polling session status (get_session / get_duo_session_status), approving tool calls (approve_duo_agent_action), listing available agents/flows (list_duo_agents).
  • Follow-ups: none — the companion get_session and list_sessions issues cover the read side.

Data Shape and Context Engineering

The tool returns immediately after creating the session; no polling happens inside the tool itself. The poll_after_seconds hint tells the caller how long to wait before calling get_session. Output is bounded to a single workflow reference. Look into MCP task spec for how to tell agent to poll again: https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks

  • Input schema (example):
{
  "tool": "start_duo_flow",
  "description": "Start an asynchronous GitLab Duo agentic flow (CI-based session) to accomplish a goal in a project.",
  "parameters": {
    "url": "string (optional; full GitLab URL encoding project path, e.g. https://gitlab.com/group/project)",
    "project_id": "int | string (optional; numeric ID or URL-encoded path such as 'gitlab-org%2Fgitlab')",
    "goal": "string (required; natural-language goal, e.g. 'Fix the flaky spec in issue #1234 and open an MR')",
    "flow": "string (optional; foundational flow reference, default: 'developer/v1'. Examples: 'code_review/v1', 'fix_pipeline/v1', 'security_review/v1')",
    "flow_item_id": "integer (optional; AI Catalog item ID of a custom flow enabled in the project; mutually exclusive with flow)"
  }
}
  • Output schema (JSON example):
{
  "workflow_id": 42,
  "flow": "developer/v1",
  "status": "created",
  "web_url": "https://gitlab.com/group/project/-/duo_workflows/42",
  "poll_after_seconds": 20
}

Backward Compatibility

The prototype MR (!246597 (closed)) already uses trigger_duo_flow as the canonical name and registers trigger_duo_developer as a tool_aliases entry. Preserve this alias so cached MCP clients continue to work. Follow the tool-renaming guidance in doc/development/duo_agent_platform/mcp/_index.md.

Resources

  • Existing GraphQL mutation: AiDuoWorkflowCreate (ee/app/graphql/mutations/ai/duo_workflows/create.rb) — accepts projectId/namespaceId, goal, workflowDefinition (this issue's flow), and aiCatalogItemVersionId (see plan step 4 for the flow_item_id mapping); authorizes via read_project/read_group on the resolved container plus authorize_foundational_flows!; returns a WorkflowType.
  • Version resolution helper: Ai::Catalog::Item#latest_released_version_with_fallback (ee/app/models/ai/catalog/item.rb).
  • Mutation-tool precedent: create_merge_request_note (app/services/mcp/tools/merge_requests/create_merge_request_note_{tool,service}.rb).
  • Catalog tool definition: ee/lib/ai/catalog/built_in_tool_definitions.rb (id: 93, name: start_flow)
  • Companion issues: get_session MCP tool (#607634 (closed)), list_sessions MCP tool (#607635 (closed))
  • MCP dev guidelines: doc/development/duo_agent_platform/mcp/_index.md; gitlab-mcp-tool-builder skill's build recipe — verify a GraphQL field exists before writing a custom one.

Implementation Plan (superseded — see note above)

  1. Two classes: Mcp::Tools::DuoWorkflows::TriggerDuoFlowTool < Mcp::Tools::Base::GraphqlTool and Mcp::Tools::DuoWorkflows::TriggerDuoFlowService < Base::GraphqlService.
  2. Operation file: ee/app/graphql/queries/mcp/duo_workflows/trigger_duo_flow.mutation.graphql, calling aiDuoWorkflowCreate(input:), selecting workflow { id workflowDefinition statusName project { webUrl } } and errors. operation_name: 'aiDuoWorkflowCreate'.
  3. url/project_id → projectId: resolve via find_parent_by_id_or_path!(:project, identifier) and pass project.to_global_id.to_s. The mutation already authorizes the container; no separate Ability.allowed? check needed.
  4. flow_item_id → aiCatalogItemVersionId: this issue's flow_item_id is an AI Catalog item id, but the mutation takes an item version GID. Resolve with Ai::Catalog::Item.find_by_id(flow_item_id)&.latest_released_version_with_fallback&.to_global_id&.to_s. Keep flow/flow_item_id mutually exclusive, validated client-side.
  5. web_url: WorkflowType has no web_url field (only resource_web_url, for a linked issue/MR, not the session). Build client-side in process_result from workflow.project.webUrl + /-/automate/agent-sessions/<numeric id>.
  6. workflow_id: unwrap the numeric id from the GraphQL id GID.
  7. poll_after_seconds: not part of the mutation response — keep as a fixed constant in the Tool/Service.
  8. process_result checks the mutation's in-band errors array and returns Response.error when non-empty.
  9. Annotations: readOnlyHint: false, destructiveHint: false (create, non-destructive).
  10. Register in EE_GRAPHQL_TOOLS (not EE_CUSTOM_TOOLS) in ee/app/services/ee/mcp/tools/manager.rb; register trigger_duo_developer as a tool_aliases entry.
  11. Specs: spec/graphql/all_queries_spec.rb coverage comes free from the committed .graphql file; add Tool/Service specs covering foundational flow, custom flow (via flow_item_id), and missing/inaccessible project. "Unknown flow" needs verification, not assumption: Ai::DuoWorkflows::Workflow has no validation rejecting an unrecognized workflow_definition string (only agent_privileges is validated) — confirm whether an unrecognized flow value should actually error at the tool level (validate client-side against Ai::FoundationalChatAgent/FoundationalFlow before calling the mutation) rather than assuming the mutation already rejects it.
  12. Set enum for source to MCP (enum added in https://gitlab.com/gitlab-org/gitlab/-/work_items/627560#note_3807213267)

Demo video from the R&D Summit: https://gitlab.com/groups/gitlab-org/-/work_items/22652#note_3589447033

Original Draft MR (superseded by the GraphQL-backed plan above): !246597 (closed)

Edited by Fred de Gier