Merge pull request #25 from thomasleveil/list_group_projects
✨ Add `list_group_projects tool`
This commit is contained in:
28
README.md
28
README.md
@ -237,6 +237,34 @@ env GITLAB_PERSONAL_ACCESS_TOKEN=your_gitlab_token GITLAB_API_URL=your_gitlab_ap
|
|||||||
- `label_id` (number/string): Label ID or name
|
- `label_id` (number/string): Label ID or name
|
||||||
- Returns: Success message
|
- Returns: Success message
|
||||||
|
|
||||||
|
14. `list_group_projects`
|
||||||
|
|
||||||
|
- List all projects in a GitLab group. 📂
|
||||||
|
- Inputs:
|
||||||
|
- `group_id` (string): Project ID or namespace/project_path
|
||||||
|
- Filtering options:
|
||||||
|
- `include_subgroups` (optional boolean): Include projects from subgroups
|
||||||
|
- `search` (optional string): Search term to filter projects
|
||||||
|
- `archived` (optional boolean): Filter for archived projects
|
||||||
|
- `visibility` (optional string): Filter by project visibility (public/internal/private)
|
||||||
|
- `with_programming_language` (optional string): Filter by programming language
|
||||||
|
- `starred` (optional boolean): Filter by starred projects
|
||||||
|
- Feature filtering:
|
||||||
|
- `with_issues_enabled` (optional boolean): Filter projects with issues feature enabled
|
||||||
|
- `with_merge_requests_enabled` (optional boolean): Filter projects with merge requests feature enabled
|
||||||
|
- `min_access_level` (optional number): Filter by minimum access level
|
||||||
|
- Pagination:
|
||||||
|
- `page` (optional number): Page number
|
||||||
|
- `per_page` (optional number): Results per page
|
||||||
|
- Sorting:
|
||||||
|
- `order_by` (optional string): Field to sort by
|
||||||
|
- `sort` (optional string): Sort direction (asc/desc)
|
||||||
|
- Additional data:
|
||||||
|
- `statistics` (optional boolean): Include project statistics
|
||||||
|
- `with_custom_attributes` (optional boolean): Include custom attributes
|
||||||
|
- `with_security_reports` (optional boolean): Include security reports
|
||||||
|
- Returns: List of projects
|
||||||
|
|
||||||
## Environment Variable Configuration
|
## Environment Variable Configuration
|
||||||
|
|
||||||
Before running the server, you need to set the following environment variables:
|
Before running the server, you need to set the following environment variables:
|
||||||
|
@ -9,7 +9,7 @@ import { fileURLToPath } from "url";
|
|||||||
import { dirname } from "path";
|
import { dirname } from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { GitLabForkSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabIssueSchema, GitLabMergeRequestSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabSearchResponseSchema, GitLabTreeSchema, GitLabCommitSchema, GitLabNamespaceSchema, GitLabNamespaceExistsResponseSchema, GitLabProjectSchema, CreateOrUpdateFileSchema, SearchRepositoriesSchema, CreateRepositorySchema, GetFileContentsSchema, PushFilesSchema, CreateIssueSchema, CreateMergeRequestSchema, ForkRepositorySchema, CreateBranchSchema, GitLabMergeRequestDiffSchema, GetMergeRequestSchema, GetMergeRequestDiffsSchema, UpdateMergeRequestSchema, ListIssuesSchema, GetIssueSchema, UpdateIssueSchema, DeleteIssueSchema, GitLabIssueLinkSchema, GitLabIssueWithLinkDetailsSchema, ListIssueLinksSchema, GetIssueLinkSchema, CreateIssueLinkSchema, DeleteIssueLinkSchema, ListNamespacesSchema, GetNamespaceSchema, VerifyNamespaceSchema, GetProjectSchema, ListProjectsSchema, ListLabelsSchema, GetLabelSchema, CreateLabelSchema, UpdateLabelSchema, DeleteLabelSchema, CreateNoteSchema, } from "./schemas.js";
|
import { GitLabForkSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabIssueSchema, GitLabMergeRequestSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabSearchResponseSchema, GitLabTreeSchema, GitLabCommitSchema, GitLabNamespaceSchema, GitLabNamespaceExistsResponseSchema, GitLabProjectSchema, CreateOrUpdateFileSchema, SearchRepositoriesSchema, CreateRepositorySchema, GetFileContentsSchema, PushFilesSchema, CreateIssueSchema, CreateMergeRequestSchema, ForkRepositorySchema, CreateBranchSchema, GitLabMergeRequestDiffSchema, GetMergeRequestSchema, GetMergeRequestDiffsSchema, UpdateMergeRequestSchema, ListIssuesSchema, GetIssueSchema, UpdateIssueSchema, DeleteIssueSchema, GitLabIssueLinkSchema, GitLabIssueWithLinkDetailsSchema, ListIssueLinksSchema, GetIssueLinkSchema, CreateIssueLinkSchema, DeleteIssueLinkSchema, ListNamespacesSchema, GetNamespaceSchema, VerifyNamespaceSchema, GetProjectSchema, ListProjectsSchema, ListLabelsSchema, GetLabelSchema, CreateLabelSchema, UpdateLabelSchema, DeleteLabelSchema, CreateNoteSchema, ListGroupProjectsSchema, } from "./schemas.js";
|
||||||
/**
|
/**
|
||||||
* Read version from package.json
|
* Read version from package.json
|
||||||
*/
|
*/
|
||||||
@ -960,6 +960,55 @@ async function deleteLabel(projectId, labelId) {
|
|||||||
// Handle errors
|
// Handle errors
|
||||||
await handleGitLabError(response);
|
await handleGitLabError(response);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* List all projects in a GitLab group
|
||||||
|
*
|
||||||
|
* @param {z.infer<typeof ListGroupProjectsSchema>} options - Options for listing group projects
|
||||||
|
* @returns {Promise<GitLabProject[]>} Array of projects in the group
|
||||||
|
*/
|
||||||
|
async function listGroupProjects(options) {
|
||||||
|
const url = new URL(`${GITLAB_API_URL}/groups/${encodeURIComponent(options.group_id)}/projects`);
|
||||||
|
// Add optional parameters to URL
|
||||||
|
if (options.include_subgroups)
|
||||||
|
url.searchParams.append('include_subgroups', 'true');
|
||||||
|
if (options.search)
|
||||||
|
url.searchParams.append('search', options.search);
|
||||||
|
if (options.order_by)
|
||||||
|
url.searchParams.append('order_by', options.order_by);
|
||||||
|
if (options.sort)
|
||||||
|
url.searchParams.append('sort', options.sort);
|
||||||
|
if (options.page)
|
||||||
|
url.searchParams.append('page', options.page.toString());
|
||||||
|
if (options.per_page)
|
||||||
|
url.searchParams.append('per_page', options.per_page.toString());
|
||||||
|
if (options.archived !== undefined)
|
||||||
|
url.searchParams.append('archived', options.archived.toString());
|
||||||
|
if (options.visibility)
|
||||||
|
url.searchParams.append('visibility', options.visibility);
|
||||||
|
if (options.with_issues_enabled !== undefined)
|
||||||
|
url.searchParams.append('with_issues_enabled', options.with_issues_enabled.toString());
|
||||||
|
if (options.with_merge_requests_enabled !== undefined)
|
||||||
|
url.searchParams.append('with_merge_requests_enabled', options.with_merge_requests_enabled.toString());
|
||||||
|
if (options.min_access_level !== undefined)
|
||||||
|
url.searchParams.append('min_access_level', options.min_access_level.toString());
|
||||||
|
if (options.with_programming_language)
|
||||||
|
url.searchParams.append('with_programming_language', options.with_programming_language);
|
||||||
|
if (options.starred !== undefined)
|
||||||
|
url.searchParams.append('starred', options.starred.toString());
|
||||||
|
if (options.statistics !== undefined)
|
||||||
|
url.searchParams.append('statistics', options.statistics.toString());
|
||||||
|
if (options.with_custom_attributes !== undefined)
|
||||||
|
url.searchParams.append('with_custom_attributes', options.with_custom_attributes.toString());
|
||||||
|
if (options.with_security_reports !== undefined)
|
||||||
|
url.searchParams.append('with_security_reports', options.with_security_reports.toString());
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
method: "GET",
|
||||||
|
headers: DEFAULT_HEADERS,
|
||||||
|
});
|
||||||
|
await handleGitLabError(response);
|
||||||
|
const projects = await response.json();
|
||||||
|
return GitLabProjectSchema.array().parse(projects);
|
||||||
|
}
|
||||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
return {
|
return {
|
||||||
tools: [
|
tools: [
|
||||||
@ -1118,6 +1167,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
description: "Delete a label from a project",
|
description: "Delete a label from a project",
|
||||||
inputSchema: zodToJsonSchema(DeleteLabelSchema),
|
inputSchema: zodToJsonSchema(DeleteLabelSchema),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "list_group_projects",
|
||||||
|
description: "List projects in a GitLab group with filtering options",
|
||||||
|
inputSchema: zodToJsonSchema(ListGroupProjectsSchema),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -1414,6 +1468,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Label deleted successfully" }, null, 2) }],
|
content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Label deleted successfully" }, null, 2) }],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "list_group_projects": {
|
||||||
|
const args = ListGroupProjectsSchema.parse(request.params.arguments);
|
||||||
|
const projects = await listGroupProjects(args);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
|
||||||
|
};
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||||
}
|
}
|
||||||
|
@ -597,3 +597,23 @@ export const DeleteLabelSchema = z.object({
|
|||||||
project_id: z.string().describe("Project ID or URL-encoded path"),
|
project_id: z.string().describe("Project ID or URL-encoded path"),
|
||||||
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
|
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
|
||||||
});
|
});
|
||||||
|
// Group projects schema
|
||||||
|
export const ListGroupProjectsSchema = z.object({
|
||||||
|
group_id: z.string().describe("Group ID or path"),
|
||||||
|
include_subgroups: z.boolean().optional().describe("Include projects from subgroups"),
|
||||||
|
search: z.string().optional().describe("Search term to filter projects"),
|
||||||
|
order_by: z.enum(['name', 'path', 'created_at', 'updated_at', 'last_activity_at']).optional().describe("Field to sort by"),
|
||||||
|
sort: z.enum(['asc', 'desc']).optional().describe("Sort direction"),
|
||||||
|
page: z.number().optional().describe("Page number"),
|
||||||
|
per_page: z.number().optional().describe("Number of results per page"),
|
||||||
|
archived: z.boolean().optional().describe("Filter for archived projects"),
|
||||||
|
visibility: z.enum(["public", "internal", "private"]).optional().describe("Filter by project visibility"),
|
||||||
|
with_issues_enabled: z.boolean().optional().describe("Filter projects with issues feature enabled"),
|
||||||
|
with_merge_requests_enabled: z.boolean().optional().describe("Filter projects with merge requests feature enabled"),
|
||||||
|
min_access_level: z.number().optional().describe("Filter by minimum access level"),
|
||||||
|
with_programming_language: z.string().optional().describe("Filter by programming language"),
|
||||||
|
starred: z.boolean().optional().describe("Filter by starred projects"),
|
||||||
|
statistics: z.boolean().optional().describe("Include project statistics"),
|
||||||
|
with_custom_attributes: z.boolean().optional().describe("Include custom attributes"),
|
||||||
|
with_security_reports: z.boolean().optional().describe("Include security reports")
|
||||||
|
});
|
||||||
|
55
index.ts
55
index.ts
@ -66,6 +66,7 @@ import {
|
|||||||
UpdateLabelSchema,
|
UpdateLabelSchema,
|
||||||
DeleteLabelSchema,
|
DeleteLabelSchema,
|
||||||
CreateNoteSchema,
|
CreateNoteSchema,
|
||||||
|
ListGroupProjectsSchema,
|
||||||
type GitLabFork,
|
type GitLabFork,
|
||||||
type GitLabReference,
|
type GitLabReference,
|
||||||
type GitLabRepository,
|
type GitLabRepository,
|
||||||
@ -1394,6 +1395,47 @@ async function deleteLabel(
|
|||||||
await handleGitLabError(response);
|
await handleGitLabError(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all projects in a GitLab group
|
||||||
|
*
|
||||||
|
* @param {z.infer<typeof ListGroupProjectsSchema>} options - Options for listing group projects
|
||||||
|
* @returns {Promise<GitLabProject[]>} Array of projects in the group
|
||||||
|
*/
|
||||||
|
async function listGroupProjects(
|
||||||
|
options: z.infer<typeof ListGroupProjectsSchema>
|
||||||
|
): Promise<GitLabProject[]> {
|
||||||
|
const url = new URL(
|
||||||
|
`${GITLAB_API_URL}/groups/${encodeURIComponent(options.group_id)}/projects`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add optional parameters to URL
|
||||||
|
if (options.include_subgroups) url.searchParams.append('include_subgroups', 'true');
|
||||||
|
if (options.search) url.searchParams.append('search', options.search);
|
||||||
|
if (options.order_by) url.searchParams.append('order_by', options.order_by);
|
||||||
|
if (options.sort) url.searchParams.append('sort', options.sort);
|
||||||
|
if (options.page) url.searchParams.append('page', options.page.toString());
|
||||||
|
if (options.per_page) url.searchParams.append('per_page', options.per_page.toString());
|
||||||
|
if (options.archived !== undefined) url.searchParams.append('archived', options.archived.toString());
|
||||||
|
if (options.visibility) url.searchParams.append('visibility', options.visibility);
|
||||||
|
if (options.with_issues_enabled !== undefined) url.searchParams.append('with_issues_enabled', options.with_issues_enabled.toString());
|
||||||
|
if (options.with_merge_requests_enabled !== undefined) url.searchParams.append('with_merge_requests_enabled', options.with_merge_requests_enabled.toString());
|
||||||
|
if (options.min_access_level !== undefined) url.searchParams.append('min_access_level', options.min_access_level.toString());
|
||||||
|
if (options.with_programming_language) url.searchParams.append('with_programming_language', options.with_programming_language);
|
||||||
|
if (options.starred !== undefined) url.searchParams.append('starred', options.starred.toString());
|
||||||
|
if (options.statistics !== undefined) url.searchParams.append('statistics', options.statistics.toString());
|
||||||
|
if (options.with_custom_attributes !== undefined) url.searchParams.append('with_custom_attributes', options.with_custom_attributes.toString());
|
||||||
|
if (options.with_security_reports !== undefined) url.searchParams.append('with_security_reports', options.with_security_reports.toString());
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
method: "GET",
|
||||||
|
headers: DEFAULT_HEADERS,
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleGitLabError(response);
|
||||||
|
const projects = await response.json();
|
||||||
|
return GitLabProjectSchema.array().parse(projects);
|
||||||
|
}
|
||||||
|
|
||||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
return {
|
return {
|
||||||
tools: [
|
tools: [
|
||||||
@ -1555,6 +1597,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|||||||
description: "Delete a label from a project",
|
description: "Delete a label from a project",
|
||||||
inputSchema: zodToJsonSchema(DeleteLabelSchema),
|
inputSchema: zodToJsonSchema(DeleteLabelSchema),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "list_group_projects",
|
||||||
|
description: "List projects in a GitLab group with filtering options",
|
||||||
|
inputSchema: zodToJsonSchema(ListGroupProjectsSchema),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -1938,6 +1985,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "list_group_projects": {
|
||||||
|
const args = ListGroupProjectsSchema.parse(request.params.arguments);
|
||||||
|
const projects = await listGroupProjects(args);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: JSON.stringify(projects, null, 2) }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||||
}
|
}
|
||||||
|
1030
package-lock.json
generated
1030
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -22,7 +22,7 @@
|
|||||||
"deploy": "npm run build && npm publish"
|
"deploy": "npm run build && npm publish"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "1.0.1",
|
"@modelcontextprotocol/sdk": "1.8.0",
|
||||||
"@types/node-fetch": "^2.6.12",
|
"@types/node-fetch": "^2.6.12",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"zod-to-json-schema": "^3.23.5"
|
"zod-to-json-schema": "^3.23.5"
|
||||||
@ -30,6 +30,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.13.10",
|
"@types/node": "^22.13.10",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.2",
|
||||||
"zod": "3.21.4"
|
"zod": "^3.24.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
21
schemas.ts
21
schemas.ts
@ -663,6 +663,27 @@ export const DeleteLabelSchema = z.object({
|
|||||||
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
|
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Group projects schema
|
||||||
|
export const ListGroupProjectsSchema = z.object({
|
||||||
|
group_id: z.string().describe("Group ID or path"),
|
||||||
|
include_subgroups: z.boolean().optional().describe("Include projects from subgroups"),
|
||||||
|
search: z.string().optional().describe("Search term to filter projects"),
|
||||||
|
order_by: z.enum(['name', 'path', 'created_at', 'updated_at', 'last_activity_at']).optional().describe("Field to sort by"),
|
||||||
|
sort: z.enum(['asc', 'desc']).optional().describe("Sort direction"),
|
||||||
|
page: z.number().optional().describe("Page number"),
|
||||||
|
per_page: z.number().optional().describe("Number of results per page"),
|
||||||
|
archived: z.boolean().optional().describe("Filter for archived projects"),
|
||||||
|
visibility: z.enum(["public", "internal", "private"]).optional().describe("Filter by project visibility"),
|
||||||
|
with_issues_enabled: z.boolean().optional().describe("Filter projects with issues feature enabled"),
|
||||||
|
with_merge_requests_enabled: z.boolean().optional().describe("Filter projects with merge requests feature enabled"),
|
||||||
|
min_access_level: z.number().optional().describe("Filter by minimum access level"),
|
||||||
|
with_programming_language: z.string().optional().describe("Filter by programming language"),
|
||||||
|
starred: z.boolean().optional().describe("Filter by starred projects"),
|
||||||
|
statistics: z.boolean().optional().describe("Include project statistics"),
|
||||||
|
with_custom_attributes: z.boolean().optional().describe("Include custom attributes"),
|
||||||
|
with_security_reports: z.boolean().optional().describe("Include security reports")
|
||||||
|
});
|
||||||
|
|
||||||
// Export types
|
// Export types
|
||||||
export type GitLabAuthor = z.infer<typeof GitLabAuthorSchema>;
|
export type GitLabAuthor = z.infer<typeof GitLabAuthorSchema>;
|
||||||
export type GitLabFork = z.infer<typeof GitLabForkSchema>;
|
export type GitLabFork = z.infer<typeof GitLabForkSchema>;
|
||||||
|
Reference in New Issue
Block a user