Add release and tags related funcions #25

Merged
hiifong merged 7 commits from yp05327/gitea-mcp:add-release into main 2025-04-10 08:22:09 +00:00

View File

@@ -154,6 +154,15 @@ The Gitea MCP Server supports the following tools:
| create_branch | Branch | Create a new branch |
| delete_branch | Branch | Delete a branch |
| 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 |
| get_file_content | File | Get the content and metadata of a file |
| create_file | File | Create a new file |

View File

@@ -154,6 +154,15 @@ Gitea MCP 服务器支持以下工具:
| create_branch | 分支 | 创建一个新分支 |
| delete_branch | 分支 | 删除一个分支 |
| list_branches | 分支 | 列出仓库中的所有分支 |
| create_release | 版本发布 | 创建一个新版本发布 |
| delete_release | 版本发布 | 删除一个版本发布 |
| get_release | 版本发布 | 获取一个版本发布 |
| get_latest_release | 版本发布 | 获取最新的版本发布 |
| list_releases | 版本发布 | 列出所有版本发布 |
| create_tag | 标签 | 创建一个新标签 |
| delete_tag | 标签 | 删除一个标签 |
| get_tag | 标签 | 获取一个标签 |
| list_tags | 标签 | 列出所有标签 |
| list_repo_commits | 提交 | 列出仓库中的所有提交 |
| get_file_content | 文件 | 获取文件的内容和元数据 |
| create_file | 文件 | 创建一个新文件 |

View File

@@ -144,7 +144,6 @@ cp gitea-mcp /usr/local/bin/
## ✅ 可用工具
Gitea MCP 伺服器支持以下工具:
| 工具 | 範圍 | 描述 |
| :--------------------------: | :------: | :--------------------------: |
| get_my_user_info | 用戶 | 獲取已認證用戶的信息 |
@@ -154,6 +153,15 @@ Gitea MCP 伺服器支持以下工具:
| create_branch | 分支 | 創建一個新分支 |
| delete_branch | 分支 | 刪除一個分支 |
| list_branches | 分支 | 列出倉庫中的所有分支 |
| create_release | 版本發布 | 創建一個新版本發布 |
| delete_release | 版本發布 | 刪除一個版本發布 |
| get_release | 版本發布 | 獲取一個版本發布 |
| get_latest_release | 版本發布 | 獲取最新的版本發布 |
| list_releases | 版本發布 | 列出所有版本發布 |
| create_tag | 標籤 | 創建一個新標籤 |
| delete_tag | 標籤 | 刪除一個標籤 |
| get_tag | 標籤 | 獲取一個標籤 |
| list_tags | 標籤 | 列出所有標籤 |
| list_repo_commits | 提交 | 列出倉庫中的所有提交 |
| get_file_content | 文件 | 獲取文件的內容和元數據 |
| create_file | 文件 | 創建一個新文件 |

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

@@ -0,0 +1,230 @@
package repo
import (
"context"
"fmt"
"time"
gitea_sdk "code.gitea.io/sdk/gitea"
"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"
"github.com/mark3labs/mcp-go/mcp"
)
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)),
)
)
// 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.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
tagName, ok := req.Params.Arguments["tag_name"].(string)
if !ok {
return nil, fmt.Errorf("tag_name is required")
}
target, ok := req.Params.Arguments["target"].(string)
if !ok {
return nil, fmt.Errorf("target is required")
}
title, ok := req.Params.Arguments["title"].(string)
if !ok {
return nil, fmt.Errorf("title is required")
}
isDraft, _ := req.Params.Arguments["is_draft"].(bool)
isPreRelease, _ := req.Params.Arguments["is_pre_release"].(bool)
_, _, err := gitea.Client().CreateRelease(owner, repo, gitea_sdk.CreateReleaseOption{
TagName: tagName,
yp05327 marked this conversation as resolved
Review

It should be asserted here as float64. All assertions of type Number must be float64

It should be asserted here as float64. All assertions of type Number must be float64
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.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
id, ok := req.Params.Arguments["id"].(float64)
yp05327 marked this conversation as resolved Outdated

as above

as above
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.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
id, ok := req.Params.Arguments["id"].(float64)
if !ok {
return nil, fmt.Errorf("id is required")
}
yp05327 marked this conversation as resolved Outdated

as above

as above
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)
}
yp05327 marked this conversation as resolved Outdated

Here you should use ptr.To

Here you should use ptr.To
d650f8c7ee8cdec99679b88e933b94256da9be5f
func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetLatestReleaseFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.Params.Arguments["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.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
isDraft, _ := req.Params.Arguments["is_draft"].(bool)
isPreRelease, _ := req.Params.Arguments["is_pre_release"].(bool)
page, _ := req.Params.Arguments["page"].(float64)
pageSize, _ := req.Params.Arguments["pageSize"].(float64)
releases, _, err := gitea.Client().ListReleases(owner, repo, gitea_sdk.ListReleasesOptions{
ListOptions: gitea_sdk.ListOptions{
Page: int(page),
PageSize: int(pageSize),
},
IsDraft: ptr.To(isDraft),
IsPreRelease: ptr.To(isPreRelease),
})
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

@@ -70,6 +70,19 @@ func RegisterTool(s *server.MCPServer) {
s.AddTool(DeleteBranchTool, DeleteBranchFn)
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
s.AddTool(ListRepoCommitsTool, ListRepoCommitsFn)
}

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

@@ -0,0 +1,174 @@
package repo
import (
"context"
"fmt"
gitea_sdk "code.gitea.io/sdk/gitea"
"gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/to"
"github.com/mark3labs/mcp-go/mcp"
)
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)),
)
)
// 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.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
tagName, ok := req.Params.Arguments["tag_name"].(string)
if !ok {
return nil, fmt.Errorf("tag_name is required")
}
target, _ := req.Params.Arguments["target"].(string)
message, _ := req.Params.Arguments["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.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
tagName, ok := req.Params.Arguments["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.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
tagName, ok := req.Params.Arguments["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.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
}
page, _ := req.Params.Arguments["page"].(float64)
pageSize, _ := req.Params.Arguments["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)
}