Derive MCP pre-approved tools from readOnlyHint annotations

What does this MR do and why?

McpConfigService kept a hand-maintained GITLAB_PREAPPROVED_TOOLS constant, so a read-only MCP tool was only pre-approved if someone remembered to add it there. It had drifted: get_merge_request_conflicts and get_work_item_types are both readOnlyHint: true and both still prompted for approval.

This derives the list from each tool's own readOnlyHint annotation instead, and takes alias names from Mcp::Tools::Manager#alias_map rather than a second hardcoded constant. GITLAB_TOOL_ALIASES had drifted too — it listed gitlab_search but not gitlab_merge_request_search. The tool_access_policies claim now derives from the same list, minus alias-only names.

Why the claim had to be reworked, and how aliases are handled

Master built GITLAB_CLAIM_PREAPPROVED_TOOLS on top of GITLAB_PREAPPROVED_TOOLS, which this MR deletes. Git auto-merges that combination with no conflict marker into a class that raises NameError at load, so the claim had to be re-expressed rather than simply dropped. If it were dropped, no GitLab name would reach tool_access_policies and every tool would prompt again — the regression from #607151.

Aliases are deliberately handled differently on each side:

  • PreApprovedTools includes aliases. Workhorse matches on the name the client actually calls, and a client may call either name.
  • The claim excludes alias-only names. The MCP server serves canonical names only, so a gitlab_-prefixed alias would match nothing. gitlab_search still appears in the claim, but from prefixing the canonical search tool — not from the alias.

Deriving alias names from alias_map rather than a constant is what keeps those two consistent. With the stale constant, gitlab_merge_request_search would have leaked into the claim as gitlab_gitlab_merge_request_search.

The tool manager is memoized per request, not per process. Its tool list comes from sweeping API::API.routes, so a process-wide cache risks pinning a list built before Grape finished compiling — which would silently drop read-only tools back into prompting for the life of the process. That is observable in specs: without ::API::API.reset_routes!, semantic_code_search falls out of the derived list.

References

  • Closes #596687 (closed)
  • Related to #606029 — stored agent definitions still lose a tool referenced by an alias. That is the & all_mcp_tools intersection, which this MR does not change
  • Builds on !247872 (merged), which removed the duplicate list on the claim side but left GITLAB_PREAPPROVED_TOOLS hand-maintained

Screenshots or screen recordings

No UI changes.

How to set up and validate locally

  1. In the Rails console, enable the feature flag and force a fresh EE-inclusive route compile. Without the reset, EE-only tools such as semantic_code_search are not yet registered and drop out of the derived list.

    Feature.enable(:mcp_client)
    ::API::API.reset_routes!
    
    service = Ai::DuoWorkflows::McpConfigService.new(
      User.first, 'fake-token', workflow_definition: 'chat'
    )
  2. Confirm nothing that used to be pre-approved was lost. This should return []:

    previously_hardcoded = %w[
      gitlab_search search semantic_code_search
      get_merge_request get_merge_request_commits
      get_merge_request_diffs get_merge_request_pipelines
      get_pipeline_jobs get_job_log
      get_issue get_workitem_notes get_merge_request_notes
      get_saved_view_work_items search_labels
      list_wiki_pages get_mcp_server_version
    ]
    
    previously_hardcoded - service.gitlab_enabled_tools
  3. Confirm the tools the old constant was missing are now pre-approved. Both should be true:

    service.gitlab_enabled_tools.include?('get_merge_request_conflicts')
    service.gitlab_enabled_tools.include?('get_work_item_types')
  4. Confirm the claim still receives gitlab_-prefixed names, and that no alias was double-prefixed. The first should be non-empty, the second []:

    service.preapproved_tool_names
    service.preapproved_tool_names.grep(/gitlab_gitlab/)
  5. Confirm write tools stay out of pre-approval. Should be false:

    service.gitlab_enabled_tools.include?('create_issue')

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