Fix MCP tools enumeration leak via non-uniform not-found errors

What does this MR do and why?

MCP tools leaked whether a private project or group exists. The shared ResourceFinder looked resources up without current_user scoping, and callers raised an "Access denied" message that differed from the "not found" one, so a caller could tell "exists but forbidden" from "does not exist" by comparing error strings. This folds authorization into the finders behind an ability: keyword so a missing record and a forbidden one raise the same "not found or inaccessible" error, applies the same treatment to merge request and work-item resolution, and standardizes every MCP tool's not-found message to that wording.

Details
  • ResourceFinder: find_project! / find_group! take an ability: keyword (default :read_project / :read_group); after the DB lookup, Ability.allowed? is checked, and if the record is missing or the check fails, the same StandardError "… not found or inaccessible" is raised. find_work_item_in_parent! is made uniform the same way. find_parent_by_id_or_path! delegates straight to the finders, and the now-redundant authorize_parent_access! / can_read_parent? helpers are removed.
  • Merge request tools (MergeRequestResolution): use the non-authorizing find_project and let MergeRequestsFinder scope by current_user, so a missing project, an inaccessible project, and a missing or inaccessible merge request all raise the same error.
  • Analytics tracking scope (EE CallTool): resolves its project/group with plain, non-authorizing lookups — the scope only feeds internal telemetry, never a response, so there is nothing to gate — and is memoized so a tool call's start and finish events share a single lookup.
  • Wording: all MCP tools (labels, wikis, jobs, pipelines, merge requests, commits) now use "<resource> not found or inaccessible" for one consistent, non-leaking message.

References

Screenshots or screen recordings

No UI changes - backend only.

How to set up and validate locally

  1. In a Rails console, create a private project and a user who is not a member.
  2. As that user, call a project-scoped MCP tool (or find_project!(private_project.id.to_s)).
  3. Observe the error is "Project '<id>' not found or inaccessible" - identical to the error for a genuinely non-existent project ID, so the two cases cannot be told apart.

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