// 引入必要的模块 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // 创建 MCP 服务器实例 // 为其设置一个名字和版本 const server = new McpServer({ name: "gitlab-mcp-server", version: "1.0.0", }); /** * GitLab API 客户端配置 * 用于连接到GitLab实例并执行操作 */ const gitlabApiClient = { baseUrl: process.env.GITLAB_API_BASE_URL || "https://gitlab.com/api/v4", token: process.env.GITLAB_ACCESS_TOKEN, async makeRequest(endpoint, options = {}) { if (!this.token) { throw new Error("GITLAB_ACCESS_TOKEN 环境变量未设置"); } const url = `${this.baseUrl}${endpoint}`; const defaultOptions = { method: "GET", headers: { "PRIVATE-TOKEN": this.token, "Content-Type": "application/json", }, }; const requestOptions = { ...defaultOptions, ...options }; const response = await fetch(url, requestOptions); if (!response.ok) { throw new Error(`GitLab API请求失败: ${response.status} ${response.statusText}`); } return await response.json(); }, /** * 创建Merge Request */ async createMergeRequest(projectId, sourceBranch, targetBranch, title, description = "", assigneeId, reviewerIds) { const body = { id: projectId, source_branch: sourceBranch, target_branch: targetBranch, title, description, ...(assigneeId !== undefined && { assignee_id: assigneeId }), ...(reviewerIds !== undefined && { reviewer_ids: reviewerIds }), }; return await this.makeRequest( `/projects/${projectId}/merge_requests`, { method: "POST", body: JSON.stringify(body), headers: { "PRIVATE-TOKEN": this.token, "Content-Type": "application/json", }, } ); }, /** * 合并Merge Request */ async mergeMergeRequest(projectId, mrIid, mergeCommitMessage = "", shouldRemoveSourceBranch = false, mergeWhenPipelineSucceeds = false) { const body = { id: projectId, merge_request_iid: mrIid, ...(mergeCommitMessage && { merge_commit_message: mergeCommitMessage }), remove_source_branch: shouldRemoveSourceBranch, merge_when_pipeline_succeeds: mergeWhenPipelineSucceeds, }; return await this.makeRequest( `/projects/${projectId}/merge_requests/${mrIid}/merge`, { method: "PUT", body: JSON.stringify(body), headers: { "PRIVATE-TOKEN": this.token, "Content-Type": "application/json", }, } ); }, /** * 获取Merge Request详情 */ async getMergeRequest(projectId, mrIid) { return await this.makeRequest(`/projects/${projectId}/merge_requests/${mrIid}`); }, /** * 关闭Merge Request */ async closeMergeRequest(projectId, mrIid) { return await this.makeRequest( `/projects/${projectId}/merge_requests/${mrIid}`, { method: "PUT", body: JSON.stringify({ state_event: "close" }), headers: { "PRIVATE-TOKEN": this.token, "Content-Type": "application/json", }, } ); } }; /** * 创建Merge Request工具 */ server.registerTool( "create_merge_request", { description: "在指定的GitLab项目中创建一个新的Merge Request", inputSchema: z.object({ projectId: z.string().describe("GitLab项目的ID或URL编码的路径"), sourceBranch: z.string().describe("源分支名称"), targetBranch: z.string().describe("目标分支名称"), title: z.string().describe("Merge Request的标题"), description: z.string().optional().describe("Merge Request的描述"), assigneeId: z.number().optional().describe("负责人ID"), reviewerIds: z.array(z.number()).optional().describe("审阅者ID数组"), }), }, async ({ projectId, sourceBranch, targetBranch, title, description = "", assigneeId, reviewerIds }) => { try { const result = await gitlabApiClient.createMergeRequest( projectId, sourceBranch, targetBranch, title, description, assigneeId, reviewerIds ); return { content: [ { type: "text", text: `成功创建Merge Request: ${result.web_url}\n标题: ${result.title}\n状态: ${result.state}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `创建Merge Request失败: ${error.message}`, }, ], isError: true, }; } } ); /** * 合并Merge Request工具 */ server.registerTool( "merge_merge_request", { description: "合并指定的GitLab Merge Request", inputSchema: z.object({ projectId: z.string().describe("GitLab项目的ID或URL编码的路径"), mrIid: z.number().describe("Merge Request的内部ID"), mergeCommitMessage: z.string().optional().describe("合并提交的消息"), shouldRemoveSourceBranch: z.boolean().optional().describe("是否在合并后删除源分支"), mergeWhenPipelineSucceeds: z.boolean().optional().describe("是否在CI管道成功后合并"), }), }, async ({ projectId, mrIid, mergeCommitMessage = "", shouldRemoveSourceBranch = false, mergeWhenPipelineSucceeds = false }) => { try { const result = await gitlabApiClient.mergeMergeRequest( projectId, mrIid, mergeCommitMessage, shouldRemoveSourceBranch, mergeWhenPipelineSucceeds ); return { content: [ { type: "text", text: `成功合并Merge Request #${mrIid}:\n状态: ${result.state}\n已合并时间: ${result.merged_at}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `合并Merge Request失败: ${error.message}`, }, ], isError: true, }; } } ); /** * 获取Merge Request详情工具 */ server.registerTool( "get_merge_request", { description: "获取指定Merge Request的详细信息", inputSchema: z.object({ projectId: z.string().describe("GitLab项目的ID或URL编码的路径"), mrIid: z.number().describe("Merge Request的内部ID"), }), }, async ({ projectId, mrIid }) => { try { const result = await gitlabApiClient.getMergeRequest(projectId, mrIid); return { content: [ { type: "text", text: `Merge Request #${mrIid} 详情:\n标题: ${result.title}\n作者: ${result.author.name}\n状态: ${result.state}\n源分支: ${result.source_branch}\n目标分支: ${result.target_branch}\n创建时间: ${result.created_at}\n更新时间: ${result.updated_at}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `获取Merge Request详情失败: ${error.message}`, }, ], isError: true, }; } } ); /** * 关闭Merge Request工具 */ server.registerTool( "close_merge_request", { description: "关闭指定的GitLab Merge Request而不合并", inputSchema: z.object({ projectId: z.string().describe("GitLab项目的ID或URL编码的路径"), mrIid: z.number().describe("Merge Request的内部ID"), }), }, async ({ projectId, mrIid }) => { try { const result = await gitlabApiClient.closeMergeRequest(projectId, mrIid); return { content: [ { type: "text", text: `成功关闭Merge Request #${mrIid}:\n标题: ${result.title}\n当前状态: ${result.state}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `关闭Merge Request失败: ${error.message}`, }, ], isError: true, }; } } ); server.registerTool( 'merge_to_test', { description: "把源分支名合并到测试分支,并部署到测试环境,默认用当前分支作为源分支", inputSchema: z.object({ projectId: z.string().describe("GitLab项目的ID"), sourceBranch: z.string().optional().describe("源分支名称,默认使用当前分支"), }), }, async ({ projectId, sourceBranch, }) => { if (!sourceBranch) { // 获取当前分支,用 git branch --show-current 命令 const { execSync } = await import('child_process'); try { sourceBranch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim(); } catch (error) { console.error('执行 git branch --show-current 命令失败:', error); return { content: [ { type: "text", text: `获取当前分支失败: ${error.message}`, }, ], isError: true, }; } } try { const result = await gitlabApiClient.createMergeRequest( projectId, sourceBranch, 'test', `Merge ${sourceBranch} to test`, // 标题 `Automated merge request from ${sourceBranch} to test branch` // 描述 ); const mrIid = result.iid; // 等待3秒钟,让GitLab有时间处理新创建的MR await new Promise(resolve => setTimeout(resolve, 3000)); // 尝试合并MR try { const mergeResult = await gitlabApiClient.mergeMergeRequest( projectId, mrIid ); return { content: [ { type: "text", text: `成功创建并合并Merge Request: ${mergeResult.web_url}\n标题: ${mergeResult.title}\n状态: ${mergeResult.state}\n合并时间: ${mergeResult.merged_at}`, }, ], }; } catch (mergeError) { // 如果合并失败,返回MR创建成功的信息,但提示合并需要审核 return { content: [ { type: "text", text: `成功创建Merge Request,但自动合并失败,请手动审核: ${result.web_url}\n标题: ${result.title}\n状态: ${result.state}\n错误信息: ${mergeError.message}`, }, ], }; } } catch (error) { return { content: [ { type: "text", text: `合并到test分支失败: ${error.message}`, }, ], isError: true, }; } } ) // 启动服务器,使用标准输入/输出(stdio)与客户端通信 async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("GitLab MCP 服务器已通过 stdio 启动"); // 日志输出到 stderr } // 运行主函数,并捕获可能的错误 main().catch((error) => { console.error("服务器运行出现致命错误:", error); process.exit(1); }); export { server };