mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2025-09-13 08:23:15 +00:00
feat: read token from header in http/sse mode (#89)
this PR introduces support for per-request authentication tokens in HTTP and SSE modes. The server now inspects incoming requests for an `Authorization: Bearer <token>` header. Previously, the server operated with a single, globally configured Gitea token. This change allows different clients to use their own tokens when communicating with the MCP server, enhancing security and flexibility. To support this, the Gitea API client initialization has been refactored: - The global singleton Gitea client has been removed. - A new `ClientFromContext` function creates a Gitea client on-demand, using a token from the request context if available, and falling back to the globally configured token otherwise. - All tool functions now retrieve the client from the context for each call. The README has also been updated to reflect the new configuration option. Update: #59 Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/89 Reviewed-by: hiifong <i@hiif.ong> Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Darren Hoo <darren.hoo@gmail.com> Co-committed-by: Darren Hoo <darren.hoo@gmail.com>
This commit is contained in:
10
README.md
10
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 <your personal access token>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <your personal access token>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 <your personal access token>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <your personal access token>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 <your personal access token>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <your personal access token>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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),
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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),
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
||||
|
7
pkg/context/context.go
Normal file
7
pkg/context/context.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package context
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
TokenContextKey = contextKey("token")
|
||||
)
|
@@ -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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user