diff --git a/README.md b/README.md index 3bb5267..8ebfbcc 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,10 @@ To configure the MCP server for Gitea, add the following to your MCP configurati { "mcpServers": { "gitea": { - "url": "http://localhost:8080/sse" + "url": "http://localhost:8080/sse", + "headers": { + "Authorization": "Bearer " + } } } } @@ -151,7 +154,10 @@ To configure the MCP server for Gitea, add the following to your MCP configurati { "mcpServers": { "gitea": { - "url": "http://localhost:8080/mcp" + "url": "http://localhost:8080/mcp", + "headers": { + "Authorization": "Bearer " + } } } } diff --git a/README.zh-cn.md b/README.zh-cn.md index f6da55e..af3d111 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -139,7 +139,10 @@ cp gitea-mcp /usr/local/bin/ { "mcpServers": { "gitea": { - "url": "http://localhost:8080/sse" + "url": "http://localhost:8080/sse", + "headers": { + "Authorization": "Bearer " + } } } } @@ -151,7 +154,10 @@ cp gitea-mcp /usr/local/bin/ { "mcpServers": { "gitea": { - "url": "http://localhost:8080/mcp" + "url": "http://localhost:8080/mcp", + "headers": { + "Authorization": "Bearer " + } } } } diff --git a/README.zh-tw.md b/README.zh-tw.md index 5c5d61a..8d26b8c 100644 --- a/README.zh-tw.md +++ b/README.zh-tw.md @@ -139,7 +139,10 @@ cp gitea-mcp /usr/local/bin/ { "mcpServers": { "gitea": { - "url": "http://localhost:8080/sse" + "url": "http://localhost:8080/sse", + "headers": { + "Authorization": "Bearer " + } } } } @@ -151,7 +154,10 @@ cp gitea-mcp /usr/local/bin/ { "mcpServers": { "gitea": { - "url": "http://localhost:8080/mcp" + "url": "http://localhost:8080/mcp", + "headers": { + "Authorization": "Bearer " + } } } } diff --git a/operation/issue/issue.go b/operation/issue/issue.go index 82d81ad..fd40d62 100644 --- a/operation/issue/issue.go +++ b/operation/issue/issue.go @@ -140,7 +140,11 @@ func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT if !ok { return to.ErrorResult(fmt.Errorf("index is required")) } - issue, _, err := gitea.Client().GetIssue(owner, repo, int64(index)) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + issue, _, err := client.GetIssue(owner, repo, int64(index)) if err != nil { return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) } @@ -177,7 +181,11 @@ func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo PageSize: int(pageSize), }, } - issues, _, err := gitea.Client().ListRepoIssues(owner, repo, opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + issues, _, err := client.ListRepoIssues(owner, repo, opt) if err != nil { return to.ErrorResult(fmt.Errorf("get %v/%v/issues err: %v", owner, repo, err)) } @@ -202,7 +210,11 @@ func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR if !ok { return to.ErrorResult(fmt.Errorf("body is required")) } - issue, _, err := gitea.Client().CreateIssue(owner, repo, gitea_sdk.CreateIssueOption{ + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + issue, _, err := client.CreateIssue(owner, repo, gitea_sdk.CreateIssueOption{ Title: title, Body: body, }) @@ -234,7 +246,11 @@ func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca opt := gitea_sdk.CreateIssueCommentOption{ Body: body, } - issueComment, _, err := gitea.Client().CreateIssueComment(owner, repo, int64(index), opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + issueComment, _, err := client.CreateIssueComment(owner, repo, int64(index), opt) if err != nil { return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err: %v", owner, repo, int64(index), err)) } @@ -280,7 +296,11 @@ func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes opt.State = ptr.To(gitea_sdk.StateType(state)) } - issue, _, err := gitea.Client().EditIssue(owner, repo, int64(index), opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + issue, _, err := 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)) } @@ -309,7 +329,11 @@ func EditIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call opt := gitea_sdk.EditIssueCommentOption{ Body: body, } - issueComment, _, err := gitea.Client().EditIssueComment(owner, repo, int64(commentID), opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + issueComment, _, err := client.EditIssueComment(owner, repo, int64(commentID), opt) if err != nil { return to.ErrorResult(fmt.Errorf("edit %v/%v/issues/comments/%v err: %v", owner, repo, int64(commentID), err)) } @@ -332,7 +356,11 @@ func GetIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*m return to.ErrorResult(fmt.Errorf("index is required")) } opt := gitea_sdk.ListIssueCommentOptions{} - issue, _, err := gitea.Client().ListIssueComments(owner, repo, int64(index), opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + issue, _, err := client.ListIssueComments(owner, repo, int64(index), opt) if err != nil { return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/comments err: %v", owner, repo, int64(index), err)) } diff --git a/operation/label/label.go b/operation/label/label.go index 8b167e3..0954508 100644 --- a/operation/label/label.go +++ b/operation/label/label.go @@ -176,7 +176,11 @@ func ListRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo PageSize: int(pageSize), }, } - labels, _, err := gitea.Client().ListRepoLabels(owner, repo, opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + labels, _, err := client.ListRepoLabels(owner, repo, opt) if err != nil { return to.ErrorResult(fmt.Errorf("list %v/%v/labels err: %v", owner, repo, err)) } @@ -198,7 +202,11 @@ func GetRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool return to.ErrorResult(fmt.Errorf("label ID is required")) } - label, _, err := gitea.Client().GetRepoLabel(owner, repo, int64(id)) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + label, _, err := client.GetRepoLabel(owner, repo, int64(id)) if err != nil { return to.ErrorResult(fmt.Errorf("get %v/%v/label/%v err: %v", owner, repo, int64(id), err)) } @@ -231,7 +239,11 @@ func CreateRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT Description: description, } - label, _, err := gitea.Client().CreateLabel(owner, repo, opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + label, _, err := client.CreateLabel(owner, repo, opt) if err != nil { return to.ErrorResult(fmt.Errorf("create %v/%v/label err: %v", owner, repo, err)) } @@ -264,7 +276,11 @@ func EditRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo opt.Description = ptr.To(description) } - label, _, err := gitea.Client().EditLabel(owner, repo, int64(id), opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + label, _, err := client.EditLabel(owner, repo, int64(id), opt) if err != nil { return to.ErrorResult(fmt.Errorf("edit %v/%v/label/%v err: %v", owner, repo, int64(id), err)) } @@ -286,7 +302,11 @@ func DeleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT return to.ErrorResult(fmt.Errorf("label ID is required")) } - _, err := gitea.Client().DeleteLabel(owner, repo, int64(id)) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, err = client.DeleteLabel(owner, repo, int64(id)) if err != nil { return to.ErrorResult(fmt.Errorf("delete %v/%v/label/%v err: %v", owner, repo, int64(id), err)) } @@ -324,7 +344,11 @@ func AddIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo Labels: labels, } - issueLabels, _, err := gitea.Client().AddIssueLabels(owner, repo, int64(index), opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + issueLabels, _, err := client.AddIssueLabels(owner, repo, int64(index), opt) if err != nil { return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) } @@ -362,7 +386,11 @@ func ReplaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca Labels: labels, } - issueLabels, _, err := gitea.Client().ReplaceIssueLabels(owner, repo, int64(index), opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + issueLabels, _, err := client.ReplaceIssueLabels(owner, repo, int64(index), opt) if err != nil { return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) } @@ -384,7 +412,11 @@ func ClearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call return to.ErrorResult(fmt.Errorf("issue index is required")) } - _, err := gitea.Client().ClearIssueLabels(owner, repo, int64(index)) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, err = client.ClearIssueLabels(owner, repo, int64(index)) if err != nil { return to.ErrorResult(fmt.Errorf("clear labels on %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) } @@ -410,7 +442,11 @@ func RemoveIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call return to.ErrorResult(fmt.Errorf("label ID is required")) } - _, err := gitea.Client().DeleteIssueLabel(owner, repo, int64(index), int64(labelID)) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, err = client.DeleteIssueLabel(owner, repo, int64(index), int64(labelID)) if err != nil { return to.ErrorResult(fmt.Errorf("remove label %v from %v/%v/issue/%v err: %v", int64(labelID), owner, repo, int64(index), err)) } diff --git a/operation/operation.go b/operation/operation.go index 1de5e3f..ccc4e94 100644 --- a/operation/operation.go +++ b/operation/operation.go @@ -1,7 +1,10 @@ package operation import ( + "context" "fmt" + "net/http" + "strings" "time" "gitea.com/gitea/gitea-mcp/operation/issue" @@ -11,6 +14,7 @@ import ( "gitea.com/gitea/gitea-mcp/operation/search" "gitea.com/gitea/gitea-mcp/operation/user" "gitea.com/gitea/gitea-mcp/operation/version" + mcpContext "gitea.com/gitea/gitea-mcp/pkg/context" "gitea.com/gitea/gitea-mcp/pkg/flag" "gitea.com/gitea/gitea-mcp/pkg/log" @@ -44,6 +48,20 @@ func RegisterTool(s *server.MCPServer) { s.DeleteTools("") } +func getContextWithToken(ctx context.Context, r *http.Request) context.Context { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + return ctx + } + + parts := strings.Split(authHeader, " ") + if len(parts) != 2 || parts[0] != "Bearer" { + return ctx + } + + return context.WithValue(ctx, mcpContext.TokenContextKey, parts[1]) +} + func Run() error { mcpServer = newMCPServer(flag.Version) RegisterTool(mcpServer) @@ -57,6 +75,7 @@ func Run() error { case "sse": sseServer := server.NewSSEServer( mcpServer, + server.WithSSEContextFunc(getContextWithToken), ) log.Infof("Gitea MCP SSE server listening on :%d", flag.Port) if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil { @@ -67,7 +86,7 @@ func Run() error { mcpServer, server.WithLogger(log.New()), server.WithHeartbeatInterval(30*time.Second), - server.WithStateLess(true), + server.WithHTTPContextFunc(getContextWithToken), ) log.Infof("Gitea MCP HTTP server listening on :%d", flag.Port) if err := httpServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil { diff --git a/operation/pull/pull.go b/operation/pull/pull.go index 4b90682..85f151b 100644 --- a/operation/pull/pull.go +++ b/operation/pull/pull.go @@ -84,7 +84,11 @@ func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp if !ok { return to.ErrorResult(fmt.Errorf("index is required")) } - pr, _, err := gitea.Client().GetPullRequest(owner, repo, int64(index)) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + pr, _, err := client.GetPullRequest(owner, repo, int64(index)) if err != nil { return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, int64(index), err)) } @@ -125,7 +129,11 @@ func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp. PageSize: int(pageSize), }, } - pullRequests, _, err := gitea.Client().ListRepoPullRequests(owner, repo, opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + pullRequests, _, err := client.ListRepoPullRequests(owner, repo, opt) if err != nil { return to.ErrorResult(fmt.Errorf("list %v/%v/pull_requests err: %v", owner, repo, err)) } @@ -159,7 +167,11 @@ func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal if !ok { return to.ErrorResult(fmt.Errorf("base is required")) } - pr, _, err := gitea.Client().CreatePullRequest(owner, repo, gitea_sdk.CreatePullRequestOption{ + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + pr, _, err := client.CreatePullRequest(owner, repo, gitea_sdk.CreatePullRequestOption{ Title: title, Body: body, Head: head, diff --git a/operation/repo/branch.go b/operation/repo/branch.go index d71cc35..1680915 100644 --- a/operation/repo/branch.go +++ b/operation/repo/branch.go @@ -76,7 +76,11 @@ func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool } oldBranch, _ := req.GetArguments()["old_branch"].(string) - _, _, err := gitea.Client().CreateBranch(owner, repo, gitea_sdk.CreateBranchOption{ + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, _, err = client.CreateBranch(owner, repo, gitea_sdk.CreateBranchOption{ BranchName: branch, OldBranchName: oldBranch, }) @@ -101,7 +105,11 @@ func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool if !ok { return to.ErrorResult(fmt.Errorf("branch is required")) } - _, _, err := gitea.Client().DeleteRepoBranch(owner, repo, branch) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, _, err = client.DeleteRepoBranch(owner, repo, branch) if err != nil { return to.ErrorResult(fmt.Errorf("delete branch error: %v", err)) } @@ -125,7 +133,11 @@ func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool PageSize: 100, }, } - branches, _, err := gitea.Client().ListRepoBranches(owner, repo, opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + branches, _, err := client.ListRepoBranches(owner, repo, opt) if err != nil { return to.ErrorResult(fmt.Errorf("list branches error: %v", err)) } diff --git a/operation/repo/commit.go b/operation/repo/commit.go index 048b767..1aa61e5 100644 --- a/operation/repo/commit.go +++ b/operation/repo/commit.go @@ -63,7 +63,11 @@ func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT SHA: sha, Path: path, } - commits, _, err := gitea.Client().ListRepoCommits(owner, repo, opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + commits, _, err := client.ListRepoCommits(owner, repo, opt) if err != nil { return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err)) } diff --git a/operation/repo/file.go b/operation/repo/file.go index 60a8998..01465df 100644 --- a/operation/repo/file.go +++ b/operation/repo/file.go @@ -124,7 +124,11 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo if !ok { return to.ErrorResult(fmt.Errorf("filePath is required")) } - content, _, err := gitea.Client().GetContents(owner, repo, ref, filePath) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + content, _, err := client.GetContents(owner, repo, ref, filePath) if err != nil { return to.ErrorResult(fmt.Errorf("get file err: %v", err)) } @@ -184,7 +188,11 @@ func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo if !ok { return to.ErrorResult(fmt.Errorf("filePath is required")) } - content, _, err := gitea.Client().ListContents(owner, repo, ref, filePath) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + content, _, err := client.ListContents(owner, repo, ref, filePath) if err != nil { return to.ErrorResult(fmt.Errorf("get dir content err: %v", err)) } @@ -216,7 +224,11 @@ func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe }, } - _, _, err := gitea.Client().CreateFile(owner, repo, filePath, opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, _, err = client.CreateFile(owner, repo, filePath, opt) if err != nil { return to.ErrorResult(fmt.Errorf("create file err: %v", err)) } @@ -253,7 +265,11 @@ func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe BranchName: branchName, }, } - _, _, err := gitea.Client().UpdateFile(owner, repo, filePath, opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, _, err = client.UpdateFile(owner, repo, filePath, opt) if err != nil { return to.ErrorResult(fmt.Errorf("update file err: %v", err)) } @@ -287,7 +303,11 @@ func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe }, SHA: sha, } - _, err := gitea.Client().DeleteFile(owner, repo, filePath, opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, err = client.DeleteFile(owner, repo, filePath, opt) if err != nil { return to.ErrorResult(fmt.Errorf("delete file err: %v", err)) } diff --git a/operation/repo/release.go b/operation/repo/release.go index 1167d08..7bdf4b5 100644 --- a/operation/repo/release.go +++ b/operation/repo/release.go @@ -134,7 +134,11 @@ func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo isPreRelease, _ := req.GetArguments()["is_pre_release"].(bool) body, _ := req.GetArguments()["body"].(string) - _, _, err := gitea.Client().CreateRelease(owner, repo, gitea_sdk.CreateReleaseOption{ + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, _, err = client.CreateRelease(owner, repo, gitea_sdk.CreateReleaseOption{ TagName: tagName, Target: target, Title: title, @@ -164,7 +168,11 @@ func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo return nil, fmt.Errorf("id is required") } - _, err := gitea.Client().DeleteRelease(owner, repo, int64(id)) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, err = client.DeleteRelease(owner, repo, int64(id)) if err != nil { return nil, fmt.Errorf("delete release error: %v", err) } @@ -187,7 +195,11 @@ func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe return nil, fmt.Errorf("id is required") } - release, _, err := gitea.Client().GetRelease(owner, repo, int64(id)) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + release, _, err := client.GetRelease(owner, repo, int64(id)) if err != nil { return nil, fmt.Errorf("get release error: %v", err) } @@ -206,7 +218,11 @@ func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call return nil, fmt.Errorf("repo is required") } - release, _, err := gitea.Client().GetLatestRelease(owner, repo) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + release, _, err := client.GetLatestRelease(owner, repo) if err != nil { return nil, fmt.Errorf("get latest release error: %v", err) } @@ -237,7 +253,11 @@ func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool page, _ := req.GetArguments()["page"].(float64) pageSize, _ := req.GetArguments()["pageSize"].(float64) - releases, _, err := gitea.Client().ListReleases(owner, repo, gitea_sdk.ListReleasesOptions{ + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + releases, _, err := client.ListReleases(owner, repo, gitea_sdk.ListReleasesOptions{ ListOptions: gitea_sdk.ListOptions{ Page: int(page), PageSize: int(pageSize), diff --git a/operation/repo/repo.go b/operation/repo/repo.go index b6c633a..76b202c 100644 --- a/operation/repo/repo.go +++ b/operation/repo/repo.go @@ -137,14 +137,17 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe } var repo *gitea_sdk.Repository - var err error + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } if organization != "" { - repo, _, err = gitea.Client().CreateOrgRepo(organization, opt) + repo, _, err = client.CreateOrgRepo(organization, opt) if err != nil { return to.ErrorResult(fmt.Errorf("create organization repository '%s' in '%s' err: %v", name, organization, err)) } } else { - repo, _, err = gitea.Client().CreateRepo(opt) + repo, _, err = client.CreateRepo(opt) if err != nil { return to.ErrorResult(fmt.Errorf("create repository '%s' err: %v", name, err)) } @@ -176,7 +179,11 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu Organization: organizationPtr, Name: namePtr, } - _, _, err := gitea.Client().CreateFork(user, repo, opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, _, err = client.CreateFork(user, repo, opt) if err != nil { return to.ErrorResult(fmt.Errorf("fork repository error: %v", err)) } @@ -199,7 +206,11 @@ func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR PageSize: int(pageSize), }, } - repos, _, err := gitea.Client().ListMyRepos(opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + repos, _, err := client.ListMyRepos(opt) if err != nil { return to.ErrorResult(fmt.Errorf("list my repositories error: %v", err)) } diff --git a/operation/repo/tag.go b/operation/repo/tag.go index b39cf45..02fcb7a 100644 --- a/operation/repo/tag.go +++ b/operation/repo/tag.go @@ -102,7 +102,11 @@ func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes target, _ := req.GetArguments()["target"].(string) message, _ := req.GetArguments()["message"].(string) - _, _, err := gitea.Client().CreateTag(owner, repo, gitea_sdk.CreateTagOption{ + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, _, err = client.CreateTag(owner, repo, gitea_sdk.CreateTagOption{ TagName: tagName, Target: target, Message: message, @@ -129,7 +133,11 @@ func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes return nil, fmt.Errorf("tag_name is required") } - _, err := gitea.Client().DeleteTag(owner, repo, tagName) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + _, err = client.DeleteTag(owner, repo, tagName) if err != nil { return nil, fmt.Errorf("delete tag error: %v", err) } @@ -152,7 +160,11 @@ func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult return nil, fmt.Errorf("tag_name is required") } - tag, _, err := gitea.Client().GetTag(owner, repo, tagName) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + tag, _, err := client.GetTag(owner, repo, tagName) if err != nil { return nil, fmt.Errorf("get tag error: %v", err) } @@ -173,7 +185,11 @@ func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu page, _ := req.GetArguments()["page"].(float64) pageSize, _ := req.GetArguments()["pageSize"].(float64) - tags, _, err := gitea.Client().ListRepoTags(owner, repo, gitea_sdk.ListRepoTagsOptions{ + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + tags, _, err := client.ListRepoTags(owner, repo, gitea_sdk.ListRepoTagsOptions{ ListOptions: gitea_sdk.ListOptions{ Page: int(page), PageSize: int(pageSize), diff --git a/operation/search/search.go b/operation/search/search.go index 71e0563..c18d252 100644 --- a/operation/search/search.go +++ b/operation/search/search.go @@ -94,7 +94,11 @@ func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR PageSize: int(pageSize), }, } - users, _, err := gitea.Client().SearchUsers(opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + users, _, err := client.SearchUsers(opt) if err != nil { return to.ErrorResult(fmt.Errorf("search users err: %v", err)) } @@ -128,7 +132,11 @@ func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo PageSize: int(pageSize), }, } - teams, _, err := gitea.Client().SearchOrgTeams(org, &opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + teams, _, err := client.SearchOrgTeams(org, &opt) if err != nil { return to.ErrorResult(fmt.Errorf("search organization teams error: %v", err)) } @@ -178,7 +186,11 @@ func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR PageSize: int(pageSize), }, } - repos, _, err := gitea.Client().SearchRepos(opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + repos, _, err := client.SearchRepos(opt) if err != nil { return to.ErrorResult(fmt.Errorf("search repos error: %v", err)) } diff --git a/operation/user/user.go b/operation/user/user.go index 5466f8e..acb62f3 100644 --- a/operation/user/user.go +++ b/operation/user/user.go @@ -79,7 +79,11 @@ func getIntArg(req mcp.CallToolRequest, name string, def int) int { // Logs invocation, fetches current user info from gitea, wraps result for MCP. func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("[User] Called GetUserInfoFn") - user, _, err := gitea.Client().GetMyUserInfo() + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + user, _, err := client.GetMyUserInfo() if err != nil { return to.ErrorResult(fmt.Errorf("get user info err: %v", err)) } @@ -100,7 +104,11 @@ func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR PageSize: pageSize, }, } - orgs, _, err := gitea.Client().ListMyOrgs(opt) + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + orgs, _, err := client.ListMyOrgs(opt) if err != nil { return to.ErrorResult(fmt.Errorf("get user orgs err: %v", err)) } diff --git a/pkg/context/context.go b/pkg/context/context.go new file mode 100644 index 0000000..1671037 --- /dev/null +++ b/pkg/context/context.go @@ -0,0 +1,7 @@ +package context + +type contextKey string + +const ( + TokenContextKey = contextKey("token") +) diff --git a/pkg/gitea/gitea.go b/pkg/gitea/gitea.go index 8d3d008..1b7bc9c 100644 --- a/pkg/gitea/gitea.go +++ b/pkg/gitea/gitea.go @@ -1,52 +1,47 @@ package gitea import ( + "context" "crypto/tls" "fmt" "net/http" - "sync" - - "gitea.com/gitea/gitea-mcp/pkg/flag" - "gitea.com/gitea/gitea-mcp/pkg/log" "code.gitea.io/sdk/gitea" + mcpContext "gitea.com/gitea/gitea-mcp/pkg/context" + "gitea.com/gitea/gitea-mcp/pkg/flag" ) -var ( - client *gitea.Client - clientOnce sync.Once -) +func NewClient(token string) (*gitea.Client, error) { + httpClient := &http.Client{ + Transport: http.DefaultTransport, + } -func Client() *gitea.Client { - clientOnce.Do(func() { - var err error - if client != nil { - return + opts := []gitea.ClientOption{ + gitea.SetToken(token), + } + if flag.Insecure { + httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{ + InsecureSkipVerify: true, } + } + opts = append(opts, gitea.SetHTTPClient(httpClient)) + if flag.Debug { + opts = append(opts, gitea.SetDebugMode()) + } + client, err := gitea.NewClient(flag.Host, opts...) + if err != nil { + return nil, fmt.Errorf("create gitea client err: %w", err) + } - httpClient := &http.Client{ - Transport: http.DefaultTransport, - } - - opts := []gitea.ClientOption{ - gitea.SetToken(flag.Token), - } - if flag.Insecure { - httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, - } - } - opts = append(opts, gitea.SetHTTPClient(httpClient)) - if flag.Debug { - opts = append(opts, gitea.SetDebugMode()) - } - client, err = gitea.NewClient(flag.Host, opts...) - if err != nil { - log.Fatalf("create gitea client err: %v", err) - } - - // Set user agent for the client - client.SetUserAgent(fmt.Sprintf("gitea-mcp-server/%s", flag.Version)) - }) - return client + // Set user agent for the client + client.SetUserAgent(fmt.Sprintf("gitea-mcp-server/%s", flag.Version)) + return client, nil +} + +func ClientFromContext(ctx context.Context) (*gitea.Client, error) { + token, ok := ctx.Value(mcpContext.TokenContextKey).(string) + if !ok { + token = flag.Token + } + return NewClient(token) }