From ffe49237b92055451b5f699466941d5892bf7de1 Mon Sep 17 00:00:00 2001 From: Taylor McCaslin Date: Mon, 25 Nov 2024 16:39:34 -0700 Subject: [PATCH 1/2] initial gitlab version --- src/gitlab/README.md | 132 +++++++++ src/gitlab/index.ts | 534 +++++++++++++++++++++++++++++++++ src/gitlab/package-lock.json | 551 +++++++++++++++++++++++++++++++++++ src/gitlab/package.json | 31 ++ src/gitlab/schemas.ts | 325 +++++++++++++++++++++ src/gitlab/tsconfig.json | 11 + 6 files changed, 1584 insertions(+) create mode 100644 src/gitlab/README.md create mode 100644 src/gitlab/index.ts create mode 100644 src/gitlab/package-lock.json create mode 100644 src/gitlab/package.json create mode 100644 src/gitlab/schemas.ts create mode 100644 src/gitlab/tsconfig.json diff --git a/src/gitlab/README.md b/src/gitlab/README.md new file mode 100644 index 00000000..fdf82552 --- /dev/null +++ b/src/gitlab/README.md @@ -0,0 +1,132 @@ +# GitLab MCP Server + +MCP Server for the GitLab API, enabling project management, file operations, and more. + +### Features + +- **Automatic Branch Creation**: When creating/updating files or pushing changes, branches are automatically created if they don't exist +- **Comprehensive Error Handling**: Clear error messages for common issues +- **Git History Preservation**: Operations maintain proper Git history without force pushing +- **Batch Operations**: Support for both single-file and multi-file operations + + +## Tools + +1. `create_or_update_file` + - Create or update a single file in a project + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `file_path` (string): Path where to create/update the file + - `content` (string): Content of the file + - `commit_message` (string): Commit message + - `branch` (string): Branch to create/update the file in + - `previous_path` (optional string): Path of the file to move/rename + - Returns: File content and commit details + +2. `push_files` + - Push multiple files in a single commit + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `branch` (string): Branch to push to + - `files` (array): Files to push, each with `file_path` and `content` + - `commit_message` (string): Commit message + - Returns: Updated branch reference + +3. `search_repositories` + - Search for GitLab projects + - Inputs: + - `search` (string): Search query + - `page` (optional number): Page number for pagination + - `per_page` (optional number): Results per page (default 20) + - Returns: Project search results + +4. `create_repository` + - Create a new GitLab project + - Inputs: + - `name` (string): Project name + - `description` (optional string): Project description + - `visibility` (optional string): 'private', 'internal', or 'public' + - `initialize_with_readme` (optional boolean): Initialize with README + - Returns: Created project details + +5. `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 + +6. `create_issue` + - Create a new issue + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `title` (string): Issue title + - `description` (optional string): Issue description + - `assignee_ids` (optional number[]): User IDs to assign + - `labels` (optional string[]): Labels to add + - `milestone_id` (optional number): Milestone ID + - Returns: Created issue details + +7. `create_merge_request` + - Create a new merge request + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `title` (string): MR title + - `description` (optional string): MR description + - `source_branch` (string): Branch containing changes + - `target_branch` (string): Branch to merge into + - `draft` (optional boolean): Create as draft MR + - `allow_collaboration` (optional boolean): Allow commits from upstream members + - Returns: Created merge request details + +8. `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` + - Create a new branch + - Inputs: + - `project_id` (string): Project ID or URL-encoded path + - `branch` (string): Name for new branch + - `ref` (optional string): Source branch/commit for new branch + - Returns: Created branch reference + +## Setup + +### Personal Access Token +[Create a GitLab Personal Access Token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) with appropriate permissions: + - Go to User Settings > Access Tokens in GitLab + - Select the required scopes: + - `api` for full API access + - `read_api` for read-only access + - `read_repository` and `write_repository` for repository operations + - Create the token and save it securely + +### Usage with Claude Desktop +Add the following to your `claude_desktop_config.json`: + +```json +{ + "gitlab": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-gitlab"], + "env": { + "GITLAB_PERSONAL_ACCESS_TOKEN": "", + "GITLAB_API_URL": "https://gitlab.com/api/v4" // Optional, for self-hosted instances + } + } +} +``` + +## Environment Variables + +- `GITLAB_PERSONAL_ACCESS_TOKEN`: Your GitLab personal access token (required) +- `GITLAB_API_URL`: Base URL for GitLab API (optional, defaults to `https://gitlab.com/api/v4`) + +## License + +This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. \ No newline at end of file diff --git a/src/gitlab/index.ts b/src/gitlab/index.ts new file mode 100644 index 00000000..9da4092c --- /dev/null +++ b/src/gitlab/index.ts @@ -0,0 +1,534 @@ +#!/usr/bin/env node + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import fetch from "node-fetch"; +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; +import { + GitLabForkSchema, + GitLabReferenceSchema, + GitLabRepositorySchema, + GitLabIssueSchema, + GitLabMergeRequestSchema, + GitLabContentSchema, + GitLabCreateUpdateFileResponseSchema, + GitLabSearchResponseSchema, + GitLabTreeSchema, + GitLabCommitSchema, + CreateRepositoryOptionsSchema, + CreateIssueOptionsSchema, + CreateMergeRequestOptionsSchema, + CreateBranchOptionsSchema, + CreateOrUpdateFileSchema, + SearchRepositoriesSchema, + CreateRepositorySchema, + GetFileContentsSchema, + PushFilesSchema, + CreateIssueSchema, + CreateMergeRequestSchema, + ForkRepositorySchema, + CreateBranchSchema, + type GitLabFork, + type GitLabReference, + type GitLabRepository, + type GitLabIssue, + type GitLabMergeRequest, + type GitLabContent, + type GitLabCreateUpdateFileResponse, + type GitLabSearchResponse, + type GitLabTree, + type GitLabCommit, + type FileOperation, +} from './schemas.js'; + +const server = new Server({ + name: "gitlab-mcp-server", + version: "0.1.0", +}, { + capabilities: { + tools: {} + } +}); + +const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN; +const GITLAB_API_URL = process.env.GITLAB_API_URL || 'https://gitlab.com/api/v4'; + +if (!GITLAB_PERSONAL_ACCESS_TOKEN) { + console.error("GITLAB_PERSONAL_ACCESS_TOKEN environment variable is not set"); + process.exit(1); +} + +async function forkProject( + projectId: string, + namespace?: string +): Promise { + const url = `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/fork`; + const queryParams = namespace ? `?namespace=${encodeURIComponent(namespace)}` : ''; + + const response = await fetch(url + queryParams, { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + } + }); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabForkSchema.parse(await response.json()); +} + +async function createBranch( + projectId: string, + options: z.infer +): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/branches`, + { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + branch: options.name, + ref: options.ref + }) + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabReferenceSchema.parse(await response.json()); +} + +async function getDefaultBranchRef(projectId: string): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}`, + { + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}` + } + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + const project = GitLabRepositorySchema.parse(await response.json()); + return project.default_branch; +} + +async function getFileContents( + projectId: string, + filePath: string, + ref?: string +): Promise { + const encodedPath = encodeURIComponent(filePath); + let url = `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/files/${encodedPath}`; + if (ref) { + url += `?ref=${encodeURIComponent(ref)}`; + } + + const response = await fetch(url, { + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}` + } + }); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + const data = GitLabContentSchema.parse(await response.json()); + + if (!Array.isArray(data) && data.content) { + data.content = Buffer.from(data.content, 'base64').toString('utf8'); + } + + return data; +} + +async function createIssue( + projectId: string, + options: z.infer +): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/issues`, + { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + title: options.title, + description: options.description, + assignee_ids: options.assignee_ids, + milestone_id: options.milestone_id, + labels: options.labels?.join(',') + }) + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabIssueSchema.parse(await response.json()); +} + +async function createMergeRequest( + projectId: string, + options: z.infer +): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/merge_requests`, + { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + title: options.title, + description: options.description, + source_branch: options.source_branch, + target_branch: options.target_branch, + allow_collaboration: options.allow_collaboration, + draft: options.draft + }) + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabMergeRequestSchema.parse(await response.json()); +} + +async function createOrUpdateFile( + projectId: string, + filePath: string, + content: string, + commitMessage: string, + branch: string, + previousPath?: string +): Promise { + const encodedPath = encodeURIComponent(filePath); + const url = `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/files/${encodedPath}`; + + const body = { + branch, + content, + commit_message: commitMessage, + ...(previousPath ? { previous_path: previousPath } : {}) + }; + + // Check if file exists + let method = "POST"; + try { + await getFileContents(projectId, filePath, branch); + method = "PUT"; + } catch (error) { + // File doesn't exist, use POST + } + + const response = await fetch(url, { + method, + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify(body) + }); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabCreateUpdateFileResponseSchema.parse(await response.json()); +} + +async function createTree( + projectId: string, + files: FileOperation[], + ref?: string +): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/tree`, + { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + files: files.map(file => ({ + file_path: file.path, + content: file.content + })), + ...(ref ? { ref } : {}) + }) + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabTreeSchema.parse(await response.json()); +} + +async function createCommit( + projectId: string, + message: string, + branch: string, + actions: FileOperation[] +): Promise { + const response = await fetch( + `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/repository/commits`, + { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + branch, + commit_message: message, + actions: actions.map(action => ({ + action: "create", + file_path: action.path, + content: action.content + })) + }) + } + ); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabCommitSchema.parse(await response.json()); +} + +async function searchProjects( + query: string, + page: number = 1, + perPage: number = 20 +): Promise { + const url = new URL(`${GITLAB_API_URL}/projects`); + url.searchParams.append("search", query); + url.searchParams.append("page", page.toString()); + url.searchParams.append("per_page", perPage.toString()); + + const response = await fetch(url.toString(), { + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}` + } + }); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + const projects = await response.json(); + return GitLabSearchResponseSchema.parse({ + count: parseInt(response.headers.get("X-Total") || "0"), + items: projects + }); +} + +async function createRepository( + options: z.infer +): Promise { + const response = await fetch(`${GITLAB_API_URL}/projects`, { + method: "POST", + headers: { + "Authorization": `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + name: options.name, + description: options.description, + visibility: options.visibility, + initialize_with_readme: options.initialize_with_readme + }) + }); + + if (!response.ok) { + throw new Error(`GitLab API error: ${response.statusText}`); + } + + return GitLabRepositorySchema.parse(await response.json()); +} + +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "create_or_update_file", + description: "Create or update a single file in a GitLab project", + inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema) + }, + { + name: "search_repositories", + description: "Search for GitLab projects", + inputSchema: zodToJsonSchema(SearchRepositoriesSchema) + }, + { + name: "create_repository", + description: "Create a new GitLab project", + inputSchema: zodToJsonSchema(CreateRepositorySchema) + }, + { + name: "get_file_contents", + description: "Get the contents of a file or directory from a GitLab project", + inputSchema: zodToJsonSchema(GetFileContentsSchema) + }, + { + name: "push_files", + description: "Push multiple files to a GitLab project in a single commit", + inputSchema: zodToJsonSchema(PushFilesSchema) + }, + { + name: "create_issue", + description: "Create a new issue in a GitLab project", + inputSchema: zodToJsonSchema(CreateIssueSchema) + }, + { + name: "create_merge_request", + description: "Create a new merge request in a GitLab project", + inputSchema: zodToJsonSchema(CreateMergeRequestSchema) + }, + { + name: "fork_repository", + description: "Fork a GitLab project to your account or specified namespace", + inputSchema: zodToJsonSchema(ForkRepositorySchema) + }, + { + name: "create_branch", + description: "Create a new branch in a GitLab project", + inputSchema: zodToJsonSchema(CreateBranchSchema) + } + ] + }; +}); + +server.setRequestHandler(CallToolRequestSchema, async (request) => { + try { + if (!request.params.arguments) { + throw new Error("Arguments are required"); + } + + switch (request.params.name) { + case "fork_repository": { + const args = ForkRepositorySchema.parse(request.params.arguments); + const fork = await forkProject(args.project_id, args.namespace); + return { toolResult: fork }; + } + + case "create_branch": { + const args = CreateBranchSchema.parse(request.params.arguments); + let ref = args.ref; + if (!ref) { + ref = await getDefaultBranchRef(args.project_id); + } + + const branch = await createBranch(args.project_id, { + name: args.branch, + ref + }); + + return { toolResult: branch }; + } + + case "search_repositories": { + const args = SearchRepositoriesSchema.parse(request.params.arguments); + const results = await searchProjects(args.search, args.page, args.per_page); + return { toolResult: results }; + } + + case "create_repository": { + const args = CreateRepositorySchema.parse(request.params.arguments); + const repository = await createRepository(args); + return { toolResult: repository }; + } + + case "get_file_contents": { + const args = GetFileContentsSchema.parse(request.params.arguments); + const contents = await getFileContents(args.project_id, args.file_path, args.ref); + return { toolResult: contents }; + } + + case "create_or_update_file": { + const args = CreateOrUpdateFileSchema.parse(request.params.arguments); + const result = await createOrUpdateFile( + args.project_id, + args.file_path, + args.content, + args.commit_message, + args.branch, + args.previous_path + ); + return { toolResult: result }; + } + + case "push_files": { + const args = PushFilesSchema.parse(request.params.arguments); + const result = await createCommit( + args.project_id, + args.commit_message, + args.branch, + args.files.map(f => ({ path: f.file_path, content: f.content })) + ); + return { toolResult: result }; + } + + case "create_issue": { + const args = CreateIssueSchema.parse(request.params.arguments); + const { project_id, ...options } = args; + const issue = await createIssue(project_id, options); + return { toolResult: issue }; + } + + case "create_merge_request": { + const args = CreateMergeRequestSchema.parse(request.params.arguments); + const { project_id, ...options } = args; + const mergeRequest = await createMergeRequest(project_id, options); + return { toolResult: mergeRequest }; + } + + default: + throw new Error(`Unknown tool: ${request.params.name}`); + } + } catch (error) { + if (error instanceof z.ZodError) { + throw new Error(`Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`); + } + throw error; + } +}); + +async function runServer() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("GitLab MCP Server running on stdio"); +} + +runServer().catch((error) => { + console.error("Fatal error in main():", error); + process.exit(1); +}); \ No newline at end of file diff --git a/src/gitlab/package-lock.json b/src/gitlab/package-lock.json new file mode 100644 index 00000000..921aad1e --- /dev/null +++ b/src/gitlab/package-lock.json @@ -0,0 +1,551 @@ +{ + "name": "@modelcontextprotocol/server-gitlab", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "packages": { + "": { + "name": "@modelcontextprotocol/server-gitlab", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "@types/node-fetch": "^2.6.12", + "node-fetch": "^3.3.2" + }, + "bin": { + "mcp-server-gitlab": "dist/index.js" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", + "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "node_modules/@types/node": { + "version": "22.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz", + "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shx": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", + "integrity": "sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3", + "shelljs": "^0.8.5" + }, + "bin": { + "shx": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/src/gitlab/package.json b/src/gitlab/package.json new file mode 100644 index 00000000..b5632a87 --- /dev/null +++ b/src/gitlab/package.json @@ -0,0 +1,31 @@ +{ + "name": "@modelcontextprotocol/server-gitlab", + "version": "0.0.1", + "description": "MCP server for using the GitLab API", + "license": "MIT", + "author": "GitLab, PBC (https://gitlab.com)", + "homepage": "https://modelcontextprotocol.io", + "bugs": "https://github.com/modelcontextprotocol/servers/issues", + "type": "module", + "bin": { + "mcp-server-gitlab": "dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc && shx chmod +x dist/*.js", + "prepare": "npm run build", + "watch": "tsc --watch" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "@types/node-fetch": "^2.6.12", + "node-fetch": "^3.3.2", + "zod-to-json-schema": "^3.23.5" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } +} diff --git a/src/gitlab/schemas.ts b/src/gitlab/schemas.ts new file mode 100644 index 00000000..108c190b --- /dev/null +++ b/src/gitlab/schemas.ts @@ -0,0 +1,325 @@ +import { z } from 'zod'; + +// Base schemas for common types +export const GitLabAuthorSchema = z.object({ + name: z.string(), + email: z.string(), + date: z.string() +}); + +// Repository related schemas +export const GitLabOwnerSchema = z.object({ + username: z.string(), // Changed from login to match GitLab API + id: z.number(), + avatar_url: z.string(), + web_url: z.string(), // Changed from html_url to match GitLab API + name: z.string(), // Added as GitLab includes full name + state: z.string() // Added as GitLab includes user state +}); + +export const GitLabRepositorySchema = z.object({ + id: z.number(), + name: z.string(), + path_with_namespace: z.string(), // Changed from full_name to match GitLab API + visibility: z.string(), // Changed from private to match GitLab API + owner: GitLabOwnerSchema, + web_url: z.string(), // Changed from html_url to match GitLab API + description: z.string().nullable(), + fork: z.boolean(), + ssh_url_to_repo: z.string(), // Changed from ssh_url to match GitLab API + http_url_to_repo: z.string(), // Changed from clone_url to match GitLab API + created_at: z.string(), + last_activity_at: z.string(), // Changed from updated_at to match GitLab API + default_branch: z.string() +}); + +// File content schemas +export const GitLabFileContentSchema = z.object({ + file_name: z.string(), // Changed from name to match GitLab API + file_path: z.string(), // Changed from path to match GitLab API + size: z.number(), + encoding: z.string(), + content: z.string(), + content_sha256: z.string(), // Changed from sha to match GitLab API + ref: z.string(), // Added as GitLab requires branch reference + blob_id: z.string(), // Added to match GitLab API + last_commit_id: z.string() // Added to match GitLab API +}); + +export const GitLabDirectoryContentSchema = z.object({ + name: z.string(), + path: z.string(), + type: z.string(), + mode: z.string(), + id: z.string(), // Changed from sha to match GitLab API + web_url: z.string() // Changed from html_url to match GitLab API +}); + +export const GitLabContentSchema = z.union([ + GitLabFileContentSchema, + z.array(GitLabDirectoryContentSchema) +]); + +// Operation schemas +export const FileOperationSchema = z.object({ + path: z.string(), + content: z.string() +}); + +// Tree and commit schemas +export const GitLabTreeEntrySchema = z.object({ + id: z.string(), // Changed from sha to match GitLab API + name: z.string(), + type: z.enum(['blob', 'tree']), + path: z.string(), + mode: z.string() +}); + +export const GitLabTreeSchema = z.object({ + id: z.string(), // Changed from sha to match GitLab API + tree: z.array(GitLabTreeEntrySchema) +}); + +export const GitLabCommitSchema = z.object({ + id: z.string(), // Changed from sha to match GitLab API + short_id: z.string(), // Added to match GitLab API + title: z.string(), // Changed from message to match GitLab API + author_name: z.string(), + author_email: z.string(), + authored_date: z.string(), + committer_name: z.string(), + committer_email: z.string(), + committed_date: z.string(), + web_url: z.string(), // Changed from html_url to match GitLab API + parent_ids: z.array(z.string()) // Changed from parents to match GitLab API +}); + +// Reference schema +export const GitLabReferenceSchema = z.object({ + name: z.string(), // Changed from ref to match GitLab API + commit: z.object({ + id: z.string(), // Changed from sha to match GitLab API + web_url: z.string() // Changed from url to match GitLab API + }) +}); + +// Input schemas for operations +export const CreateRepositoryOptionsSchema = z.object({ + name: z.string(), + description: z.string().optional(), + visibility: z.enum(['private', 'internal', 'public']).optional(), // Changed from private to match GitLab API + initialize_with_readme: z.boolean().optional() // Changed from auto_init to match GitLab API +}); + +export const CreateIssueOptionsSchema = z.object({ + title: z.string(), + description: z.string().optional(), // Changed from body to match GitLab API + assignee_ids: z.array(z.number()).optional(), // Changed from assignees to match GitLab API + milestone_id: z.number().optional(), // Changed from milestone to match GitLab API + labels: z.array(z.string()).optional() +}); + +export const CreateMergeRequestOptionsSchema = z.object({ // Changed from CreatePullRequestOptionsSchema + title: z.string(), + description: z.string().optional(), // Changed from body to match GitLab API + source_branch: z.string(), // Changed from head to match GitLab API + target_branch: z.string(), // Changed from base to match GitLab API + allow_collaboration: z.boolean().optional(), // Changed from maintainer_can_modify to match GitLab API + draft: z.boolean().optional() +}); + +export const CreateBranchOptionsSchema = z.object({ + name: z.string(), // Changed from ref to match GitLab API + ref: z.string() // The source branch/commit for the new branch +}); + +// Response schemas for operations +export const GitLabCreateUpdateFileResponseSchema = z.object({ + file_path: z.string(), + branch: z.string(), + commit_id: z.string(), // Changed from sha to match GitLab API + content: GitLabFileContentSchema.optional() +}); + +export const GitLabSearchResponseSchema = z.object({ + count: z.number(), // Changed from total_count to match GitLab API + items: z.array(GitLabRepositorySchema) +}); + +// Fork related schemas +export const GitLabForkParentSchema = z.object({ + name: z.string(), + path_with_namespace: z.string(), // Changed from full_name to match GitLab API + owner: z.object({ + username: z.string(), // Changed from login to match GitLab API + id: z.number(), + avatar_url: z.string() + }), + web_url: z.string() // Changed from html_url to match GitLab API +}); + +export const GitLabForkSchema = GitLabRepositorySchema.extend({ + forked_from_project: GitLabForkParentSchema // Changed from parent to match GitLab API +}); + +// Issue related schemas +export const GitLabLabelSchema = z.object({ + id: z.number(), + name: z.string(), + color: z.string(), + description: z.string().optional() +}); + +export const GitLabUserSchema = z.object({ + username: z.string(), // Changed from login to match GitLab API + id: z.number(), + name: z.string(), + avatar_url: z.string(), + web_url: z.string() // Changed from html_url to match GitLab API +}); + +export const GitLabMilestoneSchema = z.object({ + id: z.number(), + iid: z.number(), // Added to match GitLab API + title: z.string(), + description: z.string(), + state: z.string(), + web_url: z.string() // Changed from html_url to match GitLab API +}); + +export const GitLabIssueSchema = z.object({ + id: z.number(), + iid: z.number(), // Added to match GitLab API + project_id: z.number(), // Added to match GitLab API + title: z.string(), + description: z.string(), // Changed from body to match GitLab API + state: z.string(), + author: GitLabUserSchema, + assignees: z.array(GitLabUserSchema), + labels: z.array(GitLabLabelSchema), + milestone: GitLabMilestoneSchema.nullable(), + created_at: z.string(), + updated_at: z.string(), + closed_at: z.string().nullable(), + web_url: z.string() // Changed from html_url to match GitLab API +}); + +// Merge Request related schemas (equivalent to Pull Request) +export const GitLabMergeRequestDiffRefSchema = z.object({ + base_sha: z.string(), + head_sha: z.string(), + start_sha: z.string() +}); + +export const GitLabMergeRequestSchema = z.object({ + id: z.number(), + iid: z.number(), // Added to match GitLab API + project_id: z.number(), // Added to match GitLab API + title: z.string(), + description: z.string(), // Changed from body to match GitLab API + state: z.string(), + merged: z.boolean(), + author: GitLabUserSchema, + assignees: z.array(GitLabUserSchema), + source_branch: z.string(), // Changed from head to match GitLab API + target_branch: z.string(), // Changed from base to match GitLab API + diff_refs: GitLabMergeRequestDiffRefSchema, + web_url: z.string(), // Changed from html_url to match GitLab API + created_at: z.string(), + updated_at: z.string(), + merged_at: z.string().nullable(), + closed_at: z.string().nullable(), + merge_commit_sha: z.string().nullable() +}); + +// API Operation Parameter Schemas +const ProjectParamsSchema = z.object({ + project_id: z.string().describe("Project ID or URL-encoded path") // Changed from owner/repo to match GitLab API +}); + +export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({ + file_path: z.string().describe("Path where to create/update the file"), + content: z.string().describe("Content of the file"), + commit_message: z.string().describe("Commit message"), + branch: z.string().describe("Branch to create/update the file in"), + previous_path: z.string().optional() + .describe("Path of the file to move/rename") +}); + +export const SearchRepositoriesSchema = z.object({ + search: z.string().describe("Search query"), // Changed from query to match GitLab API + 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 const CreateRepositorySchema = z.object({ + name: z.string().describe("Repository name"), + description: z.string().optional().describe("Repository description"), + visibility: z.enum(['private', 'internal', 'public']).optional() + .describe("Repository visibility level"), + initialize_with_readme: z.boolean().optional() + .describe("Initialize with README.md") +}); + +export const GetFileContentsSchema = ProjectParamsSchema.extend({ + file_path: z.string().describe("Path to the file or directory"), + ref: z.string().optional().describe("Branch/tag/commit to get contents from") +}); + +export const PushFilesSchema = ProjectParamsSchema.extend({ + branch: z.string().describe("Branch to push to"), + files: z.array(z.object({ + file_path: z.string().describe("Path where to create the file"), + content: z.string().describe("Content of the file") + })).describe("Array of files to push"), + commit_message: z.string().describe("Commit message") +}); + +export const CreateIssueSchema = ProjectParamsSchema.extend({ + title: z.string().describe("Issue title"), + description: z.string().optional().describe("Issue description"), + assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign"), + labels: z.array(z.string()).optional().describe("Array of label names"), + milestone_id: z.number().optional().describe("Milestone ID to assign") +}); + +export const CreateMergeRequestSchema = ProjectParamsSchema.extend({ + title: z.string().describe("Merge request title"), + description: z.string().optional().describe("Merge request description"), + source_branch: z.string().describe("Branch containing changes"), + target_branch: z.string().describe("Branch to merge into"), + draft: z.boolean().optional().describe("Create as draft merge request"), + allow_collaboration: z.boolean().optional() + .describe("Allow commits from upstream members") +}); + +export const ForkRepositorySchema = ProjectParamsSchema.extend({ + namespace: z.string().optional() + .describe("Namespace to fork to (full path)") +}); + +export const CreateBranchSchema = ProjectParamsSchema.extend({ + branch: z.string().describe("Name for the new branch"), + ref: z.string().optional() + .describe("Source branch/commit for new branch") +}); + +// Export types +export type GitLabAuthor = z.infer; +export type GitLabFork = z.infer; +export type GitLabIssue = z.infer; +export type GitLabMergeRequest = z.infer; +export type GitLabRepository = z.infer; +export type GitLabFileContent = z.infer; +export type GitLabDirectoryContent = z.infer; +export type GitLabContent = z.infer; +export type FileOperation = z.infer; +export type GitLabTree = z.infer; +export type GitLabCommit = z.infer; +export type GitLabReference = z.infer; +export type CreateRepositoryOptions = z.infer; +export type CreateIssueOptions = z.infer; +export type CreateMergeRequestOptions = z.infer; +export type CreateBranchOptions = z.infer; +export type GitLabCreateUpdateFileResponse = z.infer; +export type GitLabSearchResponse = z.infer; \ No newline at end of file diff --git a/src/gitlab/tsconfig.json b/src/gitlab/tsconfig.json new file mode 100644 index 00000000..4d33cae1 --- /dev/null +++ b/src/gitlab/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "include": [ + "./**/*.ts" + ] + } + \ No newline at end of file From cfdf46fa2fe91cab27cde8e1ac73a3dad5f5b58b Mon Sep 17 00:00:00 2001 From: Taylor McCaslin Date: Mon, 25 Nov 2024 21:57:15 -0700 Subject: [PATCH 2/2] update versions to match --- src/gitlab/index.ts | 2 +- src/gitlab/package-lock.json | 4 ++-- src/gitlab/package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gitlab/index.ts b/src/gitlab/index.ts index 9da4092c..e246af4d 100644 --- a/src/gitlab/index.ts +++ b/src/gitlab/index.ts @@ -48,7 +48,7 @@ import { const server = new Server({ name: "gitlab-mcp-server", - version: "0.1.0", + version: "0.5.1", }, { capabilities: { tools: {} diff --git a/src/gitlab/package-lock.json b/src/gitlab/package-lock.json index 921aad1e..9cb28696 100644 --- a/src/gitlab/package-lock.json +++ b/src/gitlab/package-lock.json @@ -1,12 +1,12 @@ { "name": "@modelcontextprotocol/server-gitlab", - "version": "0.0.1", + "version": "0.5.1", "lockfileVersion": 1, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/server-gitlab", - "version": "0.0.1", + "version": "0.5.1", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "0.6.0", diff --git a/src/gitlab/package.json b/src/gitlab/package.json index b5632a87..e4b35fff 100644 --- a/src/gitlab/package.json +++ b/src/gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/server-gitlab", - "version": "0.0.1", + "version": "0.5.1", "description": "MCP server for using the GitLab API", "license": "MIT", "author": "GitLab, PBC (https://gitlab.com)",