├── .gitignore ├── CONTRIBUTING.md ├── CONTRIBUTING_CN.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── README_CN.md ├── docs ├── images │ ├── comment_and_close_issue.jpg │ ├── get_repo_issues.jpg │ └── implement_issue.jpg └── install │ ├── claude.md │ ├── cline.md │ ├── continue.md │ ├── cursor.md │ ├── images │ ├── cursor.png │ ├── cursor_sse.png │ ├── cursor_stdio.png │ └── windsurf_stdio.png │ ├── logos │ ├── Claude.png │ ├── Cline.png │ ├── Cursor.png │ ├── Trae.png │ └── Windsurf.png │ ├── trae.md │ └── windsurf.md ├── go.mod ├── go.sum ├── main.go ├── mcp-gitee-schema.mdc ├── npm ├── mcp-gitee-darwin-amd64 │ └── package.json ├── mcp-gitee-darwin-arm64 │ └── package.json ├── mcp-gitee-linux-amd64 │ └── package.json ├── mcp-gitee-linux-arm64 │ └── package.json ├── mcp-gitee-windows-amd64 │ └── package.json ├── mcp-gitee-windows-arm64 │ └── package.json └── mcp-gitee │ ├── bin │ └── index.js │ └── package.json ├── operations ├── issues │ ├── comment_issue.go │ ├── common_options.go │ ├── create_issue.go │ ├── get_issue_detail.go │ ├── list_issue_comments.go │ ├── list_issues.go │ └── update_issue.go ├── notifications │ └── list_user_notifications.go ├── pulls │ ├── comment_pull.go │ ├── common_options.go │ ├── create_pull.go │ ├── get_pull_detail.go │ ├── list_pull_comments.go │ ├── list_pulls.go │ ├── merge_pull.go │ └── update_pull.go ├── repository │ ├── create_repository.go │ ├── creatre_release.go │ ├── fork_repository.go │ ├── get_file_content.go │ ├── list_releases.go │ ├── list_user_repos.go │ └── search_repositories.go ├── types │ ├── basic.go │ ├── file_content.go │ ├── issue.go │ ├── notification.go │ ├── pull.go │ ├── release.go │ └── repository.go └── users │ ├── get_user_info.go │ └── search_users.go ├── smithery.yaml └── utils ├── constants.go ├── convert.go ├── errors.go ├── gitee_client.go ├── mcp.go └── tool_options_handle.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.exe~ 3 | *.dll 4 | *.so 5 | *.dylib 6 | /bin 7 | /dist 8 | /tmp 9 | 10 | 11 | *.test 12 | *.out 13 | coverage.html 14 | profile.cov 15 | 16 | /vendor/ 17 | 18 | .idea/ 19 | *.iml 20 | 21 | .vscode/ 22 | release/ 23 | 24 | .DS_Store 25 | *.log 26 | .env 27 | .env.local 28 | 29 | node_modules/ 30 | 31 | .npmrc 32 | mcp-gitee 33 | !cmd/mcp-gitee 34 | !pkg/mcp-gitee 35 | npm/mcp-gitee/README.md 36 | npm/mcp-gitee/LICENSE 37 | !npm/mcp-gitee 38 | mcp-gitee-darwin-amd64 39 | !npm/mcp-gitee-darwin-amd64/ 40 | mcp-gitee-darwin-arm64 41 | !npm/mcp-gitee-darwin-arm64 42 | mcp-gitee-linux-amd64 43 | !npm/mcp-gitee-linux-amd64 44 | mcp-gitee-linux-arm64 45 | !npm/mcp-gitee-linux-arm64 46 | mcp-gitee-windows-amd64.exe 47 | mcp-gitee-windows-arm64.exe -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Gitee MCP Server 2 | 3 | We welcome contributions from the open-source community! If you'd like to contribute to this project, please follow these guidelines: 4 | 5 | ## Getting Started 6 | 7 | 1. **Fork the Repository**: Start by forking the repository to your own Gitee account. 8 | 2. **Clone the Repository**: Clone the forked repository to your local machine. 9 | ```bash 10 | git clone https://gitee.com/your-username/mcp-gitee.git 11 | cd mcp-gitee 12 | ``` 13 | 3. **Create a Branch**: Create a new branch for your feature or bug fix. 14 | ```bash 15 | git checkout -b feature/your-feature-name 16 | ``` 17 | 18 | ## Making Changes 19 | 20 | 1. **Code Style**: Follow the existing code style and conventions. Ensure your code is well-documented. 21 | 2. **Testing**: Write tests for your changes. Ensure all tests pass. 22 | 3. **Commit Messages**: Write clear and concise commit messages. 23 | 24 | ## Submitting Changes 25 | 26 | 1. **Push Your Changes**: Push your changes to your forked repository. 27 | ```bash 28 | git push origin feature/your-feature-name 29 | ``` 30 | 2. **Create a Pull Request**: Create a pull request from your forked repository to the original repository. Provide a clear description of your changes. 31 | 32 | ## Code of Conduct 33 | 34 | Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. 35 | 36 | ## License 37 | 38 | By contributing to this project, you agree that your contributions will be licensed under its MIT License. 39 | 40 | ## Contact 41 | 42 | If you have any questions or need further assistance, feel free to reach out to the maintainers. 43 | -------------------------------------------------------------------------------- /CONTRIBUTING_CN.md: -------------------------------------------------------------------------------- 1 | # 为 Gitee MCP 服务器做贡献 2 | 3 | 我们欢迎开源社区的贡献!如果您想为这个项目做出贡献,请按照以下指南操作: 4 | 5 | ## 入门 6 | 7 | 1. **Fork 仓库**:首先将仓库 fork 到您自己的 Gitee 账户。 8 | 2. **克隆仓库**:将 fork 的仓库克隆到您的本地机器。 9 | ```bash 10 | git clone https://gitee.com/your-username/mcp-gitee.git 11 | cd mcp-gitee 12 | ``` 13 | 3. **创建分支**:为您的功能或 bug 修复创建一个新分支。 14 | ```bash 15 | git checkout -b feature/your-feature-name 16 | ``` 17 | 18 | ## 进行更改 19 | 20 | 1. **代码风格**:遵循现有的代码风格和约定。确保您的代码有良好的文档。 21 | 2. **测试**:为您的更改编写测试。确保所有测试通过。 22 | 3. **提交消息**:编写清晰简洁的提交消息。 23 | 24 | ## 提交更改 25 | 26 | 1. **推送您的更改**:将您的更改推送到您的 fork 仓库。 27 | ```bash 28 | git push origin feature/your-feature-name 29 | ``` 30 | 2. **创建 Pull Request**:从您的 fork 仓库创建一个 pull request 到原始仓库。提供清晰的更改描述。 31 | 32 | ## 行为准则 33 | 34 | 请注意,这个项目发布了一个贡献者行为准则。通过参与这个项目,您同意遵守其条款。 35 | 36 | ## 许可证 37 | 38 | 通过为这个项目做出贡献,您同意您的贡献将根据其 MIT 许可证进行许可。 39 | 40 | ## 联系 41 | 42 | 如果您有任何问题或需要进一步的帮助,请随时联系维护者。 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-bullseye AS builder 2 | 3 | # Set the working directory 4 | WORKDIR /app 5 | 6 | # Copy go.mod and go.sum files 7 | COPY go.mod go.sum ./ 8 | 9 | # Download dependencies 10 | RUN go mod download 11 | 12 | # Copy the source code 13 | COPY . . 14 | 15 | # Build the application 16 | RUN go build -o mcp-gitee . 17 | 18 | # Final stage 19 | FROM debian:bullseye-slim 20 | 21 | # Install ca-certificates for HTTPS requests 22 | RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* 23 | 24 | # Create a non-root user 25 | RUN useradd -r -u 1000 -m gitee 26 | 27 | # Set the working directory 28 | WORKDIR /app 29 | 30 | # Copy the binary from the builder stage 31 | COPY --from=builder --chown=1000:1000 /app/mcp-gitee /app/ 32 | 33 | # Use the non-root user 34 | USER gitee 35 | 36 | # Expose the port the app runs on 37 | EXPOSE 8000 38 | 39 | # Run the application 40 | ENTRYPOINT ["/app/mcp-gitee"] 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Gitee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for cross-platform build 2 | BINARY_NAME = mcp-gitee 3 | NPM_VERSION = 0.1.11 4 | GO = go 5 | OSES = darwin linux windows 6 | ARCHS = amd64 arm64 7 | 8 | 9 | # Repository information 10 | GITEE_OWNER ?= "oschina" 11 | GITEE_REPO ?= "mcp-gitee" 12 | 13 | # Flags 14 | LDFLAGS = -ldflags "-s -w" 15 | BUILD_FLAGS = -o bin/mcp-gitee $(LDFLAGS) 16 | 17 | define show_usage_info 18 | @echo "\033[32m\n🤖🤖 Build Success 🤖🤖\033[0m" 19 | @echo "\033[32mExecutable path: $(shell pwd)/bin/mcp-gitee\033[0m" 20 | @echo "\033[33m\nUsage: ./bin/mcp-gitee [options]\033[0m" 21 | @echo "\033[33mAvailable options:\033[0m" 22 | @echo "\033[33m --token= Gitee access token (or set GITEE_ACCESS_TOKEN env)\033[0m" 23 | @echo "\033[33m --api-base= Gitee API base URL (or set GITEE_API_BASE env)\033[0m" 24 | @echo "\033[33m --version Show version information\033[0m" 25 | @echo "\033[33mExample: ./bin/mcp-gitee --token=your_access_token\033[0m" 26 | @echo "\033[33mExample with env: GITEE_ACCESS_TOKEN=your_token ./bin/mcp-gitee\033[0m" 27 | endef 28 | 29 | build: 30 | $(GO) build $(BUILD_FLAGS) -v main.go 31 | @echo "Build complete." 32 | $(call show_usage_info) 33 | 34 | # Clean up generated binaries 35 | clean: 36 | rm -f bin/mcp-gitee 37 | @echo "Clean up complete." 38 | 39 | 40 | .PHONY: build-all-platforms 41 | build-all-platforms: 42 | $(foreach os,$(OSES),$(foreach arch,$(ARCHS), \ 43 | GOOS=$(os) GOARCH=$(arch) go build $(BUILD_FLAGS) -o $(BINARY_NAME)-$(os)-$(arch)$(if $(findstring windows,$(os)),.exe,) main.go; \ 44 | )) 45 | 46 | .PHONY: npm-copy-binaries 47 | npm-copy-binaries: build-all-platforms 48 | $(foreach os,$(OSES),$(foreach arch,$(ARCHS), \ 49 | EXECUTABLE=./$(BINARY_NAME)-$(os)-$(arch)$(if $(findstring windows,$(os)),.exe,); \ 50 | DIRNAME=$(BINARY_NAME)-$(os)-$(arch); \ 51 | mkdir -p ./npm/$$DIRNAME/bin; \ 52 | cp $$EXECUTABLE ./npm/$$DIRNAME/bin/; \ 53 | )) 54 | 55 | .PHONY: npm-publish 56 | npm-publish: npm-copy-binaries ## Publish the npm packages 57 | $(foreach os,$(OSES),$(foreach arch,$(ARCHS), \ 58 | DIRNAME="$(BINARY_NAME)-$(os)-$(arch)"; \ 59 | cd npm/$$DIRNAME; \ 60 | echo '//registry.npmjs.org/:_authToken=$(NPM_TOKEN)' >> .npmrc; \ 61 | jq '.version = "$(NPM_VERSION)"' package.json > tmp.json && mv tmp.json package.json; \ 62 | npm publish; \ 63 | cd ../..; \ 64 | )) 65 | cp README.md LICENSE ./npm/mcp-gitee/ 66 | echo '//registry.npmjs.org/:_authToken=$(NPM_TOKEN)' >> ./npm/mcp-gitee/.npmrc 67 | jq '.version = "$(NPM_VERSION)"' ./npm/mcp-gitee/package.json > tmp.json && mv tmp.json ./npm/mcp-gitee/package.json; \ 68 | jq '.optionalDependencies |= with_entries(.value = "$(NPM_VERSION)")' ./npm/mcp-gitee/package.json > tmp.json && mv tmp.json ./npm/mcp-gitee/package.json; \ 69 | cd npm/mcp-gitee && npm publish 70 | 71 | 72 | # Clean up release directory 73 | clean-release: 74 | rm -rf release 75 | @echo "Clean up release directory complete." 76 | 77 | # Create a tarball for the given platform 78 | define create_tarball 79 | @echo "Packaging for $(1)..." 80 | @mkdir -p release/$(1) 81 | @cp bin/mcp-gitee release/$(1)/mcp-gitee$(2) 82 | @cp LICENSE release/$(1)/ 83 | @cp README.md release/$(1)/ 84 | @cp README_CN.md release/$(1)/ 85 | @tar -czvf release/mcp-gitee-$(1).tar.gz -C release/$(1) . 86 | @rm -rf release/$(1) 87 | endef 88 | 89 | release: clean clean-release 90 | @mkdir -p release 91 | @echo "Building for Linux..." 92 | GOOS=linux GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -v main.go 93 | $(call create_tarball,linux-amd64,) 94 | @echo "Building for Windows..." 95 | GOOS=windows GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -v main.go 96 | $(call create_tarball,windows-amd64,.exe) 97 | @echo "Building for macOS..." 98 | GOOS=darwin GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -v main.go 99 | $(call create_tarball,darwin-amd64,) 100 | @echo "Building for macOS ARM..." 101 | GOOS=darwin GOARCH=arm64 $(GO) build $(BUILD_FLAGS) -v main.go 102 | $(call create_tarball,darwin-arm64,) 103 | @echo "Building for Linux ARM..." 104 | GOOS=linux GOARCH=arm $(GO) build $(BUILD_FLAGS) -v main.go 105 | $(call create_tarball,linux-arm,) 106 | @echo "Release complete. Artifacts are in the release directory." 107 | 108 | # Upload artifacts to a specific release 109 | upload-gitee-release: 110 | @echo "Uploading artifacts to gitee release..." 111 | @for file in release/*; do \ 112 | curl -X POST \ 113 | -H "Content-Type: multipart/form-data" \ 114 | -F "access_token=$(GITEE_ACCESS_TOKEN)" \ 115 | -F "owner=$(GITEE_OWNER)" \ 116 | -F "repo=$(GITEE_REPO)" \ 117 | -F "release_id=$(GITEE_RELEASE_ID)" \ 118 | -F "file=@$$file" \ 119 | https://gitee.com/api/v5/repos/$(GITEE_OWNER)/$(GITEE_REPO)/releases/$(GITEE_RELEASE_ID)/attach_files; \ 120 | done 121 | @echo "Upload complete." 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gitee MCP Server 2 | 3 | Gitee MCP Server is a Model Context Protocol (MCP) server implementation for Gitee. It provides a set of tools for interacting with Gitee's API, allowing AI assistants to manage repositories, issues, pull requests, and more. 4 | 5 | [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=gitee&config=eyJjb21tYW5kIjoibnB4IC15IEBnaXRlZS9tY3AtZ2l0ZWVAbGF0ZXN0IiwiZW52Ijp7IkdJVEVFX0FDQ0VTU19UT0tFTiI6Ijx5b3VyIHBlcnNvbmFsIGFjY2VzcyB0b2tlbj4ifX0%3D) 6 | 7 | ## Features 8 | 9 | - Interact with Gitee repositories, issues, pull requests, and notifications 10 | - Configurable API base URL to support different Gitee instances 11 | - Command-line flags for easy configuration 12 | - Supports both personal, organization, and enterprise operations 13 | - Dynamic toolset enable/disable 14 | 15 |
16 | Practical scenario: Obtain Issue from the repository, implement and create a Pull Request 17 | 18 | 1. Get repository Issues 19 | ![get_repo_issues](./docs/images/get_repo_issues.jpg) 20 | 2. Implement coding & create Pull Request based on Issue details 21 | ![implement_issue](./docs/images/implement_issue.jpg) 22 | 3. Comment & Close Issue 23 | ![comment_and_close_issue](./docs/images/comment_and_close_issue.jpg) 24 |
25 | 26 | ## Installation(This step can be skipped directly when starting npx) 27 | 28 | ### Prerequisites 29 | 30 | - Go 1.23.0 or higher 31 | - Gitee account with an access token, [Go to get](https://gitee.com/profile/personal_access_tokens) 32 | 33 | ### Building from Source 34 | 35 | 1. Clone the repository: 36 | ```bash 37 | git clone https://gitee.com/oschina/mcp-gitee.git 38 | cd mcp-gitee 39 | ``` 40 | 41 | 2. Build the project: 42 | ```bash 43 | make build 44 | ``` 45 | Move ./bin/mcp-gitee PATH env 46 | 47 | ### Use go install 48 | ```bash 49 | go install gitee.com/oschina/mcp-gitee@latest 50 | ``` 51 | 52 | ## Usage 53 | 54 | Check mcp-gitee version: 55 | 56 | ```bash 57 | mcp-gitee --version 58 | ``` 59 | 60 | ## MCP Hosts Configuration 61 |
62 | 63 | 64 | 65 | 66 | 67 |
68 | 69 | config example: [Click to view more application configuration](./docs/install/) 70 | - Connect to the official remote mcp-gitee server (no installation required) 71 | ```json 72 | { 73 | "mcpServers": { 74 | "gitee": { 75 | "url": "https://api.gitee.com/mcp", 76 | "headers": { 77 | "Authorization": "Bearer " 78 | } 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | - npx 85 | ```json 86 | { 87 | "mcpServers": { 88 | "gitee": { 89 | "command": "npx", 90 | "args": [ 91 | "-y", 92 | "@gitee/mcp-gitee@latest" 93 | ], 94 | "env": { 95 | "GITEE_API_BASE": "https://gitee.com/api/v5", 96 | "GITEE_ACCESS_TOKEN": "" 97 | } 98 | } 99 | } 100 | } 101 | ``` 102 | - executable 103 | ```json 104 | { 105 | "mcpServers": { 106 | "gitee": { 107 | "command": "mcp-gitee", 108 | "env": { 109 | "GITEE_API_BASE": "https://gitee.com/api/v5", 110 | "GITEE_ACCESS_TOKEN": "" 111 | } 112 | } 113 | } 114 | } 115 | ``` 116 | 117 | ### Command-line Options 118 | 119 | - `--token`: Gitee access token 120 | - `--api-base`: Gitee API base URL (default: https://gitee.com/api/v5) 121 | - `--version`: Show version information 122 | - `--transport`: Transport type (stdio、sse or http, default: stdio) 123 | - `--address`: The host and port to start the server on (default: localhost:8000) 124 | - `--enabled-toolsets`: Comma-separated list of tools to enable (if specified, only these tools will be enabled) 125 | - `--disabled-toolsets`: Comma-separated list of tools to disable 126 | 127 | ### Environment Variables 128 | 129 | You can also configure the server using environment variables: 130 | 131 | - `GITEE_ACCESS_TOKEN`: Gitee access token 132 | - `GITEE_API_BASE`: Gitee API base URL 133 | - `ENABLED_TOOLSETS`: Comma-separated list of tools to enable 134 | - `DISABLED_TOOLSETS`: Comma-separated list of tools to disable 135 | 136 | ### Toolset Management 137 | 138 | Toolset management supports two modes: 139 | 140 | 1. Enable specified tools (whitelist mode): 141 | - Use `--enabled-toolsets` parameter or `ENABLED_TOOLSETS` environment variable 142 | - Specify after, only listed tools will be enabled, others will be disabled 143 | - Example: `--enabled-toolsets="list_user_repos,get_file_content"` 144 | 145 | 2. Disable specified tools (blacklist mode): 146 | - Use `--disabled-toolsets` parameter or `DISABLED_TOOLSETS` environment variable 147 | - Specify after, listed tools will be disabled, others will be enabled 148 | - Example: `--disabled-toolsets="list_user_repos,get_file_content"` 149 | 150 | Note: 151 | - If both `enabled-toolsets` and `disabled-toolsets` are specified, `enabled-toolsets` takes precedence 152 | - Tool names are case-sensitive 153 | 154 | ## License 155 | 156 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details. 157 | 158 | ## Available Tools 159 | 160 | The server provides various tools for interacting with Gitee: 161 | 162 | | Tool | Category | Description | 163 | |-------------------------------------|----------|-------------| 164 | | **list_user_repos** | Repository | List user authorized repositories | 165 | | **get_file_content** | Repository | Get the content of a file in a repository | 166 | | **create_user_repo** | Repository | Create a user repository | 167 | | **create_org_repo** | Repository | Create an organization repository | 168 | | **create_enter_repo** | Repository | Create an enterprise repository | 169 | | **fork_repository** | Repository | Fork a repository | 170 | | **create_release** | Repository | Create a release for a repository | 171 | | **list_releases** | Repository | List repository releases | 172 | | **search_open_source_repositories** | Repository | Search open source repositories on Gitee | 173 | | **list_repo_pulls** | Pull Request | List pull requests in a repository | 174 | | **merge_pull** | Pull Request | Merge a pull request | 175 | | **create_pull** | Pull Request | Create a pull request | 176 | | **update_pull** | Pull Request | Update a pull request | 177 | | **get_pull_detail** | Pull Request | Get details of a pull request | 178 | | **comment_pull** | Pull Request | Comment on a pull request | 179 | | **list_pull_comments** | Pull Request | List all comments for a pull request | 180 | | **create_issue** | Issue | Create an issue | 181 | | **update_issue** | Issue | Update an issue | 182 | | **get_repo_issue_detail** | Issue | Get details of a repository issue | 183 | | **list_repo_issues** | Issue | List repository issues | 184 | | **comment_issue** | Issue | Comment on an issue | 185 | | **list_issue_comments** | Issue | List comments on an issue | 186 | | **get_user_info** | User | Get current authenticated user information | 187 | | **search_users** | User | Search for users | 188 | | **list_user_notifications** | Notification | List user notifications | 189 | 190 | ## Contribution 191 | 192 | We welcome contributions from the open-source community! If you'd like to contribute to this project, please follow these guidelines: 193 | 194 | 1. Fork the repository. 195 | 2. Create a new branch for your feature or bug fix. 196 | 3. Make your changes and ensure the code is well-documented. 197 | 4. Submit a pull request with a clear description of your changes. 198 | 199 | For more information, please refer to the [CONTRIBUTING](CONTRIBUTING.md) file. 200 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Gitee MCP Server 2 | 3 | Gitee MCP 服务器是一个用于 Gitee 的模型上下文协议(Model Context Protocol,MCP)服务器实现。它提供了一系列与 Gitee API 交互的工具,使 AI 助手能够管理仓库、问题、拉取请求等。 4 | 5 | [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=gitee&config=eyJjb21tYW5kIjoibnB4IC15IEBnaXRlZS9tY3AtZ2l0ZWVAbGF0ZXN0IiwiZW52Ijp7IkdJVEVFX0FDQ0VTU19UT0tFTiI6Ijx5b3VyIHBlcnNvbmFsIGFjY2VzcyB0b2tlbj4ifX0%3D) 6 | 7 | ## 功能特点 8 | 9 | - 与 Gitee 仓库、Issue、Pull Request 和通知进行交互 10 | - 可配置的 API 基础 URL,支持不同的 Gitee 实例 11 | - 命令行标志,便于配置 12 | - 支持个人、组织和企业操作 13 | - 支持动态启用/禁用工具集 14 | 15 |
16 | 实战场景:从仓库获取 Issue,实现并创建 Pull Request 17 | 18 | 1. 获取当前仓库 Issues 19 | ![get_repo_issues](./docs/images/get_repo_issues.jpg) 20 | 2. 根据 Issue 详情实现编码 & 创建 Pull Request 21 | ![implement_issue](./docs/images/implement_issue.jpg) 22 | 3. 评论&关闭 Issue 23 | ![comment_and_close_issue](./docs/images/comment_and_close_issue.jpg) 24 |
25 | 26 | ## 安装(npx 启动可直接跳过该步骤) 27 | 28 | ### 前提条件 29 | 30 | - Go 1.23.0 或更高版本 31 | - 拥有访问令牌的 Gitee 账户,[前往获取](https://gitee.com/profile/personal_access_tokens) 32 | 33 | ### 从源代码构建 34 | 35 | 1. 克隆仓库: 36 | ```bash 37 | git clone https://gitee.com/oschina/mcp-gitee.git 38 | cd mcp-gitee 39 | ``` 40 | 41 | 2. 构建项目: 42 | ```bash 43 | make build 44 | ``` 45 | 将 ./bin/mcp-gitee 移动至系统环境变量 46 | 47 | ### 使用 go install 安装 48 | ```bash 49 | go install gitee.com/oschina/mcp-gitee@latest 50 | ``` 51 | 52 | ## 使用方法 53 | 54 | 检查 mcp-gitee 版本: 55 | 56 | ```bash 57 | mcp-gitee --version 58 | ``` 59 | 60 | ## MCP Hosts 配置 61 | 62 |
63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 | 配置示例:[点击查看更多应用配置](./docs/install/) 71 | - 连接官方 remote mcp-gitee server(免安装) 72 | ```json 73 | { 74 | "mcpServers": { 75 | "gitee": { 76 | "url": "https://api.gitee.com/mcp", 77 | "headers": { 78 | "Authorization": "Bearer " 79 | } 80 | } 81 | } 82 | } 83 | ``` 84 | 85 | - npx 启动 86 | ```json 87 | { 88 | "mcpServers": { 89 | "gitee": { 90 | "command": "npx", 91 | "args": [ 92 | "-y", 93 | "@gitee/mcp-gitee@latest" 94 | ], 95 | "env": { 96 | "GITEE_API_BASE": "https://gitee.com/api/v5", 97 | "GITEE_ACCESS_TOKEN": "" 98 | } 99 | } 100 | } 101 | } 102 | ``` 103 | - 可执行文件启动 104 | ```json 105 | { 106 | "mcpServers": { 107 | "gitee": { 108 | "command": "mcp-gitee", 109 | "env": { 110 | "GITEE_API_BASE": "https://gitee.com/api/v5", 111 | "GITEE_ACCESS_TOKEN": "" 112 | } 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | ### 命令行选项 119 | 120 | - `--token`:Gitee 访问令牌 121 | - `--api-base`:Gitee API 基础 URL(默认:https://gitee.com/api/v5) 122 | - `--version`:显示版本信息 123 | - `--transport`:传输类型(stdio、sse 或 http,默认:stdio) 124 | - `--address`:启动服务器的主机和端口(默认:localhost:8000) 125 | - `--enabled-toolsets`: 逗号分隔的要启用的工具列表(如果指定,则只启用这些工具) 126 | - `--disabled-toolsets`: 逗号分隔的要禁用的工具列表 127 | 128 | ### 环境变量 129 | 130 | 您也可以使用环境变量配置服务器: 131 | 132 | - `GITEE_ACCESS_TOKEN`:Gitee 访问令牌 133 | - `GITEE_API_BASE`:Gitee API 基础 URL 134 | - `ENABLED_TOOLSETS`: 逗号分隔的要启用的工具列表 135 | - `DISABLED_TOOLSETS`: 逗号分隔的要禁用的工具列表 136 | 137 | ### 工具集管理 138 | 139 | 工具集管理支持两种模式: 140 | 141 | 1. 启用指定工具(白名单模式): 142 | - 使用 `--enabled-toolsets` 参数或 `ENABLED_TOOLSETS` 环境变量 143 | - 指定后,只有列出的工具会被启用,其他工具都会被禁用 144 | - 例如:`--enabled-toolsets="update_enterprise_issue,list_enterprise_repositories"` 145 | 146 | 2. 禁用指定工具(黑名单模式): 147 | - 使用 `--disabled-toolsets` 参数或 `DISABLED_TOOLSETS` 环境变量 148 | - 指定后,列出的工具会被禁用,其他工具保持启用 149 | - 例如:`--disabled-toolsets="update_enterprise_issue,list_enterprise_repositories"` 150 | 151 | 注意: 152 | - 如果同时指定了 `enabled-toolsets` 和 `disabled-toolsets`,则 `enabled-toolsets` 优先 153 | - 工具名称区分大小写 154 | 155 | ## 许可证 156 | 157 | 本项目采用 MIT 许可证。有关更多详细信息,请参阅 [LICENSE](LICENSE) 文件。 158 | 159 | ## 可用工具 160 | 161 | 服务器提供了各种与 Gitee 交互的工具: 162 | 163 | | 工具 | 类别 | 描述 | 164 | |-----------------------------|--------------|------------------| 165 | | **list_user_repos** | 仓库 | 列出用户授权的仓库 | 166 | | **get_file_content** | 仓库 | 获取仓库中文件的内容 | 167 | | **create_user_repo** | 仓库 | 创建用户仓库 | 168 | | **create_org_repo** | 仓库 | 创建组织仓库 | 169 | | **create_enter_repo** | 仓库 | 创建企业仓库 | 170 | | **fork_repository** | 仓库 | Fork 仓库 | 171 | | **create_release** | 仓库 | 为仓库创建发行版 | 172 | | **list_releases** | 仓库 | 列出仓库发行版 | 173 | | **search_open_source_repositories** | 仓库 | 搜索开源仓库 | 174 | | **list_repo_pulls** | Pull Request | 列出仓库中的拉取请求 | 175 | | **merge_pull** | Pull Request | 合并拉取请求 | 176 | | **create_pull** | Pull Request | 创建拉取请求 | 177 | | **update_pull** | Pull Request | 更新拉取请求 | 178 | | **get_pull_detail** | Pull Request | 获取拉取请求的详细信息 | 179 | | **comment_pull** | Pull Request | 评论拉取请求 | 180 | | **list_pull_comments** | Pull Request | 列出拉取请求的所有评论 | 181 | | **create_issue** | Issue | 创建 Issue | 182 | | **update_issue** | Issue | 更新 Issue | 183 | | **get_repo_issue_detail** | Issue | 获取仓库 Issue 的详细信息 | 184 | | **list_repo_issues** | Issue | 列出仓库 Issue | 185 | | **comment_issue** | Issue | 评论 Issue | 186 | | **list_issue_comments** | Issue | 列出 Issue 的评论 | 187 | | **get_user_info** | 用户 | 获取当前认证用户信息 | 188 | | **search_users** | 用户 | 搜索用户 | 189 | | **list_user_notifications** | 通知 | 列出用户通知 | 190 | 191 | ## 贡献 192 | 193 | 我们欢迎开源社区的贡献!如果您想为这个项目做出贡献,请按照以下指南操作: 194 | 195 | 1. Fork 这个仓库。 196 | 2. 为您的功能或 bug 修复创建一个新分支。 197 | 3. 进行更改,并确保代码有良好的文档。 198 | 4. 提交一个 pull request,并附上清晰的更改描述。 199 | 200 | 更多信息,请参阅 [CONTRIBUTING](CONTRIBUTING.md) 文件。 201 | -------------------------------------------------------------------------------- /docs/images/comment_and_close_issue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/images/comment_and_close_issue.jpg -------------------------------------------------------------------------------- /docs/images/get_repo_issues.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/images/get_repo_issues.jpg -------------------------------------------------------------------------------- /docs/images/implement_issue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/images/implement_issue.jpg -------------------------------------------------------------------------------- /docs/install/claude.md: -------------------------------------------------------------------------------- 1 | stdio mode: 2 | ```json 3 | { 4 | "mcpServers": { 5 | "gitee": { 6 | "command": "mcp-gitee", 7 | "env": { 8 | "GITEE_API_BASE": "https://gitee.com/api/v5", 9 | "GITEE_ACCESS_TOKEN": "" 10 | } 11 | } 12 | } 13 | } 14 | ``` -------------------------------------------------------------------------------- /docs/install/cline.md: -------------------------------------------------------------------------------- 1 | stdio mode: 2 | ```json 3 | { 4 | "mcpServers": { 5 | "gitee": { 6 | "command": "mcp-gitee", 7 | "env": { 8 | "GITEE_API_BASE": "https://gitee.com/api/v5", 9 | "GITEE_ACCESS_TOKEN": "" 10 | } 11 | } 12 | } 13 | } 14 | ``` -------------------------------------------------------------------------------- /docs/install/continue.md: -------------------------------------------------------------------------------- 1 | Continue hub 助手使用 config.yaml 规范定义。本地助手也可以通过放置在全局 .continue 文件夹中的 YAML 文件 config.yaml 进行配置(Mac 上为 ~/.continue,Windows 上为 %USERPROFILE%\.continue) 2 | 3 | 配置示例: 4 | 5 | ```yml 6 | mcpServers: 7 | - name: gitee 8 | command: mcp-gitee 9 | args: 10 | - --token 11 | - 12 | ``` -------------------------------------------------------------------------------- /docs/install/cursor.md: -------------------------------------------------------------------------------- 1 | stdio mode 2 | ```json 3 | { 4 | "mcpServers": { 5 | "gitee": { 6 | "command": "mcp-gitee", 7 | "env": { 8 | "GITEE_API_BASE": "https://gitee.com/api/v5", 9 | "GITEE_ACCESS_TOKEN": "" 10 | } 11 | } 12 | } 13 | } 14 | ``` 15 | 16 | sse mode 17 | 18 | start mcp server through sse 19 | ```bash 20 | mcp-gitee -token -transport sse 21 | ``` 22 | ```json 23 | { 24 | "mcpServers": { 25 | "gitee": { 26 | "url": "http://localhost:8000/sse", 27 | } 28 | } 29 | } 30 | ``` 31 | ![sse](./images/cursor.png) -------------------------------------------------------------------------------- /docs/install/images/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/install/images/cursor.png -------------------------------------------------------------------------------- /docs/install/images/cursor_sse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/install/images/cursor_sse.png -------------------------------------------------------------------------------- /docs/install/images/cursor_stdio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/install/images/cursor_stdio.png -------------------------------------------------------------------------------- /docs/install/images/windsurf_stdio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/install/images/windsurf_stdio.png -------------------------------------------------------------------------------- /docs/install/logos/Claude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/install/logos/Claude.png -------------------------------------------------------------------------------- /docs/install/logos/Cline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/install/logos/Cline.png -------------------------------------------------------------------------------- /docs/install/logos/Cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/install/logos/Cursor.png -------------------------------------------------------------------------------- /docs/install/logos/Trae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/install/logos/Trae.png -------------------------------------------------------------------------------- /docs/install/logos/Windsurf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oschina/mcp-gitee/42b629d52b86ec789bbc363399a5a1ec258e8de5/docs/install/logos/Windsurf.png -------------------------------------------------------------------------------- /docs/install/trae.md: -------------------------------------------------------------------------------- 1 | npx start: 2 | ```json 3 | { 4 | "mcpServers": { 5 | "gitee": { 6 | "command": "npx", 7 | "args": [ 8 | "-y", 9 | "@gitee/mcp-gitee@latest" 10 | ], 11 | "env": { 12 | "GITEE_API_BASE": "https://gitee.com/api/v5", 13 | "GITEE_ACCESS_TOKEN": "" 14 | } 15 | } 16 | } 17 | } 18 | ``` 19 | 20 | stdio mode: 21 | ```json 22 | { 23 | "mcpServers": { 24 | "gitee": { 25 | "command": "mcp-gitee", 26 | "env": { 27 | "GITEE_API_BASE": "https://gitee.com/api/v5", 28 | "GITEE_ACCESS_TOKEN": "" 29 | } 30 | } 31 | } 32 | } 33 | ``` -------------------------------------------------------------------------------- /docs/install/windsurf.md: -------------------------------------------------------------------------------- 1 | stdio mode: 2 | ```json 3 | { 4 | "mcpServers": { 5 | "gitee": { 6 | "command": "mcp-gitee", 7 | "env": { 8 | "GITEE_API_BASE": "https://gitee.com/api/v5", 9 | "GITEE_ACCESS_TOKEN": "" 10 | } 11 | } 12 | } 13 | } 14 | ``` 15 | ![stdio](./images/windsurf_stdio.png) 16 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gitee.com/oschina/mcp-gitee 2 | 3 | go 1.23.0 4 | 5 | require github.com/mark3labs/mcp-go v0.32.0 6 | 7 | require ( 8 | github.com/google/uuid v1.6.0 // indirect 9 | github.com/spf13/cast v1.9.2 // indirect 10 | github.com/yosida95/uritemplate/v3 v3.0.2 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 4 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 5 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 6 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 7 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 8 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 9 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 10 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 11 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 12 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 13 | github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8= 14 | github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 18 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 19 | github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= 20 | github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= 21 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 22 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 23 | github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= 24 | github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= 25 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 26 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | "strings" 11 | 12 | "gitee.com/oschina/mcp-gitee/operations/issues" 13 | "gitee.com/oschina/mcp-gitee/operations/notifications" 14 | "gitee.com/oschina/mcp-gitee/operations/pulls" 15 | "gitee.com/oschina/mcp-gitee/operations/repository" 16 | "gitee.com/oschina/mcp-gitee/operations/users" 17 | "gitee.com/oschina/mcp-gitee/utils" 18 | "github.com/mark3labs/mcp-go/mcp" 19 | 20 | "github.com/mark3labs/mcp-go/server" 21 | ) 22 | 23 | var ( 24 | Version = utils.Version 25 | disabledToolsetsFlag string 26 | enabledToolsetsFlag string 27 | ) 28 | 29 | func newMCPServer() *server.MCPServer { 30 | hooks := &server.Hooks{} 31 | 32 | hooks.OnBeforeCallTool = append(hooks.OnBeforeCallTool, func(ctx context.Context, id any, message *mcp.CallToolRequest) { 33 | log.Printf("[tool call] ToolName: %s, Params: %v", message.Params.Name, message.Params.Arguments) 34 | }) 35 | 36 | hooks.OnAfterCallTool = append(hooks.OnAfterCallTool, func(ctx context.Context, id any, message *mcp.CallToolRequest, result *mcp.CallToolResult) { 37 | if result != nil && result.IsError { 38 | log.Printf("[tool call error] ToolName: %s, Params: %v, error msg: %v", message.Params.Name, message.Params.Arguments, result.Content) 39 | } 40 | }) 41 | 42 | return server.NewMCPServer( 43 | "mcp-gitee", 44 | Version, 45 | server.WithToolCapabilities(true), 46 | server.WithLogging(), 47 | server.WithHooks(hooks), 48 | ) 49 | } 50 | 51 | func addTool(s *server.MCPServer, tool mcp.Tool, handleFunc server.ToolHandlerFunc) { 52 | enabledToolsets := getEnabledToolsets() 53 | if len(enabledToolsets) == 0 { 54 | s.AddTool(tool, handleFunc) 55 | return 56 | } 57 | 58 | for i := range enabledToolsets { 59 | enabledToolsets[i] = strings.TrimSpace(enabledToolsets[i]) 60 | } 61 | 62 | for _, keepTool := range enabledToolsets { 63 | if tool.Name == keepTool { 64 | s.AddTool(tool, handleFunc) 65 | return 66 | } 67 | } 68 | } 69 | 70 | func disableTools(s *server.MCPServer) { 71 | if enabledToolsetsFlag != "" { 72 | enabledToolsetsFlag = os.Getenv("ENABLED_TOOLSETS") 73 | } 74 | 75 | if enabledToolsetsFlag != "" { 76 | return 77 | } 78 | 79 | if disabledTools := getDisabledToolsets(); len(disabledTools) > 0 { 80 | s.DeleteTools(disabledTools...) 81 | } 82 | } 83 | 84 | func addTools(s *server.MCPServer) { 85 | // Repository Tools 86 | addTool(s, repository.ListUserReposTool, repository.ListUserReposHandler) 87 | addTool(s, repository.GetFileContentTool, repository.GetFileContentHandler) 88 | addTool(s, repository.NewCreateRepoTool(repository.CreateUserRepo), repository.CreateRepoHandleFunc(repository.CreateUserRepo)) 89 | addTool(s, repository.NewCreateRepoTool(repository.CreateOrgRepo), repository.CreateRepoHandleFunc(repository.CreateOrgRepo)) 90 | addTool(s, repository.NewCreateRepoTool(repository.CreateEnterRepo), repository.CreateRepoHandleFunc(repository.CreateEnterRepo)) 91 | addTool(s, repository.CreateReleaseTool, repository.CreateReleaseHandleFunc) 92 | addTool(s, repository.ListReleasesTool, repository.ListReleasesHandleFunc) 93 | addTool(s, repository.SearchReposTool, repository.SearchOpenSourceReposHandler) 94 | addTool(s, repository.ForkRepositoryTool, repository.ForkRepositoryHandler) 95 | 96 | // Pulls Tools 97 | addTool(s, pulls.NewListPullsTool(pulls.ListRepoPullsToolName), pulls.ListPullsHandleFunc(pulls.ListRepoPullsToolName)) 98 | addTool(s, pulls.CreatePullTool, pulls.CreatePullHandleFunc) 99 | addTool(s, pulls.UpdatePullTool, pulls.UpdatePullHandleFunc) 100 | addTool(s, pulls.GetPullDetailTool, pulls.GetPullDetailHandleFunc) 101 | addTool(s, pulls.CommentPullTool, pulls.CommentPullHandleFunc) 102 | addTool(s, pulls.MergePullTool, pulls.MergePullHandleFunc) 103 | addTool(s, pulls.ListPullCommentsTool, pulls.ListPullCommentsHandleFunc) 104 | 105 | // Issues Tools 106 | addTool(s, issues.CreateIssueTool, issues.CreateIssueHandleFunc) 107 | addTool(s, issues.UpdateIssueTool, issues.UpdateIssueHandleFunc) 108 | addTool(s, issues.NewGetIssueDetailTool(issues.GetRepoIssueDetailToolName), issues.GetIssueDetailHandleFunc(issues.GetRepoIssueDetailToolName)) 109 | addTool(s, issues.NewListIssuesTool(issues.ListRepoIssuesToolName), issues.ListIssuesHandleFunc(issues.ListRepoIssuesToolName)) 110 | addTool(s, issues.CommentIssueTool, issues.CommentIssueHandleFunc) 111 | addTool(s, issues.ListIssueCommentsTool, issues.ListIssueCommentsHandleFunc) 112 | 113 | // Notifications Tools 114 | addTool(s, notifications.ListUserNotificationsTool, notifications.ListUserNotificationsHandler) 115 | 116 | // Users Tools 117 | addTool(s, users.GetUserInfoTool, users.GetUserInfoHandleFunc()) 118 | addTool(s, users.SearchUsersTool, users.SearchUsersHandler) 119 | } 120 | 121 | func getDisabledToolsets() []string { 122 | if disabledToolsetsFlag == "" { 123 | disabledToolsetsFlag = os.Getenv("DISABLED_TOOLSETS") 124 | } 125 | 126 | if disabledToolsetsFlag == "" { 127 | return nil 128 | } 129 | 130 | tools := strings.Split(disabledToolsetsFlag, ",") 131 | for i := range tools { 132 | tools[i] = strings.TrimSpace(tools[i]) 133 | } 134 | 135 | return tools 136 | } 137 | 138 | func getEnabledToolsets() []string { 139 | if enabledToolsetsFlag == "" { 140 | enabledToolsetsFlag = os.Getenv("ENABLED_TOOLSETS") 141 | } 142 | if enabledToolsetsFlag == "" { 143 | return nil 144 | } 145 | tools := strings.Split(enabledToolsetsFlag, ",") 146 | for i := range tools { 147 | tools[i] = strings.TrimSpace(tools[i]) 148 | } 149 | return tools 150 | } 151 | 152 | func run(transport, addr string) error { 153 | s := newMCPServer() 154 | addTools(s) 155 | disableTools(s) 156 | 157 | switch transport { 158 | case "stdio": 159 | if err := server.ServeStdio(s); err != nil { 160 | if err == context.Canceled { 161 | return nil 162 | } 163 | return err 164 | } 165 | case "sse": 166 | srv := server.NewSSEServer(s, server.WithBaseURL(addr)) 167 | log.Printf("SSE server listening on %s", addr) 168 | if err := srv.Start(addr); err != nil { 169 | if err == context.Canceled { 170 | return nil 171 | } 172 | return fmt.Errorf("server error: %v", err) 173 | } 174 | case "http": 175 | httpServer := server.NewStreamableHTTPServer(s, 176 | server.WithStateLess(true), 177 | server.WithHTTPContextFunc(func(ctx context.Context, r *http.Request) context.Context { 178 | auth := r.Header.Get("Authorization") 179 | if len(auth) > 7 && auth[:7] == "Bearer " { 180 | token := auth[7:] 181 | ctx = context.WithValue(ctx, "access_token", token) 182 | } 183 | return ctx 184 | }), 185 | ) 186 | log.Printf("HTTP server listening on %s", addr) 187 | if err := httpServer.Start(addr); err != nil { 188 | if err == context.Canceled { 189 | return nil 190 | } 191 | return fmt.Errorf("server error: %v", err) 192 | } 193 | default: 194 | return fmt.Errorf( 195 | "invalid transport type: %s. Must be 'stdio'、'sse' or 'http'", 196 | transport, 197 | ) 198 | } 199 | return nil 200 | } 201 | 202 | func main() { 203 | accessToken := flag.String("token", "", "Gitee access token") 204 | apiBase := flag.String("api-base", "", "Gitee API base URL (default: https://gitee.com/api/v5)") 205 | showVersion := flag.Bool("version", false, "Show version information") 206 | transport := flag.String("transport", "stdio", "Transport type (stdio or sse)") 207 | addr := flag.String("address", "localhost:8000", "The host and port to start the sse/http server on") 208 | flag.StringVar(&disabledToolsetsFlag, "disabled-toolsets", "", "Comma-separated list of tools to disable") 209 | flag.StringVar(&enabledToolsetsFlag, "enabled-toolsets", "", "Comma-separated list of tools to enable (if specified, only these tools will be available)") 210 | flag.Parse() 211 | 212 | if *showVersion { 213 | fmt.Printf("Gitee MCP Server\n") 214 | fmt.Printf("Version: %s\n", Version) 215 | os.Exit(0) 216 | } 217 | 218 | if *accessToken != "" { 219 | utils.SetGiteeAccessToken(*accessToken) 220 | } 221 | 222 | if *apiBase != "" { 223 | utils.SetApiBase(*apiBase) 224 | } 225 | 226 | if err := run(*transport, *addr); err != nil { 227 | panic(err) 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /mcp-gitee-schema.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: mcp-gitee cursor rule 3 | globs: 4 | alwaysApply: true 5 | --- 6 | # MCP-Gitee Cursor Rule 7 | 8 | ## 1. 工具选择与基本规则 9 | 10 | - 用户可通过工具名称(如 list_user_repos、create_issue、create_pull 等)选择所需操作。 11 | - 每个工具均有简要描述,帮助用户理解其用途。 12 | - 工具参数分为必填和可选,必填项未填写时禁止提交,可选项未填写时自动采用默认值。 13 | - 参数类型、可选值、默认值均由系统自动校验和提示。 14 | 15 | ## 2. 参数填写与校验 16 | 17 | - 所有参数均有类型校验(如数字、字符串、布尔值)。 18 | - 枚举参数(如 visibility、state 等)仅允许选择指定范围内的值。 19 | - project_id、program_id、assignee_id 等 ID 参数必须为数字,禁止填写名称或别名。 20 | - 若参数为 ID 且用户不清楚,可先通过相关工具(如 search_users、list_user_repos)查询获取。 21 | 22 | ## 3. 工具组合与典型流程 23 | 24 | ### 3.1 仓库/项目管理 25 | 26 | - 查询我的仓库:使用 list_user_repos,支持按 affiliation、type、visibility 等筛选。 27 | - 查看仓库文件:选中仓库后,使用 get_file_content 查看 README 或其他文件。 28 | - 在仓库下创建 Issue/Pull Request:结合 create_issue 或 create_pull 工具。 29 | 30 | ### 3.2 工作项流转 31 | 32 | - 查找项目下工作项:先用 list_user_repos 找到仓库,再用 list_repo_issues。 33 | - 筛选我负责的工作项:list_repo_issues 填写 assignee_id(数字)。 34 | - 更新/评论工作项:选中 Issue 后,使用 update_issue 或 comment_issue。 35 | 36 | ### 3.3 协作与代码合并 37 | 38 | - 发起 Pull Request:list_user_repos 找到仓库,create_pull 填写分支、assignees 等。 39 | - 查看/评论 Pull Request:list_repo_pulls 查看,list_pull_comments 或 comment_pull 参与讨论。 40 | - 合并 Pull Request:merge_pull 工具。 41 | 42 | ### 3.4 版本发布 43 | 44 | - 查看 Release:list_releases。 45 | - 创建 Release:create_release,填写版本号、描述、目标分支等。 46 | 47 | ## 4. 工具间参数传递 48 | 49 | - 工具输出的仓库名、用户ID、项目ID等可直接作为下一个工具的输入。 50 | - 复杂操作建议分步进行,每步均可利用工具的筛选、查询和补全能力。 51 | 52 | ## 5. 智能提示与交互 53 | 54 | - 系统自动提示参数类型、可选值、必填项。 55 | - 参数填写错误时,系统给出明确提示并指导修正。 56 | - 支持下拉选择、自动补全,减少手动输入错误。 57 | - 不确定参数含义时,可查看工具描述或帮助文档。 58 | 59 | ## 6. 典型操作流程示例 60 | 61 | ### 示例1:新建 Issue 并指派同事 62 | 63 | 1. list_user_repos 查询仓库 64 | 2. search_users 查询同事ID 65 | 3. create_issue 填写 owner、repo、title、assignee(同事ID)、program(项目ID,数字) 66 | 67 | ### 示例2:查找并处理我负责的工作项 68 | 69 | 1. list_user_repos 查询仓库 70 | 2. list_repo_issues 填写 assignee_id(你的ID) 71 | 3. update_issue 或 comment_issue 处理任务 72 | 73 | ### 示例3:发起代码合并并通知评审 74 | 75 | 1. list_user_repos 查询仓库 76 | 2. create_pull 发起 PR,填写 assignees 77 | 3. comment_pull 留言提醒评审 78 | 79 | ### 示例3:实现 Issue 并创建 Pull Request 80 | 1. 获取 Issue 详情:get_repo_issue_detail 81 | 2. 获取 Issue 评论:list_issue_comments 82 | 3. 新建分支 83 | 4. 实现 Issue 84 | 5. 推送至远端 && 创建 Pull Request:create_pull 85 | 86 | --- 87 | 88 | **本规则适用于所有 MCP-Gitee 工具的终端用户,旨在提升操作效率、减少出错,支持多工具组合与参数智能传递。** 89 | -------------------------------------------------------------------------------- /npm/mcp-gitee-darwin-amd64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitee/mcp-gitee-darwin-amd64", 3 | "version": "0.1.11", 4 | "description": "Model Context Protocol (MCP) server for Gitee", 5 | "os": [ 6 | "darwin" 7 | ], 8 | "cpu": [ 9 | "x64" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /npm/mcp-gitee-darwin-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitee/mcp-gitee-darwin-arm64", 3 | "version": "0.1.11", 4 | "description": "Model Context Protocol (MCP) server for Gitee", 5 | "os": [ 6 | "darwin" 7 | ], 8 | "cpu": [ 9 | "arm64" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /npm/mcp-gitee-linux-amd64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitee/mcp-gitee-linux-amd64", 3 | "version": "0.1.11", 4 | "description": "Model Context Protocol (MCP) server for Gitee", 5 | "os": [ 6 | "linux" 7 | ], 8 | "cpu": [ 9 | "x64" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /npm/mcp-gitee-linux-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitee/mcp-gitee-linux-arm64", 3 | "version": "0.1.11", 4 | "description": "Model Context Protocol (MCP) server for Gitee", 5 | "os": [ 6 | "linux" 7 | ], 8 | "cpu": [ 9 | "arm64" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /npm/mcp-gitee-windows-amd64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitee/mcp-gitee-windows-amd64", 3 | "version": "0.1.11", 4 | "description": "Model Context Protocol (MCP) server for Gitee", 5 | "os": [ 6 | "win32" 7 | ], 8 | "cpu": [ 9 | "x64" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /npm/mcp-gitee-windows-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitee/mcp-gitee-windows-arm64", 3 | "version": "0.1.11", 4 | "description": "Model Context Protocol (MCP) server for Gitee", 5 | "os": [ 6 | "win32" 7 | ], 8 | "cpu": [ 9 | "arm64" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /npm/mcp-gitee/bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const childProcess = require('child_process'); 4 | 5 | const BINARY_MAP = { 6 | darwin_x64: {name: 'mcp-gitee-darwin-amd64', suffix: ''}, 7 | darwin_arm64: {name: 'mcp-gitee-darwin-arm64', suffix: ''}, 8 | linux_x64: {name: 'mcp-gitee-linux-amd64', suffix: ''}, 9 | linux_arm64: {name: 'mcp-gitee-linux-arm64', suffix: ''}, 10 | win32_x64: {name: 'mcp-gitee-windows-amd64', suffix: '.exe'}, 11 | win32_arm64: {name: 'mcp-gitee-windows-arm64', suffix: '.exe'}, 12 | }; 13 | 14 | // Resolving will fail if the optionalDependency was not installed or the platform/arch is not supported 15 | const resolveBinaryPath = () => { 16 | try { 17 | const binary = BINARY_MAP[`${process.platform}_${process.arch}`]; 18 | return require.resolve(`@gitee/${binary.name}/bin/${binary.name}${binary.suffix}`); 19 | } catch (e) { 20 | console.error(`Could not resolve binary path for platform/arch: ${process.platform}/${process.arch}`); 21 | process.exit(1); 22 | } 23 | }; 24 | 25 | childProcess.execFileSync(resolveBinaryPath(), process.argv.slice(2), { 26 | stdio: 'inherit', 27 | }); 28 | -------------------------------------------------------------------------------- /npm/mcp-gitee/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gitee/mcp-gitee", 3 | "version": "0.1.11", 4 | "description": "Model Context Protocol (MCP) server for Gitee", 5 | "main": "./bin/index.js", 6 | "bin": { 7 | "mcp-gitee": "bin/index.js" 8 | }, 9 | "optionalDependencies": { 10 | "@gitee/mcp-gitee-darwin-amd64": "0.1.11", 11 | "@gitee/mcp-gitee-darwin-arm64": "0.1.11", 12 | "@gitee/mcp-gitee-linux-amd64": "0.1.11", 13 | "@gitee/mcp-gitee-linux-arm64": "0.1.11", 14 | "@gitee/mcp-gitee-windows-amd64": "0.1.11", 15 | "@gitee/mcp-gitee-windows-arm64": "0.1.11" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://gitee.com/oschina/mcp-gitee.git" 20 | }, 21 | "keywords": [ 22 | "mcp", 23 | "gitee", 24 | "model context protocol", 25 | "model", 26 | "context", 27 | "protocol" 28 | ], 29 | "author": { 30 | "name": "Gitee", 31 | "url": "https://gitee.com/oschina" 32 | }, 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://gitee.com/oschina/mcp-gitee/issues" 36 | }, 37 | "homepage": "https://gitee.com/oschina/mcp-gitee#readme", 38 | "publishConfig": { 39 | "access": "public" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /operations/issues/comment_issue.go: -------------------------------------------------------------------------------- 1 | package issues 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | ) 11 | 12 | const ( 13 | // CommentIssueToolName is the name of the tool 14 | CommentIssueToolName = "comment_issue" 15 | ) 16 | 17 | // CommentIssueOptions defines the specific options for commenting on an issue 18 | var CommentIssueOptions = []mcp.ToolOption{ 19 | mcp.WithString( 20 | "number", 21 | mcp.Description("Issue number (case sensitive, no # prefix needed)"), 22 | mcp.Required(), 23 | ), 24 | mcp.WithString( 25 | "body", 26 | mcp.Description("The contents of the comment"), 27 | mcp.Required(), 28 | ), 29 | } 30 | 31 | // CommentIssueTool defines the tool for commenting on an issue 32 | var CommentIssueTool = func() mcp.Tool { 33 | options := utils.CombineOptions( 34 | []mcp.ToolOption{ 35 | mcp.WithDescription("Create a comment on a repository issue"), 36 | }, 37 | BasicOptions, 38 | CommentIssueOptions, 39 | ) 40 | return mcp.NewTool(CommentIssueToolName, options...) 41 | }() 42 | 43 | // CommentIssueHandleFunc handles the request to comment on an issue 44 | func CommentIssueHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 45 | // Extract required parameters from the request 46 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 47 | owner := args["owner"].(string) 48 | repo := args["repo"].(string) 49 | number := args["number"].(string) 50 | 51 | // Construct the API URL for creating a comment on an issue 52 | apiUrl := fmt.Sprintf("/repos/%s/%s/issues/%s/comments", owner, repo, number) 53 | 54 | // Create a new Gitee client with the POST method and the constructed API URL 55 | giteeClient := utils.NewGiteeClient("POST", apiUrl, utils.WithContext(ctx), utils.WithPayload(request.Params.Arguments)) 56 | 57 | // Define the response structure 58 | comment := &types.IssueComment{} 59 | 60 | // Handle the API call and return the result 61 | return giteeClient.HandleMCPResult(comment) 62 | } 63 | -------------------------------------------------------------------------------- /operations/issues/common_options.go: -------------------------------------------------------------------------------- 1 | package issues 2 | 3 | import "github.com/mark3labs/mcp-go/mcp" 4 | 5 | var BasicOptions = []mcp.ToolOption{ 6 | mcp.WithString( 7 | "owner", 8 | mcp.Description("The space address to which the repository belongs (the address path of the enterprise, organization or individual)"), 9 | mcp.Required(), 10 | ), 11 | mcp.WithString( 12 | "repo", 13 | mcp.Description("The path of the repository"), 14 | mcp.Required(), 15 | ), 16 | } 17 | 18 | var BasicIssueOptions = []mcp.ToolOption{ 19 | mcp.WithString( 20 | "title", 21 | mcp.Description("The title of the issue"), 22 | mcp.Required(), 23 | ), 24 | mcp.WithString( 25 | "body", 26 | mcp.Description("The description of the issue"), 27 | ), 28 | mcp.WithString( 29 | "issue_type", 30 | mcp.Description("Enterprise custom task type, non-enterprise users must consider it as 'task'"), 31 | ), 32 | mcp.WithString( 33 | "assignee", 34 | mcp.Description("The personal space address of the issue assignee"), 35 | ), 36 | mcp.WithString( 37 | "collaborators", 38 | mcp.Description("The personal space addresses of issue collaborators, separated by commas"), 39 | ), 40 | mcp.WithString( 41 | "milestone", 42 | mcp.Description("The milestone number"), 43 | ), 44 | mcp.WithString( 45 | "labels", 46 | mcp.Description("Comma-separated labels, name requirements are between 2-20 in length and non-special characters. Example: bug,performance"), 47 | ), 48 | mcp.WithString( 49 | "program", 50 | mcp.Description("Project ID"), 51 | ), 52 | } 53 | -------------------------------------------------------------------------------- /operations/issues/create_issue.go: -------------------------------------------------------------------------------- 1 | package issues 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | ) 11 | 12 | const ( 13 | // CreateIssueToolName is the name of the tool 14 | CreateIssueToolName = "create_issue" 15 | ) 16 | 17 | var CreateIssueTool = func() mcp.Tool { 18 | options := utils.CombineOptions( 19 | []mcp.ToolOption{ 20 | mcp.WithDescription("Create an issue"), 21 | }, 22 | BasicOptions, 23 | BasicIssueOptions, 24 | ) 25 | return mcp.NewTool(CreateIssueToolName, options...) 26 | }() 27 | 28 | func CreateIssueHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 29 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 30 | owner := args["owner"].(string) 31 | apiUrl := fmt.Sprintf("/repos/%s/issues", owner) 32 | giteeClient := utils.NewGiteeClient("POST", apiUrl, utils.WithContext(ctx), utils.WithPayload(args)) 33 | issue := &types.BasicIssue{} 34 | return giteeClient.HandleMCPResult(issue) 35 | } 36 | -------------------------------------------------------------------------------- /operations/issues/get_issue_detail.go: -------------------------------------------------------------------------------- 1 | package issues 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | const ( 14 | // Tool name constants 15 | GetRepoIssueDetailToolName = "get_repo_issue_detail" 16 | ) 17 | 18 | // Basic issue get options 19 | var basicIssueGetOptions = []mcp.ToolOption{ 20 | mcp.WithString( 21 | "number", 22 | mcp.Description("Issue number (case sensitive, no # prefix needed)"), 23 | mcp.Required(), 24 | ), 25 | } 26 | 27 | // Repository issue get options 28 | var repoIssueGetOptions = []mcp.ToolOption{ 29 | mcp.WithString( 30 | "owner", 31 | mcp.Description("The space address to which the repository belongs (the address path of the enterprise, organization or individual)"), 32 | mcp.Required(), 33 | ), 34 | mcp.WithString( 35 | "repo", 36 | mcp.Description("The path of the repository"), 37 | mcp.Required(), 38 | ), 39 | } 40 | 41 | // Enterprise issue get options 42 | var enterpriseIssueGetOptions = []mcp.ToolOption{ 43 | mcp.WithString( 44 | "enterprise", 45 | mcp.Description("Enterprise path"), 46 | mcp.Required(), 47 | ), 48 | } 49 | 50 | // Endpoint configurations 51 | var getIssueConfigs = map[string]types.EndpointConfig{ 52 | GetRepoIssueDetailToolName: { 53 | UrlTemplate: "/repos/%s/%s/issues/%s", 54 | PathParams: []string{"owner", "repo", "number"}, 55 | }, 56 | } 57 | 58 | // NewGetIssueDetailTool creates a tool for getting issue details 59 | func NewGetIssueDetailTool(getType string) mcp.Tool { 60 | _, ok := getIssueConfigs[getType] 61 | if !ok { 62 | panic("invalid get issue type: " + getType) 63 | } 64 | 65 | var options []mcp.ToolOption 66 | options = append(options, basicIssueGetOptions...) 67 | 68 | switch getType { 69 | case GetRepoIssueDetailToolName: 70 | options = append(options, mcp.WithDescription("Get the detail of an issue")) 71 | options = append(options, repoIssueGetOptions...) 72 | } 73 | 74 | return mcp.NewTool(getType, options...) 75 | } 76 | 77 | // GetIssueDetailHandleFunc handles the request to get issue details 78 | func GetIssueDetailHandleFunc(getType string) server.ToolHandlerFunc { 79 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 80 | config, ok := getIssueConfigs[getType] 81 | if !ok { 82 | errMsg := fmt.Sprintf("unsupported get issue type: %s", getType) 83 | return mcp.NewToolResultError(errMsg), fmt.Errorf(errMsg) 84 | } 85 | 86 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 87 | apiUrl := config.UrlTemplate 88 | if len(config.PathParams) > 0 { 89 | apiUrlArgs := make([]interface{}, len(config.PathParams)) 90 | for idx, param := range config.PathParams { 91 | value, ok := args[param].(string) 92 | if !ok { 93 | errMsg := fmt.Sprintf("missing required path parameter: %s", param) 94 | return mcp.NewToolResultError(errMsg), fmt.Errorf(errMsg) 95 | } 96 | apiUrlArgs[idx] = value 97 | } 98 | apiUrl = fmt.Sprintf(apiUrl, apiUrlArgs...) 99 | } 100 | 101 | // Handle optional query parameters 102 | queryParams := make(map[string]interface{}) 103 | if accessToken, ok := args["access_token"]; ok && accessToken != "" { 104 | queryParams["access_token"] = accessToken 105 | } 106 | 107 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithQuery(queryParams)) 108 | 109 | // 使用 HandleMCPResult 方法简化处理逻辑 110 | issue := &types.BasicIssue{} 111 | return giteeClient.HandleMCPResult(issue) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /operations/issues/list_issue_comments.go: -------------------------------------------------------------------------------- 1 | package issues 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | 8 | "gitee.com/oschina/mcp-gitee/operations/types" 9 | "gitee.com/oschina/mcp-gitee/utils" 10 | "github.com/mark3labs/mcp-go/mcp" 11 | ) 12 | 13 | const ( 14 | // ListIssueCommentsToolName is the name of the tool 15 | ListIssueCommentsToolName = "list_issue_comments" 16 | ) 17 | 18 | // ListIssueCommentsOptions defines the specific options for listing issue comments 19 | var ListIssueCommentsOptions = []mcp.ToolOption{ 20 | mcp.WithString( 21 | "number", 22 | mcp.Description("Issue number (case sensitive, no # prefix needed)"), 23 | mcp.Required(), 24 | ), 25 | mcp.WithString( 26 | "since", 27 | mcp.Description("Only comments updated at or after this time are returned. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ"), 28 | ), 29 | mcp.WithNumber( 30 | "page", 31 | mcp.Description("Current page number"), 32 | mcp.DefaultNumber(1), 33 | ), 34 | mcp.WithNumber( 35 | "per_page", 36 | mcp.Description("Number of results per page, maximum 100"), 37 | mcp.DefaultNumber(20), 38 | ), 39 | mcp.WithString( 40 | "order", 41 | mcp.Description("Sort direction: asc(default), desc"), 42 | mcp.DefaultString("asc"), 43 | ), 44 | } 45 | 46 | // ListIssueCommentsTool defines the tool for listing issue comments 47 | var ListIssueCommentsTool = func() mcp.Tool { 48 | options := utils.CombineOptions( 49 | []mcp.ToolOption{ 50 | mcp.WithDescription("Get all comments for a repository issue"), 51 | }, 52 | BasicOptions, 53 | ListIssueCommentsOptions, 54 | ) 55 | return mcp.NewTool(ListIssueCommentsToolName, options...) 56 | }() 57 | 58 | // ListIssueCommentsHandleFunc handles the request to list issue comments 59 | func ListIssueCommentsHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 60 | // Extract required parameters from the request 61 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 62 | owner := args["owner"].(string) 63 | repo := args["repo"].(string) 64 | number := args["number"].(string) 65 | 66 | // Construct the API URL for listing issue comments 67 | apiUrl := fmt.Sprintf("/repos/%s/%s/issues/%s/comments", owner, repo, number) 68 | 69 | // Prepare query parameters 70 | query := make(map[string]interface{}) 71 | 72 | // Add optional parameters if they exist 73 | if since, ok := args["since"].(string); ok && since != "" { 74 | query["since"] = since 75 | } 76 | 77 | if page, ok := args["page"].(float64); ok { 78 | query["page"] = strconv.Itoa(int(page)) 79 | } 80 | 81 | if perPage, ok := args["per_page"].(float64); ok { 82 | query["per_page"] = strconv.Itoa(int(perPage)) 83 | } 84 | 85 | if order, ok := args["order"].(string); ok && order != "" { 86 | query["order"] = order 87 | } 88 | 89 | // Create a new Gitee client with the GET method and the constructed API URL 90 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithQuery(query)) 91 | 92 | // Define the response structure 93 | var comments []types.IssueComment 94 | 95 | // Handle the API call and return the result 96 | return giteeClient.HandleMCPResult(&comments) 97 | } 98 | -------------------------------------------------------------------------------- /operations/issues/list_issues.go: -------------------------------------------------------------------------------- 1 | package issues 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | const ( 14 | ListRepoIssuesToolName = "list_repo_issues" 15 | ) 16 | 17 | var commonIssuesOptions = []mcp.ToolOption{ 18 | mcp.WithString( 19 | "state", 20 | mcp.Description("Issue state: open, progressing, closed, rejected"), 21 | mcp.Enum("open", "progressing", "closed", "rejected", "all"), 22 | ), 23 | mcp.WithString( 24 | "sort", 25 | mcp.Description("Sort field: creation time (created), update time (updated). Default: created"), 26 | mcp.Enum("created", "updated"), 27 | ), 28 | mcp.WithString( 29 | "direction", 30 | mcp.Description("Sort direction: ascending (asc) or descending (desc)"), 31 | mcp.Enum("asc", "desc"), 32 | ), 33 | mcp.WithString( 34 | "since", 35 | mcp.Description("Start update time in ISO 8601 format"), 36 | ), 37 | mcp.WithString( 38 | "schedule", 39 | mcp.Description("Planned start date in format: 2006-01-02T15:04:05Z"), 40 | ), 41 | mcp.WithString( 42 | "deadline", 43 | mcp.Description("Planned completion date in format: 2006-01-02T15:04:05Z"), 44 | ), 45 | mcp.WithString( 46 | "created_at", 47 | mcp.Description("Issue creation time in format: 2006-01-02T15:04:05Z"), 48 | ), 49 | mcp.WithString( 50 | "finished_at", 51 | mcp.Description("Issue completion time in format: 2006-01-02T15:04:05Z"), 52 | ), 53 | mcp.WithString( 54 | "filter", 55 | mcp.Description("Filter parameter: assigned to authorized user (assigned), created by authorized user (created), all issues involving authorized user (all). Default: assigned"), 56 | mcp.Enum("assigned", "created", "all"), 57 | ), 58 | mcp.WithString( 59 | "labels", 60 | mcp.Description("Comma-separated labels. Example: bug,performance"), 61 | ), 62 | mcp.WithNumber( 63 | "page", 64 | mcp.Description("Current page number"), 65 | mcp.DefaultNumber(1), 66 | ), 67 | mcp.WithNumber( 68 | "per_page", 69 | mcp.Description("Number of items per page, maximum 100"), 70 | mcp.DefaultNumber(20), 71 | ), 72 | } 73 | 74 | var repoIssuesOptions = []mcp.ToolOption{ 75 | mcp.WithString( 76 | "owner", 77 | mcp.Description("Repository owner's namespace (enterprise, organization or personal path)"), 78 | mcp.Required(), 79 | ), 80 | mcp.WithString( 81 | "repo", 82 | mcp.Description("Repository path"), 83 | mcp.Required(), 84 | ), 85 | } 86 | 87 | var listConfigs = map[string]types.EndpointConfig{ 88 | ListRepoIssuesToolName: { 89 | UrlTemplate: "/repos/%s/%s/issues", 90 | PathParams: []string{"owner", "repo"}, 91 | }, 92 | } 93 | 94 | func NewListIssuesTool(listType string) mcp.Tool { 95 | _, ok := listConfigs[listType] 96 | if !ok { 97 | panic("invalid list type: " + listType) 98 | } 99 | options := commonIssuesOptions 100 | switch listType { 101 | case ListRepoIssuesToolName: 102 | options = append(options, []mcp.ToolOption{ 103 | mcp.WithDescription("List all issues in a repository"), 104 | mcp.WithString( 105 | "q", 106 | mcp.Description("Search keywords"), 107 | ), 108 | }...) 109 | options = append(options, repoIssuesOptions...) 110 | } 111 | return mcp.NewTool(listType, options...) 112 | } 113 | 114 | func ListIssuesHandleFunc(listType string) server.ToolHandlerFunc { 115 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 116 | config, ok := listConfigs[listType] 117 | if !ok { 118 | errMsg := fmt.Sprintf("invalid list type: %s", listType) 119 | return mcp.NewToolResultError(errMsg), fmt.Errorf(errMsg) 120 | } 121 | 122 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 123 | apiUrl := config.UrlTemplate 124 | if len(config.PathParams) > 0 { 125 | apiUrlArgs := make([]interface{}, len(config.PathParams)) 126 | for i, param := range config.PathParams { 127 | value, ok := args[param].(string) 128 | if !ok { 129 | return nil, fmt.Errorf("missing required path parameter: %s", param) 130 | } 131 | apiUrlArgs[i] = value 132 | } 133 | apiUrl = fmt.Sprintf(apiUrl, apiUrlArgs...) 134 | } 135 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithQuery(args)) 136 | issues := make([]types.BasicIssue, 0) 137 | return giteeClient.HandleMCPResult(&issues) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /operations/issues/update_issue.go: -------------------------------------------------------------------------------- 1 | package issues 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | ) 11 | 12 | const ( 13 | UpdateIssueToolName = "update_issue" 14 | ) 15 | 16 | // UpdateIssueConfig structure for updating issues 17 | type UpdateIssueConfig struct { 18 | UrlTemplate string 19 | PathParams []string 20 | } 21 | 22 | // Configuration mapping for issue updates 23 | var updateIssueConfigs = map[string]UpdateIssueConfig{ 24 | UpdateIssueToolName: { 25 | UrlTemplate: "/repos/%s/issues/%s", 26 | PathParams: []string{"owner", "number"}, 27 | }, 28 | } 29 | 30 | var UpdateIssueTool = func() mcp.Tool { 31 | options := utils.CombineOptions( 32 | []mcp.ToolOption{ 33 | mcp.WithDescription("Update an issue"), 34 | mcp.WithString( 35 | "number", 36 | mcp.Description("The number of the issue"), 37 | mcp.Required(), 38 | ), 39 | mcp.WithString( 40 | "state", 41 | mcp.Description("The state of the issue"), 42 | mcp.Enum("open", "progressing", "closed"), 43 | ), 44 | }, 45 | BasicOptions, 46 | BasicIssueOptions, 47 | ) 48 | return mcp.NewTool(UpdateIssueToolName, options...) 49 | }() 50 | 51 | // UpdateIssueHandleFunc handles requests to update repository issues 52 | func UpdateIssueHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 53 | return UpdateIssueHandleFuncCommon(UpdateIssueToolName)(ctx, request) 54 | } 55 | 56 | // UpdateIssueHandleFuncCommon is a common handler function for processing issue update requests 57 | func UpdateIssueHandleFuncCommon(updateType string) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 58 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 59 | config, ok := updateIssueConfigs[updateType] 60 | if !ok { 61 | errMsg := fmt.Sprintf("Unsupported issue update type: %s", updateType) 62 | return mcp.NewToolResultError(errMsg), fmt.Errorf(errMsg) 63 | } 64 | 65 | apiUrl := config.UrlTemplate 66 | if len(config.PathParams) > 0 { 67 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 68 | apiUrlArgs := make([]interface{}, 0, len(config.PathParams)) 69 | for _, param := range config.PathParams { 70 | value, ok := args[param].(string) 71 | if !ok { 72 | errMsg := fmt.Sprintf("Missing required path parameter: %s", param) 73 | return mcp.NewToolResultError(errMsg), fmt.Errorf(errMsg) 74 | } 75 | apiUrlArgs = append(apiUrlArgs, value) 76 | } 77 | apiUrl = fmt.Sprintf(apiUrl, apiUrlArgs...) 78 | } 79 | 80 | giteeClient := utils.NewGiteeClient("PATCH", apiUrl, utils.WithContext(ctx), utils.WithPayload(request.Params.Arguments)) 81 | issue := &types.BasicIssue{} 82 | 83 | return giteeClient.HandleMCPResult(issue) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /operations/notifications/list_user_notifications.go: -------------------------------------------------------------------------------- 1 | package notifications 2 | 3 | import ( 4 | "context" 5 | 6 | "gitee.com/oschina/mcp-gitee/operations/types" 7 | "gitee.com/oschina/mcp-gitee/utils" 8 | "github.com/mark3labs/mcp-go/mcp" 9 | ) 10 | 11 | const ( 12 | // ListUserNotifications is the instruction to list user notifications 13 | ListUserNotifications = "list_user_notifications" 14 | ) 15 | 16 | var ListUserNotificationsTool = mcp.NewTool( 17 | ListUserNotifications, 18 | mcp.WithDescription("List all notifications for authorized user"), 19 | mcp.WithBoolean( 20 | "unread", 21 | mcp.Description("Only list unread notifications"), 22 | mcp.DefaultBool(false), 23 | ), 24 | mcp.WithBoolean( 25 | "participating", 26 | mcp.Description("Only list notifications where the user is directly participating or mentioned"), 27 | mcp.DefaultBool(false), 28 | ), 29 | mcp.WithString( 30 | "type", 31 | mcp.Description("Filter notifications of a specified type, all: all, event: event notification, referer: @ notification"), 32 | mcp.Enum("all", "event", "referer"), 33 | mcp.DefaultString("all"), 34 | ), 35 | mcp.WithString( 36 | "since", 37 | mcp.Description("Only list notifications updated after the given time, requiring the time format to be ISO 8601"), 38 | ), 39 | mcp.WithString( 40 | "before", 41 | mcp.Description("Only list notifications updated before the given time, requiring the time format to be ISO 8601"), 42 | ), 43 | mcp.WithNumber( 44 | "page", 45 | mcp.Description("Page number"), 46 | mcp.DefaultNumber(1), 47 | ), 48 | mcp.WithNumber( 49 | "per_page", 50 | mcp.Description("Number of results per page"), 51 | mcp.DefaultNumber(20), 52 | ), 53 | ) 54 | 55 | func ListUserNotificationsHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 56 | apiUrl := "/notifications/threads" 57 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 58 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithQuery(args)) 59 | 60 | notifications := &types.NotificationResult{} 61 | return giteeClient.HandleMCPResult(notifications) 62 | } 63 | -------------------------------------------------------------------------------- /operations/pulls/comment_pull.go: -------------------------------------------------------------------------------- 1 | package pulls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/utils" 8 | 9 | "github.com/mark3labs/mcp-go/mcp" 10 | ) 11 | 12 | const ( 13 | // CommentPullToolName is the name of the tool 14 | CommentPullToolName = "comment_pull" 15 | ) 16 | 17 | // CommentPullTool defines the tool for commenting on a pull request 18 | var CommentPullTool = func() mcp.Tool { 19 | options := utils.CombineOptions( 20 | BasicOptions, 21 | []mcp.ToolOption{ 22 | mcp.WithDescription("Create a comment on a pull request"), 23 | mcp.WithNumber( 24 | "number", 25 | mcp.Description("The number of the pull request, must be an integer, not a float"), 26 | mcp.Required(), 27 | ), 28 | mcp.WithString( 29 | "body", 30 | mcp.Description("The contents of the comment"), 31 | mcp.Required(), 32 | ), 33 | }, 34 | ) 35 | return mcp.NewTool(CommentPullToolName, options...) 36 | }() 37 | 38 | // CommentPullHandleFunc handles the comment pull request operation 39 | func CommentPullHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 40 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 41 | owner := args["owner"].(string) 42 | repo := args["repo"].(string) 43 | body := args["body"].(string) 44 | 45 | numberArg, exists := args["number"] 46 | if !exists { 47 | return mcp.NewToolResultError("Missing required parameter: number"), 48 | utils.NewParamError("number", "parameter is required") 49 | } 50 | 51 | number, err := utils.SafelyConvertToInt(numberArg) 52 | if err != nil { 53 | return mcp.NewToolResultError(err.Error()), err 54 | } 55 | 56 | // Prepare the API URL for commenting on a pull request 57 | apiUrl := fmt.Sprintf("/repos/%s/%s/pulls/%d/comments", owner, repo, number) 58 | 59 | // Create payload with the comment body 60 | payload := map[string]interface{}{ 61 | "body": body, 62 | } 63 | 64 | // Create a new Gitee client with the POST method and payload 65 | giteeClient := utils.NewGiteeClient("POST", apiUrl, utils.WithContext(ctx), utils.WithPayload(payload)) 66 | 67 | // Execute the request and handle the result 68 | return giteeClient.HandleMCPResult(nil) 69 | } 70 | -------------------------------------------------------------------------------- /operations/pulls/common_options.go: -------------------------------------------------------------------------------- 1 | package pulls 2 | 3 | import "github.com/mark3labs/mcp-go/mcp" 4 | 5 | var BasicOptions = []mcp.ToolOption{ 6 | mcp.WithString( 7 | "owner", 8 | mcp.Description("The space address to which the repository belongs (the address path of the enterprise, organization or individual)"), 9 | mcp.Required(), 10 | ), 11 | mcp.WithString( 12 | "repo", 13 | mcp.Description("The path of the repository"), 14 | mcp.Required(), 15 | ), 16 | } 17 | 18 | var BasicPullOptions = []mcp.ToolOption{ 19 | mcp.WithString( 20 | "body", 21 | mcp.Description("The body of the pull request"), 22 | ), 23 | mcp.WithString( 24 | "milestone_number", 25 | mcp.Description("The milestone number of the pull request"), 26 | ), 27 | mcp.WithString( 28 | "labels", 29 | mcp.Description("The labels of the pull request. example: bug,performance"), 30 | ), 31 | mcp.WithString( 32 | "issue", 33 | mcp.Description("The issue of the pull request"), 34 | ), 35 | mcp.WithString( 36 | "assignees", 37 | mcp.Description("The assignees of the pull request, example: (username1,username2)"), 38 | ), 39 | mcp.WithString( 40 | "testers", 41 | mcp.Description("The testers of the pull request, example: (username1,username2)"), 42 | ), 43 | mcp.WithNumber( 44 | "assignees_number", 45 | mcp.Description("The min number of assignees need accept of the pull request"), 46 | ), 47 | mcp.WithNumber( 48 | "testers_number", 49 | mcp.Description("The min number of testers need accept of the pull request"), 50 | ), 51 | mcp.WithBoolean( 52 | "draft", 53 | mcp.Description("Whether to set the pull request as draft"), 54 | ), 55 | } 56 | -------------------------------------------------------------------------------- /operations/pulls/create_pull.go: -------------------------------------------------------------------------------- 1 | package pulls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | ) 11 | 12 | const ( 13 | // CreatePullToolName is the name of the tool 14 | CreatePullToolName = "create_pull" 15 | ) 16 | 17 | var CreatePullTool = func() mcp.Tool { 18 | options := utils.CombineOptions( 19 | []mcp.ToolOption{ 20 | mcp.WithDescription("Create a pull request"), 21 | }, 22 | BasicOptions, 23 | BasicPullOptions, 24 | []mcp.ToolOption{ 25 | mcp.WithDescription("Create a pull request"), 26 | mcp.WithString( 27 | "title", 28 | mcp.Description("The title of the pull request"), 29 | mcp.Required(), 30 | ), 31 | mcp.WithString( 32 | "head", 33 | mcp.Description("The source branch of the pull request, Format: branch (master) or: path_with_namespace:branch (oschina/gitee:master)"), 34 | mcp.Required(), 35 | ), 36 | mcp.WithString( 37 | "base", 38 | mcp.Description("The target branch of the pull request"), 39 | mcp.Required(), 40 | ), 41 | }, 42 | ) 43 | return mcp.NewTool(CreatePullToolName, options...) 44 | }() 45 | 46 | func CreatePullHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 47 | args, _ := request.Params.Arguments.(map[string]interface{}) 48 | owner := args["owner"].(string) 49 | repo := args["repo"].(string) 50 | apiUrl := fmt.Sprintf("/repos/%s/%s/pulls", owner, repo) 51 | giteeClient := utils.NewGiteeClient("POST", apiUrl, utils.WithContext(ctx), utils.WithPayload(args)) 52 | pull := &types.BasicPull{} 53 | return giteeClient.HandleMCPResult(pull) 54 | } 55 | -------------------------------------------------------------------------------- /operations/pulls/get_pull_detail.go: -------------------------------------------------------------------------------- 1 | package pulls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | ) 11 | 12 | const ( 13 | // GetPullDetailToolName is the name of the tool 14 | GetPullDetailToolName = "get_pull_detail" 15 | ) 16 | 17 | var GetPullDetailTool = func() mcp.Tool { 18 | options := utils.CombineOptions( 19 | BasicOptions, 20 | []mcp.ToolOption{ 21 | mcp.WithDescription("Get a pull request detail"), 22 | mcp.WithNumber( 23 | "number", 24 | mcp.Description("The number of the pull request, must be an integer, not a float"), 25 | mcp.Required(), 26 | ), 27 | }, 28 | ) 29 | return mcp.NewTool(GetPullDetailToolName, options...) 30 | }() 31 | 32 | func GetPullDetailHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 33 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 34 | owner := args["owner"].(string) 35 | repo := args["repo"].(string) 36 | 37 | numberArg, exists := args["number"] 38 | if !exists { 39 | return mcp.NewToolResultError("Missing required parameter: number"), 40 | utils.NewParamError("number", "parameter is required") 41 | } 42 | 43 | number, err := utils.SafelyConvertToInt(numberArg) 44 | if err != nil { 45 | return mcp.NewToolResultError(err.Error()), err 46 | } 47 | 48 | apiUrl := fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, number) 49 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithPayload(args)) 50 | pull := &types.BasicPull{} 51 | return giteeClient.HandleMCPResult(pull) 52 | } 53 | -------------------------------------------------------------------------------- /operations/pulls/list_pull_comments.go: -------------------------------------------------------------------------------- 1 | package pulls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | 8 | "gitee.com/oschina/mcp-gitee/operations/types" 9 | "gitee.com/oschina/mcp-gitee/utils" 10 | "github.com/mark3labs/mcp-go/mcp" 11 | ) 12 | 13 | const ( 14 | // ListPullCommentsToolName is the name of the tool 15 | ListPullCommentsToolName = "list_pull_comments" 16 | ) 17 | 18 | // ListPullCommentsOptions defines the specific options for listing pull request comments 19 | var ListPullCommentsOptions = []mcp.ToolOption{ 20 | mcp.WithNumber( 21 | "number", 22 | mcp.Description("The number of the pull request, must be an integer, not a float"), 23 | mcp.Required(), 24 | ), 25 | mcp.WithString( 26 | "since", 27 | mcp.Description("Only comments updated at or after this time are returned. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ"), 28 | ), 29 | mcp.WithNumber( 30 | "page", 31 | mcp.Description("Current page number"), 32 | mcp.DefaultNumber(1), 33 | ), 34 | mcp.WithNumber( 35 | "per_page", 36 | mcp.Description("Number of results per page, maximum 100"), 37 | mcp.DefaultNumber(20), 38 | ), 39 | mcp.WithString( 40 | "order", 41 | mcp.Description("Sort direction: asc(default), desc"), 42 | mcp.DefaultString("asc"), 43 | ), 44 | } 45 | 46 | // ListPullCommentsTool defines the tool for listing pull request comments 47 | var ListPullCommentsTool = func() mcp.Tool { 48 | options := utils.CombineOptions( 49 | []mcp.ToolOption{ 50 | mcp.WithDescription("List all comments for a pull request"), 51 | }, 52 | BasicOptions, 53 | ListPullCommentsOptions, 54 | ) 55 | return mcp.NewTool(ListPullCommentsToolName, options...) 56 | }() 57 | 58 | // ListPullCommentsHandleFunc handles the request to list pull request comments 59 | func ListPullCommentsHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 60 | // Extract required parameters from the request 61 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 62 | owner := args["owner"].(string) 63 | repo := args["repo"].(string) 64 | 65 | // Extract and convert number parameter 66 | numberArg, exists := args["number"] 67 | if !exists { 68 | return mcp.NewToolResultError("Missing required parameter: number"), 69 | utils.NewParamError("number", "parameter is required") 70 | } 71 | 72 | number, err := utils.SafelyConvertToInt(numberArg) 73 | if err != nil { 74 | return mcp.NewToolResultError(err.Error()), err 75 | } 76 | 77 | // Construct the API URL for listing pull request comments 78 | apiUrl := fmt.Sprintf("/repos/%s/%s/pulls/%d/comments", owner, repo, number) 79 | 80 | // Prepare query parameters 81 | query := make(map[string]interface{}) 82 | 83 | // Add optional parameters if they exist 84 | if since, ok := args["since"].(string); ok && since != "" { 85 | query["since"] = since 86 | } 87 | 88 | if page, ok := args["page"].(float64); ok { 89 | query["page"] = strconv.Itoa(int(page)) 90 | } 91 | 92 | if perPage, ok := args["per_page"].(float64); ok { 93 | query["per_page"] = strconv.Itoa(int(perPage)) 94 | } 95 | 96 | if order, ok := args["order"].(string); ok && order != "" { 97 | query["order"] = order 98 | } 99 | 100 | // Create a new Gitee client with the GET method and the constructed API URL 101 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithQuery(query)) 102 | 103 | // Define the response structure 104 | var comments []types.PullComment 105 | 106 | // Handle the API call and return the result 107 | return giteeClient.HandleMCPResult(&comments) 108 | } 109 | -------------------------------------------------------------------------------- /operations/pulls/list_pulls.go: -------------------------------------------------------------------------------- 1 | package pulls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | const ( 14 | ListRepoPullsToolName = "list_repo_pulls" 15 | ) 16 | 17 | var commonPullsOptions = []mcp.ToolOption{ 18 | mcp.WithString( 19 | "state", 20 | mcp.Description("State of the pull request"), 21 | mcp.Enum("open", "closed", "merged", "all"), 22 | ), 23 | mcp.WithString( 24 | "head", 25 | mcp.Description("Source branch of the PR. Format: branch or username:branch"), 26 | ), 27 | mcp.WithString( 28 | "base", 29 | mcp.Description("Target branch name for the pull request"), 30 | ), 31 | mcp.WithString( 32 | "sort", 33 | mcp.Description("Sort field, default by creation time"), 34 | mcp.Enum("created", "updated", "popularity", "long-running"), 35 | ), 36 | mcp.WithString( 37 | "since", 38 | mcp.Description("Start update time in ISO 8601 format"), 39 | ), 40 | mcp.WithString( 41 | "direction", 42 | mcp.Description("Ascending/descending order"), 43 | mcp.Enum("asc", "desc"), 44 | ), 45 | mcp.WithNumber( 46 | "milestone_number", 47 | mcp.Description("Milestone number (ID)"), 48 | ), 49 | mcp.WithString( 50 | "labels", 51 | mcp.Description("Comma-separated labels, e.g.: bug,performance"), 52 | ), 53 | mcp.WithNumber( 54 | "page", 55 | mcp.Description("Current page number"), 56 | mcp.DefaultNumber(1), 57 | ), 58 | mcp.WithNumber( 59 | "per_page", 60 | mcp.Description("Items per page (max 100)"), 61 | mcp.DefaultNumber(20), 62 | ), 63 | mcp.WithString( 64 | "author", 65 | mcp.Description("PR creator's username"), 66 | ), 67 | mcp.WithString( 68 | "assignee", 69 | mcp.Description("Reviewer's username"), 70 | ), 71 | mcp.WithString( 72 | "tester", 73 | mcp.Description("Tester's username"), 74 | ), 75 | } 76 | 77 | var repoPullsOptions = []mcp.ToolOption{ 78 | mcp.WithString( 79 | "owner", 80 | mcp.Description("Repository owner's namespace"), 81 | mcp.Required(), 82 | ), 83 | mcp.WithString( 84 | "repo", 85 | mcp.Description("Repository namespace"), 86 | mcp.Required(), 87 | ), 88 | } 89 | 90 | var listConfigs = map[string]types.EndpointConfig{ 91 | ListRepoPullsToolName: { 92 | UrlTemplate: "/repos/%s/%s/pulls", 93 | PathParams: []string{"owner", "repo"}, 94 | }, 95 | } 96 | 97 | func NewListPullsTool(listType string) mcp.Tool { 98 | _, ok := listConfigs[listType] 99 | if !ok { 100 | panic("invalid list type: " + listType) 101 | } 102 | options := commonPullsOptions 103 | switch listType { 104 | case ListRepoPullsToolName: 105 | options = append(options, mcp.WithDescription("List repository pulls")) 106 | options = append(options, repoPullsOptions...) 107 | } 108 | return mcp.NewTool(listType, options...) 109 | } 110 | 111 | func ListPullsHandleFunc(listType string) server.ToolHandlerFunc { 112 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 113 | config, ok := listConfigs[listType] 114 | if !ok { 115 | errMsg := fmt.Sprintf("invalid list type: %s", listType) 116 | return mcp.NewToolResultError(errMsg), fmt.Errorf(errMsg) 117 | } 118 | 119 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 120 | apiUrl := config.UrlTemplate 121 | if len(config.PathParams) > 0 { 122 | apiUrlArgs := make([]interface{}, len(config.PathParams)) 123 | for i, param := range config.PathParams { 124 | value, ok := args[param].(string) 125 | if !ok { 126 | return nil, fmt.Errorf("missing required path parameter: %s", param) 127 | } 128 | apiUrlArgs[i] = value 129 | } 130 | apiUrl = fmt.Sprintf(apiUrl, apiUrlArgs...) 131 | } 132 | 133 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithQuery(args)) 134 | pulls := make([]types.BasicPull, 0) 135 | return giteeClient.HandleMCPResult(&pulls) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /operations/pulls/merge_pull.go: -------------------------------------------------------------------------------- 1 | package pulls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/utils" 8 | "github.com/mark3labs/mcp-go/mcp" 9 | ) 10 | 11 | const ( 12 | // MergePullToolName is the name of the tool 13 | MergePullToolName = "merge_pull" 14 | ) 15 | 16 | var MergePullTool = func() mcp.Tool { 17 | options := utils.CombineOptions( 18 | BasicOptions, 19 | []mcp.ToolOption{ 20 | mcp.WithDescription("Merge a pull request"), 21 | mcp.WithNumber( 22 | "number", 23 | mcp.Description("The number of the pull request"), 24 | mcp.Required(), 25 | ), 26 | mcp.WithString( 27 | "merge_method", 28 | mcp.Description("The merge method to use"), 29 | mcp.Enum("merge", "squash", "rebase"), 30 | mcp.DefaultString("merge"), 31 | ), 32 | mcp.WithBoolean( 33 | "prune_source_branch", 34 | mcp.Description("Whether to delete the source branch after merging"), 35 | ), 36 | mcp.WithBoolean( 37 | "close_related_issue", 38 | mcp.Description("Whether to close the related issue after merging"), 39 | ), 40 | mcp.WithString( 41 | "title", 42 | mcp.Description("The title of the merge commit"), 43 | ), 44 | mcp.WithString( 45 | "description", 46 | mcp.Description("The description of the merge commit"), 47 | ), 48 | }, 49 | ) 50 | return mcp.NewTool(MergePullToolName, options...) 51 | }() 52 | 53 | func MergePullHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 54 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 55 | owner := args["owner"].(string) 56 | repo := args["repo"].(string) 57 | 58 | numberArg, exists := args["number"] 59 | if !exists { 60 | return mcp.NewToolResultError("Missing required parameter: number"), 61 | utils.NewParamError("number", "parameter is required") 62 | } 63 | 64 | number, err := utils.SafelyConvertToInt(numberArg) 65 | if err != nil { 66 | return mcp.NewToolResultError(err.Error()), err 67 | } 68 | 69 | apiUrl := fmt.Sprintf("/repos/%s/%s/pulls/%d/merge", owner, repo, number) 70 | giteeClient := utils.NewGiteeClient("PUT", apiUrl, utils.WithContext(ctx), utils.WithPayload(args)) 71 | return giteeClient.HandleMCPResult(nil) 72 | } 73 | -------------------------------------------------------------------------------- /operations/pulls/update_pull.go: -------------------------------------------------------------------------------- 1 | package pulls 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | ) 11 | 12 | const ( 13 | // UpdatePullToolName is the name of the tool 14 | UpdatePullToolName = "update_pull" 15 | ) 16 | 17 | var UpdatePullTool = func() mcp.Tool { 18 | options := utils.CombineOptions( 19 | BasicOptions, 20 | BasicPullOptions, 21 | []mcp.ToolOption{ 22 | mcp.WithDescription("Update a pull request"), 23 | mcp.WithNumber( 24 | "number", 25 | mcp.Description("The number of the pull request"), 26 | mcp.Required(), 27 | ), 28 | mcp.WithString( 29 | "title", 30 | mcp.Description("The title of the pull request"), 31 | ), 32 | mcp.WithString( 33 | "state", 34 | mcp.Description("The state of the pull request"), 35 | mcp.Enum("closed", "open"), 36 | ), 37 | }, 38 | ) 39 | return mcp.NewTool(UpdatePullToolName, options...) 40 | }() 41 | 42 | func UpdatePullHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 43 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 44 | owner := args["owner"].(string) 45 | repo := args["repo"].(string) 46 | numberArg, exists := args["number"] 47 | if !exists { 48 | return mcp.NewToolResultError("Missing required parameter: number"), 49 | utils.NewParamError("number", "parameter is required") 50 | } 51 | number, err := utils.SafelyConvertToInt(numberArg) 52 | if err != nil { 53 | return mcp.NewToolResultError(err.Error()), err 54 | } 55 | apiUrl := fmt.Sprintf("/repos/%s/%s/pulls/%d", owner, repo, number) 56 | giteeClient := utils.NewGiteeClient("PATCH", apiUrl, utils.WithContext(ctx), utils.WithPayload(args)) 57 | pull := &types.BasicPull{} 58 | return giteeClient.HandleMCPResult(pull) 59 | } 60 | -------------------------------------------------------------------------------- /operations/repository/create_repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | const ( 14 | CreateUserRepo = "create_user_repo" 15 | CreateOrgRepo = "create_org_repo" 16 | CreateEnterRepo = "create_enterprise_repo" 17 | ) 18 | 19 | var commonRepoCreateOptions = []mcp.ToolOption{ 20 | mcp.WithString("name", mcp.Description("Repository name"), mcp.Required()), 21 | mcp.WithString("description", mcp.Description("Repository description")), 22 | mcp.WithString("homepage", mcp.Description("Repository homepage")), 23 | mcp.WithBoolean("auto_init", mcp.Description("Whether to initialize the repository with a README file"), mcp.DefaultBool(false)), 24 | mcp.WithBoolean("private", mcp.Description("Whether the repository is private"), mcp.DefaultBool(true)), 25 | mcp.WithString("path", mcp.Description("Repository path")), 26 | } 27 | 28 | var orgRepoCreateOptions = []mcp.ToolOption{ 29 | mcp.WithString("org", mcp.Description("Org path"), mcp.Required()), 30 | } 31 | 32 | var enterpriseCreateOptions = []mcp.ToolOption{ 33 | mcp.WithString("enterprise", mcp.Description("Enterprise path"), mcp.Required()), 34 | } 35 | 36 | var createConfigs = map[string]types.EndpointConfig{ 37 | CreateUserRepo: { 38 | UrlTemplate: "/user/repos", 39 | }, 40 | CreateOrgRepo: { 41 | UrlTemplate: "/orgs/%s/repos", 42 | PathParams: []string{"org"}, 43 | }, 44 | CreateEnterRepo: { 45 | UrlTemplate: "/enterprises/%s/repos", 46 | PathParams: []string{"enterprise"}, 47 | }, 48 | } 49 | 50 | func NewCreateRepoTool(createType string) mcp.Tool { 51 | _, ok := createConfigs[createType] 52 | if !ok { 53 | panic("invalid create type: " + createType) 54 | } 55 | 56 | options := commonRepoCreateOptions 57 | switch createType { 58 | case CreateOrgRepo: 59 | options = append(options, mcp.WithDescription("Create a org repository")) 60 | options = append(options, orgRepoCreateOptions...) 61 | case CreateEnterRepo: 62 | options = append(options, mcp.WithDescription("Create a enterprise repository")) 63 | options = append(options, enterpriseCreateOptions...) 64 | default: 65 | options = append(options, mcp.WithDescription("Create a user repository")) 66 | } 67 | 68 | return mcp.NewTool(createType, options...) 69 | } 70 | 71 | func CreateRepoHandleFunc(createType string) server.ToolHandlerFunc { 72 | return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 73 | config, ok := createConfigs[createType] 74 | if !ok { 75 | errMsg := fmt.Sprintf("unsupported create type: %s", createType) 76 | return mcp.NewToolResultError(errMsg), fmt.Errorf(errMsg) 77 | } 78 | 79 | apiUrl := config.UrlTemplate 80 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 81 | if len(config.PathParams) > 0 { 82 | apiUrlArgs := make([]interface{}, len(config.PathParams)) 83 | for i, param := range config.PathParams { 84 | value, ok := args[param].(string) 85 | if !ok { 86 | return nil, fmt.Errorf("missing required path parameter: %s", param) 87 | } 88 | apiUrlArgs[i] = value 89 | } 90 | apiUrl = fmt.Sprintf(apiUrl, apiUrlArgs...) 91 | } 92 | 93 | giteeClient := utils.NewGiteeClient("POST", apiUrl, utils.WithContext(ctx), utils.WithPayload(args)) 94 | repo := &types.Project{} 95 | return giteeClient.HandleMCPResult(repo) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /operations/repository/creatre_release.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | ) 11 | 12 | const ( 13 | CreateReleaseToolName = "create_release" 14 | ) 15 | 16 | var CreateReleaseTool = mcp.NewTool( 17 | CreateReleaseToolName, 18 | mcp.WithDescription("Create a release"), 19 | mcp.WithString( 20 | "owner", 21 | mcp.Description("The space address to which the repository belongs (the address path of the enterprise, organization or individual)"), 22 | mcp.Required(), 23 | ), 24 | mcp.WithString( 25 | "repo", 26 | mcp.Description("The path of the repository"), 27 | mcp.Required(), 28 | ), 29 | mcp.WithString( 30 | "tag_name", 31 | mcp.Description("The name of the tag"), 32 | mcp.Required(), 33 | ), 34 | mcp.WithString( 35 | "name", 36 | mcp.Description("The name of the release"), 37 | mcp.Required(), 38 | ), 39 | mcp.WithString( 40 | "body", 41 | mcp.Description("The description of the release"), 42 | mcp.Required(), 43 | ), 44 | mcp.WithBoolean( 45 | "prerelease", 46 | mcp.Description("Whether the release is a prerelease"), 47 | mcp.DefaultBool(false), 48 | ), 49 | mcp.WithString( 50 | "target_commitish", 51 | mcp.Description("The branch name or commit SHA, defaults to the repository's default branch"), 52 | mcp.Required(), 53 | ), 54 | ) 55 | 56 | func CreateReleaseHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 57 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 58 | if checkResult, err := utils.CheckRequired(args, "owner", "repo"); err != nil { 59 | return checkResult, err 60 | } 61 | 62 | owner := args["owner"].(string) 63 | repo := args["repo"].(string) 64 | apiUrl := fmt.Sprintf("/repos/%s/%s/releases", owner, repo) 65 | 66 | giteeClient := utils.NewGiteeClient("POST", apiUrl, utils.WithContext(ctx), utils.WithPayload(args)) 67 | 68 | release := &types.Release{} 69 | return giteeClient.HandleMCPResult(release) 70 | } 71 | -------------------------------------------------------------------------------- /operations/repository/fork_repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | ) 11 | 12 | const ( 13 | ForkRepositoryToolName = "fork_repository" 14 | ) 15 | 16 | var ForkRepositoryTool = mcp.NewTool( 17 | ForkRepositoryToolName, 18 | mcp.WithDescription("Fork a repository"), 19 | mcp.WithString( 20 | "owner", 21 | mcp.Description("The space address to which the repository belongs (the address path of the enterprise, organization or individual)"), 22 | mcp.Required(), 23 | ), 24 | mcp.WithString( 25 | "repo", 26 | mcp.Description("The path of the repository"), 27 | mcp.Required(), 28 | ), 29 | mcp.WithString( 30 | "organization", 31 | mcp.Description("The full address of the organization space to which the repository belongs, default for current user"), 32 | ), 33 | mcp.WithString( 34 | "name", 35 | mcp.Description("The name of the forked repository, default is the same as the original repository"), 36 | ), 37 | mcp.WithString( 38 | "path", 39 | mcp.Description("The path of the forked repository, default is the same as the original repository"), 40 | ), 41 | ) 42 | 43 | func ForkRepositoryHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 44 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 45 | if checkResult, err := utils.CheckRequired(args, "owner", "repo"); err != nil { 46 | return checkResult, err 47 | } 48 | owner := args["owner"].(string) 49 | repo := args["repo"].(string) 50 | 51 | apiUrl := fmt.Sprintf("/repos/%s/%s/forks", owner, repo) 52 | giteeClient := utils.NewGiteeClient("POST", apiUrl, utils.WithContext(ctx), utils.WithPayload(request.Params.Arguments)) 53 | 54 | data := &types.Project{} 55 | return giteeClient.HandleMCPResult(data) 56 | } 57 | -------------------------------------------------------------------------------- /operations/repository/get_file_content.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/url" 7 | 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | ) 11 | 12 | const ( 13 | // GetFileContentToolName is the tool name for getting file content 14 | GetFileContentToolName = "get_file_content" 15 | ) 16 | 17 | var GetFileContentTool = mcp.NewTool( 18 | GetFileContentToolName, 19 | mcp.WithDescription("Get the content of the specified file in the repository"), 20 | mcp.WithString( 21 | "owner", 22 | mcp.Description("The space address to which the repository belongs (the address path of the enterprise, organization or individual)"), 23 | mcp.Required(), 24 | ), 25 | mcp.WithString( 26 | "repo", 27 | mcp.Description("The path of the repository"), 28 | mcp.Required(), 29 | ), 30 | mcp.WithString( 31 | "path", 32 | mcp.Description("The path of the file"), 33 | mcp.Required(), 34 | ), 35 | mcp.WithString( 36 | "ref", 37 | mcp.Description("The branch name or commit ID"), 38 | ), 39 | ) 40 | 41 | func GetFileContentHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 42 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 43 | owner := args["owner"].(string) 44 | repo := args["repo"].(string) 45 | path := args["path"].(string) 46 | ref, ok := args["ref"].(string) 47 | if !ok { 48 | ref = "" 49 | } 50 | apiUrl := fmt.Sprintf("/repos/%s/%s/contents/%s", owner, repo, url.QueryEscape(path)) 51 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithQuery(map[string]interface{}{"ref": ref})) 52 | var fileContents interface{} 53 | return giteeClient.HandleMCPResult(&fileContents) 54 | } 55 | -------------------------------------------------------------------------------- /operations/repository/list_releases.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "gitee.com/oschina/mcp-gitee/operations/types" 8 | "gitee.com/oschina/mcp-gitee/utils" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | ) 11 | 12 | const ( 13 | ListReleasesToolName = "list_releases" 14 | ) 15 | 16 | var ListReleasesTool = mcp.NewTool( 17 | ListReleasesToolName, 18 | mcp.WithDescription("List repository releases"), 19 | mcp.WithString( 20 | "owner", 21 | mcp.Description("The space address to which the repository belongs (the address path of the enterprise, organization or individual)"), 22 | mcp.Required(), 23 | ), 24 | mcp.WithString( 25 | "repo", 26 | mcp.Description("The path of the repository"), 27 | mcp.Required(), 28 | ), 29 | mcp.WithNumber( 30 | "page", 31 | mcp.Description("Current page number"), 32 | mcp.DefaultNumber(1), 33 | ), 34 | mcp.WithNumber( 35 | "per_page", 36 | mcp.Description("Number of results per page, maximum 100"), 37 | mcp.DefaultNumber(20), 38 | ), 39 | mcp.WithString( 40 | "direction", 41 | mcp.Description("Optional. Ascending/descending. Not filled in is ascending"), 42 | mcp.Enum("asc", "desc"), 43 | ), 44 | ) 45 | 46 | func ListReleasesHandleFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 47 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 48 | owner := args["owner"].(string) 49 | repo := args["repo"].(string) 50 | 51 | apiUrl := fmt.Sprintf("/repos/%s/%s/releases", owner, repo) 52 | 53 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithPayload(args)) 54 | 55 | releases := make([]types.Release, 0) 56 | return giteeClient.HandleMCPResult(&releases) 57 | } 58 | -------------------------------------------------------------------------------- /operations/repository/list_user_repos.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "gitee.com/oschina/mcp-gitee/operations/types" 7 | "gitee.com/oschina/mcp-gitee/utils" 8 | "github.com/mark3labs/mcp-go/mcp" 9 | ) 10 | 11 | const ( 12 | // ListUserReposToolName is the name of the tool 13 | ListUserReposToolName = "list_user_repos" 14 | ) 15 | 16 | var ListUserReposTool = mcp.NewTool( 17 | ListUserReposToolName, 18 | mcp.WithDescription("List user authorized repositories"), 19 | mcp.WithString( 20 | "visibility", 21 | mcp.Description("Visibility of repository"), 22 | mcp.Enum("public", "private", "all"), 23 | ), 24 | mcp.WithString( 25 | "affiliation", 26 | mcp.Description("Affiliation between user and repository"), 27 | mcp.Enum("owner", "collaborator", "organization_member", "enterprise_member", "admin"), 28 | ), 29 | mcp.WithString( 30 | "type", 31 | mcp.Description("Filter user repositories: their creation (owner), personal (personal), their membership (member), public (public), private (private), cannot be used together with visibility or affiliation parameters, otherwise a 422 error will be reported"), 32 | mcp.Enum("all", "owner", "personal", "member", "public", "private"), 33 | ), 34 | mcp.WithString( 35 | "sort", 36 | mcp.Description("Sorting method: creation time (created), update time (updated), last push time (pushed), warehouse ownership and name (full_name). Default: full_name"), 37 | mcp.Enum("created", "updated", "pushed", "full_name"), 38 | mcp.DefaultString("full_name"), 39 | ), 40 | mcp.WithString( 41 | "direction", 42 | mcp.Description("Sorting direction: ascending (asc), descending (desc). Default: asc"), 43 | mcp.Enum("asc", "desc"), 44 | mcp.DefaultString("asc"), 45 | ), 46 | mcp.WithString( 47 | "q", 48 | mcp.Description("Search keywords"), 49 | ), 50 | mcp.WithNumber( 51 | "page", 52 | mcp.Description("Page number"), 53 | mcp.DefaultNumber(1), 54 | ), 55 | mcp.WithNumber( 56 | "per_page", 57 | mcp.Description("Number of results per page"), 58 | mcp.DefaultNumber(20), 59 | ), 60 | ) 61 | 62 | func ListUserReposHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 63 | apiUrl := "/user/repos" 64 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 65 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithQuery(args)) 66 | 67 | repositories := make([]types.Project, 0) 68 | return giteeClient.HandleMCPResult(&repositories) 69 | } 70 | -------------------------------------------------------------------------------- /operations/repository/search_repositories.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "gitee.com/oschina/mcp-gitee/operations/types" 7 | "gitee.com/oschina/mcp-gitee/utils" 8 | "github.com/mark3labs/mcp-go/mcp" 9 | ) 10 | 11 | const ( 12 | SearchOpenSourceRepositories = "search_open_source_repositories" 13 | ) 14 | 15 | var SearchReposTool = mcp.NewTool(SearchOpenSourceRepositories, 16 | mcp.WithDescription("Search open source repositories on Gitee"), 17 | mcp.WithString( 18 | "q", 19 | mcp.Description("Search keywords"), 20 | mcp.Required(), 21 | ), 22 | mcp.WithNumber( 23 | "from", 24 | mcp.Description("Search starting position"), 25 | mcp.DefaultNumber(0), 26 | ), 27 | mcp.WithNumber( 28 | "size", 29 | mcp.Description("Page size"), 30 | mcp.DefaultNumber(20), 31 | ), 32 | mcp.WithString( 33 | "sort_by_f", 34 | mcp.Description("Sorting field"), 35 | mcp.Enum("star", "last_push_at"), 36 | ), 37 | ) 38 | 39 | func SearchOpenSourceReposHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 40 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 41 | if checkResult, err := utils.CheckRequired(args, "q"); err != nil { 42 | return checkResult, err 43 | } 44 | 45 | apiUrl := "/search/repos" 46 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithQuery(args), utils.WithSkipAuth()) 47 | 48 | data := types.PagedResponse[types.SearchProject]{} 49 | return giteeClient.HandleMCPResult(&data) 50 | } 51 | -------------------------------------------------------------------------------- /operations/types/basic.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type PagedResponse[T any] struct { 4 | TotalCount int `json:"total_count"` 5 | Data []T `json:"data"` 6 | } 7 | 8 | type BasicUser struct { 9 | Id int `json:"id"` 10 | Login string `json:"login"` 11 | Name string `json:"name"` 12 | AvatarUrl string `json:"avatar_url"` 13 | HtmlUrl string `json:"html_url"` 14 | Remark string `json:"remark"` 15 | } 16 | 17 | type BasicEnterprise struct { 18 | Id int `json:"id"` 19 | Name string `json:"name"` 20 | Path string `json:"path"` 21 | HtmlUrl string `json:"html_url"` 22 | } 23 | 24 | type BasicProgram struct { 25 | Id int `json:"id"` 26 | Name string `json:"name"` 27 | Description string `json:"description"` 28 | Assignee BasicUser `json:"assignee"` 29 | Author BasicUser `json:"author"` 30 | } 31 | 32 | type EndpointConfig struct { 33 | UrlTemplate string 34 | PathParams []string 35 | } 36 | 37 | type IssueTypeDetail struct { 38 | Id int `json:"id"` 39 | Title string `json:"title"` 40 | } 41 | 42 | type BasicLabel struct { 43 | Id int `json:"id"` 44 | Name string `json:"name"` 45 | } 46 | -------------------------------------------------------------------------------- /operations/types/file_content.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type FileContent struct { 4 | Type string `json:"type"` 5 | Size int `json:"size"` 6 | Name string `json:"name"` 7 | Path string `json:"path"` 8 | Sha string `json:"sha"` 9 | Content string `json:"content"` 10 | } 11 | -------------------------------------------------------------------------------- /operations/types/issue.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // BasicIssue 定义了 Issue 的基本结构 4 | type BasicIssue struct { 5 | Id int `json:"id"` 6 | Number string `json:"number"` 7 | Title string `json:"title"` 8 | User BasicUser `json:"user"` 9 | State string `json:"state"` 10 | CreatedAt string `json:"created_at"` 11 | UpdatedAt string `json:"updated_at"` 12 | Body string `json:"body"` 13 | Labels []BasicLabel `json:"labels"` 14 | Assignee *BasicUser `json:"assignee"` 15 | IssueType string `json:"issue_type"` 16 | Program *BasicProgram `json:"program"` 17 | HtmlUrl string `json:"html_url"` 18 | } 19 | 20 | // IssueComment defines the structure for an issue comment 21 | type IssueComment struct { 22 | Id int `json:"id"` 23 | Body string `json:"body"` 24 | User BasicUser `json:"user"` 25 | CreatedAt string `json:"created_at"` 26 | UpdatedAt string `json:"updated_at"` 27 | } 28 | -------------------------------------------------------------------------------- /operations/types/notification.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Notification struct { 4 | Id int `json:"id"` 5 | Content string `json:"content"` 6 | Type string `json:"type"` 7 | Unread bool `json:"unread"` 8 | Mute bool `json:"mute"` 9 | UpdatedAt string `json:"updated_at"` 10 | Url string `json:"url"` 11 | HtmlUrl string `json:"html_url"` 12 | Actor BasicUser `json:"actor"` 13 | Repository Project `json:"repository"` 14 | Subject Subject `json:"subject"` 15 | Namespaces []Namespace `json:"namespaces"` 16 | } 17 | 18 | type Subject struct { 19 | Title string `json:"title"` 20 | Url string `json:"url"` 21 | LatestCommentUrl string `json:"latest_comment_url"` 22 | Type string `json:"type"` 23 | } 24 | 25 | type NotificationResult struct { 26 | TotalCount int `json:"total_count"` 27 | List []Notification `json:"list"` 28 | } 29 | -------------------------------------------------------------------------------- /operations/types/pull.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type BasicPull struct { 4 | Id int `json:"id"` 5 | Title string `json:"title"` 6 | Url string `json:"html_url"` 7 | Number int `json:"number"` 8 | State string `json:"state"` 9 | AssigneesNumber int `json:"assignees_number"` 10 | TestersNumber int `json:"testers_number"` 11 | Assignees []Assignee `json:"assignees"` 12 | Testers []Assignee `json:"testers"` 13 | CreatedAt string `json:"created_at"` 14 | UpdatedAt string `json:"updated_at"` 15 | ClosedAt string `json:"closed_at"` 16 | MergedAt string `json:"merged_at"` 17 | Creator BasicUser `json:"user"` 18 | Head Reference `json:"head"` 19 | Base Reference `json:"base"` 20 | CanMergeCheck bool `json:"can_merge_check"` 21 | Draft bool `json:"draft"` 22 | } 23 | 24 | type Reference struct { 25 | Ref string `json:"ref"` 26 | Label string `json:"label"` 27 | Sha string `json:"sha"` 28 | } 29 | 30 | type Assignee struct { 31 | BasicUser 32 | Accept bool `json:"accept"` 33 | } 34 | 35 | // PullComment defines the structure for a pull request comment 36 | type PullComment struct { 37 | Id int `json:"id"` 38 | Body string `json:"body"` 39 | User BasicUser `json:"user"` 40 | CreatedAt string `json:"created_at"` 41 | UpdatedAt string `json:"updated_at"` 42 | CommentType string `json:"comment_type"` 43 | CommentId string `json:"comment_id"` 44 | PullUrl string `json:"pull_url"` 45 | OriginalPosition string `json:"original_position"` 46 | Path string `json:"path"` 47 | Position string `json:"position"` 48 | CommitId string `json:"commit_id"` 49 | OriginalCommitId string `json:"original_commit_id"` 50 | InReplyToId int `json:"in_reply_to_id"` 51 | Url string `json:"url"` 52 | } 53 | -------------------------------------------------------------------------------- /operations/types/release.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Release struct { 4 | Id int `json:"id"` 5 | Author BasicUser `json:"author"` 6 | Name string `json:"name"` 7 | Body string `json:"body"` 8 | Prerelease bool `json:"prerelease"` 9 | TagName string `json:"tag_name"` 10 | TargetCommitish string `json:"target_commitish"` 11 | CreatedAt string `json:"created_at"` 12 | Assets []Asset `json:"assets"` 13 | } 14 | 15 | type Asset struct { 16 | BrowserDownloadUrl string `json:"browser_download_url"` 17 | Name string `json:"name"` 18 | } 19 | -------------------------------------------------------------------------------- /operations/types/repository.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Project struct { 4 | Id int `json:"id"` 5 | FullName string `json:"full_name"` 6 | HumanName string `json:"human_name"` 7 | Url string `json:"url"` 8 | Namespace Namespace `json:"namespace"` 9 | Owner BasicUser `json:"owner"` 10 | Assigner BasicUser `json:"assigner"` 11 | Description string `json:"description"` 12 | Private bool `json:"private"` 13 | Public bool `json:"public"` 14 | Internal bool `json:"internal"` 15 | Fork bool `json:"fork"` 16 | SshUrl string `json:"ssh_url"` 17 | Recommend bool `json:"recommend"` 18 | Gvp bool `json:"gvp"` 19 | StargazersCount int `json:"stargazers_count"` 20 | ForksCount int `json:"forks_count"` 21 | WatchersCount int `json:"watchers_count"` 22 | DefaultBranch string `json:"default_branch"` 23 | Members []string `json:"members"` 24 | PushedAt string `json:"pushed_at"` 25 | CreatedAt string `json:"created_at"` 26 | UpdatedAt string `json:"updated_at"` 27 | Permission map[string]bool `json:"permission"` 28 | Status string `json:"status"` 29 | Enterprise BasicEnterprise `json:"enterprise"` 30 | } 31 | 32 | type Namespace struct { 33 | Id int `json:"id"` 34 | Type string `json:"type"` 35 | Name string `json:"name"` 36 | Path string `json:"path"` 37 | HtmlUrl string `json:"html_url"` 38 | } 39 | 40 | type SearchProject struct { 41 | Id int `json:"id"` 42 | Name string `json:"name"` 43 | Url string `json:"url"` 44 | Description string `json:"description"` 45 | Stars int `json:"stars"` 46 | Forks int `json:"forks"` 47 | Languages []string `json:"languages"` 48 | LastPushAt string `json:"last_push_at"` 49 | CreatedAt string `json:"created_at"` 50 | UpdatedAt string `json:"updated_at"` 51 | } 52 | -------------------------------------------------------------------------------- /operations/users/get_user_info.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import ( 4 | "context" 5 | "gitee.com/oschina/mcp-gitee/operations/types" 6 | "gitee.com/oschina/mcp-gitee/utils" 7 | "github.com/mark3labs/mcp-go/mcp" 8 | "github.com/mark3labs/mcp-go/server" 9 | ) 10 | 11 | const ( 12 | // GetUserInfoToolName is the instruction to get authenticated user information 13 | GetUserInfoToolName = "get_user_info" 14 | ) 15 | 16 | // GetUserInfoTool defines the tool for getting authorized user information 17 | var GetUserInfoTool = mcp.NewTool( 18 | GetUserInfoToolName, 19 | mcp.WithDescription("This is a tool from the gitee MCP server.\nGet information about the authenticated user"), 20 | // No parameters needed for this endpoint as it uses the authenticated user's token 21 | ) 22 | 23 | // GetUserInfoHandler handles the request to get authorized user information 24 | func GetUserInfoHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 25 | client := utils.NewGiteeClient("GET", "/user", utils.WithContext(ctx)) 26 | 27 | var user types.BasicUser 28 | return client.HandleMCPResult(&user) 29 | } 30 | 31 | // GetUserInfoHandleFunc returns a server.ToolHandlerFunc for handling get user info requests 32 | func GetUserInfoHandleFunc() server.ToolHandlerFunc { 33 | return GetUserInfoHandler 34 | } 35 | -------------------------------------------------------------------------------- /operations/users/search_users.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import ( 4 | "context" 5 | 6 | "gitee.com/oschina/mcp-gitee/operations/types" 7 | "gitee.com/oschina/mcp-gitee/utils" 8 | "github.com/mark3labs/mcp-go/mcp" 9 | ) 10 | 11 | const ( 12 | SearchUsers = "search_users" 13 | ) 14 | 15 | var SearchUsersTool = mcp.NewTool(SearchUsers, 16 | mcp.WithDescription("Search users on Gitee"), 17 | mcp.WithString( 18 | "q", 19 | mcp.Description("Search keywords"), 20 | mcp.Required(), 21 | ), 22 | mcp.WithNumber( 23 | "page", 24 | mcp.Description("Page number"), 25 | mcp.DefaultNumber(1), 26 | ), 27 | mcp.WithNumber( 28 | "per_page", 29 | mcp.Description("Number of results per page"), 30 | mcp.DefaultNumber(20), 31 | ), 32 | ) 33 | 34 | func SearchUsersHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 35 | args, _ := utils.ConvertArgumentsToMap(request.Params.Arguments) 36 | if checkResult, err := utils.CheckRequired(args, "q"); err != nil { 37 | return checkResult, err 38 | } 39 | 40 | apiUrl := "/search/users" 41 | giteeClient := utils.NewGiteeClient("GET", apiUrl, utils.WithContext(ctx), utils.WithQuery(args), utils.WithSkipAuth()) 42 | 43 | users := make([]types.BasicUser, 0) 44 | return giteeClient.HandleMCPResult(&users) 45 | } 46 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | 2 | startCommand: 3 | type: stdio 4 | configSchema: 5 | type: object 6 | required: 7 | - accessToken 8 | properties: 9 | accessToken: 10 | type: string 11 | description: Gitee Personal Access Token 12 | apiBase: 13 | type: string 14 | default: https://gitee.com/api/v5 15 | description: Gitee API Base URL 16 | commandFunction: 17 | |- 18 | (config) => ({ 19 | command: '/app/mcp-gitee', 20 | args: ['--transport', 'stdio'], 21 | env: { 22 | GITEE_ACCESS_TOKEN: config.accessToken, 23 | GITEE_API_BASE: config.apiBase || 'https://gitee.com/api/v5' 24 | } 25 | }) 26 | exampleConfig: 27 | accessToken: dummy-token 28 | apiBase: https://gitee.com/api/v5 29 | -------------------------------------------------------------------------------- /utils/constants.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | var ( 4 | // Version gitee mcp server version 5 | Version = "0.1.11" 6 | ) 7 | -------------------------------------------------------------------------------- /utils/convert.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "reflect" 7 | ) 8 | 9 | // SafelyConvertToInt 尝试将各种类型安全地转换为 int 10 | // 支持 int, int32, int64, float32, float64, string 类型的转换 11 | // 对于浮点数,要求必须是整数值(如 1.0),不接受带小数部分的值 12 | // 对于字符串,尝试解析为整数或浮点数(浮点数必须是整数值) 13 | func SafelyConvertToInt(value interface{}) (int, error) { 14 | switch v := value.(type) { 15 | case int: 16 | return v, nil 17 | case int32: 18 | return int(v), nil 19 | case int64: 20 | return int(v), nil 21 | case float32: 22 | if float32(int(v)) == v { 23 | return int(v), nil 24 | } 25 | return 0, NewParamError("number", "must be an integer value") 26 | case float64: 27 | if float64(int(v)) == v { 28 | return int(v), nil 29 | } 30 | return 0, NewParamError("number", "must be an integer value") 31 | case string: 32 | var intValue int 33 | if _, err := fmt.Sscanf(v, "%d", &intValue); err == nil { 34 | return intValue, nil 35 | } 36 | var floatValue float64 37 | if _, err := fmt.Sscanf(v, "%f", &floatValue); err == nil { 38 | if math.Floor(floatValue) == floatValue { 39 | return int(floatValue), nil 40 | } 41 | } 42 | return 0, NewParamError("number", "must be a valid integer") 43 | default: 44 | return 0, NewParamError("number", fmt.Sprintf("unsupported type: %v", reflect.TypeOf(value))) 45 | } 46 | } 47 | 48 | // ConvertArgumentsToMap 将 any 类型的参数安全转换为 map[string]interface{} 49 | func ConvertArgumentsToMap(arguments any) (map[string]interface{}, error) { 50 | if arguments == nil { 51 | return make(map[string]interface{}), nil 52 | } 53 | 54 | if argMap, ok := arguments.(map[string]interface{}); ok { 55 | return argMap, nil 56 | } 57 | 58 | return nil, NewParamError("arguments", "arguments must be a map[string]interface{}") 59 | } 60 | -------------------------------------------------------------------------------- /utils/errors.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type ErrorType string 9 | 10 | const ( 11 | // ErrorTypeNetwork Network error 12 | ErrorTypeNetwork ErrorType = "network_error" 13 | // ErrorTypeAPI API error 14 | ErrorTypeAPI ErrorType = "api_error" 15 | // ErrorTypeAuth Auth error 16 | ErrorTypeAuth ErrorType = "auth_error" 17 | // ErrorTypeParam param error 18 | ErrorTypeParam ErrorType = "param_error" 19 | // ErrorTypeInternal Internal error 20 | ErrorTypeInternal ErrorType = "internal_error" 21 | ) 22 | 23 | type GiteeError struct { 24 | Type ErrorType `json:"type"` 25 | Message string `json:"message"` 26 | Code int `json:"code,omitempty"` 27 | Details string `json:"details,omitempty"` 28 | } 29 | 30 | func (e *GiteeError) Error() string { 31 | if e.Details != "" { 32 | return fmt.Sprintf("[%s] %s (code: %d, details: %s)", e.Type, e.Message, e.Code, e.Details) 33 | } 34 | return fmt.Sprintf("[%s] %s (code: %d)", e.Type, e.Message, e.Code) 35 | } 36 | 37 | func NewNetworkError(err error) *GiteeError { 38 | return &GiteeError{ 39 | Type: ErrorTypeNetwork, 40 | Message: "Network communication error", 41 | Details: err.Error(), 42 | } 43 | } 44 | 45 | func NewAPIError(statusCode int, body []byte) *GiteeError { 46 | var details string 47 | var apiErr struct { 48 | Message string `json:"message"` 49 | Error string `json:"error"` 50 | } 51 | 52 | if err := json.Unmarshal(body, &apiErr); err == nil { 53 | if apiErr.Message != "" { 54 | details = apiErr.Message 55 | } else if apiErr.Error != "" { 56 | details = apiErr.Error 57 | } 58 | } 59 | 60 | if details == "" { 61 | details = string(body) 62 | } 63 | 64 | return &GiteeError{ 65 | Type: ErrorTypeAPI, 66 | Message: fmt.Sprintf("API returned error status: %d", statusCode), 67 | Code: statusCode, 68 | Details: details, 69 | } 70 | } 71 | 72 | func NewAuthError() *GiteeError { 73 | return &GiteeError{ 74 | Type: ErrorTypeAuth, 75 | Message: "Authentication failed, please check your access token", 76 | } 77 | } 78 | 79 | func NewParamError(param string, details string) *GiteeError { 80 | return &GiteeError{ 81 | Type: ErrorTypeParam, 82 | Message: fmt.Sprintf("Invalid parameter: %s", param), 83 | Details: details, 84 | } 85 | } 86 | 87 | func NewInternalError(err error) *GiteeError { 88 | return &GiteeError{ 89 | Type: ErrorTypeInternal, 90 | Message: "Internal server error", 91 | Details: err.Error(), 92 | } 93 | } 94 | 95 | func IsAuthError(err error) bool { 96 | if giteeErr, ok := err.(*GiteeError); ok { 97 | return giteeErr.Type == ErrorTypeAuth 98 | } 99 | return false 100 | } 101 | 102 | func IsAPIError(err error) bool { 103 | if giteeErr, ok := err.(*GiteeError); ok { 104 | return giteeErr.Type == ErrorTypeAPI 105 | } 106 | return false 107 | } 108 | 109 | func IsNetworkError(err error) bool { 110 | if giteeErr, ok := err.(*GiteeError); ok { 111 | return giteeErr.Type == ErrorTypeNetwork 112 | } 113 | return false 114 | } 115 | 116 | func IsParamError(err error) bool { 117 | if giteeErr, ok := err.(*GiteeError); ok { 118 | return giteeErr.Type == ErrorTypeParam 119 | } 120 | return false 121 | } 122 | -------------------------------------------------------------------------------- /utils/gitee_client.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/base64" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "io/ioutil" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "runtime" 15 | "strconv" 16 | 17 | "gitee.com/oschina/mcp-gitee/operations/types" 18 | 19 | "github.com/mark3labs/mcp-go/mcp" 20 | ) 21 | 22 | const ( 23 | DefaultApiBase = "https://gitee.com/api/v5" 24 | ) 25 | 26 | var ( 27 | giteeAccessToken string 28 | apiBase string 29 | ) 30 | 31 | func SetGiteeAccessToken(token string) { 32 | giteeAccessToken = token 33 | } 34 | 35 | func SetApiBase(url string) { 36 | apiBase = url 37 | } 38 | 39 | func GetGiteeAccessToken() string { 40 | if giteeAccessToken != "" { 41 | return giteeAccessToken 42 | } 43 | return os.Getenv("GITEE_ACCESS_TOKEN") 44 | } 45 | 46 | func GetApiBase() string { 47 | if apiBase != "" { 48 | return apiBase 49 | } 50 | if envApiBase := os.Getenv("GITEE_API_BASE"); envApiBase != "" { 51 | return envApiBase 52 | } 53 | return DefaultApiBase 54 | } 55 | 56 | type GiteeClient struct { 57 | Url string 58 | Method string 59 | Payload interface{} 60 | Headers map[string]string 61 | Response *http.Response 62 | parsedUrl *url.URL 63 | Query map[string]string 64 | SkipAuth bool 65 | Ctx context.Context 66 | } 67 | 68 | type Option func(client *GiteeClient) 69 | 70 | type ErrMsgV5 struct { 71 | Message string `json:"message"` 72 | } 73 | 74 | func NewGiteeClient(method, urlString string, opts ...Option) *GiteeClient { 75 | urlString = GetApiBase() + urlString 76 | parsedUrl, err := url.Parse(urlString) 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | client := &GiteeClient{ 82 | Method: method, 83 | Url: parsedUrl.String(), 84 | parsedUrl: parsedUrl, 85 | Ctx: context.Background(), 86 | } 87 | 88 | for _, opt := range opts { 89 | opt(client) 90 | } 91 | return client 92 | } 93 | 94 | func WithQuery(query map[string]interface{}) Option { 95 | return func(client *GiteeClient) { 96 | parsedQuery := make(map[string]string) 97 | if query != nil { 98 | queryParams := client.parsedUrl.Query() 99 | for k, v := range query { 100 | parsedValue := "" 101 | switch v.(type) { 102 | case string: 103 | parsedValue = v.(string) 104 | case int: 105 | parsedValue = strconv.Itoa(v.(int)) 106 | case float32, float64: 107 | parsedValue = fmt.Sprintf("%.f", v) 108 | case bool: 109 | parsedValue = strconv.FormatBool(v.(bool)) 110 | } 111 | if parsedValue != "" { 112 | queryParams.Set(k, parsedValue) 113 | parsedQuery[k] = parsedValue 114 | } 115 | } 116 | client.parsedUrl.RawQuery = queryParams.Encode() 117 | } 118 | client.Url = client.parsedUrl.String() 119 | client.Query = parsedQuery 120 | } 121 | } 122 | 123 | func WithPayload(payload interface{}) Option { 124 | return func(client *GiteeClient) { 125 | client.Payload = payload 126 | } 127 | } 128 | 129 | func WithHeaders(headers map[string]string) Option { 130 | return func(client *GiteeClient) { 131 | client.Headers = headers 132 | } 133 | } 134 | 135 | func WithSkipAuth() Option { 136 | return func(client *GiteeClient) { 137 | client.SkipAuth = true 138 | } 139 | } 140 | 141 | func (g *GiteeClient) SetHeaders(headers map[string]string) *GiteeClient { 142 | g.Headers = headers 143 | return g 144 | } 145 | 146 | func (g *GiteeClient) Do() (*GiteeClient, error) { 147 | g.Response = nil 148 | _payload, _ := json.Marshal(g.Payload) 149 | req, err := http.NewRequest(g.Method, g.Url, bytes.NewReader(_payload)) 150 | if err != nil { 151 | return nil, NewInternalError(err) 152 | } 153 | 154 | req.Header.Set("Content-Type", "application/json") 155 | req.Header.Set("User-Agent", "mcp-gitee "+Version+" Go/"+runtime.GOOS+"/"+runtime.GOARCH+"/"+runtime.Version()) 156 | 157 | accessToken := "" 158 | if g.Ctx != nil { 159 | if v := g.Ctx.Value("access_token"); v != nil { 160 | if s, ok := v.(string); ok && s != "" { 161 | accessToken = s 162 | } 163 | } 164 | } 165 | if accessToken == "" { 166 | accessToken = GetGiteeAccessToken() 167 | } 168 | if accessToken == "" && !g.SkipAuth { 169 | return nil, NewAuthError() 170 | } 171 | 172 | req.Header.Set("Authorization", "Bearer "+accessToken) 173 | 174 | for key, value := range g.Headers { 175 | req.Header.Set(key, value) 176 | } 177 | 178 | client := &http.Client{} 179 | resp, err := client.Do(req) 180 | if err != nil { 181 | return g, NewNetworkError(err) 182 | } 183 | 184 | g.Response = resp 185 | 186 | if !g.IsSuccess() { 187 | body, _ := ioutil.ReadAll(resp.Body) 188 | return g, NewAPIError(resp.StatusCode, body) 189 | } 190 | 191 | return g, nil 192 | } 193 | 194 | func (g *GiteeClient) IsSuccess() bool { 195 | if g.Response == nil { 196 | return false 197 | } 198 | 199 | successMap := map[int]struct{}{ 200 | http.StatusOK: struct{}{}, 201 | http.StatusCreated: struct{}{}, 202 | http.StatusNoContent: struct{}{}, 203 | http.StatusFound: struct{}{}, 204 | http.StatusNotModified: struct{}{}, 205 | } 206 | 207 | if _, ok := successMap[g.Response.StatusCode]; ok { 208 | return true 209 | } 210 | return false 211 | } 212 | 213 | func (g *GiteeClient) IsFail() bool { 214 | return !g.IsSuccess() 215 | } 216 | 217 | func (g *GiteeClient) GetRespBody() ([]byte, error) { 218 | return ioutil.ReadAll(g.Response.Body) 219 | } 220 | 221 | func (g *GiteeClient) HandleMCPResult(object any) (*mcp.CallToolResult, error) { 222 | _, err := g.Do() 223 | if err != nil { 224 | switch { 225 | case IsAuthError(err): 226 | return mcp.NewToolResultError("Authentication failed: Please check your Gitee access token"), err 227 | case IsNetworkError(err): 228 | return mcp.NewToolResultError("Network error: Unable to connect to Gitee API"), err 229 | case IsAPIError(err): 230 | giteeErr := err.(*GiteeError) 231 | return mcp.NewToolResultError(fmt.Sprintf("API error (%d): %s", giteeErr.Code, giteeErr.Details)), err 232 | default: 233 | return mcp.NewToolResultError(err.Error()), err 234 | } 235 | } 236 | 237 | if object == nil { 238 | return mcp.NewToolResultText("Operation completed successfully"), nil 239 | } 240 | 241 | body, err := g.GetRespBody() 242 | if err != nil { 243 | return mcp.NewToolResultError(fmt.Sprintf("Failed to read response body: %s", err.Error())), 244 | NewInternalError(errors.New(err.Error())) 245 | } 246 | 247 | if err = json.Unmarshal(body, object); err != nil { 248 | errorMessage := fmt.Sprintf("Failed to parse response: %v", err) 249 | return mcp.NewToolResultError(errorMessage), NewInternalError(errors.New(errorMessage)) 250 | } 251 | 252 | switch v := object.(type) { 253 | case *[]types.FileContent: 254 | for i := range *v { 255 | content, err := base64.StdEncoding.DecodeString((*v)[i].Content) 256 | if err != nil { 257 | return mcp.NewToolResultError(fmt.Sprintf("Failed to decode base64 content: %s", err.Error())), 258 | NewInternalError(err) 259 | } 260 | (*v)[i].Content = string(content) 261 | } 262 | object = v 263 | case *types.FileContent: 264 | content, err := base64.StdEncoding.DecodeString(v.Content) 265 | if err != nil { 266 | return mcp.NewToolResultError(fmt.Sprintf("Failed to decode base64 content: %s", err.Error())), 267 | NewInternalError(err) 268 | } 269 | v.Content = string(content) 270 | object = v 271 | } 272 | 273 | result, err := json.MarshalIndent(object, "", " ") 274 | if err != nil { 275 | return mcp.NewToolResultError(fmt.Sprintf("Failed to format response: %s", err.Error())), 276 | NewInternalError(err) 277 | } 278 | 279 | return mcp.NewToolResultText(string(result)), nil 280 | } 281 | 282 | func WithContext(ctx context.Context) Option { 283 | return func(client *GiteeClient) { 284 | if ctx != nil { 285 | client.Ctx = ctx 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /utils/mcp.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/mark3labs/mcp-go/mcp" 4 | 5 | func CheckRequired(params map[string]interface{}, required ...string) (*mcp.CallToolResult, error) { 6 | for _, key := range required { 7 | if _, ok := params[key]; !ok { 8 | return mcp.NewToolResultError("Missing required parameter: " + key), 9 | NewParamError(key, "parameter is required") 10 | } 11 | } 12 | return nil, nil 13 | } 14 | -------------------------------------------------------------------------------- /utils/tool_options_handle.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/mark3labs/mcp-go/mcp" 4 | 5 | func CombineOptions(options ...[]mcp.ToolOption) []mcp.ToolOption { 6 | var result []mcp.ToolOption 7 | for _, option := range options { 8 | result = append(result, option...) 9 | } 10 | return result 11 | } 12 | --------------------------------------------------------------------------------