@@ -2156,3 +2156,292 @@ func DeleteLabel(getGQLClient GetGQLClientFn, t translations.TranslationHelperFu
21562156 return mcp .NewToolResultText (fmt .Sprintf ("label %s deleted successfully" , name )), nil
21572157 }
21582158}
2159+
2160+ // CRUDLabel consolidates Create/Get/Update/Delete label operations into a single tool.
2161+ func CRUDLabel (getGQLClient GetGQLClientFn , t translations.TranslationHelperFunc ) (mcp.Tool , server.ToolHandlerFunc ) {
2162+ return mcp .NewTool ("crud_label" ,
2163+ mcp .WithDescription (t ("TOOL_CRUD_LABEL_DESCRIPTION" , "Create, read, update, or delete a label in a GitHub repository. Used in context of labels in relation to GitHub resources, they are organizational tags used to categorize and filter issues and pull requests. The use of parameters depends on the specific method selected." )),
2164+ mcp .WithToolAnnotation (mcp.ToolAnnotation {
2165+ Title : t ("TOOL_CRUD_LABEL_TITLE" , "CRUD label" ),
2166+ ReadOnlyHint : ToBoolPtr (false ),
2167+ }),
2168+ mcp .WithString ("method" ,
2169+ mcp .Required (),
2170+ mcp .Description ("Operation to perform: create, get, update or delete" ),
2171+ mcp .Enum ("create" , "get" , "update" , "delete" ),
2172+ ),
2173+ mcp .WithString ("owner" ,
2174+ mcp .Description ("Repository owner" ),
2175+ ),
2176+ mcp .WithString ("repo" ,
2177+ mcp .Description ("Repository name" ),
2178+ ),
2179+ mcp .WithString ("name" ,
2180+ mcp .Description ("Label name (for get/update/delete or to create with this name)" ),
2181+ ),
2182+ mcp .WithString ("new_name" ,
2183+ mcp .Description ("New name for the label (update only)" ),
2184+ ),
2185+ mcp .WithString ("color" ,
2186+ mcp .Description ("Label color as a 6-character hex code without '#', e.g. 'f29513' (create/update)" ),
2187+ ),
2188+ mcp .WithString ("description" ,
2189+ mcp .Description ("Label description (create/update)" ),
2190+ ),
2191+ ),
2192+ func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
2193+ method , err := RequiredParam [string ](request , "method" )
2194+ if err != nil {
2195+ return mcp .NewToolResultError (err .Error ()), nil
2196+ }
2197+
2198+ // Normalize
2199+ method = strings .ToLower (method )
2200+
2201+ // Basic params used across methods
2202+ owner , _ := OptionalParam [string ](request , "owner" )
2203+ repo , _ := OptionalParam [string ](request , "repo" )
2204+ name , _ := OptionalParam [string ](request , "name" )
2205+ newName , _ := OptionalParam [string ](request , "new_name" )
2206+ color , _ := OptionalParam [string ](request , "color" )
2207+ description , _ := OptionalParam [string ](request , "description" )
2208+
2209+ client , err := getGQLClient (ctx )
2210+ if err != nil {
2211+ return nil , fmt .Errorf ("failed to get GitHub client: %w" , err )
2212+ }
2213+
2214+ switch method {
2215+ case "create" :
2216+ // Validate required params for create
2217+ if owner == "" {
2218+ return mcp .NewToolResultError ("owner is required for create" ), nil
2219+ }
2220+ if repo == "" {
2221+ return mcp .NewToolResultError ("repo is required for create" ), nil
2222+ }
2223+ if name == "" {
2224+ return mcp .NewToolResultError ("name is required for create" ), nil
2225+ }
2226+ if color == "" {
2227+ return mcp .NewToolResultError ("color is required for create" ), nil
2228+ }
2229+
2230+ // Fetch repository node ID
2231+ var repoQuery struct {
2232+ Repository struct {
2233+ ID githubv4.ID
2234+ } `graphql:"repository(owner: $owner, name: $repo)"`
2235+ }
2236+ vars := map [string ]any {
2237+ "owner" : githubv4 .String (owner ),
2238+ "repo" : githubv4 .String (repo ),
2239+ }
2240+ if err := client .Query (ctx , & repoQuery , vars ); err != nil {
2241+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx , "Failed to find repository" , err ), nil
2242+ }
2243+
2244+ input := githubv4.CreateLabelInput {
2245+ RepositoryID : repoQuery .Repository .ID ,
2246+ Name : githubv4 .String (name ),
2247+ }
2248+ if color != "" {
2249+ input .Color = githubv4 .String (color )
2250+ }
2251+ if description != "" {
2252+ d := githubv4 .String (description )
2253+ input .Description = & d
2254+ }
2255+
2256+ var mutation struct {
2257+ CreateLabel struct {
2258+ Label struct {
2259+ Name githubv4.String
2260+ ID githubv4.ID
2261+ }
2262+ } `graphql:"createLabel(input: $input)"`
2263+ }
2264+
2265+ if err := client .Mutate (ctx , & mutation , input , nil ); err != nil {
2266+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx , "Failed to create label" , err ), nil
2267+ }
2268+
2269+ return mcp .NewToolResultText (fmt .Sprintf ("label %s created successfully" , mutation .CreateLabel .Label .Name )), nil
2270+
2271+ case "get" :
2272+ // Validate required params for get
2273+ if owner == "" {
2274+ return mcp .NewToolResultError ("owner is required for get" ), nil
2275+ }
2276+ if repo == "" {
2277+ return mcp .NewToolResultError ("repo is required for get" ), nil
2278+ }
2279+ if name == "" {
2280+ return mcp .NewToolResultError ("name is required for get" ), nil
2281+ }
2282+
2283+ var query struct {
2284+ Repository struct {
2285+ Label struct {
2286+ ID githubv4.ID
2287+ Name githubv4.String
2288+ Color githubv4.String
2289+ Description githubv4.String
2290+ } `graphql:"label(name: $name)"`
2291+ } `graphql:"repository(owner: $owner, name: $repo)"`
2292+ }
2293+
2294+ vars := map [string ]any {
2295+ "owner" : githubv4 .String (owner ),
2296+ "repo" : githubv4 .String (repo ),
2297+ "name" : githubv4 .String (name ),
2298+ }
2299+
2300+ if err := client .Query (ctx , & query , vars ); err != nil {
2301+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx , "Failed to find label" , err ), nil
2302+ }
2303+
2304+ if query .Repository .Label .Name == "" {
2305+ return mcp .NewToolResultError (fmt .Sprintf ("label '%s' not found in %s/%s" , name , owner , repo )), nil
2306+ }
2307+
2308+ label := map [string ]interface {}{
2309+ "id" : fmt .Sprintf ("%v" , query .Repository .Label .ID ),
2310+ "name" : string (query .Repository .Label .Name ),
2311+ "color" : string (query .Repository .Label .Color ),
2312+ "description" : string (query .Repository .Label .Description ),
2313+ }
2314+
2315+ out , err := json .Marshal (label )
2316+ if err != nil {
2317+ return nil , fmt .Errorf ("failed to marshal label: %w" , err )
2318+ }
2319+
2320+ return mcp .NewToolResultText (string (out )), nil
2321+
2322+ case "update" :
2323+ // Validate required params for update
2324+ if owner == "" {
2325+ return mcp .NewToolResultError ("owner is required for update" ), nil
2326+ }
2327+ if repo == "" {
2328+ return mcp .NewToolResultError ("repo is required for update" ), nil
2329+ }
2330+ if name == "" {
2331+ return mcp .NewToolResultError ("name is required for update" ), nil
2332+ }
2333+ if newName == "" && color == "" && description == "" {
2334+ return mcp .NewToolResultError ("at least one of new_name, color or description must be provided for update" ), nil
2335+ }
2336+
2337+ // Fetch the label to get its GQL ID
2338+ var query struct {
2339+ Repository struct {
2340+ Label struct {
2341+ ID githubv4.ID
2342+ Name githubv4.String
2343+ } `graphql:"label(name: $name)"`
2344+ } `graphql:"repository(owner: $owner, name: $repo)"`
2345+ }
2346+
2347+ vars := map [string ]any {
2348+ "owner" : githubv4 .String (owner ),
2349+ "repo" : githubv4 .String (repo ),
2350+ "name" : githubv4 .String (name ),
2351+ }
2352+
2353+ if err := client .Query (ctx , & query , vars ); err != nil {
2354+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx , "Failed to find label" , err ), nil
2355+ }
2356+
2357+ if query .Repository .Label .Name == "" {
2358+ return mcp .NewToolResultError (fmt .Sprintf ("label '%s' not found in %s/%s" , name , owner , repo )), nil
2359+ }
2360+
2361+ input := githubv4.UpdateLabelInput {
2362+ ID : query .Repository .Label .ID ,
2363+ }
2364+ if newName != "" {
2365+ n := githubv4 .String (newName )
2366+ input .Name = & n
2367+ }
2368+ if color != "" {
2369+ c := githubv4 .String (color )
2370+ input .Color = & c
2371+ }
2372+ if description != "" {
2373+ d := githubv4 .String (description )
2374+ input .Description = & d
2375+ }
2376+
2377+ var mutation struct {
2378+ UpdateLabel struct {
2379+ Label struct {
2380+ Name githubv4.String
2381+ ID githubv4.ID
2382+ }
2383+ } `graphql:"updateLabel(input: $input)"`
2384+ }
2385+
2386+ if err := client .Mutate (ctx , & mutation , input , nil ); err != nil {
2387+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx , "Failed to update label" , err ), nil
2388+ }
2389+
2390+ return mcp .NewToolResultText (fmt .Sprintf ("label %s updated successfully" , mutation .UpdateLabel .Label .Name )), nil
2391+
2392+ case "delete" :
2393+ // Validate required params for delete
2394+ if owner == "" {
2395+ return mcp .NewToolResultError ("owner is required for delete" ), nil
2396+ }
2397+ if repo == "" {
2398+ return mcp .NewToolResultError ("repo is required for delete" ), nil
2399+ }
2400+ if name == "" {
2401+ return mcp .NewToolResultError ("name is required for delete" ), nil
2402+ }
2403+
2404+ var query struct {
2405+ Repository struct {
2406+ Label struct {
2407+ ID githubv4.ID
2408+ Name githubv4.String
2409+ } `graphql:"label(name: $name)"`
2410+ } `graphql:"repository(owner: $owner, name: $repo)"`
2411+ }
2412+
2413+ vars := map [string ]any {
2414+ "owner" : githubv4 .String (owner ),
2415+ "repo" : githubv4 .String (repo ),
2416+ "name" : githubv4 .String (name ),
2417+ }
2418+
2419+ if err := client .Query (ctx , & query , vars ); err != nil {
2420+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx , "Failed to find label" , err ), nil
2421+ }
2422+
2423+ if query .Repository .Label .Name == "" {
2424+ return mcp .NewToolResultError (fmt .Sprintf ("label '%s' not found in %s/%s" , name , owner , repo )), nil
2425+ }
2426+
2427+ input := githubv4.DeleteLabelInput {
2428+ ID : query .Repository .Label .ID ,
2429+ }
2430+
2431+ var mutation struct {
2432+ DeleteLabel struct {
2433+ Typename githubv4.String `graphql:"__typename"`
2434+ } `graphql:"deleteLabel(input: $input)"`
2435+ }
2436+
2437+ if err := client .Mutate (ctx , & mutation , input , nil ); err != nil {
2438+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx , "Failed to delete label" , err ), nil
2439+ }
2440+
2441+ return mcp .NewToolResultText (fmt .Sprintf ("label %s deleted successfully" , name )), nil
2442+ }
2443+
2444+ // Should not reach here; ensure a return value for the compiler
2445+ return mcp .NewToolResultError ("method did not return a result" ), nil
2446+ }
2447+ }
0 commit comments