mirror of
				https://gitea.com/gitea/gitea-mcp.git
				synced 2025-11-04 12:21:50 +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                 |
 | 
					|     create_pull_request      | Pull Request |                Create a new pull request                 |
 | 
				
			||||||
|         search_users         |     User     |                     Search for users                     |
 | 
					|         search_users         |     User     |                     Search for users                     |
 | 
				
			||||||
|       search_org_teams       | Organization |           Search for teams in an organization            |
 | 
					|       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                  |
 | 
					|         search_repos         |  Repository  |                 Search for repositories                  |
 | 
				
			||||||
| get_gitea_mcp_server_version |    Server    |         Get the version of the Gitea MCP Server          |
 | 
					| 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            |
 | 
					|       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
 | 
					go 1.24.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					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
 | 
						github.com/mark3labs/mcp-go v0.40.0
 | 
				
			||||||
	go.uber.org/zap v1.27.0
 | 
						go.uber.org/zap v1.27.0
 | 
				
			||||||
	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
						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.1-0.20251016220613-060554f46291 h1:oVIBPpbLraXywuuapimBbI3uMRGF0qJP3wjU2vb2bU4=
 | 
				
			||||||
code.gitea.io/sdk/gitea v0.22.0/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM=
 | 
					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 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
 | 
				
			||||||
github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM=
 | 
					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 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,6 +27,10 @@ const (
 | 
				
			|||||||
	ReplaceIssueLabelsToolName = "replace_issue_labels"
 | 
						ReplaceIssueLabelsToolName = "replace_issue_labels"
 | 
				
			||||||
	ClearIssueLabelsToolName   = "clear_issue_labels"
 | 
						ClearIssueLabelsToolName   = "clear_issue_labels"
 | 
				
			||||||
	RemoveIssueLabelToolName   = "remove_issue_label"
 | 
						RemoveIssueLabelToolName   = "remove_issue_label"
 | 
				
			||||||
 | 
					    ListOrgLabelsToolName      = "list_org_labels"
 | 
				
			||||||
 | 
					    CreateOrgLabelToolName     = "create_org_label"
 | 
				
			||||||
 | 
					    EditOrgLabelToolName       = "edit_org_label"
 | 
				
			||||||
 | 
					    DeleteOrgLabelToolName     = "delete_org_label"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
@@ -110,6 +114,43 @@ var (
 | 
				
			|||||||
		mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
 | 
							mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
 | 
				
			||||||
		mcp.WithNumber("label_id", mcp.Required(), mcp.Description("label ID to remove")),
 | 
							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() {
 | 
					func init() {
 | 
				
			||||||
@@ -149,6 +190,22 @@ func init() {
 | 
				
			|||||||
		Tool:    RemoveIssueLabelTool,
 | 
							Tool:    RemoveIssueLabelTool,
 | 
				
			||||||
		Handler: RemoveIssueLabelFn,
 | 
							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) {
 | 
					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")
 | 
						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