@cyphbt/gitlab-mcp-server
Version:
GitLab MCP Server with tag and merge request functionality - supports both Token and SSH modes
437 lines • 18 kB
JavaScript
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