UNPKG

@cyphbt/gitlab-mcp-server

Version:

GitLab MCP Server with tag and merge request functionality - supports both Token and SSH modes

437 lines 18 kB
#!/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 { GitLabClient } from './gitlab-client.js'; import { SSHGitLabClient } from './ssh-gitlab-client.js'; import { GitDetector } from './git-detector.js'; import dotenv from 'dotenv'; // 加载环境变量 dotenv.config(); // 全局变量存储 GitLab 配置和客户端 let gitlabConfig = null; let gitlabClient = null; let useSSHMode = false; let isInitializing = false; let initializationPromise = null; // 获取当前分支 function getCurrentBranch() { try { const { execSync } = require('child_process'); return execSync('git branch --show-current', { encoding: 'utf8' }).trim(); } catch (error) { return null; } } // 初始化 GitLab 配置 async function initializeGitLabConfig() { // 如果正在初始化,等待完成 if (isInitializing && initializationPromise) { return initializationPromise; } // 如果已经初始化完成,直接返回 if (gitlabClient) { return; } // 开始初始化 isInitializing = true; initializationPromise = (async () => { try { // 🔧 自动切换到 Git 根目录 const gitRoot = GitDetector.changeToGitRoot(); if (gitRoot) { console.error(`✅ 已切换到 Git 根目录,当前路径: ${gitRoot}`); } else { console.error(`⚠️ 未检测到 Git 仓库,将使用当前目录: ${process.cwd()}`); } const token = process.env.GITLAB_TOKEN; // 检查是否使用 SSH 模式 if (!token) { console.error('🔑 未设置 GITLAB_TOKEN,尝试使用 SSH 模式...'); await initializeSSHMode(); return; } // Token 模式 console.error('🔑 使用 Token 模式...'); await initializeTokenMode(token); } catch (error) { console.error(`❌ GitLab 配置初始化失败: ${error instanceof Error ? error.message : '未知错误'}`); throw error; } finally { isInitializing = false; } })(); return initializationPromise; } // 初始化 Token 模式 async function initializeTokenMode(token) { // 🔧 自动切换到 Git 根目录(如果还没有切换) const currentGitRoot = GitDetector.findGitRoot(); if (currentGitRoot && process.cwd() !== currentGitRoot) { GitDetector.changeToGitRoot(); } // 优先使用手动配置 const manualUrl = process.env.GITLAB_URL; const manualProjectId = process.env.GITLAB_PROJECT_ID; if (manualUrl && manualProjectId) { // 使用手动配置 const currentBranch = getCurrentBranch(); gitlabConfig = { url: manualUrl, token, projectId: manualProjectId, defaultBranch: currentBranch || 'main', }; gitlabClient = new GitLabClient(gitlabConfig); useSSHMode = false; console.error(`✅ Token 模式 - 使用手动配置: ${manualUrl} (ID: ${manualProjectId})`); console.error(`📍 当前分支: ${currentBranch || 'main'}`); return; } // 自动检测 GitLab 配置 const detectedConfig = await GitDetector.detectGitLabConfig(token); if (!detectedConfig) { throw new Error('无法检测 GitLab 配置,请在 .env 文件中手动设置 GITLAB_URL 和 GITLAB_PROJECT_ID'); } gitlabConfig = { url: detectedConfig.gitlabUrl, token, projectId: detectedConfig.projectId, defaultBranch: detectedConfig.currentBranch, }; gitlabClient = new GitLabClient(gitlabConfig); useSSHMode = false; console.error(`✅ Token 模式 - 自动检测到 GitLab 项目: ${detectedConfig.gitlabUrl} (ID: ${detectedConfig.projectId})`); console.error(`📍 当前分支: ${detectedConfig.currentBranch}`); } // 初始化 SSH 模式 async function initializeSSHMode() { // 🔧 自动切换到 Git 根目录(如果还没有切换) const currentGitRoot = GitDetector.findGitRoot(); if (currentGitRoot && process.cwd() !== currentGitRoot) { GitDetector.changeToGitRoot(); } // 优先使用手动配置 const manualUrl = process.env.GITLAB_URL; const manualProjectId = process.env.GITLAB_PROJECT_ID; if (manualUrl && manualProjectId) { // 使用手动配置 const currentBranch = getCurrentBranch(); gitlabConfig = { url: manualUrl, token: '', // SSH 模式不需要 token projectId: manualProjectId, defaultBranch: currentBranch || 'main', }; gitlabClient = new SSHGitLabClient(gitlabConfig); useSSHMode = true; console.error(`✅ SSH 模式 - 使用手动配置: ${manualUrl} (ID: ${manualProjectId})`); console.error(`📍 当前分支: ${currentBranch || 'main'}`); console.error(`🔧 使用 Git 命令和 GitLab CLI 进行操作`); return; } // 检测 Git 仓库信息 const gitInfo = GitDetector.detectGitInfo(); if (!gitInfo.isGitRepo) { throw new Error('当前目录不是 Git 仓库'); } const gitlabInfo = GitDetector.parseGitLabInfo(gitInfo); if (!gitlabInfo) { throw new Error('无法解析 GitLab 仓库信息,请在 .env 文件中手动设置 GITLAB_URL 和 GITLAB_PROJECT_ID'); } // 测试 SSH 连接 try { const { execSync } = await import('child_process'); execSync('git ls-remote origin', { stdio: 'ignore' }); } catch (error) { throw new Error('SSH 连接失败,请检查 SSH 密钥配置'); } gitlabConfig = { url: gitlabInfo.gitlabUrl, token: '', // SSH 模式不需要 token projectId: gitlabInfo.projectPath, // 使用项目路径作为 ID defaultBranch: gitlabInfo.currentBranch, }; gitlabClient = new SSHGitLabClient(gitlabConfig); useSSHMode = true; console.error(`✅ SSH 模式 - 检测到 GitLab 项目: ${gitlabInfo.gitlabUrl}/${gitlabInfo.projectPath}`); console.error(`📍 当前分支: ${gitlabInfo.currentBranch}`); console.error(`🔧 使用 Git 命令和 GitLab CLI 进行操作`); } // 定义工具 const tools = [ { name: 'gitlab_get_project_info', description: '获取当前 GitLab 项目的基本信息(支持 Token 和 SSH 模式)', inputSchema: { type: 'object', properties: {}, required: [], }, }, { name: 'gitlab_get_latest_tags', description: '获取 GitLab 项目的最新标签列表(支持 Token 和 SSH 模式)', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: '返回的标签数量限制,默认为 10', default: 10, }, }, required: [], }, }, { name: 'gitlab_create_tag', description: '在 GitLab 项目中创建新标签(支持 Token 和 SSH 模式)', inputSchema: { type: 'object', properties: { tag_name: { type: 'string', description: '标签名称(例如:v1.0.0)', }, ref: { type: 'string', description: '标签指向的分支或提交(例如:main, develop, commit-hash)', }, message: { type: 'string', description: '标签消息(可选)', }, release_description: { type: 'string', description: '发布描述(可选,SSH 模式需要 GitLab CLI)', }, }, required: ['tag_name', 'ref'], }, }, { name: 'gitlab_create_merge_request', description: '在 GitLab 项目中创建合并请求(SSH 模式需要 GitLab CLI)', inputSchema: { type: 'object', properties: { source_branch: { type: 'string', description: '源分支名称', }, target_branch: { type: 'string', description: '目标分支名称', }, title: { type: 'string', description: '合并请求标题', }, description: { type: 'string', description: '合并请求描述(可选)', }, remove_source_branch: { type: 'boolean', description: '合并后是否删除源分支(可选)', default: false, }, squash: { type: 'boolean', description: '是否压缩提交(可选)', default: false, }, }, required: ['source_branch', 'target_branch', 'title'], }, }, ]; // 创建 MCP 服务器 const server = new Server({ name: 'gitlab-mcp-server', version: '1.0.0', }); // 处理工具列表请求 server.setRequestHandler(ListToolsRequestSchema, async () => { // 立即返回工具列表,不等待 GitLab 初始化 return { tools, }; }); // 处理工具调用请求 server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; // 等待 GitLab 客户端初始化完成 if (!gitlabClient) { try { console.error('⏳ 等待 GitLab 客户端初始化...'); await initializeGitLabConfig(); if (!gitlabClient) { return { content: [ { type: 'text', text: '❌ GitLab 客户端初始化失败,请检查配置\n\n' + '请确保在 .env 文件中正确设置了以下配置之一:\n' + '1. Token 模式: GITLAB_TOKEN=your_actual_token\n' + '2. SSH 模式: GITLAB_URL=https://gitlab.com 和 GITLAB_PROJECT_ID=your_project_id', }, ], isError: true, }; } } catch (error) { return { content: [ { type: 'text', text: `❌ GitLab 客户端初始化失败: ${error instanceof Error ? error.message : '未知错误'}\n\n` + '请检查以下配置:\n' + '1. 确保 .env 文件存在并包含正确的配置\n' + '2. Token 模式: 设置有效的 GITLAB_TOKEN\n' + '3. SSH 模式: 设置 GITLAB_URL 和 GITLAB_PROJECT_ID,并确保 SSH 连接正常', }, ], isError: true, }; } } try { switch (name) { case 'gitlab_get_project_info': { const projectInfo = await gitlabClient.getProjectInfo(); const gitInfo = GitDetector.detectGitInfo(); const gitConfig = GitDetector.getGitConfig(); const recentCommits = GitDetector.getRecentCommits(); return { content: [ { type: 'text', text: `📋 **项目信息**\n\n` + `- **项目名称**: ${projectInfo.name}\n` + `- **项目描述**: ${projectInfo.description || '无描述'}\n` + `- **GitLab URL**: ${gitlabConfig?.url}\n` + `- **项目 ID**: ${projectInfo.id}\n` + `- **项目路径**: ${projectInfo.path_with_namespace}\n` + `- **当前分支**: ${gitInfo.currentBranch}\n` + `- **默认分支**: ${projectInfo.default_branch}\n` + `- **可见性**: ${projectInfo.visibility}\n` + `- **Web URL**: ${projectInfo.web_url}\n\n` + `🔧 **连接模式**: ${useSSHMode ? 'SSH 模式' : 'Token 模式'}\n` + `${useSSHMode ? '- 使用 Git 命令和 GitLab CLI 进行操作\n' : '- 使用 GitLab API 进行操作\n'}` + `\n👤 **Git 配置**\n` + `- **用户名**: ${gitConfig.user.name}\n` + `- **邮箱**: ${gitConfig.user.email}\n\n` + `📝 **最近提交**\n` + `${recentCommits.map(commit => `- **${commit.shortHash}** ${commit.message} (${commit.author}, ${commit.date})`).join('\n')}`, }, ], }; } case 'gitlab_get_latest_tags': { const limit = args?.limit || 10; const tags = await gitlabClient.getLatestTags(limit); return { content: [ { type: 'text', text: `获取到 ${tags.length} 个最新标签:\n\n${tags.map(tag => `- **${tag.name}**${tag.message ? `: ${tag.message}` : ''}${tag.commit ? ` (${tag.commit.short_id})` : ''}`).join('\n')}`, }, ], }; } case 'gitlab_create_tag': { if (!args?.tag_name || !args?.ref) { throw new Error('缺少必要的参数: tag_name 和 ref'); } const tagRequest = { tag_name: args.tag_name, ref: args.ref, message: args.message, release_description: args.release_description, }; const tag = await gitlabClient.createTag(tagRequest); return { content: [ { type: 'text', text: `✅ 成功创建标签 **${tag.name}**\n\n` + `- 标签名称: ${tag.name}\n` + `- 指向分支: ${args.ref}\n` + `${tag.message ? `- 消息: ${tag.message}\n` : ''}` + `${tag.release?.description ? `- 发布描述: ${tag.release.description}\n` : ''}`, }, ], }; } case 'gitlab_create_merge_request': { if (!args?.source_branch || !args?.target_branch || !args?.title) { throw new Error('缺少必要的参数: source_branch, target_branch 和 title'); } const mrRequest = { source_branch: args.source_branch, target_branch: args.target_branch, title: args.title, description: args.description, remove_source_branch: args.remove_source_branch, squash: args.squash, }; const mr = await gitlabClient.createMergeRequest(mrRequest); return { content: [ { type: 'text', text: `✅ 成功创建合并请求 **${mr.title}**\n\n` + `- MR ID: !${mr.iid}\n` + `- 标题: ${mr.title}\n` + `- 源分支: ${mr.source_branch}\n` + `- 目标分支: ${mr.target_branch}\n` + `- 状态: ${mr.state}\n` + `- 链接: ${mr.web_url}\n` + `${mr.description ? `- 描述: ${mr.description}\n` : ''}`, }, ], }; } default: throw new Error(`未知工具: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `❌ 操作失败: ${error instanceof Error ? error.message : '未知错误'}`, }, ], isError: true, }; } }); // 启动服务器 async function main() { try { // 立即启动服务器,不等待 GitLab 配置初始化 const transport = new StdioServerTransport(); await server.connect(transport); console.error('GitLab MCP 服务器已启动'); // 在后台初始化 GitLab 配置 initializeGitLabConfig().catch(error => { console.error(`⚠️ GitLab 配置初始化失败: ${error instanceof Error ? error.message : '未知错误'}`); }); } catch (error) { console.error(`❌ 服务器启动失败: ${error instanceof Error ? error.message : '未知错误'}`); process.exit(1); } } main().catch(console.error); //# sourceMappingURL=index.js.map