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 newApiTool#available?takecurrent_userand returntrue.ListToolscallstool.available?(current_user)on every tool, so theis_a?(BaseService)guard and theset_credcall 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 theai_workflowslicense or GitLab credits,Ai::Catalog.duo_agent_platform_available_for_user?, andallowed_to_use?(:duo_agent_platform). Ai::Catalogdecides 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_chatinGlobalPolicy. - 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, sincetools/listruns it once per DAP tool.
- One check for both deployments: Duo is not "Always off" (
Ai::Catalog.duo_agent_platform_available_for_user?is now public.- Docs: per-deployment conditions in
mcp_server.md, a pointer frommcp_server_tools.md, and the newavailable?(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
-
Create a personal access token with the
mcpscope for each user. -
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)
- Make
roota member of a top-level group on a Premium or Ultimate plan with DAP turned on. - 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 returns403 MCP server not enabled for any of your groupsbefore tools are filtered. - Run
list.shas 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)
-
Check that Duo is on, the instance has the
ai_workflowslicense or GitLab credits, and DAP is on for the organization. Runlist.sh: the DAP tools are listed. -
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.
-
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) -
Set Duo back to On by default (
duo_features_enabled: true, lock_duo_features_enabled: false) and turn DAP off for the organization, then runlist.sh: the DAP tools are hidden.Ai::Setting.for_organization(User.find_by_username('root').organization) .update!(feature_settings: { 'duo_agent_platform_enabled' => false }) -
Restore the setting with
feature_settings: {}, then runlist.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.