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:
Darren Hoo
2025-09-12 03:57:57 +00:00
committed by Lunny Xiao
parent dc3e120e97
commit d7addd56c4
17 changed files with 310 additions and 92 deletions

View File

@@ -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>"
}
}
}
}

View File

@@ -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>"
}
}
}
}

View File

@@ -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>"
}
}
}
}

View File

@@ -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))
}

View File

@@ -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))
}

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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))
}

View File

@@ -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))
}

View File

@@ -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))
}

View File

@@ -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),

View File

@@ -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))
}

View File

@@ -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),

View File

@@ -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))
}

View File

@@ -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
View File

@@ -0,0 +1,7 @@
package context
type contextKey string
const (
TokenContextKey = contextKey("token")
)

View File

@@ -1,35 +1,23 @@
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 Client() *gitea.Client {
clientOnce.Do(func() {
var err error
if client != nil {
return
}
func NewClient(token string) (*gitea.Client, error) {
httpClient := &http.Client{
Transport: http.DefaultTransport,
}
opts := []gitea.ClientOption{
gitea.SetToken(flag.Token),
gitea.SetToken(token),
}
if flag.Insecure {
httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{
@@ -40,13 +28,20 @@ func Client() *gitea.Client {
if flag.Debug {
opts = append(opts, gitea.SetDebugMode())
}
client, err = gitea.NewClient(flag.Host, opts...)
client, err := gitea.NewClient(flag.Host, opts...)
if err != nil {
log.Fatalf("create gitea client err: %v", err)
return nil, fmt.Errorf("create gitea client err: %w", err)
}
// Set user agent for the client
client.SetUserAgent(fmt.Sprintf("gitea-mcp-server/%s", flag.Version))
})
return client
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)
}