Skip to content
Closed
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
26 changes: 19 additions & 7 deletions src/gitlab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ MCP Server for the GitLab API, enabling project management, file operations, and
- `search` (string): Search query
- `page` (optional number): Page number for pagination
- `per_page` (optional number): Results per page (default 20)
- Returns: Project search results
- Returns: Project search results with total count

4. `create_repository`
- Create a new GitLab project
Expand All @@ -49,15 +49,27 @@ MCP Server for the GitLab API, enabling project management, file operations, and
- `initialize_with_readme` (optional boolean): Initialize with README
- Returns: Created project details

5. `get_file_contents`
5. `list_repository_tree`
- List files and directories in a repository
- Inputs:
- `project_id` (string): Project ID or URL-encoded path
- `path` (optional string): Path to list contents from
- `ref` (optional string): Branch/tag/commit to list from
- `recursive` (optional boolean): List recursively
- `per_page` (optional number): Results per page
- `page_token` (optional string): Token for pagination
- `pagination` (optional string): Pagination type
- Returns: List of files and directories with their metadata

6. `get_file_contents`
- Get contents of a file or directory
- Inputs:
- `project_id` (string): Project ID or URL-encoded path
- `file_path` (string): Path to file/directory
- `ref` (optional string): Branch/tag/commit to get contents from
- Returns: File/directory contents
- Returns: File/directory contents with metadata

6. `create_issue`
7. `create_issue`
- Create a new issue
- Inputs:
- `project_id` (string): Project ID or URL-encoded path
Expand All @@ -68,7 +80,7 @@ MCP Server for the GitLab API, enabling project management, file operations, and
- `milestone_id` (optional number): Milestone ID
- Returns: Created issue details

7. `create_merge_request`
8. `create_merge_request`
- Create a new merge request
- Inputs:
- `project_id` (string): Project ID or URL-encoded path
Expand All @@ -80,14 +92,14 @@ MCP Server for the GitLab API, enabling project management, file operations, and
- `allow_collaboration` (optional boolean): Allow commits from upstream members
- Returns: Created merge request details

8. `fork_repository`
9. `fork_repository`
- Fork a project
- Inputs:
- `project_id` (string): Project ID or URL-encoded path
- `namespace` (optional string): Namespace to fork to
- Returns: Forked project details

9. `create_branch`
10. `create_branch`
- Create a new branch
- Inputs:
- `project_id` (string): Project ID or URL-encoded path
Expand Down
48 changes: 48 additions & 0 deletions src/gitlab/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
GitLabContentSchema,
GitLabCreateUpdateFileResponseSchema,
GitLabSearchResponseSchema,
GitLabTreeItemSchema,
GitLabTreeSchema,
GitLabCommitSchema,
CreateRepositoryOptionsSchema,
Expand All @@ -27,6 +28,7 @@ import {
CreateOrUpdateFileSchema,
SearchRepositoriesSchema,
CreateRepositorySchema,
GetRepositoryTreeSchema,
GetFileContentsSchema,
PushFilesSchema,
CreateIssueSchema,
Expand All @@ -43,6 +45,7 @@ import {
type GitLabSearchResponse,
type GitLabTree,
type GitLabCommit,
type GitLabTreeItem,
type FileOperation,
} from './schemas.js';

Expand Down Expand Up @@ -111,6 +114,40 @@ async function createBranch(
return GitLabReferenceSchema.parse(await response.json());
}

async function getRepositoryTree(
projectId: string,
options: z.infer<typeof GetRepositoryTreeSchema>
): Promise<GitLabTreeItem[]> {
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(projectId)}/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);
}

async function getFileContents(
projectId: string,
filePath: string,
Expand Down Expand Up @@ -377,6 +414,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
description: "Create a new GitLab project",
inputSchema: zodToJsonSchema(CreateRepositorySchema)
},
{
name: "list_repository_tree",
description: "Get the tree of a GitLab project at a specific path",
inputSchema: zodToJsonSchema(GetRepositoryTreeSchema)
},
{
name: "get_file_contents",
description: "Get the contents of a file or directory from a GitLab project",
Expand Down Expand Up @@ -451,6 +493,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
return { content: [{ type: "text", text: JSON.stringify(repository, null, 2) }] };
}

case "list_repository_tree": {
const args = GetRepositoryTreeSchema.parse(request.params.arguments);
const tree = await getRepositoryTree(args.project_id, args);
return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
}

case "get_file_contents": {
const args = GetFileContentsSchema.parse(request.params.arguments);
const contents = await getFileContents(args.project_id, args.file_path, args.ref);
Expand Down
22 changes: 21 additions & 1 deletion src/gitlab/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ export const GitLabTreeSchema = z.object({
tree: z.array(GitLabTreeEntrySchema)
});

// Repository tree schemas
export const GitLabTreeItemSchema = z.object({
id: z.string(),
name: z.string(),
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 GitLabCommitSchema = z.object({
id: z.string(), // Changed from sha to match GitLab API
short_id: z.string(), // Added to match GitLab API
Expand Down Expand Up @@ -322,4 +341,5 @@ 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 GitLabTreeItem = z.infer<typeof GitLabTreeItemSchema>;