4 Commits
v0.3.1 ... main

Author SHA1 Message Date
Darren Hoo
d7addd56c4 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>
2025-09-12 03:57:57 +00:00
hiifong
dc3e120e97 Update operation/repo/file.go 2025-08-29 05:57:44 +00:00
marcluer
f33b04a3df feat: added parameter 'organization' to tool 'create_repo' (#88)
Using the Gitea-mcp server I was missing the ability to create repositories in other organizations. e.g.:
* I was only able to create `https://gitea.domain.com/myuser/repo` 
* I was not able to create `https://gitea.domain.com/organization/repo` 

This feature was planned, implemented and compiled by Claude Code. I have no clue about Golang.

I then took the resulting `gitea-mcp` file and sucessfully tested it on my self-hosted gitea instance:
* Creating `https://gitea.domain.com/myuser/repo` 
* Creating `https://gitea.domain.com/organization/repo` 

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/88
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: marcluer <gitea@marcluerssen.de>
Co-committed-by: marcluer <gitea@marcluerssen.de>
2025-08-29 05:37:19 +00:00
appleboy
ba07925969 refactor: refactor MCP tool registration and pagination handling (#86)
- Add documentation for MCP tool constants and tool registration
- Use configurable default values for pagination arguments in user organization queries
- Introduce registerTools helper to streamline MCP tool registration
- Refactor pagination argument parsing into a reusable getIntArg function
- Add descriptive logging for tool handler execution
- Improve code organization for defining and registering MCP tools

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/86
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-08-23 04:31:57 +00:00
17 changed files with 375 additions and 119 deletions

View File

@@ -139,7 +139,10 @@ To configure the MCP server for Gitea, add the following to your MCP configurati
{ {
"mcpServers": { "mcpServers": {
"gitea": { "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": { "mcpServers": {
"gitea": { "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": { "mcpServers": {
"gitea": { "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": { "mcpServers": {
"gitea": { "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": { "mcpServers": {
"gitea": { "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": { "mcpServers": {
"gitea": { "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 { if !ok {
return to.ErrorResult(fmt.Errorf("index is required")) 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) 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), 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/issues err: %v", owner, repo, err)) 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 { if !ok {
return to.ErrorResult(fmt.Errorf("body is required")) 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, Title: title,
Body: body, Body: body,
}) })
@@ -234,7 +246,11 @@ func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
opt := gitea_sdk.CreateIssueCommentOption{ opt := gitea_sdk.CreateIssueCommentOption{
Body: body, 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err: %v", owner, repo, int64(index), err)) 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)) 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) 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{ opt := gitea_sdk.EditIssueCommentOption{
Body: body, 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/issues/comments/%v err: %v", owner, repo, int64(commentID), err)) 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")) return to.ErrorResult(fmt.Errorf("index is required"))
} }
opt := gitea_sdk.ListIssueCommentOptions{} 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/comments err: %v", owner, repo, int64(index), err)) 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), 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("list %v/%v/labels err: %v", owner, repo, err)) 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")) 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/label/%v err: %v", owner, repo, int64(id), err)) 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, 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/%v/label err: %v", owner, repo, err)) 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) 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/label/%v err: %v", owner, repo, int64(id), err)) 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")) 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("delete %v/%v/label/%v err: %v", owner, repo, int64(id), err)) 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, 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) 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, 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) 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")) 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("clear labels on %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) 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")) 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 { 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)) 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 package operation
import ( import (
"context"
"fmt" "fmt"
"net/http"
"strings"
"time" "time"
"gitea.com/gitea/gitea-mcp/operation/issue" "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/search"
"gitea.com/gitea/gitea-mcp/operation/user" "gitea.com/gitea/gitea-mcp/operation/user"
"gitea.com/gitea/gitea-mcp/operation/version" "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/flag"
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
@@ -44,6 +48,20 @@ func RegisterTool(s *server.MCPServer) {
s.DeleteTools("") 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 { func Run() error {
mcpServer = newMCPServer(flag.Version) mcpServer = newMCPServer(flag.Version)
RegisterTool(mcpServer) RegisterTool(mcpServer)
@@ -57,6 +75,7 @@ func Run() error {
case "sse": case "sse":
sseServer := server.NewSSEServer( sseServer := server.NewSSEServer(
mcpServer, mcpServer,
server.WithSSEContextFunc(getContextWithToken),
) )
log.Infof("Gitea MCP SSE server listening on :%d", flag.Port) log.Infof("Gitea MCP SSE server listening on :%d", flag.Port)
if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil { if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
@@ -67,7 +86,7 @@ func Run() error {
mcpServer, mcpServer,
server.WithLogger(log.New()), server.WithLogger(log.New()),
server.WithHeartbeatInterval(30*time.Second), server.WithHeartbeatInterval(30*time.Second),
server.WithStateLess(true), server.WithHTTPContextFunc(getContextWithToken),
) )
log.Infof("Gitea MCP HTTP server listening on :%d", flag.Port) log.Infof("Gitea MCP HTTP server listening on :%d", flag.Port)
if err := httpServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil { 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 { if !ok {
return to.ErrorResult(fmt.Errorf("index is required")) 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, int64(index), err)) 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), 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("list %v/%v/pull_requests err: %v", owner, repo, err)) 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 { if !ok {
return to.ErrorResult(fmt.Errorf("base is required")) 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, Title: title,
Body: body, Body: body,
Head: head, Head: head,

View File

@@ -76,7 +76,11 @@ func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
} }
oldBranch, _ := req.GetArguments()["old_branch"].(string) 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, BranchName: branch,
OldBranchName: oldBranch, OldBranchName: oldBranch,
}) })
@@ -101,7 +105,11 @@ func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("branch is required")) 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("delete branch error: %v", err)) 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, 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("list branches error: %v", err)) 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, SHA: sha,
Path: path, 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err)) return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err))
} }

View File

@@ -64,7 +64,7 @@ var (
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")), mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
mcp.WithString("sha", mcp.Required(), mcp.Description("sha is the SHA for the file that already exists")), mcp.WithString("sha", mcp.Required(), mcp.Description("sha is the SHA for the file that already exists")),
mcp.WithString("content", mcp.Required(), mcp.Description("file content, base64 encoded")), mcp.WithString("content", mcp.Required(), mcp.Description("file content")),
mcp.WithString("message", mcp.Required(), mcp.Description("commit message")), mcp.WithString("message", mcp.Required(), mcp.Description("commit message")),
mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")), mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")),
) )
@@ -124,7 +124,11 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("filePath is required")) 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("get file err: %v", err)) 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 { if !ok {
return to.ErrorResult(fmt.Errorf("filePath is required")) 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("get dir content err: %v", err)) 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("create file err: %v", err)) 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, 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("update file err: %v", err)) 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, 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("delete file err: %v", err)) 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) isPreRelease, _ := req.GetArguments()["is_pre_release"].(bool)
body, _ := req.GetArguments()["body"].(string) 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, TagName: tagName,
Target: target, Target: target,
Title: title, Title: title,
@@ -164,7 +168,11 @@ func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
return nil, fmt.Errorf("id is required") 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 { if err != nil {
return nil, fmt.Errorf("delete release error: %v", err) 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") 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 { if err != nil {
return nil, fmt.Errorf("get release error: %v", err) 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") 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 { if err != nil {
return nil, fmt.Errorf("get latest release error: %v", err) 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) page, _ := req.GetArguments()["page"].(float64)
pageSize, _ := req.GetArguments()["pageSize"].(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{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: int(page),
PageSize: int(pageSize), PageSize: int(pageSize),

View File

@@ -27,7 +27,7 @@ const (
var ( var (
CreateRepoTool = mcp.NewTool( CreateRepoTool = mcp.NewTool(
CreateRepoToolName, CreateRepoToolName,
mcp.WithDescription("Create repository"), mcp.WithDescription("Create repository in personal account or organization"),
mcp.WithString("name", mcp.Required(), mcp.Description("Name of the repository to create")), mcp.WithString("name", mcp.Required(), mcp.Description("Name of the repository to create")),
mcp.WithString("description", mcp.Description("Description of the repository to create")), mcp.WithString("description", mcp.Description("Description of the repository to create")),
mcp.WithBoolean("private", mcp.Description("Whether the repository is private")), mcp.WithBoolean("private", mcp.Description("Whether the repository is private")),
@@ -38,6 +38,7 @@ var (
mcp.WithString("license", mcp.Description("License to use")), mcp.WithString("license", mcp.Description("License to use")),
mcp.WithString("readme", mcp.Description("Readme of the repository to create")), mcp.WithString("readme", mcp.Description("Readme of the repository to create")),
mcp.WithString("default_branch", mcp.Description("DefaultBranch of the repository (used when initializes and in template)")), mcp.WithString("default_branch", mcp.Description("DefaultBranch of the repository (used when initializes and in template)")),
mcp.WithString("organization", mcp.Description("Organization name to create repository in (optional - defaults to personal account)")),
) )
ForkRepoTool = mcp.NewTool( ForkRepoTool = mcp.NewTool(
@@ -120,6 +121,7 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
license, _ := req.GetArguments()["license"].(string) license, _ := req.GetArguments()["license"].(string)
readme, _ := req.GetArguments()["readme"].(string) readme, _ := req.GetArguments()["readme"].(string)
defaultBranch, _ := req.GetArguments()["default_branch"].(string) defaultBranch, _ := req.GetArguments()["default_branch"].(string)
organization, _ := req.GetArguments()["organization"].(string)
opt := gitea_sdk.CreateRepoOption{ opt := gitea_sdk.CreateRepoOption{
Name: name, Name: name,
@@ -133,9 +135,22 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
Readme: readme, Readme: readme,
DefaultBranch: defaultBranch, DefaultBranch: defaultBranch,
} }
repo, _, err := gitea.Client().CreateRepo(opt)
var repo *gitea_sdk.Repository
client, err := gitea.ClientFromContext(ctx)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create repo err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
if organization != "" {
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 = client.CreateRepo(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create repository '%s' err: %v", name, err))
}
} }
return to.TextResult(repo) return to.TextResult(repo)
} }
@@ -164,7 +179,11 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
Organization: organizationPtr, Organization: organizationPtr,
Name: namePtr, 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("fork repository error: %v", err)) return to.ErrorResult(fmt.Errorf("fork repository error: %v", err))
} }
@@ -187,7 +206,11 @@ func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
PageSize: int(pageSize), 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("list my repositories error: %v", err)) 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) target, _ := req.GetArguments()["target"].(string)
message, _ := req.GetArguments()["message"].(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, TagName: tagName,
Target: target, Target: target,
Message: message, Message: message,
@@ -129,7 +133,11 @@ func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
return nil, fmt.Errorf("tag_name is required") 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 { if err != nil {
return nil, fmt.Errorf("delete tag error: %v", err) 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") 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 { if err != nil {
return nil, fmt.Errorf("get tag error: %v", err) 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) page, _ := req.GetArguments()["page"].(float64)
pageSize, _ := req.GetArguments()["pageSize"].(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{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: int(page),
PageSize: int(pageSize), PageSize: int(pageSize),

View File

@@ -94,7 +94,11 @@ func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
PageSize: int(pageSize), 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("search users err: %v", err)) 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), 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("search organization teams error: %v", err)) 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), 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("search repos error: %v", err)) return to.ErrorResult(fmt.Errorf("search repos error: %v", err))
} }

View File

@@ -15,68 +15,102 @@ import (
) )
const ( const (
// GetMyUserInfoToolName is the unique tool name used for MCP registration and lookup of the get_my_user_info command.
GetMyUserInfoToolName = "get_my_user_info" GetMyUserInfoToolName = "get_my_user_info"
GetUserOrgsToolName = "get_user_orgs" // GetUserOrgsToolName is the unique tool name used for MCP registration and lookup of the get_user_orgs command.
GetUserOrgsToolName = "get_user_orgs"
// defaultPage is the default starting page number used for paginated organization listings.
defaultPage = 1
// defaultPageSize is the default number of organizations per page for paginated queries.
defaultPageSize = 100
) )
// Tool is the MCP tool manager instance for registering all MCP tools in this package.
var Tool = tool.New() var Tool = tool.New()
var ( var (
// GetMyUserInfoTool is the MCP tool for retrieving the current user's info.
// It is registered with a specific name and a description string.
GetMyUserInfoTool = mcp.NewTool( GetMyUserInfoTool = mcp.NewTool(
GetMyUserInfoToolName, GetMyUserInfoToolName,
mcp.WithDescription("Get my user info"), mcp.WithDescription("Get my user info"),
) )
// GetUserOrgsTool is the MCP tool for listing organizations for the authenticated user.
// It supports pagination via "page" and "pageSize" arguments with default values specified above.
GetUserOrgsTool = mcp.NewTool( GetUserOrgsTool = mcp.NewTool(
GetUserOrgsToolName, GetUserOrgsToolName,
mcp.WithDescription("Get organizations associated with the authenticated user"), mcp.WithDescription("Get organizations associated with the authenticated user"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(defaultPage)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(defaultPageSize)),
) )
) )
// init registers all MCP tools in Tool at package initialization.
// This function ensures the handler functions are registered before server usage.
func init() { func init() {
Tool.RegisterRead(server.ServerTool{ registerTools()
Tool: GetMyUserInfoTool,
Handler: GetUserInfoFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: GetUserOrgsTool,
Handler: GetUserOrgsFn,
})
} }
// registerTools registers all local MCP tool definitions and their handler functions.
// To add new functionality, append your tool/handler pair to the tools slice below.
func registerTools() {
tools := []server.ServerTool{
{Tool: GetMyUserInfoTool, Handler: GetUserInfoFn},
{Tool: GetUserOrgsTool, Handler: GetUserOrgsFn},
}
for _, t := range tools {
Tool.RegisterRead(t)
}
}
// getIntArg parses an integer argument from the MCP request arguments map.
// Returns def if missing, not a number, or less than 1. Used for pagination arguments.
func getIntArg(req mcp.CallToolRequest, name string, def int) int {
val, ok := req.GetArguments()[name].(float64)
if !ok || val < 1 {
return def
}
return int(val)
}
// GetUserInfoFn is the handler for "get_my_user_info" MCP tool requests.
// Logs invocation, fetches current user info from gitea, wraps result for MCP.
func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetUserInfoFn") 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("get user info err: %v", err)) return to.ErrorResult(fmt.Errorf("get user info err: %v", err))
} }
return to.TextResult(user) return to.TextResult(user)
} }
// GetUserOrgsFn is the handler for "get_user_orgs" MCP tool requests.
// Logs invocation, pulls validated pagination arguments from request,
// performs Gitea organization listing, and wraps the result for MCP.
func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetUserOrgsFn") log.Debugf("[User] Called GetUserOrgsFn")
page, ok := req.GetArguments()["page"].(float64) page := getIntArg(req, "page", defaultPage)
if !ok || page < 1 { pageSize := getIntArg(req, "pageSize", defaultPageSize)
page = 1
}
pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok || pageSize < 1 {
pageSize = 100
}
opt := gitea_sdk.ListOrgsOptions{ opt := gitea_sdk.ListOrgsOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), 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 { if err != nil {
return to.ErrorResult(fmt.Errorf("get user orgs err: %v", err)) return to.ErrorResult(fmt.Errorf("get user orgs err: %v", err))
} }
return to.TextResult(orgs) return to.TextResult(orgs)
} }

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,52 +1,47 @@
package gitea package gitea
import ( import (
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net/http" "net/http"
"sync"
"gitea.com/gitea/gitea-mcp/pkg/flag"
"gitea.com/gitea/gitea-mcp/pkg/log"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
mcpContext "gitea.com/gitea/gitea-mcp/pkg/context"
"gitea.com/gitea/gitea-mcp/pkg/flag"
) )
var ( func NewClient(token string) (*gitea.Client, error) {
client *gitea.Client httpClient := &http.Client{
clientOnce sync.Once Transport: http.DefaultTransport,
) }
func Client() *gitea.Client { opts := []gitea.ClientOption{
clientOnce.Do(func() { gitea.SetToken(token),
var err error }
if client != nil { if flag.Insecure {
return 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{ // Set user agent for the client
Transport: http.DefaultTransport, client.SetUserAgent(fmt.Sprintf("gitea-mcp-server/%s", flag.Version))
} return client, nil
}
opts := []gitea.ClientOption{
gitea.SetToken(flag.Token), func ClientFromContext(ctx context.Context) (*gitea.Client, error) {
} token, ok := ctx.Value(mcpContext.TokenContextKey).(string)
if flag.Insecure { if !ok {
httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{ token = flag.Token
InsecureSkipVerify: true, }
} return NewClient(token)
}
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
} }