mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2025-09-13 16:33:15 +00:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d7addd56c4 | ||
|
dc3e120e97 | ||
|
f33b04a3df | ||
|
ba07925969 | ||
|
5c2ff6dcb2 | ||
|
feaedaf604 | ||
|
a601d6b698 | ||
|
62cb6e7830 | ||
|
9fff996294 | ||
|
4c3f5149d8 |
@@ -24,18 +24,3 @@ jobs:
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
go-package: ./...
|
||||
|
||||
code-scan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run Trivy vulnerability scanner in repo mode
|
||||
uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
scan-type: 'fs'
|
||||
ignore-unfixed: true
|
||||
format: 'sarif'
|
||||
output: 'trivy-results.sarif'
|
||||
exit-code: '1'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
|
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>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
go.mod
8
go.mod
@@ -4,20 +4,26 @@ go 1.24.0
|
||||
|
||||
require (
|
||||
code.gitea.io/sdk/gitea v0.21.0
|
||||
github.com/mark3labs/mcp-go v0.35.0
|
||||
github.com/mark3labs/mcp-go v0.36.0
|
||||
go.uber.org/zap v1.27.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/42wim/httpsig v1.2.3 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
17
go.sum
17
go.sum
@@ -2,6 +2,10 @@ code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
|
||||
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
|
||||
github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
|
||||
github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||
@@ -16,12 +20,17 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mark3labs/mcp-go v0.35.0 h1:eh5bJGGVkNEaehCbPmAFqFgk/SB18YvxmsR2rnPm8BQ=
|
||||
github.com/mark3labs/mcp-go v0.35.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mark3labs/mcp-go v0.36.0 h1:rIZaijrRYPeSbJG8/qNDe0hWlGrCJ7FWHNMz2SQpTis=
|
||||
github.com/mark3labs/mcp-go v0.36.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
@@ -30,6 +39,8 @@ github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -56,6 +67,8 @@ golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
@@ -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))
|
||||
}
|
||||
|
454
operation/label/label.go
Normal file
454
operation/label/label.go
Normal file
@@ -0,0 +1,454 @@
|
||||
package label
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/log"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/ptr"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/to"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/tool"
|
||||
|
||||
gitea_sdk "code.gitea.io/sdk/gitea"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
var Tool = tool.New()
|
||||
|
||||
const (
|
||||
ListRepoLabelsToolName = "list_repo_labels"
|
||||
GetRepoLabelToolName = "get_repo_label"
|
||||
CreateRepoLabelToolName = "create_repo_label"
|
||||
EditRepoLabelToolName = "edit_repo_label"
|
||||
DeleteRepoLabelToolName = "delete_repo_label"
|
||||
AddIssueLabelsToolName = "add_issue_labels"
|
||||
ReplaceIssueLabelsToolName = "replace_issue_labels"
|
||||
ClearIssueLabelsToolName = "clear_issue_labels"
|
||||
RemoveIssueLabelToolName = "remove_issue_label"
|
||||
)
|
||||
|
||||
var (
|
||||
ListRepoLabelsTool = mcp.NewTool(
|
||||
ListRepoLabelsToolName,
|
||||
mcp.WithDescription("Lists all labels for a given repository"),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
|
||||
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
|
||||
)
|
||||
|
||||
GetRepoLabelTool = mcp.NewTool(
|
||||
GetRepoLabelToolName,
|
||||
mcp.WithDescription("Gets a single label by its ID for a repository"),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")),
|
||||
)
|
||||
|
||||
CreateRepoLabelTool = mcp.NewTool(
|
||||
CreateRepoLabelToolName,
|
||||
mcp.WithDescription("Creates a new label for a repository"),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithString("name", mcp.Required(), mcp.Description("label name")),
|
||||
mcp.WithString("color", mcp.Required(), mcp.Description("label color (hex code, e.g., #RRGGBB)")),
|
||||
mcp.WithString("description", mcp.Description("label description")),
|
||||
)
|
||||
|
||||
EditRepoLabelTool = mcp.NewTool(
|
||||
EditRepoLabelToolName,
|
||||
mcp.WithDescription("Edits an existing label in a repository"),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")),
|
||||
mcp.WithString("name", mcp.Description("new label name")),
|
||||
mcp.WithString("color", mcp.Description("new label color (hex code, e.g., #RRGGBB)")),
|
||||
mcp.WithString("description", mcp.Description("new label description")),
|
||||
)
|
||||
|
||||
DeleteRepoLabelTool = mcp.NewTool(
|
||||
DeleteRepoLabelToolName,
|
||||
mcp.WithDescription("Deletes a label from a repository"),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")),
|
||||
)
|
||||
|
||||
AddIssueLabelsTool = mcp.NewTool(
|
||||
AddIssueLabelsToolName,
|
||||
mcp.WithDescription("Adds one or more labels to an issue"),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
|
||||
mcp.WithArray("labels", mcp.Required(), mcp.Description("array of label IDs to add"), mcp.Items(map[string]interface{}{"type": "number"})),
|
||||
)
|
||||
|
||||
ReplaceIssueLabelsTool = mcp.NewTool(
|
||||
ReplaceIssueLabelsToolName,
|
||||
mcp.WithDescription("Replaces all labels on an issue"),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
|
||||
mcp.WithArray("labels", mcp.Required(), mcp.Description("array of label IDs to replace with"), mcp.Items(map[string]interface{}{"type": "number"})),
|
||||
)
|
||||
|
||||
ClearIssueLabelsTool = mcp.NewTool(
|
||||
ClearIssueLabelsToolName,
|
||||
mcp.WithDescription("Removes all labels from an issue"),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
|
||||
)
|
||||
|
||||
RemoveIssueLabelTool = mcp.NewTool(
|
||||
RemoveIssueLabelToolName,
|
||||
mcp.WithDescription("Removes a single label from an issue"),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
|
||||
mcp.WithNumber("label_id", mcp.Required(), mcp.Description("label ID to remove")),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: ListRepoLabelsTool,
|
||||
Handler: ListRepoLabelsFn,
|
||||
})
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: GetRepoLabelTool,
|
||||
Handler: GetRepoLabelFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: CreateRepoLabelTool,
|
||||
Handler: CreateRepoLabelFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: EditRepoLabelTool,
|
||||
Handler: EditRepoLabelFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: DeleteRepoLabelTool,
|
||||
Handler: DeleteRepoLabelFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: AddIssueLabelsTool,
|
||||
Handler: AddIssueLabelsFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: ReplaceIssueLabelsTool,
|
||||
Handler: ReplaceIssueLabelsFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: ClearIssueLabelsTool,
|
||||
Handler: ClearIssueLabelsFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: RemoveIssueLabelTool,
|
||||
Handler: RemoveIssueLabelFn,
|
||||
})
|
||||
}
|
||||
|
||||
func ListRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called ListRepoLabelsFn")
|
||||
owner, ok := req.GetArguments()["owner"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("owner is required"))
|
||||
}
|
||||
repo, ok := req.GetArguments()["repo"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("repo is required"))
|
||||
}
|
||||
page, ok := req.GetArguments()["page"].(float64)
|
||||
if !ok {
|
||||
page = 1
|
||||
}
|
||||
pageSize, ok := req.GetArguments()["pageSize"].(float64)
|
||||
if !ok {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
opt := gitea_sdk.ListLabelsOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: int(page),
|
||||
PageSize: int(pageSize),
|
||||
},
|
||||
}
|
||||
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))
|
||||
}
|
||||
return to.TextResult(labels)
|
||||
}
|
||||
|
||||
func GetRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called GetRepoLabelFn")
|
||||
owner, ok := req.GetArguments()["owner"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("owner is required"))
|
||||
}
|
||||
repo, ok := req.GetArguments()["repo"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("repo is required"))
|
||||
}
|
||||
id, ok := req.GetArguments()["id"].(float64)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("label ID is required"))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
return to.TextResult(label)
|
||||
}
|
||||
|
||||
func CreateRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called CreateRepoLabelFn")
|
||||
owner, ok := req.GetArguments()["owner"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("owner is required"))
|
||||
}
|
||||
repo, ok := req.GetArguments()["repo"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("repo is required"))
|
||||
}
|
||||
name, ok := req.GetArguments()["name"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("name is required"))
|
||||
}
|
||||
color, ok := req.GetArguments()["color"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("color is required"))
|
||||
}
|
||||
description, _ := req.GetArguments()["description"].(string) // Optional
|
||||
|
||||
opt := gitea_sdk.CreateLabelOption{
|
||||
Name: name,
|
||||
Color: color,
|
||||
Description: description,
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
return to.TextResult(label)
|
||||
}
|
||||
|
||||
func EditRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called EditRepoLabelFn")
|
||||
owner, ok := req.GetArguments()["owner"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("owner is required"))
|
||||
}
|
||||
repo, ok := req.GetArguments()["repo"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("repo is required"))
|
||||
}
|
||||
id, ok := req.GetArguments()["id"].(float64)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("label ID is required"))
|
||||
}
|
||||
|
||||
opt := gitea_sdk.EditLabelOption{}
|
||||
if name, ok := req.GetArguments()["name"].(string); ok {
|
||||
opt.Name = ptr.To(name)
|
||||
}
|
||||
if color, ok := req.GetArguments()["color"].(string); ok {
|
||||
opt.Color = ptr.To(color)
|
||||
}
|
||||
if description, ok := req.GetArguments()["description"].(string); ok {
|
||||
opt.Description = ptr.To(description)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
return to.TextResult(label)
|
||||
}
|
||||
|
||||
func DeleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called DeleteRepoLabelFn")
|
||||
owner, ok := req.GetArguments()["owner"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("owner is required"))
|
||||
}
|
||||
repo, ok := req.GetArguments()["repo"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("repo is required"))
|
||||
}
|
||||
id, ok := req.GetArguments()["id"].(float64)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("label ID is required"))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
return to.TextResult("Label deleted successfully")
|
||||
}
|
||||
|
||||
func AddIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called AddIssueLabelsFn")
|
||||
owner, ok := req.GetArguments()["owner"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("owner is required"))
|
||||
}
|
||||
repo, ok := req.GetArguments()["repo"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("repo is required"))
|
||||
}
|
||||
index, ok := req.GetArguments()["index"].(float64)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("issue index is required"))
|
||||
}
|
||||
labelsRaw, ok := req.GetArguments()["labels"].([]interface{})
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("labels (array of IDs) is required"))
|
||||
}
|
||||
var labels []int64
|
||||
for _, l := range labelsRaw {
|
||||
if labelID, ok := l.(float64); ok {
|
||||
labels = append(labels, int64(labelID))
|
||||
} else {
|
||||
return to.ErrorResult(fmt.Errorf("invalid label ID in labels array"))
|
||||
}
|
||||
}
|
||||
|
||||
opt := gitea_sdk.IssueLabelsOption{
|
||||
Labels: labels,
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
return to.TextResult(issueLabels)
|
||||
}
|
||||
|
||||
func ReplaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called ReplaceIssueLabelsFn")
|
||||
owner, ok := req.GetArguments()["owner"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("owner is required"))
|
||||
}
|
||||
repo, ok := req.GetArguments()["repo"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("repo is required"))
|
||||
}
|
||||
index, ok := req.GetArguments()["index"].(float64)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("issue index is required"))
|
||||
}
|
||||
labelsRaw, ok := req.GetArguments()["labels"].([]interface{})
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("labels (array of IDs) is required"))
|
||||
}
|
||||
var labels []int64
|
||||
for _, l := range labelsRaw {
|
||||
if labelID, ok := l.(float64); ok {
|
||||
labels = append(labels, int64(labelID))
|
||||
} else {
|
||||
return to.ErrorResult(fmt.Errorf("invalid label ID in labels array"))
|
||||
}
|
||||
}
|
||||
|
||||
opt := gitea_sdk.IssueLabelsOption{
|
||||
Labels: labels,
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
return to.TextResult(issueLabels)
|
||||
}
|
||||
|
||||
func ClearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called ClearIssueLabelsFn")
|
||||
owner, ok := req.GetArguments()["owner"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("owner is required"))
|
||||
}
|
||||
repo, ok := req.GetArguments()["repo"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("repo is required"))
|
||||
}
|
||||
index, ok := req.GetArguments()["index"].(float64)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("issue index is required"))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
return to.TextResult("Labels cleared successfully")
|
||||
}
|
||||
|
||||
func RemoveIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called RemoveIssueLabelFn")
|
||||
owner, ok := req.GetArguments()["owner"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("owner is required"))
|
||||
}
|
||||
repo, ok := req.GetArguments()["repo"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("repo is required"))
|
||||
}
|
||||
index, ok := req.GetArguments()["index"].(float64)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("issue index is required"))
|
||||
}
|
||||
labelID, ok := req.GetArguments()["label_id"].(float64)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("label ID is required"))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
return to.TextResult("Label removed successfully")
|
||||
}
|
@@ -1,15 +1,20 @@
|
||||
package operation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/operation/issue"
|
||||
"gitea.com/gitea/gitea-mcp/operation/label"
|
||||
"gitea.com/gitea/gitea-mcp/operation/pull"
|
||||
"gitea.com/gitea/gitea-mcp/operation/repo"
|
||||
"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"
|
||||
|
||||
@@ -28,6 +33,9 @@ func RegisterTool(s *server.MCPServer) {
|
||||
// Issue Tool
|
||||
s.AddTools(issue.Tool.Tools()...)
|
||||
|
||||
// Label Tool
|
||||
s.AddTools(label.Tool.Tools()...)
|
||||
|
||||
// Pull Tool
|
||||
s.AddTools(pull.Tool.Tools()...)
|
||||
|
||||
@@ -40,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)
|
||||
@@ -53,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 {
|
||||
@@ -63,6 +86,7 @@ func Run() error {
|
||||
mcpServer,
|
||||
server.WithLogger(log.New()),
|
||||
server.WithHeartbeatInterval(30*time.Second),
|
||||
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))
|
||||
}
|
||||
|
@@ -1,8 +1,11 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
@@ -30,6 +33,7 @@ var (
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithString("ref", mcp.Required(), mcp.Description("ref can be branch/tag/commit")),
|
||||
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
|
||||
mcp.WithBoolean("withLines", mcp.Description("whether to return file content with lines")),
|
||||
)
|
||||
|
||||
GetDirContentTool = mcp.NewTool(
|
||||
@@ -60,7 +64,7 @@ var (
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
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("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("branch_name", mcp.Required(), mcp.Description("branch name")),
|
||||
)
|
||||
@@ -100,6 +104,11 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
type ContentLine struct {
|
||||
LineNumber int `json:"line"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called GetFileFn")
|
||||
owner, ok := req.GetArguments()["owner"].(string)
|
||||
@@ -115,10 +124,52 @@ 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))
|
||||
}
|
||||
withLines, _ := req.GetArguments()["withLines"].(bool)
|
||||
if withLines {
|
||||
rawContent, err := base64.StdEncoding.DecodeString(*content.Content)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("decode base64 content err: %v", err))
|
||||
}
|
||||
|
||||
contentLines := make([]ContentLine, 0)
|
||||
line := 0
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(rawContent))
|
||||
|
||||
for scanner.Scan() {
|
||||
line++
|
||||
|
||||
contentLines = append(contentLines, ContentLine{
|
||||
LineNumber: line,
|
||||
Content: scanner.Text(),
|
||||
})
|
||||
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("scan content err: %v", err))
|
||||
}
|
||||
|
||||
// remove the last blank line if exists
|
||||
// git does not consider the last line as a new line
|
||||
if contentLines[len(contentLines)-1].Content == "" {
|
||||
contentLines = contentLines[:len(contentLines)-1]
|
||||
}
|
||||
|
||||
contentBytes, err := json.MarshalIndent(contentLines, "", " ")
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("marshal content lines err: %v", err))
|
||||
}
|
||||
contentStr := string(contentBytes)
|
||||
content.Content = &contentStr
|
||||
}
|
||||
return to.TextResult(content)
|
||||
}
|
||||
|
||||
@@ -137,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))
|
||||
}
|
||||
@@ -169,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))
|
||||
}
|
||||
@@ -206,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))
|
||||
}
|
||||
@@ -240,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))
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ var (
|
||||
mcp.WithString("title", mcp.Required(), mcp.Description("release title")),
|
||||
mcp.WithBoolean("is_draft", mcp.Description("Whether the release is draft"), mcp.DefaultBool(false)),
|
||||
mcp.WithBoolean("is_pre_release", mcp.Description("Whether the release is pre-release"), mcp.DefaultBool(false)),
|
||||
mcp.WithString("body", mcp.Description("release body")),
|
||||
)
|
||||
|
||||
DeleteReleaseTool = mcp.NewTool(
|
||||
@@ -131,11 +132,17 @@ func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
|
||||
}
|
||||
isDraft, _ := req.GetArguments()["is_draft"].(bool)
|
||||
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,
|
||||
Note: body,
|
||||
IsDraft: isDraft,
|
||||
IsPrerelease: isPreRelease,
|
||||
})
|
||||
@@ -161,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)
|
||||
}
|
||||
@@ -184,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)
|
||||
}
|
||||
@@ -203,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)
|
||||
}
|
||||
@@ -234,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),
|
||||
|
@@ -27,7 +27,7 @@ const (
|
||||
var (
|
||||
CreateRepoTool = mcp.NewTool(
|
||||
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("description", mcp.Description("Description of the repository to create")),
|
||||
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("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("organization", mcp.Description("Organization name to create repository in (optional - defaults to personal account)")),
|
||||
)
|
||||
|
||||
ForkRepoTool = mcp.NewTool(
|
||||
@@ -120,6 +121,7 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
|
||||
license, _ := req.GetArguments()["license"].(string)
|
||||
readme, _ := req.GetArguments()["readme"].(string)
|
||||
defaultBranch, _ := req.GetArguments()["default_branch"].(string)
|
||||
organization, _ := req.GetArguments()["organization"].(string)
|
||||
|
||||
opt := gitea_sdk.CreateRepoOption{
|
||||
Name: name,
|
||||
@@ -133,9 +135,22 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
|
||||
Readme: readme,
|
||||
DefaultBranch: defaultBranch,
|
||||
}
|
||||
repo, _, err := gitea.Client().CreateRepo(opt)
|
||||
|
||||
var repo *gitea_sdk.Repository
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
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)
|
||||
}
|
||||
@@ -164,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))
|
||||
}
|
||||
@@ -187,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))
|
||||
}
|
||||
|
@@ -15,68 +15,102 @@ import (
|
||||
)
|
||||
|
||||
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"
|
||||
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 (
|
||||
// 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(
|
||||
GetMyUserInfoToolName,
|
||||
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(
|
||||
GetUserOrgsToolName,
|
||||
mcp.WithDescription("Get organizations associated with the authenticated user"),
|
||||
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
|
||||
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
|
||||
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(defaultPage)),
|
||||
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() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: GetMyUserInfoTool,
|
||||
Handler: GetUserInfoFn,
|
||||
})
|
||||
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: GetUserOrgsTool,
|
||||
Handler: GetUserOrgsFn,
|
||||
})
|
||||
registerTools()
|
||||
}
|
||||
|
||||
// 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) {
|
||||
log.Debugf("Called GetUserInfoFn")
|
||||
user, _, err := gitea.Client().GetMyUserInfo()
|
||||
log.Debugf("[User] Called GetUserInfoFn")
|
||||
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))
|
||||
}
|
||||
|
||||
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) {
|
||||
log.Debugf("Called GetUserOrgsFn")
|
||||
page, ok := req.GetArguments()["page"].(float64)
|
||||
if !ok || page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize, ok := req.GetArguments()["pageSize"].(float64)
|
||||
if !ok || pageSize < 1 {
|
||||
pageSize = 100
|
||||
}
|
||||
log.Debugf("[User] Called GetUserOrgsFn")
|
||||
page := getIntArg(req, "page", defaultPage)
|
||||
pageSize := getIntArg(req, "pageSize", defaultPageSize)
|
||||
|
||||
opt := gitea_sdk.ListOrgsOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: int(page),
|
||||
PageSize: int(pageSize),
|
||||
Page: page,
|
||||
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))
|
||||
}
|
||||
|
||||
return to.TextResult(orgs)
|
||||
}
|
||||
|
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,48 +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)
|
||||
}
|
||||
})
|
||||
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