This commit is contained in:
150
operation/repo/branch.go
Normal file
150
operation/repo/branch.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/annotation"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/params"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/to"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
const (
|
||||
CreateBranchToolName = "create_branch"
|
||||
DeleteBranchToolName = "delete_branch"
|
||||
ListBranchesToolName = "list_branches"
|
||||
)
|
||||
|
||||
var (
|
||||
CreateBranchTool = mcp.NewTool(
|
||||
CreateBranchToolName,
|
||||
mcp.WithToolAnnotation(annotation.Write("Create a new branch")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("branch", mcp.Required()),
|
||||
mcp.WithString("old_branch", mcp.Description("source branch (default: repo default)")),
|
||||
)
|
||||
|
||||
DeleteBranchTool = mcp.NewTool(
|
||||
DeleteBranchToolName,
|
||||
mcp.WithToolAnnotation(annotation.Destructive("Delete a branch")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("branch", mcp.Required()),
|
||||
)
|
||||
|
||||
ListBranchesTool = mcp.NewTool(
|
||||
ListBranchesToolName,
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("List repository branches")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)),
|
||||
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
|
||||
)
|
||||
)
|
||||
|
||||
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) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
branch, err := params.GetString(args, "branch")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
oldBranch, _ := args["old_branch"].(string)
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
_, _, err = client.Repositories.CreateBranch(ctx, owner, repo, gitea_sdk.CreateBranchOption{
|
||||
BranchName: branch,
|
||||
OldBranchName: oldBranch,
|
||||
})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("create branch error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult("Branch Created")
|
||||
}
|
||||
|
||||
func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
branch, err := params.GetString(args, "branch")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
_, _, err = client.Repositories.DeleteRepoBranch(ctx, owner, repo, branch)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("delete branch error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult("Branch Deleted")
|
||||
}
|
||||
|
||||
func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
page, pageSize := params.GetPagination(args, 30)
|
||||
opt := gitea_sdk.ListRepoBranchesOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
branches, _, err := client.Repositories.ListRepoBranches(ctx, owner, repo, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list branches error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult(slimBranches(branches))
|
||||
}
|
||||
109
operation/repo/commit.go
Normal file
109
operation/repo/commit.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/annotation"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/params"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/to"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
const (
|
||||
ListRepoCommitsToolName = "list_commits"
|
||||
GetCommitToolName = "get_commit"
|
||||
)
|
||||
|
||||
var (
|
||||
ListRepoCommitsTool = mcp.NewTool(
|
||||
ListRepoCommitsToolName,
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("List repository commits")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("sha", mcp.Description("starting SHA or branch")),
|
||||
mcp.WithString("path", mcp.Description("only commits touching this path")),
|
||||
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)),
|
||||
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)),
|
||||
)
|
||||
|
||||
GetCommitTool = mcp.NewTool(
|
||||
GetCommitToolName,
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("Get commit details")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("sha", mcp.Required()),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: ListRepoCommitsTool,
|
||||
Handler: ListRepoCommitsFn,
|
||||
})
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: GetCommitTool,
|
||||
Handler: GetCommitFn,
|
||||
})
|
||||
}
|
||||
|
||||
func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
page, pageSize := params.GetPagination(args, 30)
|
||||
sha, _ := args["sha"].(string)
|
||||
path, _ := args["path"].(string)
|
||||
opt := gitea_sdk.ListCommitOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
SHA: sha,
|
||||
Path: path,
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
commits, _, err := client.Repositories.ListRepoCommits(ctx, owner, repo, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err))
|
||||
}
|
||||
return to.TextResult(slimCommits(commits))
|
||||
}
|
||||
|
||||
func GetCommitFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
sha, err := params.GetString(args, "sha")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
commit, _, err := client.Repositories.GetSingleCommit(ctx, owner, repo, sha)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get commit %v err: %v", sha, err))
|
||||
}
|
||||
return to.TextResult(slimCommit(commit))
|
||||
}
|
||||
283
operation/repo/file.go
Normal file
283
operation/repo/file.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/annotation"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/params"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/to"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
const (
|
||||
GetFileToolName = "get_file_contents"
|
||||
GetDirToolName = "get_dir_contents"
|
||||
CreateOrUpdateFileToolName = "create_or_update_file"
|
||||
DeleteFileToolName = "delete_file"
|
||||
)
|
||||
|
||||
var (
|
||||
GetFileContentTool = mcp.NewTool(
|
||||
GetFileToolName,
|
||||
mcp.WithDescription("Get file content and metadata"),
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("Get file content")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("ref", mcp.Required(), mcp.Description("branch, tag, or commit SHA")),
|
||||
mcp.WithString("path", mcp.Required()),
|
||||
mcp.WithBoolean("withLines", mcp.Description("return numbered lines")),
|
||||
)
|
||||
|
||||
GetDirContentTool = mcp.NewTool(
|
||||
GetDirToolName,
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("Get directory contents")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("ref", mcp.Required(), mcp.Description("branch, tag, or commit SHA")),
|
||||
mcp.WithString("path", mcp.Required()),
|
||||
)
|
||||
|
||||
CreateOrUpdateFileTool = mcp.NewTool(
|
||||
CreateOrUpdateFileToolName,
|
||||
mcp.WithDescription("Create or update a file (provide sha to update an existing file)."),
|
||||
mcp.WithToolAnnotation(annotation.Write("Create or update a file")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("path", mcp.Required()),
|
||||
mcp.WithString("content", mcp.Required()),
|
||||
mcp.WithString("message", mcp.Required(), mcp.Description("commit message")),
|
||||
mcp.WithString("branch_name", mcp.Required()),
|
||||
mcp.WithString("sha", mcp.Description("existing file SHA (omit to create)")),
|
||||
mcp.WithString("new_branch_name", mcp.Description("new branch (create only)")),
|
||||
)
|
||||
|
||||
DeleteFileTool = mcp.NewTool(
|
||||
DeleteFileToolName,
|
||||
mcp.WithToolAnnotation(annotation.Destructive("Delete a file")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("path", mcp.Required()),
|
||||
mcp.WithString("message", mcp.Required(), mcp.Description("commit message")),
|
||||
mcp.WithString("branch_name", mcp.Required()),
|
||||
mcp.WithString("sha", mcp.Required()),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: GetFileContentTool,
|
||||
Handler: GetFileContentFn,
|
||||
})
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: GetDirContentTool,
|
||||
Handler: GetDirContentFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: CreateOrUpdateFileTool,
|
||||
Handler: CreateOrUpdateFileFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: DeleteFileTool,
|
||||
Handler: DeleteFileFn,
|
||||
})
|
||||
}
|
||||
|
||||
type ContentLine struct {
|
||||
LineNumber int `json:"line"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
ref, _ := args["ref"].(string)
|
||||
filePath, err := params.GetString(args, "path")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
content, _, err := client.Repositories.GetContents(ctx, owner, repo, ref, filePath)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get file err: %v", err))
|
||||
}
|
||||
withLines, _ := args["withLines"].(bool)
|
||||
if withLines {
|
||||
rawContent, err := base64.StdEncoding.DecodeString(*content.Content)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("decode base64 content err: %v", err))
|
||||
}
|
||||
|
||||
contentLines := make([]ContentLine, 0)
|
||||
line := 0
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(rawContent))
|
||||
|
||||
for scanner.Scan() {
|
||||
line++
|
||||
|
||||
contentLines = append(contentLines, ContentLine{
|
||||
LineNumber: line,
|
||||
Content: scanner.Text(),
|
||||
})
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("scan content err: %v", err))
|
||||
}
|
||||
|
||||
// remove the last blank line if exists
|
||||
// git does not consider the last line as a new line
|
||||
if len(contentLines) > 0 && contentLines[len(contentLines)-1].Content == "" {
|
||||
contentLines = contentLines[:len(contentLines)-1]
|
||||
}
|
||||
|
||||
contentBytes, err := json.MarshalIndent(contentLines, "", " ")
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("marshal content lines err: %v", err))
|
||||
}
|
||||
contentStr := string(contentBytes)
|
||||
content.Content = &contentStr
|
||||
}
|
||||
return to.TextResult(slimContents(content))
|
||||
}
|
||||
|
||||
func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
ref, _ := args["ref"].(string)
|
||||
filePath, err := params.GetString(args, "path")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
content, _, err := client.Repositories.ListContents(ctx, owner, repo, ref, filePath)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get dir content err: %v", err))
|
||||
}
|
||||
return to.TextResult(slimDirEntries(content))
|
||||
}
|
||||
|
||||
func CreateOrUpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
filePath, err := params.GetString(args, "path")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
content, _ := args["content"].(string)
|
||||
message, _ := args["message"].(string)
|
||||
branchName, _ := args["branch_name"].(string)
|
||||
sha, _ := args["sha"].(string)
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
|
||||
if sha != "" {
|
||||
// Update existing file
|
||||
opt := gitea_sdk.UpdateFileOptions{
|
||||
SHA: sha,
|
||||
Content: base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
FileOptions: gitea_sdk.FileOptions{
|
||||
Message: message,
|
||||
BranchName: branchName,
|
||||
},
|
||||
}
|
||||
_, _, err = client.Repositories.UpdateFile(ctx, owner, repo, filePath, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("update file err: %v", err))
|
||||
}
|
||||
return to.TextResult("Update file success")
|
||||
}
|
||||
|
||||
// Create new file
|
||||
opt := gitea_sdk.CreateFileOptions{
|
||||
Content: base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
FileOptions: gitea_sdk.FileOptions{
|
||||
Message: message,
|
||||
BranchName: branchName,
|
||||
},
|
||||
}
|
||||
if newBranch, ok := args["new_branch_name"].(string); ok && newBranch != "" {
|
||||
opt.NewBranchName = newBranch
|
||||
}
|
||||
_, _, err = client.Repositories.CreateFile(ctx, owner, repo, filePath, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("create file err: %v", err))
|
||||
}
|
||||
return to.TextResult("Create file success")
|
||||
}
|
||||
|
||||
func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
filePath, err := params.GetString(args, "path")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
message, _ := args["message"].(string)
|
||||
branchName, _ := args["branch_name"].(string)
|
||||
sha, err := params.GetString(args, "sha")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
opt := gitea_sdk.DeleteFileOptions{
|
||||
FileOptions: gitea_sdk.FileOptions{
|
||||
Message: message,
|
||||
BranchName: branchName,
|
||||
},
|
||||
SHA: sha,
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
_, err = client.Repositories.DeleteFile(ctx, owner, repo, filePath, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("delete file err: %v", err))
|
||||
}
|
||||
return to.TextResult("Delete file success")
|
||||
}
|
||||
249
operation/repo/release.go
Normal file
249
operation/repo/release.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/annotation"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/params"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/to"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
"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.WithToolAnnotation(annotation.Write("Create a release")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("tag_name", mcp.Required()),
|
||||
mcp.WithString("target", mcp.Required(), mcp.Description("commitish")),
|
||||
mcp.WithString("title", mcp.Required()),
|
||||
mcp.WithBoolean("is_draft"),
|
||||
mcp.WithBoolean("is_pre_release"),
|
||||
mcp.WithString("body"),
|
||||
)
|
||||
|
||||
DeleteReleaseTool = mcp.NewTool(
|
||||
DeleteReleaseToolName,
|
||||
mcp.WithToolAnnotation(annotation.Destructive("Delete a release")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithNumber("id", mcp.Required()),
|
||||
)
|
||||
|
||||
GetReleaseTool = mcp.NewTool(
|
||||
GetReleaseToolName,
|
||||
mcp.WithDescription("Get a release by ID"),
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("Get release details")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithNumber("id", mcp.Required()),
|
||||
)
|
||||
|
||||
GetLatestReleaseTool = mcp.NewTool(
|
||||
GetLatestReleaseToolName,
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("Get latest release")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
)
|
||||
|
||||
ListReleasesTool = mcp.NewTool(
|
||||
ListReleasesToolName,
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("List releases")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithBoolean("is_draft"),
|
||||
mcp.WithBoolean("is_pre_release"),
|
||||
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)),
|
||||
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), 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,
|
||||
})
|
||||
}
|
||||
|
||||
func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
tagName, err := params.GetString(args, "tag_name")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
target, err := params.GetString(args, "target")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
title, err := params.GetString(args, "title")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
isDraft, _ := args["is_draft"].(bool)
|
||||
isPreRelease, _ := args["is_pre_release"].(bool)
|
||||
body, _ := args["body"].(string)
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
_, _, err = client.Releases.CreateRelease(ctx, owner, repo, gitea_sdk.CreateReleaseOption{
|
||||
TagName: tagName,
|
||||
Target: target,
|
||||
Title: title,
|
||||
Note: body,
|
||||
IsDraft: isDraft,
|
||||
IsPrerelease: isPreRelease,
|
||||
})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("create release error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult("Release Created")
|
||||
}
|
||||
|
||||
func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
id, err := params.GetIndex(args, "id")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
_, err = client.Releases.DeleteRelease(ctx, owner, repo, id)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("delete release error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult("Release deleted successfully")
|
||||
}
|
||||
|
||||
func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
id, err := params.GetIndex(args, "id")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
release, _, err := client.Releases.GetRelease(ctx, owner, repo, id)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get release error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult(slimRelease(release))
|
||||
}
|
||||
|
||||
func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
release, _, err := client.Releases.GetLatestRelease(ctx, owner, repo)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get latest release error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult(slimRelease(release))
|
||||
}
|
||||
|
||||
func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
page, pageSize := params.GetPagination(args, 20)
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
releases, _, err := client.Releases.ListReleases(ctx, owner, repo, gitea_sdk.ListReleasesOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
IsDraft: params.GetOptionalBoolPtr(args, "is_draft"),
|
||||
IsPreRelease: params.GetOptionalBoolPtr(args, "is_pre_release"),
|
||||
})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list releases error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult(slimReleases(releases))
|
||||
}
|
||||
210
operation/repo/repo.go
Normal file
210
operation/repo/repo.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/annotation"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/params"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/slim"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/to"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/tool"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
var Tool = tool.New()
|
||||
|
||||
const (
|
||||
CreateRepoToolName = "create_repo"
|
||||
ForkRepoToolName = "fork_repo"
|
||||
ListMyReposToolName = "list_my_repos"
|
||||
ListOrgReposToolName = "list_org_repos"
|
||||
)
|
||||
|
||||
var (
|
||||
CreateRepoTool = mcp.NewTool(
|
||||
CreateRepoToolName,
|
||||
mcp.WithToolAnnotation(annotation.Write("Create a new repository")),
|
||||
mcp.WithString("name", mcp.Required()),
|
||||
mcp.WithString("description"),
|
||||
mcp.WithBoolean("private"),
|
||||
mcp.WithString("issue_labels"),
|
||||
mcp.WithBoolean("auto_init"),
|
||||
mcp.WithBoolean("template"),
|
||||
mcp.WithString("gitignores"),
|
||||
mcp.WithString("license"),
|
||||
mcp.WithString("readme"),
|
||||
mcp.WithString("default_branch"),
|
||||
mcp.WithString("trust_model", mcp.Enum("default", "collaborator", "committer", "collaboratorcommitter")),
|
||||
mcp.WithString("object_format_name", mcp.Enum("sha1", "sha256")),
|
||||
mcp.WithString("organization", mcp.Description("defaults to personal account")),
|
||||
)
|
||||
|
||||
ForkRepoTool = mcp.NewTool(
|
||||
ForkRepoToolName,
|
||||
mcp.WithToolAnnotation(annotation.Write("Fork a repository")),
|
||||
mcp.WithString("user", mcp.Required(), mcp.Description("owner of source repo")),
|
||||
mcp.WithString("repo", mcp.Required()),
|
||||
mcp.WithString("organization", mcp.Description("target org")),
|
||||
mcp.WithString("name", mcp.Description("fork name")),
|
||||
)
|
||||
|
||||
ListMyReposTool = mcp.NewTool(
|
||||
ListMyReposToolName,
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("List my repositories")),
|
||||
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)),
|
||||
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)),
|
||||
)
|
||||
|
||||
ListOrgReposTool = mcp.NewTool(
|
||||
ListOrgReposToolName,
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("List organization repositories")),
|
||||
mcp.WithString("org", mcp.Required()),
|
||||
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)),
|
||||
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(100), mcp.Min(1)),
|
||||
)
|
||||
)
|
||||
|
||||
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,
|
||||
})
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: ListOrgReposTool,
|
||||
Handler: ListOrgReposFn,
|
||||
})
|
||||
}
|
||||
|
||||
func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
name, err := params.GetString(args, "name")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
description, _ := args["description"].(string)
|
||||
private, _ := args["private"].(bool)
|
||||
issueLabels, _ := args["issue_labels"].(string)
|
||||
autoInit, _ := args["auto_init"].(bool)
|
||||
template, _ := args["template"].(bool)
|
||||
gitignores, _ := args["gitignores"].(string)
|
||||
license, _ := args["license"].(string)
|
||||
readme, _ := args["readme"].(string)
|
||||
defaultBranch, _ := args["default_branch"].(string)
|
||||
trustModel, _ := args["trust_model"].(string)
|
||||
objectFormatName, _ := args["object_format_name"].(string)
|
||||
organization, _ := args["organization"].(string)
|
||||
|
||||
opt := gitea_sdk.CreateRepoOption{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Private: private,
|
||||
IssueLabels: issueLabels,
|
||||
AutoInit: autoInit,
|
||||
Template: template,
|
||||
Gitignores: gitignores,
|
||||
License: license,
|
||||
Readme: readme,
|
||||
DefaultBranch: defaultBranch,
|
||||
TrustModel: gitea_sdk.TrustModel(trustModel),
|
||||
ObjectFormatName: objectFormatName,
|
||||
}
|
||||
|
||||
var repo *gitea_sdk.Repository
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
if organization != "" {
|
||||
repo, _, err = client.Repositories.CreateOrgRepo(ctx, organization, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("create organization repository '%s' in '%s' err: %v", name, organization, err))
|
||||
}
|
||||
} else {
|
||||
repo, _, err = client.Repositories.CreateRepo(ctx, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("create repository '%s' err: %v", name, err))
|
||||
}
|
||||
}
|
||||
return to.TextResult(slim.Repo(repo))
|
||||
}
|
||||
|
||||
func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
user, err := params.GetString(args, "user")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
opt := gitea_sdk.CreateForkOption{
|
||||
Organization: params.GetOptionalStringPtr(args, "organization"),
|
||||
Name: params.GetOptionalStringPtr(args, "name"),
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
_, _, err = client.Repositories.CreateFork(ctx, user, repo, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("fork repository error: %v", err))
|
||||
}
|
||||
return to.TextResult("Fork success")
|
||||
}
|
||||
|
||||
func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
page, pageSize := params.GetPagination(req.GetArguments(), 30)
|
||||
opt := gitea_sdk.ListReposOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
repos, _, err := client.Repositories.ListMyRepos(ctx, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list my repositories error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult(slim.Repos(repos))
|
||||
}
|
||||
|
||||
func ListOrgReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
org, err := params.GetString(req.GetArguments(), "org")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
page, pageSize := params.GetPagination(req.GetArguments(), 100)
|
||||
opt := gitea_sdk.ListOrgReposOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
repos, _, err := client.Repositories.ListOrgRepos(ctx, org, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list organization '%s' repositories error: %v", org, err))
|
||||
}
|
||||
return to.TextResult(repos)
|
||||
}
|
||||
178
operation/repo/slim.go
Normal file
178
operation/repo/slim.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"gitea.com/gitea/gitea-mcp/pkg/slim"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
)
|
||||
|
||||
func slimBranch(b *gitea_sdk.Branch) map[string]any {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
m := map[string]any{
|
||||
"name": b.Name,
|
||||
"protected": b.Protected,
|
||||
}
|
||||
if b.Commit != nil {
|
||||
m["commit_sha"] = b.Commit.ID
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func slimBranches(branches []*gitea_sdk.Branch) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(branches))
|
||||
for _, b := range branches {
|
||||
out = append(out, slimBranch(b))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func slimCommit(c *gitea_sdk.Commit) map[string]any {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
m := map[string]any{
|
||||
"sha": c.SHA,
|
||||
"html_url": c.HTMLURL,
|
||||
"created": c.Created,
|
||||
}
|
||||
if c.RepoCommit != nil {
|
||||
m["message"] = c.RepoCommit.Message
|
||||
if c.RepoCommit.Author != nil {
|
||||
m["author"] = map[string]any{
|
||||
"name": c.RepoCommit.Author.Name,
|
||||
"email": c.RepoCommit.Author.Email,
|
||||
"date": c.RepoCommit.Author.Date,
|
||||
}
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func slimCommits(commits []*gitea_sdk.Commit) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(commits))
|
||||
for _, c := range commits {
|
||||
out = append(out, slimCommit(c))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func slimTag(t *gitea_sdk.Tag) map[string]any {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
m := map[string]any{
|
||||
"name": t.Name,
|
||||
"message": t.Message,
|
||||
}
|
||||
if t.Commit != nil {
|
||||
m["commit_sha"] = t.Commit.SHA
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func slimTags(tags []*gitea_sdk.Tag) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(tags))
|
||||
for _, t := range tags {
|
||||
m := map[string]any{
|
||||
"name": t.Name,
|
||||
}
|
||||
if t.Commit != nil {
|
||||
m["commit_sha"] = t.Commit.SHA
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func slimRelease(r *gitea_sdk.Release) map[string]any {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"id": r.ID,
|
||||
"tag_name": r.TagName,
|
||||
"target": r.Target,
|
||||
"title": r.Title,
|
||||
"body": r.Note,
|
||||
"draft": r.IsDraft,
|
||||
"prerelease": r.IsPrerelease,
|
||||
"html_url": r.HTMLURL,
|
||||
"author": slim.UserLogin(r.Publisher),
|
||||
"created_at": r.CreatedAt,
|
||||
"published_at": r.PublishedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func slimReleases(releases []*gitea_sdk.Release) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(releases))
|
||||
for _, r := range releases {
|
||||
out = append(out, slimRelease(r))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func slimContents(c *gitea_sdk.ContentsResponse) map[string]any {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
m := map[string]any{
|
||||
"name": c.Name,
|
||||
"path": c.Path,
|
||||
"sha": c.SHA,
|
||||
"type": c.Type,
|
||||
"size": c.Size,
|
||||
}
|
||||
if c.Content != nil {
|
||||
m["content"] = *c.Content
|
||||
}
|
||||
if c.Encoding != nil {
|
||||
m["encoding"] = *c.Encoding
|
||||
}
|
||||
if c.HTMLURL != nil {
|
||||
m["html_url"] = *c.HTMLURL
|
||||
}
|
||||
if c.DownloadURL != nil {
|
||||
m["download_url"] = *c.DownloadURL
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func slimTree(t *gitea_sdk.GitTreeResponse) map[string]any {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
entries := make([]map[string]any, 0, len(t.Entries))
|
||||
for _, e := range t.Entries {
|
||||
entries = append(entries, map[string]any{
|
||||
"path": e.Path,
|
||||
"mode": e.Mode,
|
||||
"type": e.Type,
|
||||
"size": e.Size,
|
||||
"sha": e.SHA,
|
||||
})
|
||||
}
|
||||
return map[string]any{
|
||||
"sha": t.SHA,
|
||||
"truncated": t.Truncated,
|
||||
"total_count": t.TotalCount,
|
||||
"tree": entries,
|
||||
}
|
||||
}
|
||||
|
||||
func slimDirEntries(entries []*gitea_sdk.ContentsResponse) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(entries))
|
||||
for _, c := range entries {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, map[string]any{
|
||||
"name": c.Name,
|
||||
"path": c.Path,
|
||||
"type": c.Type,
|
||||
"size": c.Size,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
109
operation/repo/slim_test.go
Normal file
109
operation/repo/slim_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
)
|
||||
|
||||
func TestSlimTag(t *testing.T) {
|
||||
tag := &gitea_sdk.Tag{
|
||||
Name: "v1.0.0",
|
||||
Message: "Release v1.0.0",
|
||||
Commit: &gitea_sdk.CommitMeta{SHA: "abc123"},
|
||||
}
|
||||
|
||||
m := slimTag(tag)
|
||||
if m["name"] != "v1.0.0" {
|
||||
t.Errorf("expected name v1.0.0, got %v", m["name"])
|
||||
}
|
||||
if m["message"] != "Release v1.0.0" {
|
||||
t.Errorf("expected message, got %v", m["message"])
|
||||
}
|
||||
|
||||
// List variant omits message
|
||||
list := slimTags([]*gitea_sdk.Tag{tag})
|
||||
if _, ok := list[0]["message"]; ok {
|
||||
t.Error("Tags list should omit message")
|
||||
}
|
||||
if list[0]["name"] != "v1.0.0" {
|
||||
t.Errorf("expected name in list, got %v", list[0]["name"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlimRelease(t *testing.T) {
|
||||
r := &gitea_sdk.Release{
|
||||
ID: 1,
|
||||
TagName: "v1.0.0",
|
||||
Title: "First Release",
|
||||
Note: "Release notes",
|
||||
IsDraft: false,
|
||||
Publisher: &gitea_sdk.User{UserName: "alice"},
|
||||
}
|
||||
|
||||
m := slimRelease(r)
|
||||
if m["tag_name"] != "v1.0.0" {
|
||||
t.Errorf("expected tag_name v1.0.0, got %v", m["tag_name"])
|
||||
}
|
||||
if m["body"] != "Release notes" {
|
||||
t.Errorf("expected body from Note field, got %v", m["body"])
|
||||
}
|
||||
if m["author"] != "alice" {
|
||||
t.Errorf("expected author alice, got %v", m["author"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlimContents(t *testing.T) {
|
||||
content := "package main"
|
||||
encoding := "base64"
|
||||
htmlURL := "https://gitea.com/org/repo/src/branch/main/main.go"
|
||||
c := &gitea_sdk.ContentsResponse{
|
||||
Name: "main.go",
|
||||
Path: "main.go",
|
||||
SHA: "abc123",
|
||||
Type: "file",
|
||||
Size: 12,
|
||||
Content: &content,
|
||||
Encoding: &encoding,
|
||||
HTMLURL: &htmlURL,
|
||||
}
|
||||
|
||||
m := slimContents(c)
|
||||
if m["name"] != "main.go" {
|
||||
t.Errorf("expected name main.go, got %v", m["name"])
|
||||
}
|
||||
if m["content"] != "package main" {
|
||||
t.Errorf("expected content, got %v", m["content"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlimDirEntries(t *testing.T) {
|
||||
entries := []*gitea_sdk.ContentsResponse{
|
||||
{Name: "src", Path: "src", Type: "dir", Size: 0},
|
||||
{Name: "main.go", Path: "main.go", Type: "file", Size: 100},
|
||||
}
|
||||
|
||||
result := slimDirEntries(entries)
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("expected 2 entries, got %d", len(result))
|
||||
}
|
||||
if result[0]["name"] != "src" {
|
||||
t.Errorf("expected first entry name src, got %v", result[0]["name"])
|
||||
}
|
||||
// Dir entries should not have content
|
||||
if _, ok := result[0]["content"]; ok {
|
||||
t.Error("dir entries should not have content field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlimTags_Nil(t *testing.T) {
|
||||
if r := slimTags(nil); len(r) != 0 {
|
||||
t.Errorf("expected empty slice, got %v", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlimReleases_Nil(t *testing.T) {
|
||||
if r := slimReleases(nil); len(r) != 0 {
|
||||
t.Errorf("expected empty slice, got %v", r)
|
||||
}
|
||||
}
|
||||
195
operation/repo/tag.go
Normal file
195
operation/repo/tag.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/annotation"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/params"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/to"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
"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.WithToolAnnotation(annotation.Write("Create a tag")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("tag_name", mcp.Required()),
|
||||
mcp.WithString("target", mcp.Description("commitish")),
|
||||
mcp.WithString("message", mcp.Description("tag message")),
|
||||
)
|
||||
|
||||
DeleteTagTool = mcp.NewTool(
|
||||
DeleteTagToolName,
|
||||
mcp.WithToolAnnotation(annotation.Destructive("Delete a tag")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("tag_name", mcp.Required()),
|
||||
)
|
||||
|
||||
GetTagTool = mcp.NewTool(
|
||||
GetTagToolName,
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("Get tag details")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("tag_name", mcp.Required()),
|
||||
)
|
||||
|
||||
ListTagsTool = mcp.NewTool(
|
||||
ListTagsToolName,
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("List tags")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)),
|
||||
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), 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,
|
||||
})
|
||||
}
|
||||
|
||||
func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
tagName, err := params.GetString(args, "tag_name")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
target, _ := args["target"].(string)
|
||||
message, _ := args["message"].(string)
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
_, _, err = client.Repositories.CreateTag(ctx, owner, repo, gitea_sdk.CreateTagOption{
|
||||
TagName: tagName,
|
||||
Target: target,
|
||||
Message: message,
|
||||
})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("create tag error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult("Tag Created")
|
||||
}
|
||||
|
||||
func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
tagName, err := params.GetString(args, "tag_name")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
_, err = client.Repositories.DeleteTag(ctx, owner, repo, tagName)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("delete tag error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult("Tag deleted")
|
||||
}
|
||||
|
||||
func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
tagName, err := params.GetString(args, "tag_name")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
tag, _, err := client.Repositories.GetTag(ctx, owner, repo, tagName)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get tag error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult(slimTag(tag))
|
||||
}
|
||||
|
||||
func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
page := params.GetOptionalInt(args, "page", 1)
|
||||
pageSize := params.GetOptionalInt(args, "per_page", 20)
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
tags, _, err := client.Repositories.ListRepoTags(ctx, owner, repo, gitea_sdk.ListRepoTagsOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: int(page),
|
||||
PageSize: int(pageSize),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list tags error: %v", err))
|
||||
}
|
||||
|
||||
return to.TextResult(slimTags(tags))
|
||||
}
|
||||
73
operation/repo/tree.go
Normal file
73
operation/repo/tree.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/annotation"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/params"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/to"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
const (
|
||||
GetRepoTreeToolName = "get_repository_tree"
|
||||
)
|
||||
|
||||
var GetRepoTreeTool = mcp.NewTool(
|
||||
GetRepoTreeToolName,
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("Get repository file tree")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("tree_sha", mcp.Required(), mcp.Description("SHA, branch, or tag")),
|
||||
mcp.WithBoolean("recursive"),
|
||||
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)),
|
||||
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: GetRepoTreeTool,
|
||||
Handler: GetRepoTreeFn,
|
||||
})
|
||||
}
|
||||
|
||||
func GetRepoTreeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
treeSHA, err := params.GetString(args, "tree_sha")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
recursive, _ := args["recursive"].(bool)
|
||||
page, pageSize := params.GetPagination(args, 30)
|
||||
|
||||
opt := gitea_sdk.ListTreeOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
Ref: treeSHA,
|
||||
Recursive: recursive,
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
tree, _, err := client.Git.GetTrees(ctx, owner, repo, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get repository tree err: %v", err))
|
||||
}
|
||||
return to.TextResult(slimTree(tree))
|
||||
}
|
||||
52
operation/repo/tree_test.go
Normal file
52
operation/repo/tree_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
)
|
||||
|
||||
func TestSlimTree(t *testing.T) {
|
||||
tree := &gitea_sdk.GitTreeResponse{
|
||||
SHA: "abc123",
|
||||
TotalCount: 2,
|
||||
Truncated: false,
|
||||
Entries: []gitea_sdk.GitEntry{
|
||||
{Path: "src", Mode: "040000", Type: "tree", Size: 0, SHA: "def456"},
|
||||
{Path: "main.go", Mode: "100644", Type: "blob", Size: 42, SHA: "789abc"},
|
||||
},
|
||||
}
|
||||
|
||||
m := slimTree(tree)
|
||||
if m["sha"] != "abc123" {
|
||||
t.Errorf("expected sha abc123, got %v", m["sha"])
|
||||
}
|
||||
if m["total_count"] != 2 {
|
||||
t.Errorf("expected total_count 2, got %v", m["total_count"])
|
||||
}
|
||||
entries := m["tree"].([]map[string]any)
|
||||
if len(entries) != 2 {
|
||||
t.Fatalf("expected 2 entries, got %d", len(entries))
|
||||
}
|
||||
if entries[0]["path"] != "src" {
|
||||
t.Errorf("expected first entry path src, got %v", entries[0]["path"])
|
||||
}
|
||||
if entries[1]["type"] != "blob" {
|
||||
t.Errorf("expected second entry type blob, got %v", entries[1]["type"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlimTreeNil(t *testing.T) {
|
||||
if m := slimTree(nil); m != nil {
|
||||
t.Errorf("expected nil, got %v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRepoTreeToolRequired(t *testing.T) {
|
||||
for _, field := range []string{"owner", "repo", "tree_sha"} {
|
||||
if !slices.Contains(GetRepoTreeTool.InputSchema.Required, field) {
|
||||
t.Errorf("expected %q to be required", field)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user