From 49bc91aa314e656fa13fac06e666a71a294e1615 Mon Sep 17 00:00:00 2001 From: Rory Logue <87331957+rorylogue@users.noreply.github.com> Date: Mon, 25 Nov 2024 20:23:04 +0000 Subject: [PATCH 01/16] fix: some wording --- src/sqlite/src/mcp_server_sqlite/server.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sqlite/src/mcp_server_sqlite/server.py b/src/sqlite/src/mcp_server_sqlite/server.py index 983c1387..442204ff 100644 --- a/src/sqlite/src/mcp_server_sqlite/server.py +++ b/src/sqlite/src/mcp_server_sqlite/server.py @@ -16,8 +16,8 @@ PROMPT_TEMPLATE = """ The assistants goal is to walkthrough an informative demo of MCP. To demonstrate the Model Context Protocol (MCP) we will leverage this example server to interact with an SQLite database. It is important that you first explain to the user what is going on. The user has downloaded and installed the SQLite MCP Server and is now ready to use it. -The have selected the MCP menu item which is contained within a parent menu denoted by the paperclip icon. Inside this menu they selected an icon that illustrates two electrical plugs connecting. This is the MCP menu. -Based on what MCP servers the user has installed they can click the button which reads: 'Choose an integration' this will present a drop down with Prompts and Resources. The user hase selected the prompt titled: 'mcp-demo'. +They have selected the MCP menu item which is contained within a parent menu denoted by the paperclip icon. Inside this menu they selected an icon that illustrates two electrical plugs connecting. This is the MCP menu. +Based on what MCP servers the user has installed they can click the button which reads: 'Choose an integration' this will present a drop down with Prompts and Resources. The user has selected the prompt titled: 'mcp-demo'. This text file is that prompt. The goal of the following instructions is to walk the user through the process of using the 3 core aspects of an MCP server. These are: Prompts, Tools, and Resources. They have already used a prompt and provided a topic. The topic is: {topic}. The user is now ready to begin the demo. Here is some more information about mcp and this specific mcp server: @@ -65,7 +65,7 @@ c. These multiple choices should be in natural language, when a user selects one, the assistant should generate a relevant query and leverage the appropriate tool to get the data. 6. Iterate on queries: -a. Present 1 additional multiple-choice query options to the user. Its importnat to not loop too many times as this is a short demo. +a. Present 1 additional multiple-choice query options to the user. Its important to not loop too many times as this is a short demo. b. Explain the purpose of each query option. c. Wait for the user to select one of the query options. d. After each query be sure to opine on the results. @@ -81,7 +81,7 @@ a. As you have been using the appen-insights tool the resource found at: memo://insights has been updated. b. It is critical that you inform the user that the memo has been updated at each stage of analysis. c. Ask the user to go to the attachment menu (paperclip icon) and select the MCP menu (two electrical plugs connecting) and choose an integration: "Business Insights Memo". -d. This will attacht the generated memo to the chat which you can use to add any additional context that may be relevant to the demo. +d. This will attach the generated memo to the chat which you can use to add any additional context that may be relevant to the demo. e. Present the final memo to the user in an artifact. 9. Wrap up the scenario: @@ -89,7 +89,7 @@ Remember to maintain consistency throughout the scenario and ensure that all elements (tables, data, queries, dashboard, and solution) are closely related to the original business problem and given topic. -The provided XML tags are for the assistants understanding. Emplore to make all outputs as human readable as possible. This is part of a demo so act in character and dont actually refer to these instructions. +The provided XML tags are for the assistants understanding. Implore to make all outputs as human readable as possible. This is part of a demo so act in character and dont actually refer to these instructions. Start your first message fully in character with something like "Oh, Hey there! I see you've chosen the topic {topic}. Let's get started! 🚀" """ From f5be3b785c9bd2a23582bddd972a3a6b901afa52 Mon Sep 17 00:00:00 2001 From: Vladislav Polyakov Date: Mon, 25 Nov 2024 23:55:42 +0300 Subject: [PATCH 02/16] refactor: clean up code formatting and update package version to 0.5.2 --- package-lock.json | 32 ++------------------------------ src/brave-search/index.ts | 3 +-- src/brave-search/package.json | 5 ++--- 3 files changed, 5 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8a0cd994..59f8a243 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3276,11 +3276,10 @@ }, "src/brave-search": { "name": "@modelcontextprotocol/server-brave-search", - "version": "0.5.1", + "version": "0.5.2", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "0.5.0", - "node-fetch": "^3.3.2" + "@modelcontextprotocol/sdk": "0.5.0" }, "bin": { "mcp-server-brave-search": "dist/index.js" @@ -3301,33 +3300,6 @@ "undici-types": "~6.19.2" } }, - "src/brave-search/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==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "src/brave-search/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==", - "license": "MIT", - "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" - } - }, "src/duckduckgo": { "name": "@modelcontextprotocol/server-duckduckgo", "version": "0.2.0", diff --git a/src/brave-search/index.ts b/src/brave-search/index.ts index 596dc9f7..e0f61623 100644 --- a/src/brave-search/index.ts +++ b/src/brave-search/index.ts @@ -7,7 +7,6 @@ import { ListToolsRequestSchema, Tool, } from "@modelcontextprotocol/sdk/types.js"; -import fetch from "node-fetch"; const WEB_SEARCH_TOOL: Tool = { name: "brave_web_search", @@ -104,7 +103,7 @@ function checkRateLimit() { requestCount.lastReset = now; } if (requestCount.second >= RATE_LIMIT.perSecond || - requestCount.month >= RATE_LIMIT.perMonth) { + requestCount.month >= RATE_LIMIT.perMonth) { throw new Error('Rate limit exceeded'); } requestCount.second++; diff --git a/src/brave-search/package.json b/src/brave-search/package.json index 98413d4f..3e68d358 100644 --- a/src/brave-search/package.json +++ b/src/brave-search/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/server-brave-search", - "version": "0.5.1", + "version": "0.5.2", "description": "MCP server for Brave Search API integration", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -19,8 +19,7 @@ "watch": "tsc --watch" }, "dependencies": { - "@modelcontextprotocol/sdk": "0.5.0", - "node-fetch": "^3.3.2" + "@modelcontextprotocol/sdk": "0.5.0" }, "devDependencies": { "@types/node": "^20.10.0", From c5333fe21de3c44935d3786c938fb015ffb14e92 Mon Sep 17 00:00:00 2001 From: Mahesh Murag Date: Mon, 25 Nov 2024 22:11:06 +0100 Subject: [PATCH 03/16] Add uvx/pip Install Instructions to README (#38) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 07a479b0..fb385ce2 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ pip install mcp-server-git python -m mcp_server_git ``` +Follow [these](https://docs.astral.sh/uv/getting-started/installation/) instructions to install `uv` / `uvx` and [these](https://pip.pypa.io/en/stable/installation/) to install `pip`. + ### Using an MCP Client However, running a server on its own isn't very useful, and should instead be configured into an MCP client. For example, here's the Claude Desktop configuration to use the above server: From aba99119dd13b9668315f02304917557d3465a63 Mon Sep 17 00:00:00 2001 From: Karthik Kreen Krishnan Date: Tue, 26 Nov 2024 14:55:00 +1300 Subject: [PATCH 04/16] Updated Puppeteer readme --- src/puppeteer/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/puppeteer/README.md b/src/puppeteer/README.md index e1d91b87..6bd67474 100644 --- a/src/puppeteer/README.md +++ b/src/puppeteer/README.md @@ -52,6 +52,19 @@ The server provides access to two types of resources: - JavaScript execution - Basic web interaction (navigation, clicking, form filling) +## Configuration to use Puppeteer Server +Here's the Claude Desktop configuration to use the Puppeter server: + +```json +{ + "mcpServers": { + "puppeteer": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-puppeteer", "http://example.com"] + } + } +} + ## 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. From ce0b743873fde3fb471b3a9c9715c04f191d54b9 Mon Sep 17 00:00:00 2001 From: Karthik Kreen Krishnan Date: Tue, 26 Nov 2024 16:52:07 +1300 Subject: [PATCH 05/16] removed argument --- src/puppeteer/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/puppeteer/README.md b/src/puppeteer/README.md index 6bd67474..e81c69e6 100644 --- a/src/puppeteer/README.md +++ b/src/puppeteer/README.md @@ -60,7 +60,7 @@ Here's the Claude Desktop configuration to use the Puppeter server: "mcpServers": { "puppeteer": { "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-puppeteer", "http://example.com"] + "args": ["-y", "@modelcontextprotocol/server-puppeteer"] } } } From a54651477cf8849ba800336856f4f700bfef0e02 Mon Sep 17 00:00:00 2001 From: Rahim Nathwani Date: Mon, 25 Nov 2024 20:04:06 -0800 Subject: [PATCH 06/16] Fix missing mcpServers keys in README code examples --- src/brave-search/README.md | 15 ++++++++++----- src/everything/README.md | 12 +++++++++--- src/filesystem/README.md | 13 ++++++++++--- src/gdrive/README.md | 11 ++++++++--- src/github/README.md | 15 ++++++++++----- src/google-maps/README.md | 15 ++++++++++----- src/memory/README.md | 11 ++++++++--- src/postgres/README.md | 12 +++++++++--- src/slack/README.md | 17 +++++++++++------ 9 files changed, 85 insertions(+), 36 deletions(-) diff --git a/src/brave-search/README.md b/src/brave-search/README.md index 2d5caef9..2d5ae0df 100644 --- a/src/brave-search/README.md +++ b/src/brave-search/README.md @@ -38,11 +38,16 @@ Add this to your `claude_desktop_config.json`: ```json { - "brave-search": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-brave-search"], - "env": { - "BRAVE_API_KEY": "YOUR_API_KEY_HERE" + "mcpServers": { + "brave-search": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-brave-search" + ], + "env": { + "BRAVE_API_KEY": "YOUR_API_KEY_HERE" + } } } } diff --git a/src/everything/README.md b/src/everything/README.md index 69281a06..b00a6635 100644 --- a/src/everything/README.md +++ b/src/everything/README.md @@ -78,8 +78,14 @@ Add to your `claude_desktop_config.json`: ```json { - "everything": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-everything"] + "mcpServers": { + "everything": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-everything" + ] + } } } +``` diff --git a/src/filesystem/README.md b/src/filesystem/README.md index 08ec4617..c2950cd5 100644 --- a/src/filesystem/README.md +++ b/src/filesystem/README.md @@ -82,9 +82,16 @@ Node.js server implementing Model Context Protocol (MCP) for filesystem operatio Add this to your `claude_desktop_config.json`: ```json { - "filesystem": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Desktop", "/path/to/other/allowed/dir"] + "mcpServers": { + "filesystem": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-filesystem", + "/Users/username/Desktop", + "/path/to/other/allowed/dir" + ] + } } } ``` diff --git a/src/gdrive/README.md b/src/gdrive/README.md index 16dadf2b..9a795f0c 100644 --- a/src/gdrive/README.md +++ b/src/gdrive/README.md @@ -51,9 +51,14 @@ To integrate this server with the desktop app, add the following to your app's s ```json { - "gdrive": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-gdrive"] + "mcpServers": { + "gdrive": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-gdrive" + ] + } } } ``` diff --git a/src/github/README.md b/src/github/README.md index d966721c..cfd268a8 100644 --- a/src/github/README.md +++ b/src/github/README.md @@ -117,11 +117,16 @@ To use this with Claude Desktop, add the following to your `claude_desktop_confi ```json { - "github": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-github"], - "env": { - "GITHUB_PERSONAL_ACCESS_TOKEN": "" + "mcpServers": { + "github": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-github" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "" + } } } } diff --git a/src/google-maps/README.md b/src/google-maps/README.md index a7b53e43..962f031a 100644 --- a/src/google-maps/README.md +++ b/src/google-maps/README.md @@ -61,11 +61,16 @@ Add the following to your `claude_desktop_config.json`: ```json { - "google-maps": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-google-maps"], - "env": { - "GOOGLE_MAPS_API_KEY": "" + "mcpServers": { + "google-maps": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-google-maps" + ], + "env": { + "GOOGLE_MAPS_API_KEY": "" + } } } } diff --git a/src/memory/README.md b/src/memory/README.md index 572ac917..66bdbb41 100644 --- a/src/memory/README.md +++ b/src/memory/README.md @@ -130,9 +130,14 @@ Example: Add this to your claude_desktop_config.json: ```json { - "memory": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-memory"] + "mcpServers": { + "memory": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-memory" + ] + } } } ``` diff --git a/src/postgres/README.md b/src/postgres/README.md index 5fe8d4de..9a16af77 100644 --- a/src/postgres/README.md +++ b/src/postgres/README.md @@ -26,9 +26,15 @@ To use this server with the Claude Desktop app, add the following configuration ```json { - "postgres": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"] + "mcpServers": { + "postgres": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-postgres", + "postgresql://localhost/mydb" + ] + } } } ``` diff --git a/src/slack/README.md b/src/slack/README.md index 0f964e37..ca5d4965 100644 --- a/src/slack/README.md +++ b/src/slack/README.md @@ -98,12 +98,17 @@ Add the following to your `claude_desktop_config.json`: ```json { - "slack": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-slack"], - "env": { - "SLACK_BOT_TOKEN": "xoxb-your-bot-token", - "SLACK_TEAM_ID": "T01234567" + "mcpServers": { + "slack": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-slack" + ], + "env": { + "SLACK_BOT_TOKEN": "xoxb-your-bot-token", + "SLACK_TEAM_ID": "T01234567" + } } } } From 3bc45b5c6c79e0fd4b9bd02035668530561e55ee Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Tue, 26 Nov 2024 19:39:07 +0000 Subject: [PATCH 07/16] Remove slack_search_messages from README `slack_search_messages` was removed but still remains in the README. We need to correct this. Fixes #61. --- src/slack/README.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/slack/README.md b/src/slack/README.md index ca5d4965..b3d80a21 100644 --- a/src/slack/README.md +++ b/src/slack/README.md @@ -49,22 +49,15 @@ MCP Server for the Slack API, enabling Claude to interact with Slack workspaces. - `thread_ts` (string): Timestamp of the parent message - Returns: List of replies with their content and metadata -7. `slack_search_messages` - - Search for messages across channels - - Required inputs: - - `query` (string): The search query - - Optional inputs: - - `count` (number, default: 5): Number of results to return - - Returns: Matching messages with their context -8. `slack_get_users` +7. `slack_get_users` - Get list of workspace users with basic profile information - Optional inputs: - `cursor` (string): Pagination cursor for next page - `limit` (number, default: 100, max: 200): Maximum users to return - Returns: List of users with their basic profiles -9. `slack_get_user_profile` +8. `slack_get_user_profile` - Get detailed profile information for a specific user - Required inputs: - `user_id` (string): The user's ID From ffe49237b92055451b5f699466941d5892bf7de1 Mon Sep 17 00:00:00 2001 From: Taylor McCaslin Date: Mon, 25 Nov 2024 16:39:34 -0700 Subject: [PATCH 08/16] 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 09/16] 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)", From 238587042d61fd455bfdb130cf376e3fc1da63b6 Mon Sep 17 00:00:00 2001 From: ExecuteAutomation Date: Wed, 27 Nov 2024 14:13:08 +1300 Subject: [PATCH 10/16] Added Hover and Select Capabliity --- src/puppeteer/README.md | 11 ++++++ src/puppeteer/index.ts | 83 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/puppeteer/README.md b/src/puppeteer/README.md index e81c69e6..a951a9a3 100644 --- a/src/puppeteer/README.md +++ b/src/puppeteer/README.md @@ -22,12 +22,22 @@ A Model Context Protocol server that provides browser automation capabilities us - Click elements on the page - Input: `selector` (string): CSS selector for element to click +- **puppeteer_hover** + - Hover elements on the page + - Input: `selector` (string): CSS selector for element to hover + - **puppeteer_fill** - Fill out input fields - Inputs: - `selector` (string): CSS selector for input field - `value` (string): Value to fill +- **puppeteer_select** + - Select an element with SELECT tag + - Inputs: + - `selector` (string): CSS selector for element to select + - `value` (string): Value to select + - **puppeteer_evaluate** - Execute JavaScript in the browser console - Input: `script` (string): JavaScript code to execute @@ -64,6 +74,7 @@ Here's the Claude Desktop configuration to use the Puppeter server: } } } +``` ## License diff --git a/src/puppeteer/index.ts b/src/puppeteer/index.ts index 9ad05cbe..d3aa2a30 100644 --- a/src/puppeteer/index.ts +++ b/src/puppeteer/index.ts @@ -64,6 +64,29 @@ const TOOLS: Tool[] = [ required: ["selector", "value"], }, }, + { + name: "puppeteer_select", + description: "Select an element on the page with Select tag", + inputSchema: { + type: "object", + properties: { + selector: { type: "string", description: "CSS selector for element to select" }, + value: { type: "string", description: "Value to select" }, + }, + required: ["selector", "value"], + }, + }, + { + name: "puppeteer_hover", + description: "Hover an element on the page", + inputSchema: { + type: "object", + properties: { + selector: { type: "string", description: "CSS selector for element to hover" }, + }, + required: ["selector"], + }, + }, { name: "puppeteer_evaluate", description: "Execute JavaScript in the browser console", @@ -88,7 +111,7 @@ async function ensureBrowser() { browser = await puppeteer.launch({ headless: false }); const pages = await browser.pages(); page = pages[0]; - + page.on("console", (msg) => { const logEntry = `[${msg.type()}] ${msg.text()}`; consoleLogs.push(logEntry); @@ -122,7 +145,7 @@ async function handleToolCall(name: string, args: any): Promise<{ toolResult: Ca const height = args.height ?? 600; await page.setViewport({ width, height }); - const screenshot = await (args.selector ? + const screenshot = await (args.selector ? (await page.$(args.selector))?.screenshot({ encoding: "base64" }) : page.screenshot({ encoding: "base64", fullPage: false })); @@ -210,12 +233,62 @@ async function handleToolCall(name: string, args: any): Promise<{ toolResult: Ca }; } + case "puppeteer_select": + try { + await page.waitForSelector(args.selector); + await page.select(args.selector, args.value); + return { + toolResult: { + content: [{ + type: "text", + text: `Selected ${args.selector} with: ${args.value}`, + }], + isError: false, + }, + }; + } catch (error) { + return { + toolResult: { + content: [{ + type: "text", + text: `Failed to select ${args.selector}: ${(error as Error).message}`, + }], + isError: true, + }, + }; + } + + case "puppeteer_hover": + try { + await page.waitForSelector(args.selector); + await page.hover(args.selector); + return { + toolResult: { + content: [{ + type: "text", + text: `Hovered ${args.selector}`, + }], + isError: false, + }, + }; + } catch (error) { + return { + toolResult: { + content: [{ + type: "text", + text: `Failed to hover ${args.selector}: ${(error as Error).message}`, + }], + isError: true, + }, + }; + } + case "puppeteer_evaluate": try { const result = await page.evaluate((script) => { const logs: string[] = []; const originalConsole = { ...console }; - + ['log', 'info', 'warn', 'error'].forEach(method => { (console as any)[method] = (...args: any[]) => { logs.push(`[${method}] ${args.join(' ')}`); @@ -301,7 +374,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => ({ server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri.toString(); - + if (uri === "console://logs") { return { contents: [{ @@ -333,7 +406,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS, })); -server.setRequestHandler(CallToolRequestSchema, async (request) => +server.setRequestHandler(CallToolRequestSchema, async (request) => handleToolCall(request.params.name, request.params.arguments ?? {}) ); From 75e6fa14815866d6db472bd5cd30474ce09e783e Mon Sep 17 00:00:00 2001 From: Sean Lynch Date: Wed, 27 Nov 2024 09:30:02 -0800 Subject: [PATCH 11/16] Remove docs on Anthropic summary feature no longer present --- src/sqlite/README.md | 5 ++--- src/sqlite/src/mcp_server_sqlite/server.py | 2 +- src/sqlite/uv.lock | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/sqlite/README.md b/src/sqlite/README.md index 533c7616..5f211c41 100644 --- a/src/sqlite/README.md +++ b/src/sqlite/README.md @@ -1,7 +1,7 @@ # SQLite MCP Server ## Overview -A Model Context Protocol (MCP) server implementation that provides database interaction and business intelligence capabilities through SQLite. This server enables running SQL queries, analyzing business data, and automatically generating business insight memos that can be enhanced with Claude's analysis when an Anthropic API key is provided. +A Model Context Protocol (MCP) server implementation that provides database interaction and business intelligence capabilities through SQLite. This server enables running SQL queries, analyzing business data, and automatically generating business insight memos. ## Components @@ -9,7 +9,6 @@ A Model Context Protocol (MCP) server implementation that provides database inte The server exposes a single dynamic resource: - `memo://insights`: A continuously updated business insights memo that aggregates discovered insights during analysis - Auto-updates as new insights are discovered via the append-insight tool - - Optional enhancement through Claude for professional formatting (requires Anthropic API key) ### Prompts The server provides a demonstration prompt: @@ -25,7 +24,7 @@ The server offers six core tools: #### Query Tools - `read-query` - Execute SELECT queries to read data from the database - - Input: + - Input: - `query` (string): The SELECT SQL query to execute - Returns: Query results as array of objects diff --git a/src/sqlite/src/mcp_server_sqlite/server.py b/src/sqlite/src/mcp_server_sqlite/server.py index 442204ff..6437cc41 100644 --- a/src/sqlite/src/mcp_server_sqlite/server.py +++ b/src/sqlite/src/mcp_server_sqlite/server.py @@ -25,7 +25,7 @@ Prompts: This server provides a pre-written prompt called "mcp-demo" that helps users create and analyze database scenarios. The prompt accepts a "topic" argument and guides users through creating tables, analyzing data, and generating insights. For example, if a user provides "retail sales" as the topic, the prompt will help create relevant database tables and guide the analysis process. Prompts basically serve as interactive templates that help structure the conversation with the LLM in a useful way. Resources: -This server exposes one key resource: "memo://insights", which is a business insights memo that gets automatically updated throughout the analysis process. As users analyze the database and discover insights, the memo resource gets updated in real-time to reflect new findings. The memo can even be enhanced with Claude's help if an Anthropic API key is provided, turning raw insights into a well-structured business document. Resources act as living documents that provide context to the conversation. +This server exposes one key resource: "memo://insights", which is a business insights memo that gets automatically updated throughout the analysis process. As users analyze the database and discover insights, the memo resource gets updated in real-time to reflect new findings. Resources act as living documents that provide context to the conversation. Tools: This server provides several SQL-related tools: "read-query": Executes SELECT queries to read data from the database diff --git a/src/sqlite/uv.lock b/src/sqlite/uv.lock index 4fad0aa5..369d3c0a 100644 --- a/src/sqlite/uv.lock +++ b/src/sqlite/uv.lock @@ -139,7 +139,7 @@ wheels = [ [[package]] name = "mcp-server-sqlite" -version = "0.4.1" +version = "0.5.1" source = { editable = "." } dependencies = [ { name = "mcp" }, From b9b08f85cc1abc927ee28e54a292202125e1fa10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Di=20Biase?= Date: Wed, 27 Nov 2024 16:45:02 -0300 Subject: [PATCH 12/16] Readme: Add missing servers to the featured server list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Luis Di Biase --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index fb385ce2..20279d70 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,13 @@ Each MCP server is implemented with either the [Typescript MCP SDK](https://gith - **[Filesystem](src/filesystem)** - Secure file operations with configurable access controls - **[GitHub](src/github)** - Repository management, file operations, and GitHub API integration +- **[GitLab](src/gitlab)** - GitLab API, enabling project management +- **[Git](src/git)** - Tools to read, search, and manipulate Git repositories - **[Google Drive](src/gdrive)** - File access and search capabilities for Google Drive - **[PostgreSQL](src/postgres)** - Read-only database access with schema inspection +- **[Sqlite](src/sqlite)** - Database interaction and business intelligence capabilities - **[Slack](src/slack)** - Channel management and messaging capabilities +- **[Sentry](src/sentry)** - Retrieving and analyzing issues from Sentry.io - **[Memory](src/memory)** - Knowledge graph-based persistent memory system - **[Puppeteer](src/puppeteer)** - Browser automation and web scraping - **[Brave Search](src/brave-search)** - Web and local search using Brave's Search API From 10e4bdfe2af2c9d82a23b87d0f17c4f33c5e0b1d Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Wed, 27 Nov 2024 22:15:16 +0000 Subject: [PATCH 13/16] update package-lock.json --- package-lock.json | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/package-lock.json b/package-lock.json index 59f8a243..3079d7c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -183,6 +183,10 @@ "resolved": "src/github", "link": true }, + "node_modules/@modelcontextprotocol/server-gitlab": { + "resolved": "src/gitlab", + "link": true + }, "node_modules/@modelcontextprotocol/server-google-maps": { "resolved": "src/google-maps", "link": true @@ -3491,6 +3495,58 @@ "url": "https://opencollective.com/node-fetch" } }, + "src/gitlab": { + "version": "0.5.1", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "0.6.0", + "@types/node-fetch": "^2.6.12", + "node-fetch": "^3.3.2", + "zod-to-json-schema": "^3.23.5" + }, + "bin": { + "mcp-server-gitlab": "dist/index.js" + }, + "devDependencies": { + "shx": "^0.3.4", + "typescript": "^5.6.2" + } + }, + "src/gitlab/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" + } + }, + "src/gitlab/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" + } + }, + "src/gitlab/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" + } + }, "src/google-maps": { "name": "@modelcontextprotocol/server-google-maps", "version": "0.5.1", From 1dd170da1731cf293b272c1d9b965f40dca5dd40 Mon Sep 17 00:00:00 2001 From: p13rr0m <16443611+p13rr0m@users.noreply.github.com> Date: Wed, 27 Nov 2024 23:22:17 +0100 Subject: [PATCH 14/16] Fix broken discussions link in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb385ce2..bbf8d207 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file ## 💬 Community -- [GitHub Discussions](https://github.com/modelcontextprotocol/servers/discussions) +- [GitHub Discussions](https://github.com/orgs/modelcontextprotocol/discussions) ## ⭐ Support From e8a26bbf534511d00208ca989987a3b1a852e33a Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Wed, 27 Nov 2024 22:42:47 +0000 Subject: [PATCH 15/16] lint clean --- src/git/src/mcp_server_git/__init__.py | 1 - src/git/src/mcp_server_git/server.py | 6 ++---- src/sentry/src/mcp_server_sentry/server.py | 1 - src/sqlite/src/mcp_server_sqlite/__init__.py | 1 - src/sqlite/src/mcp_server_sqlite/server.py | 1 - 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/git/src/mcp_server_git/__init__.py b/src/git/src/mcp_server_git/__init__.py index 2dfb6e5c..22700187 100644 --- a/src/git/src/mcp_server_git/__init__.py +++ b/src/git/src/mcp_server_git/__init__.py @@ -1,5 +1,4 @@ import click -from functools import partial from pathlib import Path import logging import sys diff --git a/src/git/src/mcp_server_git/server.py b/src/git/src/mcp_server_git/server.py index d4fe3328..fe1e3f59 100644 --- a/src/git/src/mcp_server_git/server.py +++ b/src/git/src/mcp_server_git/server.py @@ -1,5 +1,4 @@ import logging -import json from pathlib import Path from typing import Sequence from mcp.server import Server @@ -14,8 +13,7 @@ ) from enum import Enum import git -from pydantic import BaseModel, Field -from typing import List, Optional +from pydantic import BaseModel class GitStatus(BaseModel): repo_path: str @@ -32,7 +30,7 @@ class GitCommit(BaseModel): class GitAdd(BaseModel): repo_path: str - files: List[str] + files: list[str] class GitReset(BaseModel): repo_path: str diff --git a/src/sentry/src/mcp_server_sentry/server.py b/src/sentry/src/mcp_server_sentry/server.py index f2d7f4e6..1760311c 100644 --- a/src/sentry/src/mcp_server_sentry/server.py +++ b/src/sentry/src/mcp_server_sentry/server.py @@ -1,6 +1,5 @@ import asyncio from dataclasses import dataclass -from typing import Any from urllib.parse import urlparse import click diff --git a/src/sqlite/src/mcp_server_sqlite/__init__.py b/src/sqlite/src/mcp_server_sqlite/__init__.py index d2246942..1dc9f008 100644 --- a/src/sqlite/src/mcp_server_sqlite/__init__.py +++ b/src/sqlite/src/mcp_server_sqlite/__init__.py @@ -1,7 +1,6 @@ from . import server import asyncio import argparse -import os def main(): diff --git a/src/sqlite/src/mcp_server_sqlite/server.py b/src/sqlite/src/mcp_server_sqlite/server.py index 6437cc41..1b97a6a4 100644 --- a/src/sqlite/src/mcp_server_sqlite/server.py +++ b/src/sqlite/src/mcp_server_sqlite/server.py @@ -1,6 +1,5 @@ import sqlite3 import logging -from logging.handlers import RotatingFileHandler from contextlib import closing from pathlib import Path from mcp.server.models import InitializationOptions From b16a541c2e7eaab1ae77cd6ab8091f29d6cbeb0e Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Thu, 28 Nov 2024 14:35:52 +0900 Subject: [PATCH 16/16] chore: update server.py minor fix --- src/fetch/src/mcp_server_fetch/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fetch/src/mcp_server_fetch/server.py b/src/fetch/src/mcp_server_fetch/server.py index 63cae6fa..04ecad3c 100644 --- a/src/fetch/src/mcp_server_fetch/server.py +++ b/src/fetch/src/mcp_server_fetch/server.py @@ -47,7 +47,7 @@ def get_robots_txt_url(url: str) -> str: async def check_may_autonomously_fetch_url(url: str, user_agent: str): """ Check if the URL can be fetched by the user agent according to the robots.txt file. - Raises an McpError if not. + Raises a McpError if not. """ from httpx import AsyncClient, HTTPError