Skip to content

feat: add list_group_members tool for member search - #633

Merged
zereight merged 1 commit into
mainfrom
feat/list-group-members
Aug 2, 2026
Merged

feat: add list_group_members tool for member search#633
zereight merged 1 commit into
mainfrom
feat/list-group-members

Conversation

@zereight

@zereight zereight commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add list_group_members MCP tool to search and list GitLab group members by name or username (query), with optional inherited members and pagination
  • Mirrors existing list_project_members against GET /groups/:id/members
  • Fixes add member search #631

Test plan

  • node --import tsx/esm --test test/test-list-group-members.ts
  • npm run check:skill-sync
  • node --import tsx/esm --test test/test-toolset-filtering.ts
  • CI mock test suite

Made with Cursor

Closes #631 by exposing GitLab group member listing with query,
inheritance, and pagination support alongside list_project_members.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a read-only tool for listing group members.
    • Supports searching by name or username, filtering by user IDs, including inherited members, excluding users, and paginating results.
    • Added the tool to the default Projects & Namespaces toolkit.
  • Documentation

    • Updated the tool catalog, README, workflow guidance, and changelog with the new capability.
  • Tests

    • Added coverage for direct and inherited memberships, filtering, pagination, response handling, and endpoint behavior.

Walkthrough

Changes

Group member listing

Layer / File(s) Summary
Member listing contract and handler
schemas.ts, index.ts
Adds ListGroupMembersSchema and ListGroupMembersOptions. The handler supports direct or inherited membership, filtering, pagination, error validation, and response parsing.
Tool registration and toolset wiring
tools/registry.ts
Registers list_group_members as read-only and adds it to the default projects toolset.
Member listing integration validation
test/test-list-group-members.ts, test/test-toolset-filtering.ts
Tests endpoint selection, query forwarding, response parsing, endpoint exclusivity, cleanup, and the updated tool count.
Member listing documentation
CHANGELOG.md, README.md, docs/tools/index.md, docs/tools/projects.md, skills/gitlab-mcp/SKILL.md
Documents the new tool, its parameters, and updated tool counts. Shows it as tool 89 and renumbers later README entries.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ToolHandler
  participant listGroupMembers
  participant GitLabAPI
  ToolHandler->>listGroupMembers: validated group-member options
  listGroupMembers->>GitLabAPI: request direct or inherited members with filters
  GitLabAPI-->>listGroupMembers: member response or error
  listGroupMembers-->>ToolHandler: serialized member results
Loading

Suggested reviewers: xunzou, killusions, vrajpal-jhala

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: adding the list_group_members tool for member search.
Description check ✅ Passed The description accurately covers the new tool, supported options, endpoint, issue, and test plan.
Linked Issues check ✅ Passed The changes satisfy issue #631 by adding group member search and listing through the GitLab group members endpoint.
Out of Scope Changes check ✅ Passed The implementation, tests, registry updates, tool counts, and documentation changes support the stated group member listing objective.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/list-group-members
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/list-group-members

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 9-11: Consolidate the changelog entry by removing the duplicate
`##### Added` heading near the top and moving the `list_group_members` bullet
under the existing `##### Added` section.

In `@schemas.ts`:
- Around line 3391-3405: Update ListGroupMembersSchema to reject requests that
set both include_inheritance: true and skip_users, while preserving valid
direct-member and inherited-member requests. Add schema coverage for the invalid
combination, specifically include_inheritance: true with skip_users.

In `@test/test-list-group-members.ts`:
- Around line 158-169: Extend the “should pass query to the members endpoint”
test around callListGroupMembers to supply page and per_page alongside query,
capture those request parameters in the existing mock, and assert both values
are forwarded. Preserve the existing query assertion.
- Line 67: Update the catch clause in the relevant test parse-error handling
block to omit the unused binding, changing it to a parameterless catch while
preserving the existing catch body.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a41b08ac-43b2-45f3-bdda-3dbd81ff4ce3

📥 Commits

Reviewing files that changed from the base of the PR and between f523db6 and 11701f6.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • README.md
  • docs/tools/index.md
  • docs/tools/projects.md
  • index.ts
  • schemas.ts
  • skills/gitlab-mcp/SKILL.md
  • test/test-list-group-members.ts
  • test/test-toolset-filtering.ts
  • tools/registry.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: test
🧰 Additional context used
🪛 ast-grep (0.45.0)
test/test-list-group-members.ts

[warning] 2-2: Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from 'child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🪛 ESLint
test/test-list-group-members.ts

[error] 67-67: 'e' is defined but never used.

(@typescript-eslint/no-unused-vars)

🪛 LanguageTool
README.md

[grammar] ~736-~736: Ensure spelling is correct
Context: ...Add an emoji reaction to a work item (e.g. thumbsup, rocket, eyes) 188. `delete_work_item_e...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (8)
index.ts (1)

452-453: LGTM!

Also applies to: 10941-10949

tools/registry.ts (1)

170-170: LGTM!

Also applies to: 686-690, 1407-1407, 1760-1760

README.md (1)

638-638: LGTM!

Also applies to: 639-766

docs/tools/index.md (1)

52-60: LGTM!

docs/tools/projects.md (1)

11-11: LGTM!

Also applies to: 111-128

skills/gitlab-mcp/SKILL.md (2)

61-61: LGTM!


8-20: 📐 Maintainability & Code Quality

No count change needed.

215 tools across 20 toolsets + execute_graphql + discover_tools matches the 217 total, and docs/tools/ page counts agree with dependency_proxy (4) and labels (5).

			> Likely an incorrect or invalid review comment.
test/test-toolset-filtering.ts (1)

37-37: LGTM!

Comment thread CHANGELOG.md
Comment on lines +9 to +11
##### Added

- Add `list_group_members` tool for searching group members by name or username ([#631](https://github.com/zereight/gitlab-mcp/issues/631))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use one Unreleased Added section.

CHANGELOG.md already has an ##### Added heading at Line 21. Move this bullet under that heading and remove the duplicate heading at Lines 9-10.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 9 - 11, Consolidate the changelog entry by
removing the duplicate `##### Added` heading near the top and moving the
`list_group_members` bullet under the existing `##### Added` section.

Comment thread schemas.ts
Comment on lines +3391 to +3405
export const ListGroupMembersSchema = z.object({
group_id: z.string().describe("Group ID or URL-encoded path"),
query: z.string().optional().describe("Search for members by name or username"),
user_ids: z.array(z.coerce.number()).optional().describe("Filter by user IDs"),
skip_users: z.array(z.coerce.number()).optional().describe("User IDs to exclude"),
include_inheritance: z.coerce
.boolean()
.optional()
.describe("Include inherited members. Defaults to false."),
per_page: z.coerce
.number()
.optional()
.describe("Number of items per page (default: 20, max: 100)"),
page: z.coerce.number().optional().describe("Page number for pagination (default: 1)"),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)schemas\.ts$|(^|/)index\.ts$' || true

echo "== relevant schema occurrences =="
rg -n "ListGroupMembersSchema|listGroupMembers|skip_users|include_inheritance|modes?/all|members/all" -S .

echo "== schemas context =="
sed -n '3360,3420p' schemas.ts || true

echo "== implementation context =="
rg -n "ListGroupMembersSchema|listGroupMembers|members/all|members\\?|skip_users" -S --glob '*.ts' .

Repository: zereight/gitlab-mcp

Length of output: 6648


🌐 Web query:

GitLab API list group members skip_users members/all include_inheritance

💡 Result:

In the GitLab API, the behavior regarding membership inheritance is determined by the specific endpoint chosen rather than a dedicated parameter like "include_inheritance" [1][2]. To list group members, you use two primary endpoints: 1. List direct members: GET /groups/:id/members This endpoint returns only members directly assigned to the specified group [1][3]. It does not include members inherited from ancestor groups or invited groups [1][2]. This endpoint supports the skip_users parameter, which allows you to filter out specific user IDs from the results [1][2]. 2. List all members (including inherited): GET /groups/:id/members/all This endpoint returns all members of the specified group, including those inherited from ancestor groups and invited users [1][2]. When a user is a member of both the group and an ancestor group, this endpoint returns the membership with the highest access level, representing the user's effective permission [4]. There is no "include_inheritance" parameter; inheritance is implicit when using the /members/all endpoint [1][2]. The skip_users parameter is available for the direct members endpoint (/members) but is not documented as a parameter for the /members/all endpoint [1][2].

Citations:


Disallow skip_users for inherited group members.

listGroupMembers forwards skip_users[] unconditionally, so include_inheritance: true sends it to /groups/:id/members/all. skip_users is documented for the direct /groups/:id/members endpoint only; reject this combination in ListGroupMembersSchema and add a test for include_inheritance: true with skip_users.

Proposed fix
 export const ListGroupMembersSchema = z.object({
   group_id: z.string().describe("Group ID or URL-encoded path"),
   query: z.string().optional().describe("Search for members by name or username"),
   user_ids: z.array(z.coerce.number()).optional().describe("Filter by user IDs"),
   skip_users: z.array(z.coerce.number()).optional().describe("User IDs to exclude"),
   include_inheritance: z.coerce.boolean().optional(),
   per_page: z.coerce.number().optional(),
   page: z.coerce.number().optional(),
+}).superRefine((value, ctx) => {
+  if (value.include_inheritance && value.skip_users?.length) {
+    ctx.addIssue({
+      code: z.ZodIssueCode.custom,
+      path: ["skip_users"],
+      message: "skip_users is not supported when include_inheritance is true",
+    });
+  }
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@schemas.ts` around lines 3391 - 3405, Update ListGroupMembersSchema to reject
requests that set both include_inheritance: true and skip_users, while
preserving valid direct-member and inherited-member requests. Add schema
coverage for the invalid combination, specifically include_inheritance: true
with skip_users.

if (content) {
try {
resolve(JSON.parse(content));
} catch (e) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the unused catch binding.

ESLint reports that e is unused. This can fail the lint check. Use catch { ... } because this branch does not use the parse error.

Proposed fix
-            } catch (e) {
+            } catch {
               reject(new Error(`Failed to parse tool output JSON: ${content}`));
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (e) {
} catch {
🧰 Tools
🪛 ESLint

[error] 67-67: 'e' is defined but never used.

(@typescript-eslint/no-unused-vars)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/test-list-group-members.ts` at line 67, Update the catch clause in the
relevant test parse-error handling block to omit the unused binding, changing it
to a parameterless catch while preserving the existing catch body.

Source: Linters/SAST tools

Comment on lines +158 to +169
describe('with query', () => {
test('should pass query to the members endpoint', async () => {
await callListGroupMembers(
{ group_id: TEST_GROUP_ID, query: 'alice' },
{
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN
}
);

assert.strictEqual(queryParam, 'alice', 'Query parameter should be forwarded');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add pagination forwarding coverage.

The tool contract supports pagination, but this test only records query. Record and assert page and per_page for a request that supplies both options. This prevents regressions where the handler drops pagination parameters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/test-list-group-members.ts` around lines 158 - 169, Extend the “should
pass query to the members endpoint” test around callListGroupMembers to supply
page and per_page alongside query, capture those request parameters in the
existing mock, and assert both values are forwarded. Preserve the existing query
assertion.

@zereight
zereight merged commit dec5e4f into main Aug 2, 2026
12 checks passed
@zereight
zereight deleted the feat/list-group-members branch August 2, 2026 14:28
@zereight zereight mentioned this pull request Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add member search

1 participant