From bccd5f29c398a994de29e4b01fdf14cd6f6cf55c Mon Sep 17 00:00:00 2001 From: Bob Date: Wed, 7 May 2025 14:11:37 +0200 Subject: [PATCH] feat: Gitlab list repository tree tool --- index.ts | 56 +++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 4 ++-- schemas.ts | 20 +++++++++++++---- 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/index.ts b/index.ts index 531aa02..862b569 100644 --- a/index.ts +++ b/index.ts @@ -113,6 +113,10 @@ import { type UpdateWikiPageOptions, type DeleteWikiPageOptions, type GitLabWikiPage, + GitLabTreeItemSchema, + GetRepositoryTreeSchema, + type GitLabTreeItem, + type GetRepositoryTreeOptions, } from "./schemas.js"; /** @@ -389,6 +393,11 @@ const allTools = [ description: "Delete a wiki page from a GitLab project", inputSchema: zodToJsonSchema(DeleteWikiPageSchema), }, + { + name: "get_repository_tree", + description: "Get the repository tree for a GitLab project (list files and directories)", + inputSchema: zodToJsonSchema(GetRepositoryTreeSchema), + }, ]; // Define which tools are read-only @@ -2000,6 +2009,45 @@ async function deleteWikiPage(projectId: string, slug: string): Promise { await handleGitLabError(response); } +/** + * Get the repository tree for a project + * @param {string} projectId - The ID or URL-encoded path of the project + * @param {GetRepositoryTreeOptions} options - Options for the tree + * @returns {Promise} + */ +async function getRepositoryTree( + options: GetRepositoryTreeOptions +): Promise { + const queryParams = new URLSearchParams(); + if (options.path) queryParams.append("path", options.path); + if (options.ref) queryParams.append("ref", options.ref); + if (options.recursive) queryParams.append("recursive", "true"); + if (options.per_page) queryParams.append("per_page", options.per_page.toString()); + if (options.page_token) queryParams.append("page_token", options.page_token); + if (options.pagination) queryParams.append("pagination", options.pagination); + + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(options.project_id)}/repository/tree?${queryParams.toString()}`, + { + headers: { + Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json", + }, + } + ); + + if (response.status === 404) { + throw new Error("Repository or path not found"); + } + + if (!response.ok) { + throw new Error(`Failed to get repository tree: ${response.statusText}`); + } + + const data = await response.json(); + return z.array(GitLabTreeItemSchema).parse(data); +} + server.setRequestHandler(ListToolsRequestSchema, async () => { // Apply read-only filter first const tools0 = GITLAB_READ_ONLY_MODE @@ -2579,6 +2627,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { }; } + case "get_repository_tree": { + const args = GetRepositoryTreeSchema.parse(request.params.arguments); + const tree = await getRepositoryTree(args); + return { + content: [{ type: "text", text: JSON.stringify(tree, null, 2) }], + }; + } + default: throw new Error(`Unknown tool: ${request.params.name}`); } diff --git a/package-lock.json b/package-lock.json index 5a74dbd..ed5c4f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@zereight/mcp-gitlab", - "version": "1.0.32", + "version": "1.0.33", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@zereight/mcp-gitlab", - "version": "1.0.32", + "version": "1.0.33", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "1.8.0", diff --git a/schemas.ts b/schemas.ts index 7afe6b9..2510217 100644 --- a/schemas.ts +++ b/schemas.ts @@ -160,17 +160,27 @@ export const FileOperationSchema = z.object({ }); // Tree and commit schemas -export const GitLabTreeEntrySchema = z.object({ - id: z.string(), // Changed from sha to match GitLab API +export const GitLabTreeItemSchema = z.object({ + id: z.string(), name: z.string(), - type: z.enum(["blob", "tree"]), + type: z.enum(["tree", "blob"]), path: z.string(), mode: z.string(), }); +export const GetRepositoryTreeSchema = z.object({ + project_id: z.string().describe("The ID or URL-encoded path of the project"), + path: z.string().optional().describe("The path inside the repository"), + ref: z.string().optional().describe("The name of a repository branch or tag. Defaults to the default branch."), + recursive: z.boolean().optional().describe("Boolean value to get a recursive tree"), + per_page: z.number().optional().describe("Number of results to show per page"), + page_token: z.string().optional().describe("The tree record ID for pagination"), + pagination: z.string().optional().describe("Pagination method (keyset)") +}); + export const GitLabTreeSchema = z.object({ id: z.string(), // Changed from sha to match GitLab API - tree: z.array(GitLabTreeEntrySchema), + tree: z.array(GitLabTreeItemSchema), }); export const GitLabCommitSchema = z.object({ @@ -1033,3 +1043,5 @@ export type CreateWikiPageOptions = z.infer; export type UpdateWikiPageOptions = z.infer; export type DeleteWikiPageOptions = z.infer; export type GitLabWikiPage = z.infer; +export type GitLabTreeItem = z.infer; +export type GetRepositoryTreeOptions = z.infer;