List Duo Agent Platform MCP tools only when DAP is available

What does this MR do and why?

Phase 2 of #631747 (closed). Phase 1 made duo_agent_platform a default toolset, so header-less clients see the five DAP tools even when the user can't use Duo Agent Platform. This MR lists them in tools/list only when DAP is available for the user.

It also passes the user into available?(current_user) and stops calling set_cred on shared tool instances. Those instances are shared by all requests, so the old approach could check one user's availability as another user under concurrent requests.

Changes
  • BaseService#available? and the new ApiTool#available? take current_user and return true. ListTools calls tool.available?(current_user) on every tool, so the is_a?(BaseService) guard and the set_cred call are gone.
  • New Mcp::Tools::DuoWorkflows::Availability, included by the five DAP tool services:
    • One check for both deployments: Duo is not "Always off" (duo_never_on?), the instance has the ai_workflows license or GitLab credits, Ai::Catalog.duo_agent_platform_available_for_user?, and allowed_to_use?(:duo_agent_platform).
    • Ai::Catalog decides whether this is GitLab.com. There, it checks for a top-level group on a supported plan with DAP on, or GitLab credits. The two instance conditions always pass on GitLab.com: its instance Duo setting is always on, and its instance license is mapped to Ultimate. On Self-Managed it reads the organization's DAP setting.
    • "Off by default" keeps the tools listed, because groups can still turn Duo on and run DAP. Only "Always off" hides them, matching access_duo_agentic_chat in GlobalPolicy.
    • The bare allowed_to_use? call follows other container-less Duo checks, such as the agentic chat entry point and the Slack app's seat check.
    • Memoized per user per request with Gitlab::SafeRequestStore, since tools/list runs it once per DAP tool.
  • Ai::Catalog.duo_agent_platform_available_for_user? is now public.
  • Docs: per-deployment conditions in mcp_server.md, a pointer from mcp_server_tools.md, and the new available?(current_user) signature in the development docs.

Listing is not authorization. Each tool still checks project and namespace permissions when called. tools/call is unchanged.

References

  • #631747 (closed)
  • Phase 1: 2c63ec430dd1 (Include duo_agent_platform MCP toolset by default)

Screenshots or screen recordings

No UI changes.

How to set up and validate locally

Tested against the real POST /api/v4/mcp endpoint in a GDK, with two users: one with DAP available (root) and one test user. GitLab.com and Self-Managed take different code paths, so test both. GITLAB_SIMULATE_SAAS in <gdk>/env.runit switches between them; restart rails-web after changing it.

Setup

  1. Create a personal access token with the mcp scope for each user.

  2. Save this as list.sh. It prints the tool count and the DAP tools for each header value:

    #!/bin/bash
    for h in "" duo_agent_platform all; do
      H=(); [ -n "$h" ] && H=(-H "X-Gitlab-Enabled-Mcp-Server-Toolsets: $h")
      curl -sk https://gdk.test:3443/api/v4/mcp \
        -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" "${H[@]}" \
        -d '{"jsonrpc":"2.0","method":"tools/list","id":"1"}' |
        jq -r --arg h "${h:-<none>}" '"\($h): total=\(.result.tools | length) dap=\([.result.tools[].name | select(test("duo"))])"'
    done

When DAP is available, each header lists all five DAP tools: start_duo_session, list_duo_sessions, get_duo_session, send_duo_session_input, list_duo_agents_and_flows. When it isn't, the counts drop by exactly 5 and dap=[]. The one tool left under the duo_agent_platform header is the always-on meta tool.

GitLab.com (GITLAB_SIMULATE_SAAS=1)

  1. Make root a member of a top-level group on a Premium or Ultimate plan with DAP turned on.
  2. Give the test user only a free-plan top-level group, with the MCP server turned on (namespace_settings.mcp_server_enabled). Without a group like that, every MCP request returns 403 MCP server not enabled for any of your groups before tools are filtered.
  3. Run list.sh as each user. If you just changed group membership, clear the user's cached root group IDs first (Rails.cache.delete(['users', user.id, Ai::UserAuthorizable::ROOT_GROUP_IDS_CACHE_KEY])).

Self-Managed (GITLAB_SIMULATE_SAAS=0)

  1. Check that Duo is on, the instance has the ai_workflows license or GitLab credits, and DAP is on for the organization. Run list.sh: the DAP tools are listed.

  2. Set Duo to Off by default for the instance, then run list.sh: the DAP tools are still listed, because groups can turn Duo on.

    ApplicationSetting.current.update!(duo_features_enabled: false, lock_duo_features_enabled: false)

    Application settings are cached for about 60 seconds per process, so the first requests after a change can show the old result. Re-run if they do.

  3. Set Duo to Always off, then run list.sh: the DAP tools are hidden.

    ApplicationSetting.current.update!(duo_features_enabled: false, lock_duo_features_enabled: true)
  4. Set Duo back to On by default (duo_features_enabled: true, lock_duo_features_enabled: false) and turn DAP off for the organization, then run list.sh: the DAP tools are hidden.

    Ai::Setting.for_organization(User.find_by_username('root').organization)
      .update!(feature_settings: { 'duo_agent_platform_enabled' => false })
  5. Restore the setting with feature_settings: {}, then run list.sh: the DAP tools are listed again.

Observed results

Observed at 5dbe1a65f0ba, before the review changes. Counts are no header / duo_agent_platform / all. The "Duo off" row used duo_features_enabled: false without the lock, which is "Off by default". Since a67f2b20c47e that setting lists the tools, and only "Always off" hides them. That change, and the GitLab.com seat check from 15f37b34287e, are covered by the unit specs with real records.

Deployment Setup root Test user
GitLab.com root in a DAP group, test user in a free group only 42 / 6 / 47, DAP listed 37 / 1 / 42, DAP hidden
Self-Managed Duo on, org DAP on 42 / 6 / 47, DAP listed 42 / 6 / 47, DAP listed
Self-Managed Duo off 37 / 1 / 42, DAP hidden 37 / 1 / 42, DAP hidden
Self-Managed Duo on, org DAP off 37 / 1 / 42, DAP hidden 37 / 1 / 42, DAP hidden
Self-Managed Duo on, org DAP restored 42 / 6 / 47, DAP listed 42 / 6 / 47, DAP listed

Not tested by hand, because the GDK has both a license and credits and seats are hard to toggle: no license or credits, credits without the ai_workflows license, and a user without a Duo seat (on GitLab.com or Self-Managed). The unit specs cover these with real records.

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.

Edited by Terri Chu

Merge request reports

Loading
Loading