16 Commits

Author SHA1 Message Date
appleboy
5308fbfb2b docs: add Table of Contents to all README translations (#46)
- Add a Table of Contents section to the README files in English, Simplified Chinese, and Traditional Chinese for improved navigation.

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/46
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-05-27 12:32:27 +00:00
Hubert Wawrzyńczyk
a7061f9b64 fix: make API bool parameters in search_repos and list_releases optional (#40) (#44)
Fix #40

Left the `mcp.DefaultBool(false)` for `is_draft` and `is_pre_release` in `list_releases`, because I guess they are default, but it's up to the client whether to set them or not.
11e04b5b8d/operation/repo/release.go (L67-L68)

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/44
Reviewed-by: Bo-Yi Wu (吳柏毅) <appleboy.tw@gmail.com>
Co-authored-by: Hubert Wawrzyńczyk <hubert@fit-it.pl>
Co-committed-by: Hubert Wawrzyńczyk <hubert@fit-it.pl>
2025-05-27 12:20:47 +00:00
appleboy
f25cc0de8c feat: add HTTP server mode with updated docs and localization (#45)
- Update download instructions for clarity and consistency in all README files
- Add example configuration for HTTP mode to all README files
- Expand transport type support to include "http" in command-line flags and documentation
- Implement HTTP server mode in the application entrypoint
- Update log output behavior to include "http" mode alongside "sse" for stdout logging
- Refine Chinese README translations for greater accuracy and localization

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/45
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-05-27 12:17:37 +00:00
Bo-Yi Wu
417ef26da0 build: add VS Code server config and enable versioning of settings (#43)
- Remove .vscode directory from .gitignore to allow versioning of VS Code settings
- Add a VS Code server configuration file with prompts for Gitea host, access token, and insecure connection option
- Configure a stdio-based server launch for gitea-mcp with relevant environment variables

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/43
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-05-27 04:15:22 +00:00
Bo-Yi Wu
34ca5d45db refactor(args): request argument access and update dependencies (#42)
- Update dependencies to newer versions in go.mod
- Refactor all request argument accesses to use req.GetArguments() instead of direct access to req.Params.Arguments
- Change variable declaration for ListRepoCommitsTool from a grouped var block to a single var statement for consistency

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/42
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-05-26 06:10:10 +00:00
Bo-Yi Wu
796fd4682d docs: document get_user_orgs tool in Chinese guides (#41)
- Add get_user_orgs tool to the list of supported tools in both Simplified and Traditional Chinese documentation

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/41
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-05-26 06:01:59 +00:00
hiifong
95c036bf3a docker sse (#37)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/37
Co-authored-by: hiifong <f@ilo.nz>
Co-committed-by: hiifong <f@ilo.nz>
2025-04-20 09:14:14 +00:00
hiifong
70b9ac5b80 Support read only mode (#36)
Fix: #35
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/36
Co-authored-by: hiifong <f@ilo.nz>
Co-committed-by: hiifong <f@ilo.nz>
2025-04-20 09:09:29 +00:00
techknowlogick
59e699aac7 Add get_user_orgs tool (#34)
Fix #33

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/34
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2025-04-18 01:30:44 +00:00
yp05327
26c50d53bd Add gitlens to vscode extentions (#31)
Co-authored-by: hiifong <i@hiif.ong>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/31
Reviewed-by: hiifong <i@hiif.ong>
Co-authored-by: yp05327 <576951401@qq.com>
Co-committed-by: yp05327 <576951401@qq.com>
2025-04-11 11:54:12 +00:00
hiifong
7bfc596a58 fix debug mode default value (#29)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/29
2025-04-11 10:01:59 +00:00
yp05327
966d617670 Add EditIssue (#30)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/30
Reviewed-by: hiifong <i@hiif.ong>
Co-authored-by: yp05327 <576951401@qq.com>
Co-committed-by: yp05327 <576951401@qq.com>
2025-04-11 10:01:41 +00:00
hiifong
af27b685d4 feat: Add debug (#28)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/28
Reviewed-by: yp05327 <576951401@qq.com>
Co-authored-by: hiifong <i@hiif.ong>
Co-committed-by: hiifong <i@hiif.ong>
2025-04-11 06:48:01 +00:00
yp05327
fac6e1d8d1 Include error info in some functions (#27)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/27
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: yp05327 <576951401@qq.com>
Co-committed-by: yp05327 <576951401@qq.com>
2025-04-11 06:06:16 +00:00
yp05327
f656c92cda Encode content to base64 in UpdateFileFn (#26)
Same to CreateFileFn

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/26
Reviewed-by: hiifong <i@hiif.ong>
Co-authored-by: yp05327 <576951401@qq.com>
Co-committed-by: yp05327 <576951401@qq.com>
2025-04-11 05:03:36 +00:00
yp05327
af0975d93f Add release and tags related funcions (#25)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/25
Reviewed-by: hiifong Mr <i@hiif.ong>
Co-authored-by: yp05327 <576951401@qq.com>
Co-committed-by: yp05327 <576951401@qq.com>
2025-04-10 08:22:09 +00:00
27 changed files with 1147 additions and 238 deletions

View File

@@ -11,7 +11,8 @@
"golang.go", "golang.go",
"stylelint.vscode-stylelint", "stylelint.vscode-stylelint",
"DavidAnson.vscode-markdownlint", "DavidAnson.vscode-markdownlint",
"github.copilot" "github.copilot",
"eamodio.gitlens"
] ]
} }
} }

3
.gitignore vendored
View File

@@ -1,7 +1,4 @@
.idea .idea
.vscode
gitea-mcp gitea-mcp
gitea-mcp.exe gitea-mcp.exe
*.log *.log

35
.vscode/mcp.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{
// 💡 Inputs are prompted on first server start, then stored securely by VS Code.
"inputs": [
{
"type": "promptString",
"id": "gitea-host",
"description": "Gitea Host",
"password": false
},
{
"type": "promptString",
"id": "gitea-token",
"description": "Gitea Access Token",
"password": true
},
{
"type": "promptString",
"id": "gitea-insecure",
"description": "Allow insecure connections (e.g., self-signed certificates)",
"default": "false"
}
],
"servers": {
"gitea": {
"type": "stdio",
"command": "gitea-mcp",
"args": ["-t", "stdio"],
"env": {
"GITEA_HOST": "${input:gitea-host}",
"GITEA_ACCESS_TOKEN": "${input:gitea-token}",
"GITEA_INSECURE": "${input:gitea-insecure}"
}
}
}
}

View File

@@ -20,6 +20,8 @@ RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=${VERSION}" -o gitea-
# Final stage # Final stage
FROM debian:bullseye-slim FROM debian:bullseye-slim
ENV GITEA_MODE=stdio
WORKDIR /app WORKDIR /app
# Install ca-certificates for HTTPS requests # Install ca-certificates for HTTPS requests
@@ -34,4 +36,4 @@ COPY --from=builder --chown=1000:1000 /app/gitea-mcp .
# Use the non-root user # Use the non-root user
USER gitea-mcp USER gitea-mcp
CMD ["/app/gitea-mcp", "-t", "stdio"] CMD ["/app/gitea-mcp"]

View File

@@ -6,6 +6,22 @@
[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders) [![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
## Table of Contents
- [Gitea MCP Server](#gitea-mcp-server)
- [Table of Contents](#table-of-contents)
- [What is Gitea?](#what-is-gitea)
- [What is MCP?](#what-is-mcp)
- [🚧 Installation](#-installation)
- [Usage with VS Code](#usage-with-vs-code)
- [📥 Download the official binary release](#-download-the-official-binary-release)
- [🔧 Build from Source](#-build-from-source)
- [📁 Add to PATH](#-add-to-path)
- [🚀 Usage](#-usage)
- [✅ Available Tools](#-available-tools)
- [🐛 Debugging](#-debugging)
- [🛠 Troubleshooting](#-troubleshooting)
## What is Gitea? ## What is Gitea?
Gitea is a community-managed lightweight code hosting solution written in Go. It is published under the MIT license. Gitea provides Git hosting including a repository viewer, issue tracking, pull requests, and more. Gitea is a community-managed lightweight code hosting solution written in Go. It is published under the MIT license. Gitea provides Git hosting including a repository viewer, issue tracking, pull requests, and more.
@@ -59,7 +75,7 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
### 📥 Download the official binary release ### 📥 Download the official binary release
You can download the official release from [here](https://gitea.com/gitea/gitea-mcp/releases). You can download the official release from [official Gitea MCP binary releases](https://gitea.com/gitea/gitea-mcp/releases).
### 🔧 Build from Source ### 🔧 Build from Source
@@ -129,6 +145,18 @@ To configure the MCP server for Gitea, add the following to your MCP configurati
} }
``` ```
- **http mode**
```json
{
"mcpServers": {
"gitea": {
"url": "http://localhost:8080/mcp"
}
}
}
```
**Default log path**: `$HOME/.gitea-mcp/gitea-mcp.log` **Default log path**: `$HOME/.gitea-mcp/gitea-mcp.log`
> [!NOTE] > [!NOTE]
@@ -146,14 +174,24 @@ list all my repositories
The Gitea MCP Server supports the following tools: The Gitea MCP Server supports the following tools:
| Tool | Scope | Description | | Tool | Scope | Description |
| :--------------------------: | :----------: | :---------------------------------------------------: | | :--------------------------: | :----------: | :------------------------------------------------------: |
| get_my_user_info | User | Get the information of the authenticated user | | get_my_user_info | User | Get the information of the authenticated user |
| get_user_orgs | User | Get organizations associated with the authenticated user |
| create_repo | Repository | Create a new repository | | create_repo | Repository | Create a new repository |
| fork_repo | Repository | Fork a repository | | fork_repo | Repository | Fork a repository |
| list_my_repos | Repository | List all repositories owned by the authenticated user | | list_my_repos | Repository | List all repositories owned by the authenticated user |
| create_branch | Branch | Create a new branch | | create_branch | Branch | Create a new branch |
| delete_branch | Branch | Delete a branch | | delete_branch | Branch | Delete a branch |
| list_branches | Branch | List all branches in a repository | | list_branches | Branch | List all branches in a repository |
| create_release | Release | Create a new release in a repository |
| delete_release | Release | Delete a release from a repository |
| get_release | Release | Get a release |
| get_latest_release | Release | Get the latest release in a repository |
| list_releases | Release | List all releases in a repository |
| create_tag | Tag | Create a new tag |
| delete_tag | Tag | Delete a tag |
| get_tag | Tag | Get a tag |
| list_tags | Tag | List all tags in a repository |
| list_repo_commits | Commit | List all commits in a repository | | list_repo_commits | Commit | List all commits in a repository |
| get_file_content | File | Get the content and metadata of a file | | get_file_content | File | Get the content and metadata of a file |
| create_file | File | Create a new file | | create_file | File | Create a new file |
@@ -163,6 +201,7 @@ The Gitea MCP Server supports the following tools:
| list_repo_issues | Issue | List all issues in a repository | | list_repo_issues | Issue | List all issues in a repository |
| create_issue | Issue | Create a new issue | | create_issue | Issue | Create a new issue |
| create_issue_comment | Issue | Create a comment on an issue | | create_issue_comment | Issue | Create a comment on an issue |
| edit_issue | Issue | Edit a issue |
| get_pull_request_by_index | Pull Request | Get a pull request by its index | | get_pull_request_by_index | Pull Request | Get a pull request by its index |
| list_repo_pull_requests | Pull Request | List all pull requests in a repository | | list_repo_pull_requests | Pull Request | List all pull requests in a repository |
| create_pull_request | Pull Request | Create a new pull request | | create_pull_request | Pull Request | Create a new pull request |

View File

@@ -6,6 +6,22 @@
[![在 VS Code 中使用 Docker 安装](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![在 VS Code Insiders 中使用 Docker 安装](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders) [![在 VS Code 中使用 Docker 安装](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![在 VS Code Insiders 中使用 Docker 安装](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
## 目录
- [Gitea MCP 服务器](#gitea-mcp-服务器)
- [目录](#目录)
- [什么是 Gitea](#什么是-gitea)
- [什么是 MCP](#什么是-mcp)
- [🚧 安装](#-安装)
- [在 VS Code 中使用](#在-vs-code-中使用)
- [📥 下载官方 Gitea MCP 二进制版本](#-下载官方-gitea-mcp-二进制版本)
- [🔧 从源代码构建](#-从源代码构建)
- [📁 添加到 PATH](#-添加到-path)
- [🚀 使用](#-使用)
- [✅ 可用工具](#-可用工具)
- [🐛 调试](#-调试)
- [🛠 疑难排解](#-疑难排解)
## 什么是 Gitea ## 什么是 Gitea
Gitea 是一个由社区管理的轻量级代码托管解决方案,使用 Go 语言编写。它以 MIT 许可证发布。Gitea 提供 Git 托管,包括仓库查看器、问题追踪、拉取请求等功能。 Gitea 是一个由社区管理的轻量级代码托管解决方案,使用 Go 语言编写。它以 MIT 许可证发布。Gitea 提供 Git 托管,包括仓库查看器、问题追踪、拉取请求等功能。
@@ -57,9 +73,9 @@ Model Context Protocol (MCP) 是一种协议,允许通过聊天界面整合各
} }
``` ```
### 📥 下载官方二进制版本 ### 📥 下载官方 Gitea MCP 二进制版本
您可以从[这里](https://gitea.com/gitea/gitea-mcp/releases)下载官方版本。 您可以从[官方 Gitea MCP 二进制版本](https://gitea.com/gitea/gitea-mcp/releases)下载官方版本。
### 🔧 从源代码构建 ### 🔧 从源代码构建
@@ -129,6 +145,18 @@ cp gitea-mcp /usr/local/bin/
} }
``` ```
- **http 模式**
```json
{
"mcpServers": {
"gitea": {
"url": "http://localhost:8080/mcp"
}
}
}
```
**默认日志路径**: `$HOME/.gitea-mcp/gitea-mcp.log` **默认日志路径**: `$HOME/.gitea-mcp/gitea-mcp.log`
> [!注意] > [!注意]
@@ -148,12 +176,22 @@ Gitea MCP 服务器支持以下工具:
| 工具 | 范围 | 描述 | | 工具 | 范围 | 描述 |
| :--------------------------: | :------: | :--------------------------: | | :--------------------------: | :------: | :--------------------------: |
| get_my_user_info | 用户 | 获取已认证用户的信息 | | get_my_user_info | 用户 | 获取已认证用户的信息 |
| get_user_orgs | 用户 | 获取已认证用户关联的组织 |
| create_repo | 仓库 | 创建一个新仓库 | | create_repo | 仓库 | 创建一个新仓库 |
| fork_repo | 仓库 | 复刻一个仓库 | | fork_repo | 仓库 | 复刻一个仓库 |
| list_my_repos | 仓库 | 列出已认证用户拥有的所有仓库 | | list_my_repos | 仓库 | 列出已认证用户拥有的所有仓库 |
| create_branch | 分支 | 创建一个新分支 | | create_branch | 分支 | 创建一个新分支 |
| delete_branch | 分支 | 删除一个分支 | | delete_branch | 分支 | 删除一个分支 |
| list_branches | 分支 | 列出仓库中的所有分支 | | list_branches | 分支 | 列出仓库中的所有分支 |
| create_release | 版本发布 | 创建一个新版本发布 |
| delete_release | 版本发布 | 删除一个版本发布 |
| get_release | 版本发布 | 获取一个版本发布 |
| get_latest_release | 版本发布 | 获取最新的版本发布 |
| list_releases | 版本发布 | 列出所有版本发布 |
| create_tag | 标签 | 创建一个新标签 |
| delete_tag | 标签 | 删除一个标签 |
| get_tag | 标签 | 获取一个标签 |
| list_tags | 标签 | 列出所有标签 |
| list_repo_commits | 提交 | 列出仓库中的所有提交 | | list_repo_commits | 提交 | 列出仓库中的所有提交 |
| get_file_content | 文件 | 获取文件的内容和元数据 | | get_file_content | 文件 | 获取文件的内容和元数据 |
| create_file | 文件 | 创建一个新文件 | | create_file | 文件 | 创建一个新文件 |
@@ -163,6 +201,7 @@ Gitea MCP 服务器支持以下工具:
| list_repo_issues | 问题 | 列出仓库中的所有问题 | | list_repo_issues | 问题 | 列出仓库中的所有问题 |
| create_issue | 问题 | 创建一个新问题 | | create_issue | 问题 | 创建一个新问题 |
| create_issue_comment | 问题 | 在问题上创建评论 | | create_issue_comment | 问题 | 在问题上创建评论 |
| edit_issue | 问题 | 编辑一个问题 |
| get_pull_request_by_index | 拉取请求 | 根据索引获取拉取请求 | | get_pull_request_by_index | 拉取请求 | 根据索引获取拉取请求 |
| list_repo_pull_requests | 拉取请求 | 列出仓库中的所有拉取请求 | | list_repo_pull_requests | 拉取请求 | 列出仓库中的所有拉取请求 |
| create_pull_request | 拉取请求 | 创建一个新拉取请求 | | create_pull_request | 拉取请求 | 创建一个新拉取请求 |

View File

@@ -6,6 +6,22 @@
[![在 VS Code 中使用 Docker 安裝](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![在 VS Code Insiders 中使用 Docker 安裝](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders) [![在 VS Code 中使用 Docker 安裝](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![在 VS Code Insiders 中使用 Docker 安裝](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
## 目錄
- [Gitea MCP 伺服器](#gitea-mcp-伺服器)
- [目錄](#目錄)
- [什麼是 Gitea](#什麼是-gitea)
- [什麼是 MCP](#什麼是-mcp)
- [🚧 安裝](#-安裝)
- [在 VS Code 中使用](#在-vs-code-中使用)
- [📥 下載官方 Gitea MCP 二進位版本](#-下載官方-gitea-mcp-二進位版本)
- [🔧 從源代碼構建](#-從源代碼構建)
- [📁 添加到 PATH](#-添加到-path)
- [🚀 使用](#-使用)
- [✅ 可用工具](#-可用工具)
- [🐛 調試](#-調試)
- [🛠 疑難排解](#-疑難排解)
## 什麼是 Gitea ## 什麼是 Gitea
Gitea 是一個由社群管理的輕量級代碼託管解決方案,使用 Go 語言編寫。它以 MIT 許可證發布。Gitea 提供 Git 託管,包括倉庫查看器、問題追蹤、拉取請求等功能。 Gitea 是一個由社群管理的輕量級代碼託管解決方案,使用 Go 語言編寫。它以 MIT 許可證發布。Gitea 提供 Git 託管,包括倉庫查看器、問題追蹤、拉取請求等功能。
@@ -57,9 +73,9 @@ Model Context Protocol (MCP) 是一種協議,允許通過聊天界面整合各
} }
``` ```
### 📥 下載官方二進版本 ### 📥 下載官方 Gitea MCP 二進版本
您可以從[這裡](https://gitea.com/gitea/gitea-mcp/releases)下載官方版本。 您可以從[官方 Gitea MCP 二進位版本](https://gitea.com/gitea/gitea-mcp/releases)下載官方版本。
### 🔧 從源代碼構建 ### 🔧 從源代碼構建
@@ -129,11 +145,23 @@ cp gitea-mcp /usr/local/bin/
} }
``` ```
**默認日誌路徑**: `$HOME/.gitea-mcp/gitea-mcp.log` - **http 模式**
```json
{
"mcpServers": {
"gitea": {
"url": "http://localhost:8080/mcp"
}
}
}
```
**預設日誌路徑**: `$HOME/.gitea-mcp/gitea-mcp.log`
> [!注意] > [!注意]
> 您可以通過命令參數或環境變提供您的 Gitea 主機和訪問令牌。 > 您可以通過命令參數或環境變提供您的 Gitea 主機和訪問令牌。
> 命令參數具有最高優先 > 命令參數具有最高優先
一切設置完成後,請嘗試在您的 MCP 兼容聊天框中輸入以下內容: 一切設置完成後,請嘗試在您的 MCP 兼容聊天框中輸入以下內容:
@@ -148,12 +176,22 @@ Gitea MCP 伺服器支持以下工具:
| 工具 | 範圍 | 描述 | | 工具 | 範圍 | 描述 |
| :--------------------------: | :------: | :--------------------------: | | :--------------------------: | :------: | :--------------------------: |
| get_my_user_info | 用戶 | 獲取已認證用戶的信息 | | get_my_user_info | 用戶 | 獲取已認證用戶的信息 |
| get_user_orgs | 用戶 | 取得已認證用戶所屬組織 |
| create_repo | 倉庫 | 創建一個新倉庫 | | create_repo | 倉庫 | 創建一個新倉庫 |
| fork_repo | 倉庫 | 復刻一個倉庫 | | fork_repo | 倉庫 | 復刻一個倉庫 |
| list_my_repos | 倉庫 | 列出已認證用戶擁有的所有倉庫 | | list_my_repos | 倉庫 | 列出已認證用戶擁有的所有倉庫 |
| create_branch | 分支 | 創建一個新分支 | | create_branch | 分支 | 創建一個新分支 |
| delete_branch | 分支 | 刪除一個分支 | | delete_branch | 分支 | 刪除一個分支 |
| list_branches | 分支 | 列出倉庫中的所有分支 | | list_branches | 分支 | 列出倉庫中的所有分支 |
| create_release | 版本發布 | 創建一個新版本發布 |
| delete_release | 版本發布 | 刪除一個版本發布 |
| get_release | 版本發布 | 獲取一個版本發布 |
| get_latest_release | 版本發布 | 獲取最新的版本發布 |
| list_releases | 版本發布 | 列出所有版本發布 |
| create_tag | 標籤 | 創建一個新標籤 |
| delete_tag | 標籤 | 刪除一個標籤 |
| get_tag | 標籤 | 獲取一個標籤 |
| list_tags | 標籤 | 列出所有標籤 |
| list_repo_commits | 提交 | 列出倉庫中的所有提交 | | list_repo_commits | 提交 | 列出倉庫中的所有提交 |
| get_file_content | 文件 | 獲取文件的內容和元數據 | | get_file_content | 文件 | 獲取文件的內容和元數據 |
| create_file | 文件 | 創建一個新文件 | | create_file | 文件 | 創建一個新文件 |
@@ -163,6 +201,7 @@ Gitea MCP 伺服器支持以下工具:
| list_repo_issues | 問題 | 列出倉庫中的所有問題 | | list_repo_issues | 問題 | 列出倉庫中的所有問題 |
| create_issue | 問題 | 創建一個新問題 | | create_issue | 問題 | 創建一個新問題 |
| create_issue_comment | 問題 | 在問題上創建評論 | | create_issue_comment | 問題 | 在問題上創建評論 |
| edit_issue | 問題 | 編輯一個問題 |
| get_pull_request_by_index | 拉取請求 | 根據索引獲取拉取請求 | | get_pull_request_by_index | 拉取請求 | 根據索引獲取拉取請求 |
| list_repo_pull_requests | 拉取請求 | 列出倉庫中的所有拉取請求 | | list_repo_pull_requests | 拉取請求 | 列出倉庫中的所有拉取請求 |
| create_pull_request | 拉取請求 | 創建一個新拉取請求 | | create_pull_request | 拉取請求 | 創建一個新拉取請求 |
@@ -173,7 +212,7 @@ Gitea MCP 伺服器支持以下工具:
## 🐛 調試 ## 🐛 調試
要啟用調試模式,請在使用 sse 模式運行 Gitea MCP 伺服器時添加 `-d` 要啟用調試模式,請在使用 sse 模式運行 Gitea MCP 伺服器時添加 `-d` 標:
```sh ```sh
./gitea-mcp -t sse [--port 8080] --token <your personal access token> -d ./gitea-mcp -t sse [--port 8080] --token <your personal access token> -d

View File

@@ -11,26 +11,23 @@ import (
) )
var ( var (
transport string
host string host string
port int port int
token string token string
debug bool
) )
func init() { func init() {
flag.StringVar( flag.StringVar(
&transport, &flagPkg.Mode,
"t", "t",
"stdio", "stdio",
"Transport type (stdio or sse)", "Transport type (stdio, sse or http)",
) )
flag.StringVar( flag.StringVar(
&transport, &flagPkg.Mode,
"transport", "transport",
"stdio", "stdio",
"Transport type (stdio or sse)", "Transport type (stdio, sse or http)",
) )
flag.StringVar( flag.StringVar(
&host, &host,
@@ -42,7 +39,7 @@ func init() {
&port, &port,
"port", "port",
8080, 8080,
"sse port", "see or http port",
) )
flag.StringVar( flag.StringVar(
&token, &token,
@@ -51,16 +48,16 @@ func init() {
"Your personal access token", "Your personal access token",
) )
flag.BoolVar( flag.BoolVar(
&debug, &flagPkg.ReadOnly,
"d", "read-only",
true, false,
"debug mode", "Read-only mode",
) )
flag.BoolVar( flag.BoolVar(
&debug, &flagPkg.Debug,
"debug", "d",
true, false,
"debug mode", "debug mode (If -d flag is provided, debug mode will be enabled by default)",
) )
flag.BoolVar( flag.BoolVar(
&flagPkg.Insecure, &flagPkg.Insecure,
@@ -83,13 +80,16 @@ func init() {
flagPkg.Token = os.Getenv("GITEA_ACCESS_TOKEN") flagPkg.Token = os.Getenv("GITEA_ACCESS_TOKEN")
} }
flagPkg.Mode = transport if os.Getenv("GITEA_MODE") != "" {
flagPkg.Mode = os.Getenv("GITEA_MODE")
if debug {
flagPkg.Debug = debug
} }
if !debug {
flagPkg.Debug = os.Getenv("GITEA_DEBUG") == "true" if os.Getenv("GITEA_READONLY") == "true" {
flagPkg.ReadOnly = true
}
if os.Getenv("GITEA_DEBUG") == "true" {
flagPkg.Debug = true
} }
// Set insecure mode based on environment variable // Set insecure mode based on environment variable
@@ -98,9 +98,9 @@ func init() {
} }
} }
func Execute(version string) { func Execute() {
defer log.Default().Sync() defer log.Default().Sync()
if err := operation.Run(transport, version); err != nil { if err := operation.Run(); err != nil {
if err == context.Canceled { if err == context.Canceled {
log.Info("Server shutdown due to context cancellation") log.Info("Server shutdown due to context cancellation")
return return

9
go.mod
View File

@@ -4,19 +4,20 @@ go 1.24.0
require ( require (
code.gitea.io/sdk/gitea v0.21.0 code.gitea.io/sdk/gitea v0.21.0
github.com/mark3labs/mcp-go v0.18.0 github.com/mark3labs/mcp-go v0.30.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )
require ( require (
github.com/42wim/httpsig v1.2.2 // indirect github.com/42wim/httpsig v1.2.3 // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-fed/httpsig v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect
github.com/spf13/cast v1.8.0 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/crypto v0.38.0 // indirect
golang.org/x/sys v0.32.0 // indirect golang.org/x/sys v0.33.0 // indirect
) )

32
go.sum
View File

@@ -1,21 +1,33 @@
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4= code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA= code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
github.com/42wim/httpsig v1.2.2 h1:ofAYoHUNs/MJOLqQ8hIxeyz2QxOz8qdSVvp3PX/oPgA= github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
github.com/42wim/httpsig v1.2.2/go.mod h1:P/UYo7ytNBFwc+dg35IubuAUIs8zj5zzFIgUCEl55WY= github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/mark3labs/mcp-go v0.18.0 h1:YuhgIVjNlTG2ZOwmrkORWyPTp0dz1opPEqvsPtySXao= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/mark3labs/mcp-go v0.18.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mark3labs/mcp-go v0.30.0 h1:Taz7fiefkxY/l8jz1nA90V+WdM2eoMtlvwfWforVYbo=
github.com/mark3labs/mcp-go v0.30.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
@@ -29,18 +41,18 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -2,12 +2,17 @@ package main
import ( import (
"gitea.com/gitea/gitea-mcp/cmd" "gitea.com/gitea/gitea-mcp/cmd"
"gitea.com/gitea/gitea-mcp/pkg/flag"
) )
var ( var (
Version = "dev" Version = "dev"
) )
func main() { func init() {
cmd.Execute(Version) flag.Version = Version
}
func main() {
cmd.Execute()
} }

View File

@@ -6,18 +6,23 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/ptr"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
var Tool = tool.New()
const ( const (
GetIssueByIndexToolName = "get_issue_by_index" GetIssueByIndexToolName = "get_issue_by_index"
ListRepoIssuesToolName = "list_repo_issues" ListRepoIssuesToolName = "list_repo_issues"
CreateIssueToolName = "create_issue" CreateIssueToolName = "create_issue"
CreateIssueCommentToolName = "create_issue_comment" CreateIssueCommentToolName = "create_issue_comment"
EditIssueToolName = "edit_issue"
) )
var ( var (
@@ -55,26 +60,54 @@ var (
mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")), mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
mcp.WithString("body", mcp.Required(), mcp.Description("issue comment body")), mcp.WithString("body", mcp.Required(), mcp.Description("issue comment body")),
) )
EditIssueTool = mcp.NewTool(
EditIssueToolName,
mcp.WithDescription("edit issue"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
mcp.WithString("title", mcp.Description("issue title"), mcp.DefaultString("")),
mcp.WithString("body", mcp.Description("issue body content")),
mcp.WithArray("assignees", mcp.Description("usernames to assign to this issue"), mcp.Items(map[string]interface{}{"type": "string"})),
mcp.WithNumber("milestone", mcp.Description("milestone number")),
mcp.WithString("state", mcp.Description("issue state, one of open, closed, all")),
)
) )
func RegisterTool(s *server.MCPServer) { func init() {
s.AddTool(GetIssueByIndexTool, GetIssueByIndexFn) Tool.RegisterRead(server.ServerTool{
s.AddTool(ListRepoIssuesTool, ListRepoIssuesFn) Tool: GetIssueByIndexTool,
s.AddTool(CreateIssueTool, CreateIssueFn) Handler: GetIssueByIndexFn,
s.AddTool(CreateIssueCommentTool, CreateIssueCommentFn) })
Tool.RegisterRead(server.ServerTool{
Tool: ListRepoIssuesTool,
Handler: ListRepoIssuesFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: CreateIssueTool,
Handler: CreateIssueFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: CreateIssueCommentTool,
Handler: CreateIssueCommentFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: EditIssueTool,
Handler: EditIssueFn,
})
} }
func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetIssueByIndexFn") log.Debugf("Called GetIssueByIndexFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
index, ok := req.Params.Arguments["index"].(float64) index, ok := req.GetArguments()["index"].(float64)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("index is required")) return to.ErrorResult(fmt.Errorf("index is required"))
} }
@@ -88,23 +121,23 @@ func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListIssuesFn") log.Debugf("Called ListIssuesFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
state, ok := req.Params.Arguments["state"].(string) state, ok := req.GetArguments()["state"].(string)
if !ok { if !ok {
state = "all" state = "all"
} }
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }
@@ -124,19 +157,19 @@ func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateIssueFn") log.Debugf("Called CreateIssueFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
title, ok := req.Params.Arguments["title"].(string) title, ok := req.GetArguments()["title"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("title is required")) return to.ErrorResult(fmt.Errorf("title is required"))
} }
body, ok := req.Params.Arguments["body"].(string) body, ok := req.GetArguments()["body"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("body is required")) return to.ErrorResult(fmt.Errorf("body is required"))
} }
@@ -145,7 +178,7 @@ func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
Body: body, Body: body,
}) })
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/%v/issue err", owner, repo)) return to.ErrorResult(fmt.Errorf("create %v/%v/issue err: %v", owner, repo, err))
} }
return to.TextResult(issue) return to.TextResult(issue)
@@ -153,19 +186,19 @@ func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateIssueCommentFn") log.Debugf("Called CreateIssueCommentFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
index, ok := req.Params.Arguments["index"].(float64) index, ok := req.GetArguments()["index"].(float64)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("index is required")) return to.ErrorResult(fmt.Errorf("index is required"))
} }
body, ok := req.Params.Arguments["body"].(string) body, ok := req.GetArguments()["body"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("body is required")) return to.ErrorResult(fmt.Errorf("body is required"))
} }
@@ -174,8 +207,54 @@ func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
} }
issueComment, _, err := gitea.Client().CreateIssueComment(owner, repo, int64(index), opt) issueComment, _, err := gitea.Client().CreateIssueComment(owner, repo, int64(index), opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err", owner, repo, int64(index))) return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err: %v", owner, repo, int64(index), err))
} }
return to.TextResult(issueComment) return to.TextResult(issueComment)
} }
func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditIssueFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(fmt.Errorf("repo is required"))
}
index, ok := req.GetArguments()["index"].(float64)
if !ok {
return to.ErrorResult(fmt.Errorf("index is required"))
}
opt := gitea_sdk.EditIssueOption{}
title, ok := req.GetArguments()["title"].(string)
if ok {
opt.Title = title
}
body, ok := req.GetArguments()["body"].(string)
if ok {
opt.Body = ptr.To(body)
}
assignees, ok := req.GetArguments()["assignees"].([]string)
if ok {
opt.Assignees = assignees
}
milestone, ok := req.GetArguments()["milestone"].(float64)
if ok {
opt.Milestone = ptr.To(int64(milestone))
}
state, ok := req.GetArguments()["state"].(string)
if ok {
opt.State = ptr.To(gitea_sdk.StateType(state))
}
issue, _, err := gitea.Client().EditIssue(owner, repo, int64(index), opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
}
return to.TextResult(issue)
}

View File

@@ -15,35 +15,34 @@ import (
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
var ( var mcpServer *server.MCPServer
mcpServer *server.MCPServer
)
func RegisterTool(s *server.MCPServer) { func RegisterTool(s *server.MCPServer) {
// User Tool // User Tool
user.RegisterTool(s) s.AddTools(user.Tool.Tools()...)
// Repo Tool // Repo Tool
repo.RegisterTool(s) s.AddTools(repo.Tool.Tools()...)
// Issue Tool // Issue Tool
issue.RegisterTool(s) s.AddTools(issue.Tool.Tools()...)
// Pull Tool // Pull Tool
pull.RegisterTool(s) s.AddTools(pull.Tool.Tools()...)
// Search Tool // Search Tool
search.RegisterTool(s) s.AddTools(search.Tool.Tools()...)
// Version Tool // Version Tool
version.RegisterTool(s) s.AddTools(version.Tool.Tools()...)
s.DeleteTools("")
} }
func Run(transport, version string) error { func Run() error {
flag.Version = version mcpServer = newMCPServer(flag.Version)
mcpServer = newMCPServer(version)
RegisterTool(mcpServer) RegisterTool(mcpServer)
switch transport { switch flag.Mode {
case "stdio": case "stdio":
if err := server.ServeStdio(mcpServer); err != nil { if err := server.ServeStdio(mcpServer); err != nil {
return err return err
@@ -54,8 +53,14 @@ func Run(transport, version string) error {
if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil { if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
return err return err
} }
case "http":
httpServer := server.NewStreamableHTTPServer(mcpServer)
log.Infof("Gitea MCP HTTP server listening on :%d", flag.Port)
if err := httpServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
return err
}
default: default:
return fmt.Errorf("invalid transport type: %s. Must be 'stdio' or 'sse'", transport) return fmt.Errorf("invalid transport type: %s. Must be 'stdio', 'sse' or 'http'", flag.Mode)
} }
return nil return nil
} }
@@ -64,6 +69,7 @@ func newMCPServer(version string) *server.MCPServer {
return server.NewMCPServer( return server.NewMCPServer(
"Gitea MCP Server", "Gitea MCP Server",
version, version,
server.WithToolCapabilities(true),
server.WithLogging(), server.WithLogging(),
) )
} }

View File

@@ -7,12 +7,15 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
var Tool = tool.New()
const ( const (
GetPullRequestByIndexToolName = "get_pull_request_by_index" GetPullRequestByIndexToolName = "get_pull_request_by_index"
ListRepoPullRequestsToolName = "list_repo_pull_requests" ListRepoPullRequestsToolName = "list_repo_pull_requests"
@@ -52,23 +55,32 @@ var (
) )
) )
func RegisterTool(s *server.MCPServer) { func init() {
s.AddTool(GetPullRequestByIndexTool, GetPullRequestByIndexFn) Tool.RegisterRead(server.ServerTool{
s.AddTool(ListRepoPullRequestsTool, ListRepoPullRequestsFn) Tool: GetPullRequestByIndexTool,
s.AddTool(CreatePullRequestTool, CreatePullRequestFn) Handler: GetPullRequestByIndexFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: ListRepoPullRequestsTool,
Handler: ListRepoPullRequestsFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: CreatePullRequestTool,
Handler: CreatePullRequestFn,
})
} }
func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetPullRequestByIndexFn") log.Debugf("Called GetPullRequestByIndexFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
index, ok := req.Params.Arguments["index"].(float64) index, ok := req.GetArguments()["index"].(float64)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("index is required")) return to.ErrorResult(fmt.Errorf("index is required"))
} }
@@ -82,25 +94,25 @@ func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoPullRequests") log.Debugf("Called ListRepoPullRequests")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
state, _ := req.Params.Arguments["state"].(string) state, _ := req.GetArguments()["state"].(string)
sort, ok := req.Params.Arguments["sort"].(string) sort, ok := req.GetArguments()["sort"].(string)
if !ok { if !ok {
sort = "recentupdate" sort = "recentupdate"
} }
milestone, _ := req.Params.Arguments["milestone"].(float64) milestone, _ := req.GetArguments()["milestone"].(float64)
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }
@@ -123,27 +135,27 @@ func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreatePullRequestFn") log.Debugf("Called CreatePullRequestFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
title, ok := req.Params.Arguments["title"].(string) title, ok := req.GetArguments()["title"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("title is required")) return to.ErrorResult(fmt.Errorf("title is required"))
} }
body, ok := req.Params.Arguments["body"].(string) body, ok := req.GetArguments()["body"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("body is required")) return to.ErrorResult(fmt.Errorf("body is required"))
} }
head, ok := req.Params.Arguments["head"].(string) head, ok := req.GetArguments()["head"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("head is required")) return to.ErrorResult(fmt.Errorf("head is required"))
} }
base, ok := req.Params.Arguments["base"].(string) base, ok := req.GetArguments()["base"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("base is required")) return to.ErrorResult(fmt.Errorf("base is required"))
} }

View File

@@ -10,6 +10,7 @@ import (
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
) )
const ( const (
@@ -44,21 +45,36 @@ var (
) )
) )
func init() {
Tool.RegisterWrite(server.ServerTool{
Tool: CreateBranchTool,
Handler: CreateBranchFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: DeleteBranchTool,
Handler: DeleteBranchFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: ListBranchesTool,
Handler: ListBranchesFn,
})
}
func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateBranchFn") log.Debugf("Called CreateBranchFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
branch, ok := req.Params.Arguments["branch"].(string) branch, ok := req.GetArguments()["branch"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("branch is required")) return to.ErrorResult(fmt.Errorf("branch is required"))
} }
oldBranch, _ := req.Params.Arguments["old_branch"].(string) oldBranch, _ := req.GetArguments()["old_branch"].(string)
_, _, err := gitea.Client().CreateBranch(owner, repo, gitea_sdk.CreateBranchOption{ _, _, err := gitea.Client().CreateBranch(owner, repo, gitea_sdk.CreateBranchOption{
BranchName: branch, BranchName: branch,
@@ -73,15 +89,15 @@ func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteBranchFn") log.Debugf("Called DeleteBranchFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
branch, ok := req.Params.Arguments["branch"].(string) branch, ok := req.GetArguments()["branch"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("branch is required")) return to.ErrorResult(fmt.Errorf("branch is required"))
} }
@@ -95,11 +111,11 @@ func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListBranchesFn") log.Debugf("Called ListBranchesFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }

View File

@@ -10,14 +10,14 @@ import (
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
) )
const ( const (
ListRepoCommitsToolName = "list_repo_commits" ListRepoCommitsToolName = "list_repo_commits"
) )
var ( var ListRepoCommitsTool = mcp.NewTool(
ListRepoCommitsTool = mcp.NewTool(
ListRepoCommitsToolName, ListRepoCommitsToolName,
mcp.WithDescription("List repository commits"), mcp.WithDescription("List repository commits"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
@@ -27,28 +27,34 @@ var (
mcp.WithNumber("page", mcp.Required(), mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Required(), mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("page_size", mcp.Required(), mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)), mcp.WithNumber("page_size", mcp.Required(), mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)),
) )
)
func init() {
Tool.RegisterRead(server.ServerTool{
Tool: ListRepoCommitsTool,
Handler: ListRepoCommitsFn,
})
}
func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoCommitsFn") log.Debugf("Called ListRepoCommitsFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("page is required")) return to.ErrorResult(fmt.Errorf("page is required"))
} }
pageSize, ok := req.Params.Arguments["page_size"].(float64) pageSize, ok := req.GetArguments()["page_size"].(float64)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("page_size is required")) return to.ErrorResult(fmt.Errorf("page_size is required"))
} }
sha, _ := req.Params.Arguments["sha"].(string) sha, _ := req.GetArguments()["sha"].(string)
path, _ := req.Params.Arguments["path"].(string) path, _ := req.GetArguments()["path"].(string)
opt := gitea_sdk.ListCommitOptions{ opt := gitea_sdk.ListCommitOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: int(page),

View File

@@ -11,6 +11,7 @@ import (
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
) )
const ( const (
@@ -66,18 +67,37 @@ var (
) )
) )
func init() {
Tool.RegisterRead(server.ServerTool{
Tool: GetFileContentTool,
Handler: GetFileContentFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: CreateFileTool,
Handler: CreateFileFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: UpdateFileTool,
Handler: UpdateFileFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: DeleteFileTool,
Handler: DeleteFileFn,
})
}
func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetFileFn") log.Debugf("Called GetFileFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
ref, _ := req.Params.Arguments["ref"].(string) ref, _ := req.GetArguments()["ref"].(string)
filePath, ok := req.Params.Arguments["filePath"].(string) filePath, ok := req.GetArguments()["filePath"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("filePath is required")) return to.ErrorResult(fmt.Errorf("filePath is required"))
} }
@@ -90,21 +110,21 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateFileFn") log.Debugf("Called CreateFileFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
filePath, ok := req.Params.Arguments["filePath"].(string) filePath, ok := req.GetArguments()["filePath"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("filePath is required")) return to.ErrorResult(fmt.Errorf("filePath is required"))
} }
content, _ := req.Params.Arguments["content"].(string) content, _ := req.GetArguments()["content"].(string)
message, _ := req.Params.Arguments["message"].(string) message, _ := req.GetArguments()["message"].(string)
branchName, _ := req.Params.Arguments["branch_name"].(string) branchName, _ := req.GetArguments()["branch_name"].(string)
opt := gitea_sdk.CreateFileOptions{ opt := gitea_sdk.CreateFileOptions{
Content: base64.StdEncoding.EncodeToString([]byte(content)), Content: base64.StdEncoding.EncodeToString([]byte(content)),
FileOptions: gitea_sdk.FileOptions{ FileOptions: gitea_sdk.FileOptions{
@@ -122,29 +142,29 @@ func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UpdateFileFn") log.Debugf("Called UpdateFileFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
filePath, ok := req.Params.Arguments["filePath"].(string) filePath, ok := req.GetArguments()["filePath"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("filePath is required")) return to.ErrorResult(fmt.Errorf("filePath is required"))
} }
sha, ok := req.Params.Arguments["sha"].(string) sha, ok := req.GetArguments()["sha"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("sha is required")) return to.ErrorResult(fmt.Errorf("sha is required"))
} }
content, _ := req.Params.Arguments["content"].(string) content, _ := req.GetArguments()["content"].(string)
message, _ := req.Params.Arguments["message"].(string) message, _ := req.GetArguments()["message"].(string)
branchName, _ := req.Params.Arguments["branch_name"].(string) branchName, _ := req.GetArguments()["branch_name"].(string)
opt := gitea_sdk.UpdateFileOptions{ opt := gitea_sdk.UpdateFileOptions{
SHA: sha, SHA: sha,
Content: content, Content: base64.StdEncoding.EncodeToString([]byte(content)),
FileOptions: gitea_sdk.FileOptions{ FileOptions: gitea_sdk.FileOptions{
Message: message, Message: message,
BranchName: branchName, BranchName: branchName,
@@ -159,21 +179,21 @@ func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteFileFn") log.Debugf("Called DeleteFileFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
filePath, ok := req.Params.Arguments["filePath"].(string) filePath, ok := req.GetArguments()["filePath"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("filePath is required")) return to.ErrorResult(fmt.Errorf("filePath is required"))
} }
message, _ := req.Params.Arguments["message"].(string) message, _ := req.GetArguments()["message"].(string)
branchName, _ := req.Params.Arguments["branch_name"].(string) branchName, _ := req.GetArguments()["branch_name"].(string)
sha, ok := req.Params.Arguments["sha"].(string) sha, ok := req.GetArguments()["sha"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("sha is required")) return to.ErrorResult(fmt.Errorf("sha is required"))
} }

263
operation/repo/release.go Normal file
View File

@@ -0,0 +1,263 @@
package repo
import (
"context"
"fmt"
"time"
"gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/ptr"
"gitea.com/gitea/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
CreateReleaseToolName = "create_release"
DeleteReleaseToolName = "delete_release"
GetReleaseToolName = "get_release"
GetLatestReleaseToolName = "get_latest_release"
ListReleasesToolName = "list_releases"
)
var (
CreateReleaseTool = mcp.NewTool(
CreateReleaseToolName,
mcp.WithDescription("Create release"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")),
mcp.WithString("target", mcp.Required(), mcp.Description("target commitish")),
mcp.WithString("title", mcp.Required(), mcp.Description("release title")),
mcp.WithBoolean("is_draft", mcp.Description("Whether the release is draft"), mcp.DefaultBool(false)),
mcp.WithBoolean("is_pre_release", mcp.Description("Whether the release is pre-release"), mcp.DefaultBool(false)),
)
DeleteReleaseTool = mcp.NewTool(
DeleteReleaseToolName,
mcp.WithDescription("Delete release"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("id", mcp.Required(), mcp.Description("release id")),
)
GetReleaseTool = mcp.NewTool(
GetReleaseToolName,
mcp.WithDescription("Get release"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("id", mcp.Required(), mcp.Description("release id")),
)
GetLatestReleaseTool = mcp.NewTool(
GetLatestReleaseToolName,
mcp.WithDescription("Get latest release"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
ListReleasesTool = mcp.NewTool(
ListReleasesToolName,
mcp.WithDescription("List releases"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithBoolean("is_draft", mcp.Description("Whether the release is draft"), mcp.DefaultBool(false)),
mcp.WithBoolean("is_pre_release", mcp.Description("Whether the release is pre-release"), mcp.DefaultBool(false)),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(20), mcp.Min(1)),
)
)
func init() {
Tool.RegisterWrite(server.ServerTool{
Tool: CreateReleaseTool,
Handler: CreateReleaseFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: DeleteReleaseTool,
Handler: DeleteReleaseFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: GetReleaseTool,
Handler: GetReleaseFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: GetLatestReleaseTool,
Handler: GetLatestReleaseFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: ListReleasesTool,
Handler: ListReleasesFn,
})
}
// To avoid return too many tokens, we need to provide at least information as possible
// llm can call get release to get more information
type ListReleaseResult struct {
ID int64 `json:"id"`
TagName string `json:"tag_name"`
Target string `json:"target_commitish"`
Title string `json:"title"`
IsDraft bool `json:"draft"`
IsPrerelease bool `json:"prerelease"`
CreatedAt time.Time `json:"created_at"`
PublishedAt time.Time `json:"published_at"`
}
func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateReleasesFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
tagName, ok := req.GetArguments()["tag_name"].(string)
if !ok {
return nil, fmt.Errorf("tag_name is required")
}
target, ok := req.GetArguments()["target"].(string)
if !ok {
return nil, fmt.Errorf("target is required")
}
title, ok := req.GetArguments()["title"].(string)
if !ok {
return nil, fmt.Errorf("title is required")
}
isDraft, _ := req.GetArguments()["is_draft"].(bool)
isPreRelease, _ := req.GetArguments()["is_pre_release"].(bool)
_, _, err := gitea.Client().CreateRelease(owner, repo, gitea_sdk.CreateReleaseOption{
TagName: tagName,
Target: target,
Title: title,
IsDraft: isDraft,
IsPrerelease: isPreRelease,
})
if err != nil {
return nil, fmt.Errorf("create release error: %v", err)
}
return mcp.NewToolResultText("Release Created"), nil
}
func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteReleaseFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
id, ok := req.GetArguments()["id"].(float64)
if !ok {
return nil, fmt.Errorf("id is required")
}
_, err := gitea.Client().DeleteRelease(owner, repo, int64(id))
if err != nil {
return nil, fmt.Errorf("delete release error: %v", err)
}
return to.TextResult("Release deleted successfully")
}
func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetReleaseFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
id, ok := req.GetArguments()["id"].(float64)
if !ok {
return nil, fmt.Errorf("id is required")
}
release, _, err := gitea.Client().GetRelease(owner, repo, int64(id))
if err != nil {
return nil, fmt.Errorf("get release error: %v", err)
}
return to.TextResult(release)
}
func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetLatestReleaseFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
release, _, err := gitea.Client().GetLatestRelease(owner, repo)
if err != nil {
return nil, fmt.Errorf("get latest release error: %v", err)
}
return to.TextResult(release)
}
func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListReleasesFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
var pIsDraft *bool
isDraft, ok := req.GetArguments()["is_draft"].(bool)
if ok {
pIsDraft = ptr.To(isDraft)
}
var pIsPreRelease *bool
isPreRelease, ok := req.GetArguments()["is_pre_release"].(bool)
if ok {
pIsPreRelease = ptr.To(isPreRelease)
}
page, _ := req.GetArguments()["page"].(float64)
pageSize, _ := req.GetArguments()["pageSize"].(float64)
releases, _, err := gitea.Client().ListReleases(owner, repo, gitea_sdk.ListReleasesOptions{
ListOptions: gitea_sdk.ListOptions{
Page: int(page),
PageSize: int(pageSize),
},
IsDraft: pIsDraft,
IsPreRelease: pIsPreRelease,
})
if err != nil {
return nil, fmt.Errorf("list releases error: %v", err)
}
results := make([]ListReleaseResult, len(releases))
for _, release := range releases {
results = append(results, ListReleaseResult{
ID: release.ID,
TagName: release.TagName,
Target: release.Target,
Title: release.Title,
IsDraft: release.IsDraft,
IsPrerelease: release.IsPrerelease,
CreatedAt: release.CreatedAt,
PublishedAt: release.PublishedAt,
})
}
return to.TextResult(results)
}

View File

@@ -9,12 +9,15 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/ptr" "gitea.com/gitea/gitea-mcp/pkg/ptr"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
var Tool = tool.New()
const ( const (
CreateRepoToolName = "create_repo" CreateRepoToolName = "create_repo"
ForkRepoToolName = "fork_repo" ForkRepoToolName = "fork_repo"
@@ -54,6 +57,21 @@ var (
) )
) )
func init() {
Tool.RegisterWrite(server.ServerTool{
Tool: CreateRepoTool,
Handler: CreateRepoFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: ForkRepoTool,
Handler: ForkRepoFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: ListMyReposTool,
Handler: ListMyReposFn,
})
}
func RegisterTool(s *server.MCPServer) { func RegisterTool(s *server.MCPServer) {
s.AddTool(CreateRepoTool, CreateRepoFn) s.AddTool(CreateRepoTool, CreateRepoFn)
s.AddTool(ForkRepoTool, ForkRepoFn) s.AddTool(ForkRepoTool, ForkRepoFn)
@@ -70,25 +88,38 @@ func RegisterTool(s *server.MCPServer) {
s.AddTool(DeleteBranchTool, DeleteBranchFn) s.AddTool(DeleteBranchTool, DeleteBranchFn)
s.AddTool(ListBranchesTool, ListBranchesFn) s.AddTool(ListBranchesTool, ListBranchesFn)
// Release
s.AddTool(CreateReleaseTool, CreateReleaseFn)
s.AddTool(DeleteReleaseTool, DeleteReleaseFn)
s.AddTool(GetReleaseTool, GetReleaseFn)
s.AddTool(GetLatestReleaseTool, GetLatestReleaseFn)
s.AddTool(ListReleasesTool, ListReleasesFn)
// Tag
s.AddTool(CreateTagTool, CreateTagFn)
s.AddTool(DeleteTagTool, DeleteTagFn)
s.AddTool(GetTagTool, GetTagFn)
s.AddTool(ListTagsTool, ListTagsFn)
// Commit // Commit
s.AddTool(ListRepoCommitsTool, ListRepoCommitsFn) s.AddTool(ListRepoCommitsTool, ListRepoCommitsFn)
} }
func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateRepoFn") log.Debugf("Called CreateRepoFn")
name, ok := req.Params.Arguments["name"].(string) name, ok := req.GetArguments()["name"].(string)
if !ok { if !ok {
return to.ErrorResult(errors.New("repository name is required")) return to.ErrorResult(errors.New("repository name is required"))
} }
description, _ := req.Params.Arguments["description"].(string) description, _ := req.GetArguments()["description"].(string)
private, _ := req.Params.Arguments["private"].(bool) private, _ := req.GetArguments()["private"].(bool)
issueLabels, _ := req.Params.Arguments["issue_labels"].(string) issueLabels, _ := req.GetArguments()["issue_labels"].(string)
autoInit, _ := req.Params.Arguments["auto_init"].(bool) autoInit, _ := req.GetArguments()["auto_init"].(bool)
template, _ := req.Params.Arguments["template"].(bool) template, _ := req.GetArguments()["template"].(bool)
gitignores, _ := req.Params.Arguments["gitignores"].(string) gitignores, _ := req.GetArguments()["gitignores"].(string)
license, _ := req.Params.Arguments["license"].(string) license, _ := req.GetArguments()["license"].(string)
readme, _ := req.Params.Arguments["readme"].(string) readme, _ := req.GetArguments()["readme"].(string)
defaultBranch, _ := req.Params.Arguments["default_branch"].(string) defaultBranch, _ := req.GetArguments()["default_branch"].(string)
opt := gitea_sdk.CreateRepoOption{ opt := gitea_sdk.CreateRepoOption{
Name: name, Name: name,
@@ -111,20 +142,20 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ForkRepoFn") log.Debugf("Called ForkRepoFn")
user, ok := req.Params.Arguments["user"].(string) user, ok := req.GetArguments()["user"].(string)
if !ok { if !ok {
return to.ErrorResult(errors.New("user name is required")) return to.ErrorResult(errors.New("user name is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(errors.New("repository name is required")) return to.ErrorResult(errors.New("repository name is required"))
} }
organization, ok := req.Params.Arguments["organization"].(string) organization, ok := req.GetArguments()["organization"].(string)
organizationPtr := ptr.To(organization) organizationPtr := ptr.To(organization)
if !ok || organization == "" { if !ok || organization == "" {
organizationPtr = nil organizationPtr = nil
} }
name, ok := req.Params.Arguments["name"].(string) name, ok := req.GetArguments()["name"].(string)
namePtr := ptr.To(name) namePtr := ptr.To(name)
if !ok || name == "" { if !ok || name == "" {
namePtr = nil namePtr = nil
@@ -135,18 +166,18 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
} }
_, _, err := gitea.Client().CreateFork(user, repo, opt) _, _, err := gitea.Client().CreateFork(user, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("fork repository error %v", err)) return to.ErrorResult(fmt.Errorf("fork repository error: %v", err))
} }
return to.TextResult("Fork success") return to.TextResult("Fork success")
} }
func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListMyReposFn") log.Debugf("Called ListMyReposFn")
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }

195
operation/repo/tag.go Normal file
View File

@@ -0,0 +1,195 @@
package repo
import (
"context"
"fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
CreateTagToolName = "create_tag"
DeleteTagToolName = "delete_tag"
GetTagToolName = "get_tag"
ListTagsToolName = "list_tags"
)
var (
CreateTagTool = mcp.NewTool(
CreateTagToolName,
mcp.WithDescription("Create tag"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")),
mcp.WithString("target", mcp.Description("target commitish"), mcp.DefaultString("")),
mcp.WithString("message", mcp.Description("tag message"), mcp.DefaultString("")),
)
DeleteTagTool = mcp.NewTool(
DeleteTagToolName,
mcp.WithDescription("Delete tag"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")),
)
GetTagTool = mcp.NewTool(
GetTagToolName,
mcp.WithDescription("Get tag"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")),
)
ListTagsTool = mcp.NewTool(
ListTagsToolName,
mcp.WithDescription("List tags"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(20), mcp.Min(1)),
)
)
func init() {
Tool.RegisterWrite(server.ServerTool{
Tool: CreateTagTool,
Handler: CreateTagFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: DeleteTagTool,
Handler: DeleteTagFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: GetTagTool,
Handler: GetTagFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: ListTagsTool,
Handler: ListTagsFn,
})
}
// To avoid return too many tokens, we need to provide at least information as possible
// llm can call get tag to get more information
type ListTagResult struct {
ID string `json:"id"`
Name string `json:"name"`
Commit *gitea_sdk.CommitMeta `json:"commit"`
// message may be a long text, so we should not provide it here
}
func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateTagFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
tagName, ok := req.GetArguments()["tag_name"].(string)
if !ok {
return nil, fmt.Errorf("tag_name is required")
}
target, _ := req.GetArguments()["target"].(string)
message, _ := req.GetArguments()["message"].(string)
_, _, err := gitea.Client().CreateTag(owner, repo, gitea_sdk.CreateTagOption{
TagName: tagName,
Target: target,
Message: message,
})
if err != nil {
return nil, fmt.Errorf("create tag error: %v", err)
}
return mcp.NewToolResultText("Tag Created"), nil
}
func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteTagFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
tagName, ok := req.GetArguments()["tag_name"].(string)
if !ok {
return nil, fmt.Errorf("tag_name is required")
}
_, err := gitea.Client().DeleteTag(owner, repo, tagName)
if err != nil {
return nil, fmt.Errorf("delete tag error: %v", err)
}
return to.TextResult("Tag deleted")
}
func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetTagFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
tagName, ok := req.GetArguments()["tag_name"].(string)
if !ok {
return nil, fmt.Errorf("tag_name is required")
}
tag, _, err := gitea.Client().GetTag(owner, repo, tagName)
if err != nil {
return nil, fmt.Errorf("get tag error: %v", err)
}
return to.TextResult(tag)
}
func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListTagsFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
page, _ := req.GetArguments()["page"].(float64)
pageSize, _ := req.GetArguments()["pageSize"].(float64)
tags, _, err := gitea.Client().ListRepoTags(owner, repo, gitea_sdk.ListRepoTagsOptions{
ListOptions: gitea_sdk.ListOptions{
Page: int(page),
PageSize: int(pageSize),
},
})
if err != nil {
return nil, fmt.Errorf("list tags error: %v", err)
}
results := make([]ListTagResult, 0, len(tags))
for _, tag := range tags {
results = append(results, ListTagResult{
ID: tag.ID,
Name: tag.Name,
Commit: tag.Commit,
})
}
return to.TextResult(results)
}

View File

@@ -8,12 +8,15 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/ptr" "gitea.com/gitea/gitea-mcp/pkg/ptr"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
var Tool = tool.New()
const ( const (
SearchUsersToolName = "search_users" SearchUsersToolName = "search_users"
SearchOrgTeamsToolName = "search_org_teams" SearchOrgTeamsToolName = "search_org_teams"
@@ -55,23 +58,32 @@ var (
) )
) )
func RegisterTool(s *server.MCPServer) { func init() {
s.AddTool(SearchUsersTool, SearchUsersFn) Tool.RegisterRead(server.ServerTool{
s.AddTool(SearOrgTeamsTool, SearchOrgTeamsFn) Tool: SearchUsersTool,
s.AddTool(SearchReposTool, SearchReposFn) Handler: SearchUsersFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: SearOrgTeamsTool,
Handler: SearchOrgTeamsFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: SearchReposTool,
Handler: SearchReposFn,
})
} }
func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called SearchUsersFn") log.Debugf("Called SearchUsersFn")
keyword, ok := req.Params.Arguments["keyword"].(string) keyword, ok := req.GetArguments()["keyword"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("keyword is required")) return to.ErrorResult(fmt.Errorf("keyword is required"))
} }
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }
@@ -91,20 +103,20 @@ func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called SearchOrgTeamsFn") log.Debugf("Called SearchOrgTeamsFn")
org, ok := req.Params.Arguments["org"].(string) org, ok := req.GetArguments()["org"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("organization is required")) return to.ErrorResult(fmt.Errorf("organization is required"))
} }
query, ok := req.Params.Arguments["query"].(string) query, ok := req.GetArguments()["query"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("query is required")) return to.ErrorResult(fmt.Errorf("query is required"))
} }
includeDescription, _ := req.Params.Arguments["includeDescription"].(bool) includeDescription, _ := req.GetArguments()["includeDescription"].(bool)
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }
@@ -125,22 +137,30 @@ func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called SearchReposFn") log.Debugf("Called SearchReposFn")
keyword, ok := req.Params.Arguments["keyword"].(string) keyword, ok := req.GetArguments()["keyword"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("keyword is required")) return to.ErrorResult(fmt.Errorf("keyword is required"))
} }
keywordIsTopic, _ := req.Params.Arguments["keywordIsTopic"].(bool) keywordIsTopic, _ := req.GetArguments()["keywordIsTopic"].(bool)
keywordInDescription, _ := req.Params.Arguments["keywordInDescription"].(bool) keywordInDescription, _ := req.GetArguments()["keywordInDescription"].(bool)
ownerID, _ := req.Params.Arguments["ownerID"].(float64) ownerID, _ := req.GetArguments()["ownerID"].(float64)
isPrivate, _ := req.Params.Arguments["isPrivate"].(bool) var pIsPrivate *bool
isArchived, _ := req.Params.Arguments["isArchived"].(bool) isPrivate, ok := req.GetArguments()["isPrivate"].(bool)
sort, _ := req.Params.Arguments["sort"].(string) if ok {
order, _ := req.Params.Arguments["order"].(string) pIsPrivate = ptr.To(isPrivate)
page, ok := req.Params.Arguments["page"].(float64) }
var pIsArchived *bool
isArchived, ok := req.GetArguments()["isArchived"].(bool)
if ok {
pIsArchived = ptr.To(isArchived)
}
sort, _ := req.GetArguments()["sort"].(string)
order, _ := req.GetArguments()["order"].(string)
page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }
@@ -149,8 +169,8 @@ func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
KeywordIsTopic: keywordIsTopic, KeywordIsTopic: keywordIsTopic,
KeywordInDescription: keywordInDescription, KeywordInDescription: keywordInDescription,
OwnerID: int64(ownerID), OwnerID: int64(ownerID),
IsPrivate: ptr.To(isPrivate), IsPrivate: pIsPrivate,
IsArchived: ptr.To(isArchived), IsArchived: pIsArchived,
Sort: sort, Sort: sort,
Order: order, Order: order,
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{

View File

@@ -7,24 +7,44 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
const ( const (
GetMyUserInfoToolName = "get_my_user_info" GetMyUserInfoToolName = "get_my_user_info"
GetUserOrgsToolName = "get_user_orgs"
) )
var Tool = tool.New()
var ( var (
GetMyUserInfoTool = mcp.NewTool( GetMyUserInfoTool = mcp.NewTool(
GetMyUserInfoToolName, GetMyUserInfoToolName,
mcp.WithDescription("Get my user info"), mcp.WithDescription("Get my user info"),
) )
GetUserOrgsTool = mcp.NewTool(
GetUserOrgsToolName,
mcp.WithDescription("Get organizations associated with the authenticated user"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
) )
func RegisterTool(s *server.MCPServer) { func init() {
s.AddTool(GetMyUserInfoTool, GetUserInfoFn) Tool.RegisterRead(server.ServerTool{
Tool: GetMyUserInfoTool,
Handler: GetUserInfoFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: GetUserOrgsTool,
Handler: GetUserOrgsFn,
})
} }
func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -36,3 +56,27 @@ func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
return to.TextResult(user) return to.TextResult(user)
} }
func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetUserOrgsFn")
page, ok := req.GetArguments()["page"].(float64)
if !ok || page < 1 {
page = 1
}
pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok || pageSize < 1 {
pageSize = 100
}
opt := gitea_sdk.ListOrgsOptions{
ListOptions: gitea_sdk.ListOptions{
Page: int(page),
PageSize: int(pageSize),
},
}
orgs, _, err := gitea.Client().ListMyOrgs(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("get user orgs err: %v", err))
}
return to.TextResult(orgs)
}

View File

@@ -7,11 +7,14 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/flag" "gitea.com/gitea/gitea-mcp/pkg/flag"
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
var Tool = tool.New()
const ( const (
GetGiteaMCPServerVersion = "get_gitea_mcp_server_version" GetGiteaMCPServerVersion = "get_gitea_mcp_server_version"
) )
@@ -23,8 +26,11 @@ var (
) )
) )
func RegisterTool(s *server.MCPServer) { func init() {
s.AddTool(GetGiteaMCPServerVersionTool, GetGiteaMCPServerVersionFn) Tool.RegisterRead(server.ServerTool{
Tool: GetGiteaMCPServerVersionTool,
Handler: GetGiteaMCPServerVersionFn,
})
} }
func GetGiteaMCPServerVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetGiteaMCPServerVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {

View File

@@ -8,5 +8,6 @@ var (
Mode string Mode string
Insecure bool Insecure bool
ReadOnly bool
Debug bool Debug bool
) )

View File

@@ -35,6 +35,9 @@ func Client() *gitea.Client {
} }
opts = append(opts, gitea.SetHTTPClient(httpClient)) opts = append(opts, gitea.SetHTTPClient(httpClient))
} }
if flag.Debug {
opts = append(opts, gitea.SetDebugMode())
}
client, err = gitea.NewClient(flag.Host, opts...) client, err = gitea.NewClient(flag.Host, opts...)
if err != nil { if err != nil {
log.Fatalf("create gitea client err: %v", err) log.Fatalf("create gitea client err: %v", err)

View File

@@ -39,7 +39,7 @@ func Default() *zap.Logger {
MaxAge: 30, MaxAge: 30,
})) }))
if flag.Mode == "sse" { if flag.Mode == "http" || flag.Mode == "sse" {
wss = append(wss, zapcore.AddSync(os.Stdout)) wss = append(wss, zapcore.AddSync(os.Stdout))
} }

37
pkg/tool/tool.go Normal file
View File

@@ -0,0 +1,37 @@
package tool
import (
"gitea.com/gitea/gitea-mcp/pkg/flag"
"github.com/mark3labs/mcp-go/server"
)
type Tool struct {
write []server.ServerTool
read []server.ServerTool
}
func New() *Tool {
return &Tool{
write: make([]server.ServerTool, 100),
read: make([]server.ServerTool, 100),
}
}
func (t *Tool) RegisterWrite(s server.ServerTool) {
t.write = append(t.write, s)
}
func (t *Tool) RegisterRead(s server.ServerTool) {
t.read = append(t.read, s)
}
func (t *Tool) Tools() []server.ServerTool {
tools := make([]server.ServerTool, 0, len(t.write)+len(t.read))
if flag.ReadOnly {
tools = append(tools, t.read...)
return tools
}
tools = append(tools, t.write...)
tools = append(tools, t.read...)
return tools
}