package repo import ( "context" "errors" "fmt" "git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/to" gitea_sdk "code.gitea.io/sdk/gitea" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) const ( GetRepoRefToolName = "get_repo_ref" ListAllGitRefsToolName = "list_all_git_refs" GetTreeToolName = "get_tree" GetRepoNoteToolName = "get_repo_note" CompareCommitsToolName = "compare_commits" ) var ( GetRepoRefTool = mcp.NewTool( GetRepoRefToolName, mcp.WithDescription("Get a git reference from a repository"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("ref", mcp.Required(), mcp.Description("git reference (e.g., refs/heads/main, refs/tags/v1.0)")), ) ListAllGitRefsTool = mcp.NewTool( ListAllGitRefsToolName, mcp.WithDescription("List all git references in a repository"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), ) GetTreeTool = mcp.NewTool( GetTreeToolName, mcp.WithDescription("Get the tree of a repository at a given SHA"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("sha", mcp.Required(), mcp.Description("SHA of the tree")), mcp.WithBoolean("recursive", mcp.Description("show all items recursively")), ) GetRepoNoteTool = mcp.NewTool( GetRepoNoteToolName, mcp.WithDescription("Get a git note for a commit"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("sha", mcp.Required(), mcp.Description("commit SHA")), ) CompareCommitsTool = mcp.NewTool( CompareCommitsToolName, mcp.WithDescription("Compare two commits in a repository"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("base", mcp.Required(), mcp.Description("base commit SHA or branch")), mcp.WithString("head", mcp.Required(), mcp.Description("head commit SHA or branch")), ) ) func init() { Tool.RegisterRead(server.ServerTool{Tool: GetRepoRefTool, Handler: GetRepoRefFn}) Tool.RegisterRead(server.ServerTool{Tool: ListAllGitRefsTool, Handler: ListAllGitRefsFn}) Tool.RegisterRead(server.ServerTool{Tool: GetTreeTool, Handler: GetTreeFn}) Tool.RegisterRead(server.ServerTool{Tool: GetRepoNoteTool, Handler: GetRepoNoteFn}) Tool.RegisterRead(server.ServerTool{Tool: CompareCommitsTool, Handler: CompareCommitsFn}) } func GetRepoRefFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called GetRepoRefFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) ref, _ := req.GetArguments()["ref"].(string) if owner == "" || repo == "" || ref == "" { return to.ErrorResult(errors.New("owner, repo, and ref are required")) } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } reference, _, err := client.GetRepoRef(owner, repo, ref) if err != nil { return to.ErrorResult(fmt.Errorf("get repo ref err: %v", err)) } return to.TextResult(reference) } func ListAllGitRefsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ListAllGitRefsFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) if owner == "" || repo == "" { return to.ErrorResult(errors.New("owner and repo are required")) } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } refs, _, err := client.ListAllGitRefs(owner, repo) if err != nil { return to.ErrorResult(fmt.Errorf("list git refs err: %v", err)) } return to.TextResult(refs) } func GetTreeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called GetTreeFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) sha, _ := req.GetArguments()["sha"].(string) if owner == "" || repo == "" || sha == "" { return to.ErrorResult(errors.New("owner, repo, and sha are required")) } opt := gitea_sdk.ListTreeOptions{ Ref: sha, } if v, ok := req.GetArguments()["recursive"].(bool); ok && v { opt.Recursive = true } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } tree, _, err := client.GetTrees(owner, repo, opt) if err != nil { return to.ErrorResult(fmt.Errorf("get tree err: %v", err)) } return to.TextResult(tree) } func GetRepoNoteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called GetRepoNoteFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) sha, _ := req.GetArguments()["sha"].(string) if owner == "" || repo == "" || sha == "" { return to.ErrorResult(errors.New("owner, repo, and sha are required")) } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } note, _, err := client.GetRepoNote(owner, repo, sha, gitea_sdk.GetRepoNoteOptions{}) if err != nil { return to.ErrorResult(fmt.Errorf("get repo note err: %v", err)) } return to.TextResult(note) } func CompareCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called CompareCommitsFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) base, _ := req.GetArguments()["base"].(string) head, _ := req.GetArguments()["head"].(string) if owner == "" || repo == "" || base == "" || head == "" { return to.ErrorResult(errors.New("owner, repo, base, and head are required")) } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } compare, _, err := client.CompareCommits(owner, repo, base, head) if err != nil { return to.ErrorResult(fmt.Errorf("compare commits err: %v", err)) } return to.TextResult(compare) }