Draft: Expose Duo Agent Platform flows and agents through the MCP server

Prototype

What does this MR do and why?

Adds five MCP server tools that expose the Duo Agent Platform to external MCP clients (Claude Code, Cursor, etc.), covering discovery plus both execution models. Prototype for Make DAP Agents available via MCP for external tools.

Tool What it does Cycle
list_duo_agents Discovers foundational agents, custom agents, foundational flows, and custom flows available to the caller in a project — no hardcoded names/IDs needed instant, read-only
ask_duo_agent Sends a single prompt to a custom AI Catalog agent or a foundational chat agent (duo_planner, analytics_agent, ...) and returns the answer synchronously seconds, sync
approve_duo_agent_action Approves/rejects a session paused at a tool-call approval gate and returns the agent's continuation seconds, sync
trigger_duo_flow Starts any foundational flow (developer/v1 default, e.g. security_review/v1, code_review/v1) or a custom AI Catalog flow (flow_item_id) as a CI workload minutes, async
get_duo_session_status Polls a session; returns progress, the final answer, or approval guidance —

Former names trigger_duo_developer, ask_custom_agent, and get_duo_workflow_status keep working through the MCP manager's tool alias mechanism.

The interesting part is Ai::DuoWorkflows::ExecuteAgentTurnService: it opens the Duo Workflow Service ExecuteWorkflow gRPC stream directly from Rails — the same stream the browser uses via Workhorse for agentic chat — so agent turns run without a CI workload. While the turn runs, it proxies DWS runHTTPRequest actions back to the local API with Workhorse-signed requests (mirroring workhorse/internal/ai_assist/duoworkflow/actions.go), so checkpoints persist and paused approval gates can be resumed.

With this, an external agent can drive the full loop over MCP: discover what's available → trigger a flow → poll → get the result, or ask an agent → hit an approval gate → approve → receive the performed action — keeping the human (or calling agent) in the loop without ever opening the GitLab UI.

Out of scope / known gaps (prototype)

  • No specs yet, no feature flag
  • Rails holds the gRPC stream for the duration of a sync turn (90s hard timeout) — needs a thread-budget discussion before production
  • The Workhorse-JWT signing from Rails deserves a security review
  • Push-style delivery (MCP SSE transport) intentionally deferred; polling covers the caller-is-an-agent use case

References

Screenshots or screen recordings

N/A — CLI/API only. Example session via Claude Code:

list_duo_agents(project_id: "gitlab-duo/test")
→ foundational agents (duo_planner, ...), custom agents, flows — with IDs/references

ask_duo_agent(project_id: "gitlab-duo/test", foundational_agent: "duo_planner",
              prompt: "Create one issue titled 'Claude approval test'")
→ paused: "call approve_duo_agent_action with workflow_id=290"

approve_duo_agent_action(workflow_id: 290, decision: "approve")
→ "✅ Created issue #6 Claude approval test"

How to set up and validate locally

  1. Create a PAT with the mcp scope; ensure a root group has the MCP server enabled (Duo settings)

  2. List tools:

    curl -s -X POST http://gdk.test:3000/api/v4/mcp \
      -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'
  3. Discover available agents and flows:

    curl -s -X POST http://gdk.test:3000/api/v4/mcp \
      -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
      -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_duo_agents","arguments":{"project_id":"gitlab-duo/test"}}}' | jq '.result.structuredContent'
  4. Sync agent turn (seconds):

    curl -s -X POST http://gdk.test:3000/api/v4/mcp \
      -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
      -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"ask_duo_agent","arguments":{"project_id":"gitlab-duo/test","foundational_agent":"duo_planner","prompt":"Draft 3 issue titles for: add dark mode"}}}' | jq -r '.result.content[0].text'
  5. Flow (minutes; requires Docker + runner): trigger security_review/v1 with an MR URL as the goal via trigger_duo_flow, then poll get_duo_session_status until finished — the review is published on the MR

  6. Approval loop: ask the planner to create an issue → it pauses → approve_duo_agent_action with the returned workflow_id → issue is created

MR acceptance checklist intentionally skipped — prototype/challenge MR.

Edited by Jannik Lehmann

Merge request reports

Loading
Loading