Implement get_user MCP Tool and service

Note

Before picking up this work: this issue adds an MCP tool. Please follow the Adding a new tool guidance first. That includes creating an MCP Tool Proposal, using verb_object naming (get_ / list_ / save_ / delete_), and the shared resource-identification input base classes.

Proposal

Add a new MCP tool get_user for user lookup (resolve a username or name to a user id, for use with assignee_ids/reviewer_ids in other tools).

Build it as Base::GraphqlTool + Base::GraphqlService, not REST. GraphQL already exposes both the search and the single-lookup paths:

  • Search → Query.users (Resolvers::UsersResolver): search (name/username/primary email), usernames: [String], plus active/humans/admins/group_id/sort. Returns a UserType connection.
  • Single lookup → Query.user(username:) or Query.user(id:) (Resolvers::UserResolver, exactly-one-of).
  • Current user → Query.currentUser (no args), folded into get_user via a me: true flag rather than a separate get_current_user tool. This keeps the tool surface smaller and follows the existing precedent in list_merge_requests, which folds "me" semantics into a scope param instead of a dedicated tool.

Arguments

  • username (optional) → Query.user(username:).
  • id (optional) → Query.user(id:).
  • me (optional, must be true) → Query.currentUser (no args). Omit username/id when set.
  • Exactly one of username, id, or me is required — model as oneOf.
  • Pagination is cursor-based (first/after), not page/per_page — idiomatic for the GraphQL MCP tools.

Input schema

{
  "type": "object",
  "properties": {
    "username": { "type": "string", "description": "Username to resolve to a user." },
    "id": { "type": "integer", "description": "Numeric user ID to resolve." },
    "me": { "type": "boolean", "const": true,
            "description": "Resolve the authenticated user. Omit username and id when set." }
  },
  "oneOf": [
    { "required": ["username"] },
    { "required": ["id"] },
    { "required": ["me"] }
  ],
  "additionalProperties": false
}

Implementation notes

  • Query selection branches on me: trueQuery.currentUser; otherwise Query.user.
  • process_result handles both failure modes: currentUser never 404s, user(...) can.
  • Lead the tool description with the self-lookup use case so the agent reliably picks me: true for "what's my user id" (the bootstrap step for assignee_ids/reviewer_ids).
  • Do not make current-user lookup implicit (identifier absent ⇒ self) — an agent that forgets username would silently get its own record. Keep me: true explicit.

Use case

Many MCP operations need user ids (e.g. assignee_ids/reviewer_ids). The existing search tool with scope: users is heavyweight; this is a focused lookup. Referenced in the planning roadmap (#562157) and governance issue (#567747).

Out of scope

    • search (optional) → Query.users(search:). The search tool can be used for searching, recommending not adding this for the initial implementation

Verification steps

Automated tool-selection check — adapt snippet 6028693 ("MCP tool get_repository_file tester") to get_user:

  • Retarget TOOL=mcp__gdk__get_user and set ALLOW="${TOOL},mcp__gdk__search" so the run measures get_user vs the heavyweight search (scope: users) — the real selection tension here (there is no local equivalent to compete with).
  • Drop the seeded local files and move Read/Glob/Grep/LS into --disallowedTools; a user lookup has no local fallback.
  • Preflight greps for get_user instead of get_repository_file.
  • The tally already captures each run's .input — classify on the argument shape too, so me: true vs username mistakes surface.

Selection prompts (want get_user, one input shape each):

Prompt Expected call
"What is my GitLab user id?" get_user { me: true }
"Who am I logged in as?" get_user { me: true }
"What's the user id for username root?" get_user { username: "root" }
"Which username has user id 1?" get_user { id: 1 }
"I want to assign an issue to root — what id do I use?" get_user { username: "root" }
"Find all users whose name contains 'Admin'." search (control: get_user should NOT win)

Functional permutation check (input contract) — call the tool directly over JSON-RPC tools/call against your GDK and confirm each shape:

  • Valid: { username }, { id }, { me: true } each resolve.
  • Invalid (must be rejected by the oneOf): none supplied, { username, id }, { me: true, username }.

Manual exercise via MCP Inspector (swap in your own GDK URL):

npx -y @modelcontextprotocol/inspector -- env NODE_TLS_REJECT_UNAUTHORIZED=0 mise x -- npx -y mcp-remote https://gdk.test:3443/api/v4/mcp --debug

Parent Epic: &20529 (closed)

Edited by 🤖 GitLab Bot 🤖