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], plusactive/humans/admins/group_id/sort. Returns aUserTypeconnection. - Single lookup →
Query.user(username:)orQuery.user(id:)(Resolvers::UserResolver, exactly-one-of). - Current user →
Query.currentUser(no args), folded intoget_uservia ame: trueflag rather than a separateget_current_usertool. This keeps the tool surface smaller and follows the existing precedent inlist_merge_requests, which folds "me" semantics into ascopeparam instead of a dedicated tool.
Arguments
username(optional) →Query.user(username:).id(optional) →Query.user(id:).me(optional, must betrue) →Query.currentUser(no args). Omitusername/idwhen set.- Exactly one of
username,id, ormeis required — model asoneOf. - Pagination is cursor-based (
first/after), notpage/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:true→Query.currentUser; otherwiseQuery.user. process_resulthandles both failure modes:currentUsernever 404s,user(...)can.- Lead the tool
descriptionwith the self-lookup use case so the agent reliably picksme: truefor "what's my user id" (the bootstrap step forassignee_ids/reviewer_ids). - Do not make current-user lookup implicit (identifier absent ⇒ self) — an agent that forgets
usernamewould silently get its own record. Keepme: trueexplicit.
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_userand setALLOW="${TOOL},mcp__gdk__search"so the run measuresget_uservs the heavyweightsearch(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/LSinto--disallowedTools; a user lookup has no local fallback. - Preflight greps for
get_userinstead ofget_repository_file. - The tally already captures each run's
.input— classify on the argument shape too, some: truevsusernamemistakes 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 --debugParent Epic: &20529 (closed)