package repo import ( "context" "errors" "fmt" "codeberg.org/goern/forgejo-mcp/v2/operation/params" "codeberg.org/goern/forgejo-mcp/v2/pkg/forgejo" "codeberg.org/goern/forgejo-mcp/v2/pkg/log" "codeberg.org/goern/forgejo-mcp/v2/pkg/ptr" "codeberg.org/goern/forgejo-mcp/v2/pkg/to" forgejo_sdk "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) const ( CreateRepoToolName = "create_repo" ForkRepoToolName = "fork_repo" ListMyReposToolName = "list_my_repos" ) var ( CreateRepoTool = mcp.NewTool( CreateRepoToolName, mcp.WithDescription("Create repo"), mcp.WithString("name", mcp.Required(), mcp.Description("Repo name")), mcp.WithString("description", mcp.Description(params.Description)), mcp.WithString("owner", mcp.Description("Owner/org name")), mcp.WithBoolean("private", mcp.Description(params.Private)), mcp.WithString("issue_labels", mcp.Description("Issue label set")), mcp.WithBoolean("auto_init", mcp.Description("Auto-initialize")), mcp.WithBoolean("template", mcp.Description("Template repo")), mcp.WithString("gitignores", mcp.Description("Gitignore templates")), mcp.WithString("license", mcp.Description("License")), mcp.WithString("readme", mcp.Description("README content")), mcp.WithString("default_branch", mcp.Description("Default branch")), ) ForkRepoTool = mcp.NewTool( ForkRepoToolName, mcp.WithDescription("Fork repo"), mcp.WithString("user", mcp.Required(), mcp.Description(params.User)), mcp.WithString("repo", mcp.Required(), mcp.Description(params.Repo)), mcp.WithString("organization", mcp.Description("Org name")), mcp.WithString("name", mcp.Description("Fork name")), ) ListMyReposTool = mcp.NewTool( ListMyReposToolName, mcp.WithDescription("List my repos"), mcp.WithNumber("page", mcp.Required(), mcp.Description(params.Page), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("limit", mcp.Required(), mcp.Description(params.Limit), mcp.DefaultNumber(100), mcp.Min(1)), ) ) func RegisterTool(s *server.MCPServer) { s.AddTool(CreateRepoTool, CreateRepoFn) s.AddTool(ForkRepoTool, ForkRepoFn) s.AddTool(ListMyReposTool, ListMyReposFn) // File s.AddTool(GetFileContentTool, GetFileContentFn) s.AddTool(CreateFileTool, CreateFileFn) s.AddTool(UpdateFileTool, UpdateFileFn) s.AddTool(DeleteFileTool, DeleteFileFn) // Branch s.AddTool(CreateBranchTool, CreateBranchFn) s.AddTool(DeleteBranchTool, DeleteBranchFn) s.AddTool(ListBranchesTool, ListBranchesFn) // Commit s.AddTool(ListRepoCommitsTool, ListRepoCommitsFn) // Contents / Tree s.AddTool(ListRepoContentsTool, ListRepoContentsFn) s.AddTool(GetRepoTreeTool, GetRepoTreeFn) } func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called CreateRepoFn") name, ok := req.GetArguments()["name"].(string) if !ok { return to.ErrorResult(errors.New("repository name is required")) } description, _ := req.GetArguments()["description"].(string) owner, _ := req.GetArguments()["owner"].(string) private, _ := req.GetArguments()["private"].(bool) issueLabels, _ := req.GetArguments()["issue_labels"].(string) autoInit, _ := req.GetArguments()["auto_init"].(bool) template, _ := req.GetArguments()["template"].(bool) gitignores, _ := req.GetArguments()["gitignores"].(string) license, _ := req.GetArguments()["license"].(string) readme, _ := req.GetArguments()["readme"].(string) defaultBranch, _ := req.GetArguments()["default_branch"].(string) opt := forgejo_sdk.CreateRepoOption{ Name: name, Description: description, Private: private, IssueLabels: issueLabels, AutoInit: autoInit, Template: template, Gitignores: gitignores, License: license, Readme: readme, DefaultBranch: defaultBranch, } var repo *forgejo_sdk.Repository var err error var client *forgejo_sdk.Client if owner != "" { client, err = forgejo.Client(ctx) if err != nil { return to.ErrorResult(err) } repo, _, err = client.CreateOrgRepo(owner, opt) } else { client, err = forgejo.Client(ctx) if err != nil { return to.ErrorResult(err) } repo, _, err = client.CreateRepo(opt) } if err != nil { return to.ErrorResult(fmt.Errorf("create repo err: %w", err)) } return to.TextResult(repo) } func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ForkRepoFn") user, ok := req.GetArguments()["user"].(string) if !ok { return to.ErrorResult(errors.New("user name is required")) } repo, ok := req.GetArguments()["repo"].(string) if !ok { return to.ErrorResult(errors.New("repository name is required")) } organization, ok := req.GetArguments()["organization"].(string) organizationPtr := ptr.To(organization) if !ok || organization == "" { organizationPtr = nil } name, ok := req.GetArguments()["name"].(string) namePtr := ptr.To(name) if !ok || name == "" { namePtr = nil } opt := forgejo_sdk.CreateForkOption{ Organization: organizationPtr, Name: namePtr, } client, err := forgejo.Client(ctx) if err != nil { return to.ErrorResult(err) } _, _, err = client.CreateFork(user, repo, opt) if err != nil { return to.ErrorResult(fmt.Errorf("fork repository error %w", err)) } return to.TextResult("Fork success") } func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ListMyReposFn") page, _ := to.Float64(req.GetArguments()["page"]) if page == 0 { page = 1 } limit, _ := to.Float64(req.GetArguments()["limit"]) if limit == 0 { limit = 100 } opt := forgejo_sdk.ListReposOptions{ ListOptions: forgejo_sdk.ListOptions{ Page: int(page), PageSize: int(limit), }, } client, err := forgejo.Client(ctx) if err != nil { return to.ErrorResult(err) } repos, _, err := client.ListMyRepos(opt) if err != nil { return to.ErrorResult(fmt.Errorf("list my repositories error: %w", err)) } return to.TextResult(repos) }