Skip to content

Add list_members tool to gitlab server #1663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/gitlab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ MCP Server for the GitLab API, enabling project management, file operations, and
- `ref` (optional string): Source branch/commit for new branch
- Returns: Created branch reference

10. `list_members`
- List all members of a project
- Inputs:
- `project_id` (string): Project ID or URL-encoded path
- `include_inheritance` (optional boolean): Include inherited members
- `page` (optional number): Page number for pagination (default: 1)
- `per_page` (optional number): Results per page (default: 20)
- Returns: List of members with their access levels and details

## Setup

### Personal Access Token
Expand Down
48 changes: 48 additions & 0 deletions src/gitlab/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
GitLabSearchResponseSchema,
GitLabTreeSchema,
GitLabCommitSchema,
GitLabMembersResponseSchema,
CreateRepositoryOptionsSchema,
CreateIssueOptionsSchema,
CreateMergeRequestOptionsSchema,
Expand All @@ -33,6 +34,7 @@ import {
CreateMergeRequestSchema,
ForkRepositorySchema,
CreateBranchSchema,
ListMembersSchema,
type GitLabFork,
type GitLabReference,
type GitLabRepository,
Expand All @@ -44,6 +46,7 @@ import {
type GitLabTree,
type GitLabCommit,
type FileOperation,
type GitLabMembersResponse,
} from './schemas.js';

const server = new Server({
Expand Down Expand Up @@ -359,6 +362,35 @@ async function createRepository(
return GitLabRepositorySchema.parse(await response.json());
}

async function listMembers(
projectId: string,
includeInheritance: boolean = false,
page: number = 1,
perPage: number = 20
): Promise<GitLabMembersResponse> {
// Build URL for project members, with or without inheritance
const url = includeInheritance
? `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/members/all`
: `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/members`;

const urlObj = new URL(url);
urlObj.searchParams.append("page", page.toString());
urlObj.searchParams.append("per_page", perPage.toString());

const response = await fetch(urlObj.toString(), {
headers: {
"Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`
}
});

if (!response.ok) {
throw new Error(`GitLab API error: ${response.statusText} (${response.status})`);
}

const members = await response.json();
return GitLabMembersResponseSchema.parse(members);
}

server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
Expand Down Expand Up @@ -406,6 +438,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
name: "create_branch",
description: "Create a new branch in a GitLab project",
inputSchema: zodToJsonSchema(CreateBranchSchema)
},
{
name: "list_members",
description: "List members of a GitLab project",
inputSchema: zodToJsonSchema(ListMembersSchema)
}
]
};
Expand Down Expand Up @@ -495,6 +532,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
return { content: [{ type: "text", text: JSON.stringify(mergeRequest, null, 2) }] };
}

case "list_members": {
const args = ListMembersSchema.parse(request.params.arguments);
const members = await listMembers(
args.project_id,
args.include_inheritance,
args.page,
args.per_page
);
return { content: [{ type: "text", text: JSON.stringify(members, null, 2) }] };
}

default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
Expand Down
31 changes: 30 additions & 1 deletion src/gitlab/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ export const GitLabAuthorSchema = z.object({
date: z.string()
});

// Member schema
export const GitLabMemberSchema = z.object({
id: z.number(),
username: z.string(),
name: z.string(),
state: z.string(),
avatar_url: z.string(),
web_url: z.string(),
access_level: z.number(),
email: z.string().optional(),
group_saml_identity: z.object({
extern_uid: z.string(),
provider: z.string(),
saml_provider_id: z.number()
}).nullable().optional(),
override: z.boolean().optional()
});

export const GitLabMembersResponseSchema = z.array(GitLabMemberSchema);

// Repository related schemas
export const GitLabOwnerSchema = z.object({
username: z.string(), // Changed from login to match GitLab API
Expand Down Expand Up @@ -304,6 +324,13 @@ export const CreateBranchSchema = ProjectParamsSchema.extend({
.describe("Source branch/commit for new branch")
});

export const ListMembersSchema = ProjectParamsSchema.extend({
include_inheritance: z.boolean().optional()
.describe("Include members inherited from parent groups (use 'all' endpoint)"),
page: z.number().optional().describe("Page number for pagination (default: 1)"),
per_page: z.number().optional().describe("Number of results per page (default: 20)")
});

// Export types
export type GitLabAuthor = z.infer<typeof GitLabAuthorSchema>;
export type GitLabFork = z.infer<typeof GitLabForkSchema>;
Expand All @@ -322,4 +349,6 @@ export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>;
export type CreateMergeRequestOptions = z.infer<typeof CreateMergeRequestOptionsSchema>;
export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>;
export type GitLabCreateUpdateFileResponse = z.infer<typeof GitLabCreateUpdateFileResponseSchema>;
export type GitLabSearchResponse = z.infer<typeof GitLabSearchResponseSchema>;
export type GitLabSearchResponse = z.infer<typeof GitLabSearchResponseSchema>;
export type GitLabMember = z.infer<typeof GitLabMemberSchema>;
export type GitLabMembersResponse = z.infer<typeof GitLabMembersResponseSchema>;