mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2025-10-29 09:21:49 +00:00
This PR adds full support for managing organization-level labels via MCP. It uses the newly added SDK APIs now available on main branch, see https://gitea.com/gitea/go-sdk/issues/732. Registers following tools under label module and wires them into the MCP server as read/write tools: - list_org_labels: list labels defined at the organization level (pagination supported) - create_org_label: create a label in an organization (name, color, description, exclusive) - edit_org_label: edit an organization label (name, color, description, exclusive) - delete_org_label: delete an organization label by ID Dependency note: go.mod/go.sum updated to use the SDK main branch pseudo-version that includes the org-label APIs. If you prefer to merge only after a tagged SDK release, I can bump the dependency to the new tag as soon as it’s available. Thanks for considering! Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/102 Reviewed-by: Bo-Yi Wu (吳柏毅) <appleboy.tw@gmail.com> Co-authored-by: Daniel <danielwichers@gmail.com> Co-committed-by: Daniel <danielwichers@gmail.com>
This commit is contained in:
@@ -201,6 +201,10 @@ The Gitea MCP Server supports the following tools:
|
||||
| create_pull_request | Pull Request | Create a new pull request |
|
||||
| search_users | User | Search for users |
|
||||
| search_org_teams | Organization | Search for teams in an organization |
|
||||
| list_org_labels | Organization | List labels defined at organization level |
|
||||
| create_org_label | Organization | Create a label in an organization |
|
||||
| edit_org_label | Organization | Edit a label in an organization |
|
||||
| delete_org_label | Organization | Delete a label in an organization |
|
||||
| search_repos | Repository | Search for repositories |
|
||||
| get_gitea_mcp_server_version | Server | Get the version of the Gitea MCP Server |
|
||||
| list_wiki_pages | Wiki | List all wiki pages in a repository |
|
||||
|
||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module gitea.com/gitea/gitea-mcp
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
code.gitea.io/sdk/gitea v0.22.0
|
||||
code.gitea.io/sdk/gitea v0.22.1-0.20251016220613-060554f46291
|
||||
github.com/mark3labs/mcp-go v0.40.0
|
||||
go.uber.org/zap v1.27.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1,5 +1,5 @@
|
||||
code.gitea.io/sdk/gitea v0.22.0 h1:HCKq7bX/HQ85Nw7c/HAhWgRye+vBp5nQOE8Md1+9Ef0=
|
||||
code.gitea.io/sdk/gitea v0.22.0/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM=
|
||||
code.gitea.io/sdk/gitea v0.22.1-0.20251016220613-060554f46291 h1:oVIBPpbLraXywuuapimBbI3uMRGF0qJP3wjU2vb2bU4=
|
||||
code.gitea.io/sdk/gitea v0.22.1-0.20251016220613-060554f46291/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM=
|
||||
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=
|
||||
|
||||
@@ -27,6 +27,10 @@ const (
|
||||
ReplaceIssueLabelsToolName = "replace_issue_labels"
|
||||
ClearIssueLabelsToolName = "clear_issue_labels"
|
||||
RemoveIssueLabelToolName = "remove_issue_label"
|
||||
ListOrgLabelsToolName = "list_org_labels"
|
||||
CreateOrgLabelToolName = "create_org_label"
|
||||
EditOrgLabelToolName = "edit_org_label"
|
||||
DeleteOrgLabelToolName = "delete_org_label"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -110,6 +114,43 @@ var (
|
||||
mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
|
||||
mcp.WithNumber("label_id", mcp.Required(), mcp.Description("label ID to remove")),
|
||||
)
|
||||
|
||||
ListOrgLabelsTool = mcp.NewTool(
|
||||
ListOrgLabelsToolName,
|
||||
mcp.WithDescription("Lists labels defined at organization level"),
|
||||
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
|
||||
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
|
||||
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
|
||||
)
|
||||
|
||||
CreateOrgLabelTool = mcp.NewTool(
|
||||
CreateOrgLabelToolName,
|
||||
mcp.WithDescription("Creates a new label for an organization"),
|
||||
mcp.WithString("org", mcp.Required(), mcp.Description("organization 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")),
|
||||
mcp.WithBoolean("exclusive", mcp.Description("whether the label is exclusive"), mcp.DefaultBool(false)),
|
||||
)
|
||||
|
||||
EditOrgLabelTool = mcp.NewTool(
|
||||
EditOrgLabelToolName,
|
||||
mcp.WithDescription("Edits an existing organization label"),
|
||||
mcp.WithString("org", mcp.Required(), mcp.Description("organization 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")),
|
||||
mcp.WithBoolean("exclusive", mcp.Description("whether the label is exclusive")),
|
||||
)
|
||||
|
||||
DeleteOrgLabelTool = mcp.NewTool(
|
||||
DeleteOrgLabelToolName,
|
||||
mcp.WithDescription("Deletes an organization label by ID"),
|
||||
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
|
||||
mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")),
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -149,6 +190,22 @@ func init() {
|
||||
Tool: RemoveIssueLabelTool,
|
||||
Handler: RemoveIssueLabelFn,
|
||||
})
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: ListOrgLabelsTool,
|
||||
Handler: ListOrgLabelsFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: CreateOrgLabelTool,
|
||||
Handler: CreateOrgLabelFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: EditOrgLabelTool,
|
||||
Handler: EditOrgLabelFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: DeleteOrgLabelTool,
|
||||
Handler: DeleteOrgLabelFn,
|
||||
})
|
||||
}
|
||||
|
||||
func ListRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
@@ -452,3 +509,128 @@ func RemoveIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
|
||||
}
|
||||
return to.TextResult("Label removed successfully")
|
||||
}
|
||||
|
||||
func ListOrgLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called ListOrgLabelsFn")
|
||||
org, ok := req.GetArguments()["org"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("org 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.ListOrgLabelsOptions{
|
||||
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.ListOrgLabels(org, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list %v/labels err: %v", org, err))
|
||||
}
|
||||
return to.TextResult(labels)
|
||||
}
|
||||
|
||||
func CreateOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called CreateOrgLabelFn")
|
||||
org, ok := req.GetArguments()["org"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("org 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)
|
||||
exclusive, _ := req.GetArguments()["exclusive"].(bool)
|
||||
|
||||
opt := gitea_sdk.CreateOrgLabelOption{
|
||||
Name: name,
|
||||
Color: color,
|
||||
Description: description,
|
||||
Exclusive: exclusive,
|
||||
}
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
label, _, err := client.CreateOrgLabel(org, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("create %v/labels err: %v", org, err))
|
||||
}
|
||||
return to.TextResult(label)
|
||||
}
|
||||
|
||||
func EditOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called EditOrgLabelFn")
|
||||
org, ok := req.GetArguments()["org"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("org is required"))
|
||||
}
|
||||
id, ok := req.GetArguments()["id"].(float64)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("label ID is required"))
|
||||
}
|
||||
|
||||
opt := gitea_sdk.EditOrgLabelOption{}
|
||||
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)
|
||||
}
|
||||
if exclusive, ok := req.GetArguments()["exclusive"].(bool); ok {
|
||||
opt.Exclusive = ptr.To(exclusive)
|
||||
}
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
label, _, err := client.EditOrgLabel(org, int64(id), opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("edit %v/labels/%v err: %v", org, int64(id), err))
|
||||
}
|
||||
return to.TextResult(label)
|
||||
}
|
||||
|
||||
func DeleteOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called DeleteOrgLabelFn")
|
||||
org, ok := req.GetArguments()["org"].(string)
|
||||
if !ok {
|
||||
return to.ErrorResult(fmt.Errorf("org 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.DeleteOrgLabel(org, int64(id))
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("delete %v/labels/%v err: %v", org, int64(id), err))
|
||||
}
|
||||
return to.TextResult("Label deleted successfully")
|
||||
}
|
||||
Reference in New Issue
Block a user