List security MCP tools only when they are licensed

What does this MR do and why?

Some MCP security tools were listed for everyone, even when the user can't use the feature they wrap. This MR hides them in tools/list when the user doesn't have the license.

The four tools affected:

  • list_vulnerabilities, get_vulnerability, and save_vulnerability need the security dashboard (security_dashboard).
  • attach_scan_profile needs security scan profiles (security_scan_profiles).

Both are Ultimate features.

When the tools are listed:

  • GitLab Self-Managed and GitLab Dedicated: when the instance license includes the feature.
  • GitLab.com: when the user belongs to at least one top-level group on a plan that includes the feature (Ultimate, including trials). Access to a project in that group also counts.

The rule applies even when the client asks for the code_security toolset in the X-Gitlab-Enabled-Mcp-Server-Toolsets header or uses all. The toolset filter runs first, so the check only runs when code_security is requested.

This MR changes listing only. Calling a tool directly works as before, and each tool still checks permissions. A check at call time is a follow-up on the issue.

Known edge: on GitLab.com, a user whose only access is a public open-source project with a legacy open-source license grant won't see the tools listed. They can still call them. Hiding is the safer side.

No feature flag, because this only narrows what is listed. No database changes. EE only.

Implementation notes:

  • New Mcp::Tools::Security::Availability module, modeled on Mcp::Tools::DuoWorkflows::Availability. The four services include it, and attach_scan_profile overrides the feature name.
  • The result is cached per user and feature for the request, not on the shared tool object.
  • The test instance license has no Ultimate features, so the annotation test in the list_tools request spec now stubs them. A new context checks the listed and hidden cases.

References

Screenshots or screen recordings

Not applicable. There is no UI change.

How to set up and validate locally

Before you start:

  • Run GDK with the MCP server enabled and an Ultimate license.
  • Have an oauth token with the mcp scope in TOKEN.

Use this command to list the tools:

curl -s -X POST "https://gdk.test:3443/api/v4/mcp" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "X-Gitlab-Enabled-Mcp-Server-Toolsets: code_security" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":"1"}' | jq '.result.tools[].name'

You can also use an MCP client or MCP Inspector instead of curl.

  1. Run the command. Check that list_vulnerabilities, get_vulnerability, save_vulnerability, and attach_scan_profile are listed.

  2. To check GitLab.com behavior, turn on SaaS simulation: add export GITLAB_SIMULATE_SAAS=1 to env.runit, then run gdk restart.

  3. In the Rails console, pick a top-level group your user belongs to. Make sure the user is in no other Ultimate group. Put the group on Ultimate:

    group = Group.find_by_full_path('your-group')
    GitlabSubscription.find_or_initialize_by(namespace: group).update!(hosted_plan: Plan.find_by(name: 'ultimate'))
  4. Run the command again. The four security tools are listed.

  5. Put the group on Premium:

    group.gitlab_subscription.update!(hosted_plan: Plan.find_by(name: 'premium'))
  6. Run the command again. The four security tools are gone. No restart is needed between plan changes.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in performance, reliability, security, and maintainability.

🤖 Generated with Claude Code

Edited by Jessie Young

Merge request reports

Loading
Loading