diff --git a/README.md b/README.md index 1abe609..e04e7ec 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,490 @@ -# GitLab MCP 服务器 +# GitLab MCP Server -这是一个 Model Context Protocol (MCP) 服务器,专门用于与 GitLab 交互,主要功能包括创建和合并 Merge Request。 +一个支持 GitLab 操作的 Model Context Protocol (MCP) 服务器,主要用于创建和管理 Merge Requests。 -## 功能 +## 功能特性 -- `create_merge_request`: 在指定的 GitLab 项目中创建一个新的 Merge Request -- `merge_merge_request`: 合并指定的 GitLab Merge Request -- `get_merge_request`: 获取指定 Merge Request 的详细信息 -- `close_merge_request`: 关闭指定的 GitLab Merge Request 而不合并 +- ✅ 创建 Merge Request +- ✅ 合并 Merge Request +- ✅ 获取 Merge Request 详情 +- ✅ 关闭 Merge Request +- ✅ 快捷合并到测试分支 +- ✅ 支持 Stdio 和 Streamable HTTP 两种传输方式 +- ✅ 支持通过 npx 直接运行,无需安装 -## 环境变量 +## 快速开始 -- `GITLAB_ACCESS_TOKEN`: 用于访问 GitLab API 的访问令牌 -- `GITLAB_API_BASE_URL`: GitLab API 的基础 URL(默认为 `https://gitlab.com/api/v4`) +### 方式一:使用 npx(推荐,无需安装) -## 安装和运行 +```bash +# 1. 设置环境变量 +export GITLAB_ACCESS_TOKEN="your_gitlab_token_here" -1. 首先,确保你已经安装了 Node.js 和 npm。 -2. 安装依赖: - ```bash - npm install - ``` -3. 设置环境变量: - ```bash - export GITLAB_ACCESS_TOKEN="your_gitlab_access_token" - export GITLAB_API_BASE_URL="https://your-gitlab-instance.com/api/v4" # 可选,仅当使用自托管的 GitLab 时 - ``` -4. 运行 GitLab MCP 服务器: - ```bash - npm run gitlab - ``` +# 2. 直接运行(默认 stdio 模式) +npx gitlab-mcp-server -## 架构 +# 或使用 HTTP 模式 +npx gitlab-mcp-server --transport http --port 3000 -该项目使用一个封装的 `gitlabApiClient` 对象来处理所有与 GitLab API 的交互,这使得代码更模块化和可维护。API 客户端包含以下方法: +# 查看帮助 +npx gitlab-mcp-server --help +``` -- `createMergeRequest`: 创建 Merge Request -- `mergeMergeRequest`: 合并 Merge Request -- `getMergeRequest`: 获取 Merge Request 详情 -- `closeMergeRequest`: 关闭 Merge Request +### 方式二:本地安装运行 -## 工具详情 +```bash +# 1. 克隆或下载项目 +git clone +cd gitlab-mcp-server -### create_merge_request +# 2. 安装依赖 +npm install -创建一个新的 Merge Request。 +# 3. 设置环境变量 +export GITLAB_ACCESS_TOKEN="your_gitlab_token_here" +export GITLAB_API_BASE_URL="https://gitlab.com/api/v4" # 可选 -参数: -- `projectId`: GitLab 项目的 ID 或 URL 编码的路径 -- `sourceBranch`: 源分支名称 -- `targetBranch`: 目标分支名称 -- `title`: Merge Request 的标题 -- `description`: (可选) Merge Request 的描述 -- `assigneeId`: (可选) 负责人 ID -- `reviewerIds`: (可选) 审阅者 ID 数组 +# 4. 运行 +npm start # 默认 stdio 模式 +``` -### merge_merge_request +## 环境变量配置 -合并一个 Merge Request。 +### Windows (PowerShell) +```powershell +# 临时设置(当前会话) +$env:GITLAB_ACCESS_TOKEN="your_token_here" +$env:GITLAB_API_BASE_URL="https://gitlab.com/api/v4" -参数: -- `projectId`: GitLab 项目的 ID 或 URL 编码的路径 -- `mrIid`: Merge Request 的内部 ID -- `mergeCommitMessage`: (可选) 合并提交的消息 -- `shouldRemoveSourceBranch`: (可选) 是否在合并后删除源分支 -- `mergeWhenPipelineSucceeds`: (可选) 是否在 CI 管道成功后合并 +# 永久设置(用户级别) +[System.Environment]::SetEnvironmentVariable('GITLAB_ACCESS_TOKEN', 'your_token_here', 'User') +``` -### get_merge_request +### Linux/Mac +```bash +# 临时设置 +export GITLAB_ACCESS_TOKEN="your_token_here" +export GITLAB_API_BASE_URL="https://gitlab.com/api/v4" -获取 Merge Request 的详细信息。 +# 永久设置(添加到 ~/.bashrc 或 ~/.zshrc) +echo 'export GITLAB_ACCESS_TOKEN="your_token_here"' >> ~/.bashrc +source ~/.bashrc +``` -参数: -- `projectId`: GitLab 项目的 ID 或 URL 编码的路径 -- `mrIid`: Merge Request 的内部 ID +### 如何获取 GitLab Access Token -### close_merge_request +1. 登录 GitLab +2. 进入 **Settings** > **Access Tokens** +3. 创建新的 Personal Access Token +4. 选择 `api` 权限 +5. 复制生成的 token -关闭一个 Merge Request 而不合并。 +## 使用方式 -参数: -- `projectId`: GitLab 项目的 ID 或 URL 编码的路径 -- `mrIid`: Merge Request 的内部 ID +### 通过 npx 运行(推荐) +```bash +# Stdio 模式(默认) +npx gitlab-mcp-server -## 配置 MCP 服务器 +# HTTP 模式 +npx gitlab-mcp-server --transport http --port 3000 -``` json +# 使用简写参数 +npx gitlab-mcp-server -t http -p 8080 + +# 查看帮助 +npx gitlab-mcp-server --help +``` + +### 本地运行 + +```bash +# 默认 stdio 模式 +node index.js +npm start + +# 明确指定 stdio 模式 +node index.js --transport stdio +npm run start:stdio + +# HTTP 模式 +node index.js --transport http --port 3000 +npm run start:http + +# 开发模式(HTTP) +npm run dev:http +``` + +### 命令行参数 + +``` +用法: + node index.js [选项] + +选项: + -t, --transport 传输方式: 'stdio' 或 'http' (默认: stdio) + -p, --port HTTP 服务器端口 (默认: 3000, 仅在 http 模式下有效) + -h, --help 显示帮助信息 + +示例: + node index.js # stdio 模式 + node index.js --transport http --port 3000 # HTTP 模式 + npx gitlab-mcp-server -t http -p 8080 # HTTP 模式(使用 npx) +``` + +## 传输模式说明 + +### Stdio 模式(默认) + +通过标准输入/输出与客户端通信,这是 MCP 的标准传输方式。 + +**适用场景:** +- 与 Claude Desktop 或其他 MCP 客户端集成 +- 作为子进程运行 +- 本地开发和使用 + +**特点:** +- MCP 标准传输方式 +- 低延迟 +- 适合本地集成 + +### Streamable HTTP 模式 + +使用最新的 Streamable HTTP 传输协议,支持远程访问和多客户端连接。 + +**适用场景:** +- 远程服务器部署 +- 多客户端访问 +- 调试和测试 +- 需要 HTTP 接口的场景 + +**特点:** +- 基于 HTTP POST 的消息传输 +- 支持 Server-Sent Events (SSE) 流式响应 +- 支持断线重连和消息重放 +- 更好的网络适应性 + +**HTTP 端点:** +- `POST /message` - MCP 消息端点(接收 JSON-RPC 请求,返回 JSON 或 SSE 流) + +**测试 HTTP 模式:** +```bash +# 启动服务器 +npm run start:http + +# 在另一个终端发送测试请求 +curl -X POST http://localhost:3000/message \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' +``` + +## 在 MCP 客户端中使用 + +### Claude Desktop 配置(使用 npx) + +编辑 Claude Desktop 的配置文件: +- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` +- **Mac**: `~/Library/Application Support/Claude/claude_desktop_config.json` +- **Linux**: `~/.config/Claude/claude_desktop_config.json` + +```json { "mcpServers": { - "gitlab-mcp-server": { - "command": "node", - "args": [ - "/your/code/path/index-gitlab.js" - ], + "gitlab": { + "command": "npx", + "args": ["gitlab-mcp-server"], "env": { - "GITLAB_API_BASE_URL": "https://git.ifengqun.com/api/v4", - "GITLAB_ACCESS_TOKEN": "your_gitlab_token" + "GITLAB_ACCESS_TOKEN": "your_token_here", + "GITLAB_API_BASE_URL": "https://gitlab.com/api/v4" } } } } -``` \ No newline at end of file +``` + +### 使用本地路径 + +```json +{ + "mcpServers": { + "gitlab": { + "command": "node", + "args": ["d:/path/to/gitlab-mcp-server/index.js"], + "env": { + "GITLAB_ACCESS_TOKEN": "your_token_here" + } + } + } +} +``` + +### HTTP 模式配置 + +如果你部署了 HTTP 模式的服务器,客户端可以通过 HTTP 连接: + +```json +{ + "mcpServers": { + "gitlab-http": { + "url": "http://localhost:3000/message", + "transport": "streamable-http", + "env": { + "GITLAB_ACCESS_TOKEN": "your_token_here" + } + } + } +} +``` + +## 可用工具 + +### 1. create_merge_request + +在指定的 GitLab 项目中创建一个新的 Merge Request。 + +**参数:** +- `projectId` (string): GitLab 项目的 ID 或 URL 编码的路径 +- `sourceBranch` (string): 源分支名称 +- `targetBranch` (string): 目标分支名称 +- `title` (string): Merge Request 的标题 +- `description` (string, 可选): Merge Request 的描述 +- `assigneeId` (number, 可选): 负责人 ID +- `reviewerIds` (array, 可选): 审阅者 ID 数组 + +**示例:** +```javascript +{ + "tool": "create_merge_request", + "arguments": { + "projectId": "123", + "sourceBranch": "feature/new-feature", + "targetBranch": "main", + "title": "Add new feature", + "description": "This PR adds a new feature", + "assigneeId": 456, + "reviewerIds": [789, 101] + } +} +``` + +### 2. merge_merge_request + +合并指定的 GitLab Merge Request。 + +**参数:** +- `projectId` (string): GitLab 项目的 ID 或 URL 编码的路径 +- `mrIid` (number): Merge Request 的内部 ID +- `mergeCommitMessage` (string, 可选): 合并提交的消息 +- `shouldRemoveSourceBranch` (boolean, 可选): 是否在合并后删除源分支 +- `mergeWhenPipelineSucceeds` (boolean, 可选): 是否在 CI 管道成功后合并 + +**示例:** +```javascript +{ + "tool": "merge_merge_request", + "arguments": { + "projectId": "123", + "mrIid": 42, + "mergeCommitMessage": "Merge feature branch", + "shouldRemoveSourceBranch": true, + "mergeWhenPipelineSucceeds": false + } +} +``` + +### 3. get_merge_request + +获取指定 Merge Request 的详细信息。 + +**参数:** +- `projectId` (string): GitLab 项目的 ID 或 URL 编码的路径 +- `mrIid` (number): Merge Request 的内部 ID + +**示例:** +```javascript +{ + "tool": "get_merge_request", + "arguments": { + "projectId": "123", + "mrIid": 42 + } +} +``` + +### 4. close_merge_request + +关闭指定的 GitLab Merge Request 而不合并。 + +**参数:** +- `projectId` (string): GitLab 项目的 ID 或 URL 编码的路径 +- `mrIid` (number): Merge Request 的内部 ID + +**示例:** +```javascript +{ + "tool": "close_merge_request", + "arguments": { + "projectId": "123", + "mrIid": 42 + } +} +``` + +### 5. merge_to_test + +快捷工具:将源分支合并到测试分支(test)并自动部署。 + +**参数:** +- `projectId` (string): GitLab 项目的 ID +- `sourceBranch` (string): 源分支名称(如果未提供,将自动获取当前分支) + +**示例:** +```javascript +{ + "tool": "merge_to_test", + "arguments": { + "projectId": "123", + "sourceBranch": "feature/my-feature" + } +} +``` + +## 常见问题 + +### Q: 如何找到项目 ID? +**A:** +1. 进入 GitLab 项目页面 +2. 项目 ID 显示在项目名称下方 +3. 或者使用项目的 URL 编码路径,如 `username%2Fproject-name` + +### Q: Stdio 和 HTTP 模式有什么区别? +**A:** +- **Stdio 模式**: 通过标准输入/输出通信,MCP 标准方式,适合本地集成 +- **HTTP 模式**: 使用 Streamable HTTP 协议,适合远程访问、多客户端和调试 + +### Q: 什么是 Streamable HTTP? +**A:** Streamable HTTP 是 MCP 的最新 HTTP 传输协议,相比旧的 SSE 传输有以下优势: +- 统一的连接模型(单个 POST 请求即可发送和接收) +- 支持断线重连和消息重放 +- 更好的网络适应性 +- 更高的可靠性 + +### Q: 端口被占用怎么办? +**A:** 使用不同的端口: +```bash +node index.js --transport http --port 8080 +``` + +### Q: 如何查看详细日志? +**A:** 日志输出到 stderr,可以重定向到文件: +```bash +node index.js --transport http 2> server.log +``` + +### Q: npx 和本地安装有什么区别? +**A:** +- **npx**: 无需安装,直接运行最新版本,适合快速测试和使用 +- **本地安装**: 需要先安装依赖,但可以离线使用,适合开发和定制 + +## 开发 + +### 使用 MCP Inspector 调试 + +```bash +# Stdio 模式 +npm run dev + +# HTTP 模式 +npm run dev:http +``` + +MCP Inspector 会启动一个交互式界面,让你可以测试服务器的各项功能。 + +### 项目结构 + +``` +gitlab-mcp-server/ +├── index.js # 主入口文件 +├── package.json # 项目配置 +├── README.md # 文档 +├── .npmignore # npm 发布配置 +└── .gitignore # Git 忽略配置 +``` + +## 发布到 npm + +如果你想发布自己的版本到 npm: + +```bash +# 1. 登录 npm +npm login + +# 2. 更新版本号 +npm version patch # 或 minor, major + +# 3. 发布 +npm publish + +# 4. 发布后,其他人可以通过 npx 使用 +npx your-package-name +``` + +## 技术栈 + +- **@modelcontextprotocol/sdk**: MCP SDK (最新版本,支持 Streamable HTTP) +- **zod**: 参数验证 +- **Node.js**: 运行时环境 + +## 更新日志 + +### [1.1.0] - 2025-12-15 + +**新增功能:** +- ✨ 支持通过命令行参数选择传输方式(stdio 或 http) +- ✨ 使用最新的 Streamable HTTP 传输协议(替代已弃用的 SSE) +- ✨ 支持通过 npx 直接运行,无需安装 +- ✨ 添加 `bin` 字段,支持 `gitlab-mcp-server` 和 `gitlab-mcp` 命令 +- ✨ 添加命令行帮助信息 `--help` + +**改进:** +- 📝 完善文档,整合所有内容到 README +- 🔧 默认使用 stdio 模式(MCP 标准) +- 🔧 升级 SDK 到最新版本,移除已弃用的 API +- 🔧 优化服务器启动逻辑,支持参数化配置 + +**技术细节:** +- 使用 `StreamableHTTPServerTransport` 替代已弃用的 `SSEServerTransport` +- 升级 `@modelcontextprotocol/sdk` 到最新版本(1.24.0+) +- 添加 DNS rebinding 保护(SDK 1.24.0 的安全特性) + +### [1.0.0] - 初始版本 + +**功能:** +- ✅ 创建 Merge Request +- ✅ 合并 Merge Request +- ✅ 获取 Merge Request 详情 +- ✅ 关闭 Merge Request +- ✅ 快捷合并到测试分支 + +## 许可证 + +ISC + +## 作者 + +Jasery + +## 贡献 + +欢迎提交 Issue 和 Pull Request! + +## 支持 + +如果遇到问题,请检查: +1. ✅ 环境变量 `GITLAB_ACCESS_TOKEN` 是否正确设置 +2. ✅ GitLab API 是否可访问 +3. ✅ Token 是否有足够的权限(需要 `api` 权限) +4. ✅ 项目 ID 是否正确 +5. ✅ 使用的 SDK 版本是否为最新版本(1.24.0+) \ No newline at end of file diff --git a/gitlab-index.js b/gitlab-index.js deleted file mode 100644 index 9db90b9..0000000 --- a/gitlab-index.js +++ /dev/null @@ -1,490 +0,0 @@ -// 引入必要的模块 -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: JSON.stringify({ - success: true, - message: "成功创建Merge Request", - data: { - url: result.web_url, - title: result.title, - state: result.state, - iid: result.iid, - id: result.id - } - }, null, 2), - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: "text", - text: JSON.stringify({ - success: false, - message: "创建Merge Request失败", - error: error.message - }, null, 2), - }, - ], - 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: JSON.stringify({ - success: true, - message: `成功合并Merge Request #${mrIid}`, - data: { - iid: result.iid, - state: result.state, - mergedAt: result.merged_at, - mergedBy: result.merged_by?.name, - id: result.id - } - }, null, 2), - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: "text", - text: JSON.stringify({ - success: false, - message: "合并Merge Request失败", - error: error.message - }, null, 2), - }, - ], - 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: JSON.stringify({ - success: true, - message: `Merge Request #${mrIid} 详情`, - data: { - iid: result.iid, - title: result.title, - author: result.author.name, - state: result.state, - sourceBranch: result.source_branch, - targetBranch: result.target_branch, - createdAt: result.created_at, - updatedAt: result.updated_at, - description: result.description, - url: result.web_url - } - }, null, 2), - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: "text", - text: JSON.stringify({ - success: false, - message: "获取Merge Request详情失败", - error: error.message - }, null, 2), - }, - ], - 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: JSON.stringify({ - success: true, - message: `成功关闭Merge Request #${mrIid}`, - data: { - iid: result.iid, - title: result.title, - state: result.state, - updatedAt: result.updated_at, - id: result.id - } - }, null, 2), - }, - ], - }; - } catch (error) { - return { - content: [ - { - type: "text", - text: JSON.stringify({ - success: false, - message: "关闭Merge Request失败", - error: error.message - }, null, 2), - }, - ], - isError: true, - }; - } - } -); - - -server.registerTool( - 'merge_to_test', - { - description: "把源分支名合并到测试分支,并部署到测试环境", - inputSchema: z.object({ - projectId: z.string().describe("GitLab项目projectId"), - sourceBranch: z.string().describe("源分支名称,默认请使用git branch --show-current获取当前分支传递"), - }), - }, - - 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: JSON.stringify({ - success: false, - message: "获取当前分支失败", - error: error.message - }, null, 2), - }, - ], - 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: JSON.stringify({ - success: true, - message: "成功创建并合并Merge Request", - data: { - url: mergeResult.web_url, - title: mergeResult.title, - state: mergeResult.state, - mergedAt: mergeResult.merged_at, - iid: mergeResult.iid, - id: mergeResult.id - } - }, null, 2), - }, - ], - }; - } catch (mergeError) { - // 如果合并失败,返回MR创建成功的信息,但提示合并需要审核 - return { - content: [ - { - type: "text", - text: JSON.stringify({ - success: false, - message: "成功创建Merge Request,但自动合并失败,请手动审核", - data: { - url: result.web_url, - title: result.title, - state: result.state, - iid: result.iid, - id: result.id - }, - error: mergeError.message - }, null, 2), - }, - ], - }; - } - } catch (error) { - return { - content: [ - { - type: "text", - text: JSON.stringify({ - success: false, - message: "合并到test分支失败", - error: error.message - }, null, 2), - }, - ], - 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 }; \ No newline at end of file diff --git a/index.js b/index.js index e537e4f..1141828 100644 --- a/index.js +++ b/index.js @@ -1,113 +1,587 @@ +#!/usr/bin/env node + // 引入必要的模块 import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { z } from "zod"; // 创建 MCP 服务器实例 // 为其设置一个名字和版本 const server = new McpServer({ - name: "my-first-nodejs-mcp-server", + name: "gitlab-mcp-server", version: "1.0.0", }); + /** - * 完善版的打招呼工具 - * 1. 使用 registerTool 方法进行更完整的描述 - * 2. 利用 Zod 库定义严格的参数模式和验证规则 + * GitLab API 客户端配置 + * 用于连接到GitLab实例并执行操作 */ -server.registerTool('say_hello', +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", { - // 工具描述至关重要,它直接帮助AI理解工具的用途、适用场景和参数要求 - description: "向指定的用户生成一条个性化的问候语。该工具适用于在对话开始时或需要欢迎用户时使用。", - // 使用 Zod 定义参数模式,这是确保AI正确传参的关键[8](@ref) + description: "在指定的GitLab项目中创建一个新的Merge Request", inputSchema: z.object({ - // 第一个参数:用户名 - name: z.string() - .min(1, { message: "姓名不能为空字符串。" }) // 基础验证:确保非空字符串 - .max(50, { message: "姓名长度不能超过50个字符。" }) // 防止过长输入 - .describe("被问候者的姓名。请尽量提供完整的姓名或常用称呼。"), // 清晰的参数描述 - // 第二个参数(可选):语言/风格 - language: z.enum(["casual", "formal", "warm"]) // 枚举类型,将AI的输入严格限制在预设的选项内 - .default("casual") // 设置默认值,当AI未提供此参数时使用 - .describe("问候的语言风格。'casual'为随意,'formal'为正式,'warm'为热情。默认为'casual'。"), + 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 ({ name, language }) => { - // 根据不同的语言风格生成不同的问候语 - const greetings = { - casual: `嘿,${name}!最近怎么样?`, - formal: `您好,${name}。很高兴见到您。`, - warm: `欢迎你,${name}!真高兴你今天能来!`, - }; + async ({ projectId, sourceBranch, targetBranch, title, description = "", assigneeId, reviewerIds }) => { + try { + const result = await gitlabApiClient.createMergeRequest( + projectId, + sourceBranch, + targetBranch, + title, + description, + assigneeId, + reviewerIds + ); - const greetingMessage = greetings[language] || greetings.casual; + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: true, + message: "成功创建Merge Request", + data: { + url: result.web_url, + title: result.title, + state: result.state, + iid: result.iid, + id: result.id + } + }, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: false, + message: "创建Merge Request失败", + error: error.message + }, null, 2), + }, + ], + isError: true, + }; + } + } +); - // MCP 工具要求返回特定的格式 - return { - content: [ - { - type: "text", // 内容类型为文本 - text: greetingMessage, - }, - ], - }; +/** + * 合并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: JSON.stringify({ + success: true, + message: `成功合并Merge Request #${mrIid}`, + data: { + iid: result.iid, + state: result.state, + mergedAt: result.merged_at, + mergedBy: result.merged_by?.name, + id: result.id + } + }, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: false, + message: "合并Merge Request失败", + error: error.message + }, null, 2), + }, + ], + 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: JSON.stringify({ + success: true, + message: `Merge Request #${mrIid} 详情`, + data: { + iid: result.iid, + title: result.title, + author: result.author.name, + state: result.state, + sourceBranch: result.source_branch, + targetBranch: result.target_branch, + createdAt: result.created_at, + updatedAt: result.updated_at, + description: result.description, + url: result.web_url + } + }, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: false, + message: "获取Merge Request详情失败", + error: error.message + }, null, 2), + }, + ], + 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: JSON.stringify({ + success: true, + message: `成功关闭Merge Request #${mrIid}`, + data: { + iid: result.iid, + title: result.title, + state: result.state, + updatedAt: result.updated_at, + id: result.id + } + }, null, 2), + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: false, + message: "关闭Merge Request失败", + error: error.message + }, null, 2), + }, + ], + isError: true, + }; + } + } +); + + +server.registerTool( + 'merge_to_test', + { + description: "把源分支名合并到测试分支,并部署到测试环境", + inputSchema: z.object({ + projectId: z.string().describe("GitLab项目projectId"), + sourceBranch: z.string().describe("源分支名称,默认请使用git branch --show-current获取当前分支传递"), + }), + }, + + 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: JSON.stringify({ + success: false, + message: "获取当前分支失败", + error: error.message + }, null, 2), + }, + ], + 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: JSON.stringify({ + success: true, + message: "成功创建并合并Merge Request", + data: { + url: mergeResult.web_url, + title: mergeResult.title, + state: mergeResult.state, + mergedAt: mergeResult.merged_at, + iid: mergeResult.iid, + id: mergeResult.id + } + }, null, 2), + }, + ], + }; + } catch (mergeError) { + // 如果合并失败,返回MR创建成功的信息,但提示合并需要审核 + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: false, + message: "成功创建Merge Request,但自动合并失败,请手动审核", + data: { + url: result.web_url, + title: result.title, + state: result.state, + iid: result.iid, + id: result.id + }, + error: mergeError.message + }, null, 2), + }, + ], + }; + } + } catch (error) { + return { + content: [ + { + type: "text", + text: JSON.stringify({ + success: false, + message: "合并到test分支失败", + error: error.message + }, null, 2), + }, + ], + isError: true, + }; + } + } +) + + +/** + * 解析命令行参数 + */ +function parseArgs() { + const args = process.argv.slice(2); + const config = { + transport: 'stdio', // 默认使用 stdio + port: 3000, // 默认 HTTP 端口 + }; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg === '--transport' || arg === '-t') { + const value = args[i + 1]; + if (value === 'stdio' || value === 'http') { + config.transport = value; + i++; // 跳过下一个参数 + } else { + console.error(`错误: --transport 参数必须是 'stdio' 或 'http'`); + process.exit(1); + } + } else if (arg === '--port' || arg === '-p') { + const value = parseInt(args[i + 1], 10); + if (!isNaN(value) && value > 0 && value < 65536) { + config.port = value; + i++; // 跳过下一个参数 + } else { + console.error(`错误: --port 参数必须是 1-65535 之间的有效端口号`); + process.exit(1); + } + } else if (arg === '--help' || arg === '-h') { + console.log(` +GitLab MCP 服务器 + +用法: + node index.js [选项] + npx gitlab-mcp-server [选项] + +选项: + -t, --transport 传输方式: 'stdio' 或 'http' (默认: stdio) + -p, --port HTTP 服务器端口 (默认: 3000, 仅在 http 模式下有效) + -h, --help 显示帮助信息 + +说明: + - stdio 模式: 使用标准输入/输出(MCP 标准),适合与 MCP 客户端集成 + - http 模式: 使用 Streamable HTTP 传输,适合远程访问和调试 + +示例: + node index.js # stdio 模式 + node index.js --transport http --port 3000 # HTTP 模式 + npx gitlab-mcp-server # stdio 模式 + npx gitlab-mcp-server -t http -p 8080 # HTTP 模式 + +环境变量: + GITLAB_ACCESS_TOKEN GitLab 访问令牌(必需) + GITLAB_API_BASE_URL GitLab API 基础 URL(可选,默认: https://gitlab.com/api/v4) + `); + process.exit(0); + } + } + + return config; +} + +/** + * 启动 HTTP 服务器(使用 Streamable HTTP 传输) + */ +async function startHttpServer(port) { + const transport = new StreamableHTTPServerTransport({ + endpoint: '/message', + port: port, }); -// // 在 server.tool() 方法内,实现一个获取天气的工具 -// server.registerTool( -// "get_weather", -// { -// city: z.string().describe("要查询天气的城市名称"), -// }, -// async ({ city }) => { -// // 重要:API密钥等敏感信息应从环境变量读取 -// const apiKey = process.env.WEATHER_API_KEY; -// if (!apiKey) { -// throw new Error("WEATHER_API_KEY 环境变量未设置"); -// } + await server.connect(transport); + + console.error(`GitLab MCP 服务器已启动 (Streamable HTTP 模式)`); + console.error(`监听端口: ${port}`); + console.error(`MCP 端点: http://localhost:${port}/message`); + console.error(`\n使用 POST 请求发送消息到 /message 端点`); +} -// try { -// // 调用外部天气 API -// const response = await fetch( -// `https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${city}&aqi=no` -// ); -// const weatherData = await response.json(); - -// // 处理并返回需要的信息 -// return { -// content: [ -// { -// type: "text", -// text: `城市 ${city} 的天气情况: -// 温度:${weatherData.current.temp_c}°C -// 天气状况:${weatherData.current.condition.text} -// `, -// }, -// ], -// }; -// } catch (error) { -// // 良好的错误处理至关重要 -// return { -// content: [ -// { -// type: "text", -// text: `获取天气信息失败: ${error.message}`, -// }, -// ], -// isError: true, // 标记这是一个错误结果 -// }; -// } -// } -// ); - -// 启动服务器,使用标准输入/输出(stdio)与客户端通信 -async function main() { +/** + * 启动 Stdio 服务器 + */ +async function startStdioServer() { const transport = new StdioServerTransport(); await server.connect(transport); - console.error("MCP 服务器已通过 stdio 启动"); // 日志输出到 stderr + console.error("GitLab MCP 服务器已启动 (Stdio 模式)"); } +// 主函数:根据参数选择传输方式启动服务器 +async function main() { + const config = parseArgs(); + + if (config.transport === 'http') { + await startHttpServer(config.port); + } else { + await startStdioServer(); + } +} + + // 运行主函数,并捕获可能的错误 main().catch((error) => { console.error("服务器运行出现致命错误:", error); process.exit(1); -}); \ No newline at end of file +}); diff --git a/package-lock.json b/package-lock.json index 24b09a2..c48031f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,21 @@ { - "name": "my-nodejs-mcp-server", - "version": "1.0.0", + "name": "gitlab-mcp-server", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "my-nodejs-mcp-server", - "version": "1.0.0", + "name": "gitlab-mcp-server", + "version": "1.1.0", "license": "ISC", "dependencies": { - "@modelcontextprotocol/sdk": "^1.22.0", + "@modelcontextprotocol/sdk": "^1.24.3", "zod": "^3.25.76" }, + "bin": { + "gitlab-mcp": "index.js", + "gitlab-mcp-server": "index.js" + }, "devDependencies": { "@modelcontextprotocol/inspector": "^0.17.2" } @@ -221,9 +225,10 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.22.0", - "resolved": "https://registry.npmmirror.com/@modelcontextprotocol/sdk/-/sdk-1.22.0.tgz", - "integrity": "sha512-VUpl106XVTCpDmTBil2ehgJZjhyLY2QZikzF8NvTXtLRF1CvO5iEE2UNZdVIUer35vFOwMKYeUGbjJtvPWan3g==", + "version": "1.24.3", + "resolved": "https://registry.npmmirror.com/@modelcontextprotocol/sdk/-/sdk-1.24.3.tgz", + "integrity": "sha512-YgSHW29fuzKKAHTGe9zjNoo+yF8KaQPzDC2W9Pv41E7/57IfY+AMGJ/aDFlgTLcVVELoggKE4syABCE75u3NCw==", + "license": "MIT", "dependencies": { "ajv": "^8.17.1", "ajv-formats": "^3.0.1", @@ -234,20 +239,25 @@ "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" }, "engines": { "node": ">=18" }, "peerDependencies": { - "@cfworker/json-schema": "^4.1.1" + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { "@cfworker/json-schema": { "optional": true + }, + "zod": { + "optional": false } } }, @@ -2178,6 +2188,15 @@ "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmmirror.com/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3277,11 +3296,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "version": "3.25.0", + "resolved": "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", + "license": "ISC", "peerDependencies": { - "zod": "^3.24.1" + "zod": "^3.25 || ^4" } } } diff --git a/package.json b/package.json index c5a1fb9..b3a5e28 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,18 @@ { "name": "gitlab-mcp-server", - "version": "1.0.0", + "version": "1.1.0", "main": "index.js", "type": "module", + "bin": { + "gitlab-mcp-server": "./index.js", + "gitlab-mcp": "./index.js" + }, "scripts": { "start": "node index.js", - "gitlab": "npx @modelcontextprotocol/inspector node gitlab-index.js", - "dev": "npx @modelcontextprotocol/inspector node index.js" + "start:stdio": "node index.js --transport stdio", + "start:http": "node index.js --transport http --port 3000", + "dev": "npx @modelcontextprotocol/inspector node index.js", + "dev:http": "node index.js --transport http --port 3000" }, "keywords": [ "mcp", @@ -17,7 +23,7 @@ "license": "ISC", "description": "A Model Context Protocol server for GitLab operations, primarily for creating and merging merge requests", "dependencies": { - "@modelcontextprotocol/sdk": "^1.22.0", + "@modelcontextprotocol/sdk": "^1.24.3", "zod": "^3.25.76" }, "devDependencies": {