Skip to content

Commit 440921a

Browse files
author
Martim Pimentel
committed
feat: add user retrieval functions and schemas for GitLab API integration
1 parent 886faf5 commit 440921a

File tree

2 files changed

+105
-8
lines changed

2 files changed

+105
-8
lines changed

index.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ import {
3737
GitLabNamespaceExistsResponseSchema,
3838
GitLabProjectSchema,
3939
GitLabLabelSchema,
40+
GitLabUserSchema,
41+
GitLabUsersResponseSchema,
42+
GetUsersSchema,
4043
CreateRepositoryOptionsSchema,
4144
CreateIssueOptionsSchema,
4245
CreateMergeRequestOptionsSchema,
@@ -108,6 +111,8 @@ import {
108111
type GitLabNamespaceExistsResponse,
109112
type GitLabProject,
110113
type GitLabLabel,
114+
type GitLabUser,
115+
type GitLabUsersResponse,
111116
// Discussion Types
112117
type GitLabDiscussionNote, // Added
113118
type GitLabDiscussion,
@@ -418,6 +423,11 @@ const allTools = [
418423
"Get the repository tree for a GitLab project (list files and directories)",
419424
inputSchema: zodToJsonSchema(GetRepositoryTreeSchema),
420425
},
426+
{
427+
name: "get_users",
428+
description: "Get GitLab user details by usernames",
429+
inputSchema: zodToJsonSchema(GetUsersSchema),
430+
},
421431
];
422432

423433
// Define which tools are read-only
@@ -440,6 +450,7 @@ const readOnlyTools = [
440450
"list_labels",
441451
"get_label",
442452
"list_group_projects",
453+
"get_users",
443454
];
444455

445456
// Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI
@@ -2255,6 +2266,65 @@ async function getRepositoryTree(
22552266
return z.array(GitLabTreeItemSchema).parse(data);
22562267
}
22572268

2269+
/**
2270+
* Get a single user from GitLab
2271+
*
2272+
* @param {string} username - The username to look up
2273+
* @returns {Promise<GitLabUser | null>} The user data or null if not found
2274+
*/
2275+
async function getUser(username: string): Promise<GitLabUser | null> {
2276+
try {
2277+
const url = new URL(`${GITLAB_API_URL}/users`);
2278+
url.searchParams.append("username", username);
2279+
2280+
const response = await fetch(url.toString(), {
2281+
...DEFAULT_FETCH_CONFIG,
2282+
});
2283+
2284+
await handleGitLabError(response);
2285+
2286+
const users = await response.json();
2287+
2288+
// GitLab returns an array of users that match the username
2289+
if (Array.isArray(users) && users.length > 0) {
2290+
// Find exact match for username (case-sensitive)
2291+
const exactMatch = users.find(user => user.username === username);
2292+
if (exactMatch) {
2293+
return GitLabUserSchema.parse(exactMatch);
2294+
}
2295+
}
2296+
2297+
// No matching user found
2298+
return null;
2299+
} catch (error) {
2300+
console.error(`Error fetching user by username '${username}':`, error);
2301+
return null;
2302+
}
2303+
}
2304+
2305+
/**
2306+
* Get multiple users from GitLab
2307+
*
2308+
* @param {string[]} usernames - Array of usernames to look up
2309+
* @returns {Promise<GitLabUsersResponse>} Object with usernames as keys and user objects or null as values
2310+
*/
2311+
async function getUsers(usernames: string[]): Promise<GitLabUsersResponse> {
2312+
const users: Record<string, GitLabUser | null> = {};
2313+
2314+
// Process usernames sequentially to avoid rate limiting
2315+
for (const username of usernames) {
2316+
try {
2317+
const user = await getUser(username);
2318+
users[username] = user;
2319+
} catch (error) {
2320+
console.error(`Error processing username '${username}':`, error);
2321+
users[username] = null;
2322+
}
2323+
}
2324+
2325+
return GitLabUsersResponseSchema.parse(users);
2326+
}
2327+
22582328
server.setRequestHandler(ListToolsRequestSchema, async () => {
22592329
// Apply read-only filter first
22602330
const tools0 = GITLAB_READ_ONLY_MODE
@@ -2621,6 +2691,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
26212691
content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
26222692
};
26232693
}
2694+
2695+
case "get_users": {
2696+
const args = GetUsersSchema.parse(request.params.arguments);
2697+
const usersMap = await getUsers(args.usernames);
2698+
2699+
return {
2700+
content: [{ type: "text", text: JSON.stringify(usersMap, null, 2) }],
2701+
};
2702+
}
26242703

26252704
case "create_note": {
26262705
const args = CreateNoteSchema.parse(request.params.arguments);

schemas.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,30 @@ export const GitLabAuthorSchema = z.object({
77
date: z.string(),
88
});
99

10+
// User schemas
11+
export const GitLabUserSchema = z.object({
12+
username: z.string(), // Changed from login to match GitLab API
13+
id: z.number(),
14+
name: z.string(),
15+
avatar_url: z.string(),
16+
web_url: z.string(), // Changed from html_url to match GitLab API
17+
});
18+
19+
export const GetUsersSchema = z.object({
20+
usernames: z.array(z.string()).describe("Array of usernames to search for"),
21+
});
22+
23+
export const GitLabUsersResponseSchema = z.record(
24+
z.string(),
25+
z.object({
26+
id: z.number(),
27+
username: z.string(),
28+
name: z.string(),
29+
avatar_url: z.string(),
30+
web_url: z.string(),
31+
}).nullable()
32+
);
33+
1034
// Namespace related schemas
1135

1236
// Base schema for project-related operations
@@ -283,14 +307,6 @@ export const GitLabLabelSchema = z.object({
283307
is_project_label: z.boolean().optional(),
284308
});
285309

286-
export const GitLabUserSchema = z.object({
287-
username: z.string(), // Changed from login to match GitLab API
288-
id: z.number(),
289-
name: z.string(),
290-
avatar_url: z.string(),
291-
web_url: z.string(), // Changed from html_url to match GitLab API
292-
});
293-
294310
export const GitLabMilestoneSchema = z.object({
295311
id: z.number(),
296312
iid: z.number(), // Added to match GitLab API
@@ -1103,3 +1119,5 @@ export type GetRepositoryTreeOptions = z.infer<typeof GetRepositoryTreeSchema>;
11031119
export type MergeRequestThreadPosition = z.infer<typeof MergeRequestThreadPositionSchema>;
11041120
export type CreateMergeRequestThreadOptions = z.infer<typeof CreateMergeRequestThreadSchema>;
11051121
export type CreateMergeRequestNoteOptions = z.infer<typeof CreateMergeRequestNoteSchema>;
1122+
export type GitLabUser = z.infer<typeof GitLabUserSchema>;
1123+
export type GitLabUsersResponse = z.infer<typeof GitLabUsersResponseSchema>;

0 commit comments

Comments
 (0)