Repository: github/github-mcp-server (2025-08-13T16:14:48Z)

Issue:
Support for setting 'Type' field (feature, bug, task) in MCP
Currently, the MCP server does not support setting a 'Type' field (such as feature, bug, or task) for issues or pull requests. It would be very helpful if MCP could allow users to specify the type of an issue or PR, making it easier to categorize and manage work. Please consider adding support for this feature. Thank you!


Discussion:
@williammartin Adding my experience here as I have a similar use case and I'd like to work on this.

I believe a potential approach could involve using `labels` directly on issues and pull requests. This would require the ability to manage these `labels` – by defining the `labels` toolsets (GH MCP don't have `labels` currently) – and then applying them to the relevant issues and PRs. LMK if I'm missing something.

@radar07 Issue Types are a first class thing in GitHub now: https://docs.github.com/en/issues/tracking-your-work-with-issues/configuring-issues/managing-issue-types-in-an-organization

I think that exposing this would be a reasonable addition.
@williammartin Thanks for the details. [The `Type` is added to the `IssueRequest` in the latest version of `go-github`](https://github.com/google/go-github/pull/3567). Since this is not in the `v69` that we use in the `github-mcp-server` we can not proceed with this version. 🤔 
It's not a problem to bump go-github.
Thanks @williammartin. I'll be working on bumping `go-github` to `v72`.



Patch:
diff --git a/README.md b/README.md
index ce27bdb06..e4543ecf5 100644
--- a/README.md
+++ b/README.md
@@ -533,6 +533,7 @@ The following sets of tools are available (all are on by default):
   - `owner`: Repository owner (string, required)
   - `repo`: Repository name (string, required)
   - `title`: Issue title (string, required)
+  - `type`: Type of this issue (string, optional)
 
 - **get_issue** - Get issue details
   - `issue_number`: The number of the issue (number, required)
@@ -600,6 +601,7 @@ The following sets of tools are available (all are on by default):
   - `repo`: Repository name (string, required)
   - `state`: New state (string, optional)
   - `title`: New title (string, optional)
+  - `type`: New issue type (string, optional)
 
 </details>
 
diff --git a/pkg/github/__toolsnaps__/create_issue.snap b/pkg/github/__toolsnaps__/create_issue.snap
index f065b0183..d11c41c0e 100644
--- a/pkg/github/__toolsnaps__/create_issue.snap
+++ b/pkg/github/__toolsnaps__/create_issue.snap
@@ -39,6 +39,10 @@
       "title": {
         "description": "Issue title",
         "type": "string"
+      },
+      "type": {
+        "description": "Type of this issue",
+        "type": "string"
       }
     },
     "required": [
diff --git a/pkg/github/__toolsnaps__/update_issue.snap b/pkg/github/__toolsnaps__/update_issue.snap
index 4bcae7ba7..d95579159 100644
--- a/pkg/github/__toolsnaps__/update_issue.snap
+++ b/pkg/github/__toolsnaps__/update_issue.snap
@@ -51,6 +51,10 @@
       "title": {
         "description": "New title",
         "type": "string"
+      },
+      "type": {
+        "description": "New issue type",
+        "type": "string"
       }
     },
     "required": [
diff --git a/pkg/github/issues.go b/pkg/github/issues.go
index 3a1440489..80fe22f9d 100644
--- a/pkg/github/issues.go
+++ b/pkg/github/issues.go
@@ -553,39 +553,39 @@ func RemoveSubIssue(getClient GetClientFn, t translations.TranslationHelperFunc)
 			}
 
 			client, err := getClient(ctx)
-            if err != nil {
-                return nil, fmt.Errorf("failed to get GitHub client: %w", err)
-            }
-
-            subIssueRequest := github.SubIssueRequest{
-                SubIssueID: int64(subIssueID),
-            }
-
-            subIssue, resp, err := client.SubIssue.Remove(ctx, owner, repo, int64(issueNumber), subIssueRequest)
-            if err != nil {
-                return ghErrors.NewGitHubAPIErrorResponse(ctx,
-                    "failed to remove sub-issue",
-                    resp,
-                    err,
-                ), nil
-            }
-            defer func() { _ = resp.Body.Close() }()
-
-            if resp.StatusCode != http.StatusOK {
-                body, err := io.ReadAll(resp.Body)
-                if err != nil {
-                    return nil, fmt.Errorf("failed to read response body: %w", err)
-                }
-                return mcp.NewToolResultError(fmt.Sprintf("failed to remove sub-issue: %s", string(body))), nil
-            }
-
-            r, err := json.Marshal(subIssue)
-            if err != nil {
-                return nil, fmt.Errorf("failed to marshal response: %w", err)
-            }
-
-            return mcp.NewToolResultText(string(r)), nil
-        }
+			if err != nil {
+				return nil, fmt.Errorf("failed to get GitHub client: %w", err)
+			}
+
+			subIssueRequest := github.SubIssueRequest{
+				SubIssueID: int64(subIssueID),
+			}
+
+			subIssue, resp, err := client.SubIssue.Remove(ctx, owner, repo, int64(issueNumber), subIssueRequest)
+			if err != nil {
+				return ghErrors.NewGitHubAPIErrorResponse(ctx,
+					"failed to remove sub-issue",
+					resp,
+					err,
+				), nil
+			}
+			defer func() { _ = resp.Body.Close() }()
+
+			if resp.StatusCode != http.StatusOK {
+				body, err := io.ReadAll(resp.Body)
+				if err != nil {
+					return nil, fmt.Errorf("failed to read response body: %w", err)
+				}
+				return mcp.NewToolResultError(fmt.Sprintf("failed to remove sub-issue: %s", string(body))), nil
+			}
+
+			r, err := json.Marshal(subIssue)
+			if err != nil {
+				return nil, fmt.Errorf("failed to marshal response: %w", err)
+			}
+
+			return mcp.NewToolResultText(string(r)), nil
+		}
 }
 
 // ReprioritizeSubIssue creates a tool to reprioritize a sub-issue to a different position in the parent list.
@@ -788,6 +788,9 @@ func CreateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t
 			mcp.WithNumber("milestone",
 				mcp.Description("Milestone number"),
 			),
+			mcp.WithString("type",
+				mcp.Description("Type of this issue"),
+			),
 		),
 		func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 			owner, err := RequiredParam[string](request, "owner")
@@ -832,6 +835,12 @@ func CreateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t
 				milestoneNum = &milestone
 			}
 
+			// Get optional type
+			issueType, err := OptionalParam[string](request, "type")
+			if err != nil {
+				return mcp.NewToolResultError(err.Error()), nil
+			}
+
 			// Create the issue request
 			issueRequest := &github.IssueRequest{
 				Title:     github.Ptr(title),
@@ -841,6 +850,10 @@ func CreateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t
 				Milestone: milestoneNum,
 			}
 
+			if issueType != "" {
+				issueRequest.Type = github.Ptr(issueType)
+			}
+
 			client, err := getClient(ctx)
 			if err != nil {
 				return nil, fmt.Errorf("failed to get GitHub client: %w", err)
@@ -1129,6 +1142,9 @@ func UpdateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t
 			mcp.WithNumber("milestone",
 				mcp.Description("New milestone number"),
 			),
+			mcp.WithString("type",
+				mcp.Description("New issue type"),
+			),
 		),
 		func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 			owner, err := RequiredParam[string](request, "owner")
@@ -1199,6 +1215,15 @@ func UpdateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t
 				issueRequest.Milestone = &milestoneNum
 			}
 
+			// Get issue type
+			issueType, err := OptionalParam[string](request, "type")
+			if err != nil {
+				return mcp.NewToolResultError(err.Error()), nil
+			}
+			if issueType != "" {
+				issueRequest.Type = github.Ptr(issueType)
+			}
+
 			client, err := getClient(ctx)
 			if err != nil {
 				return nil, fmt.Errorf("failed to get GitHub client: %w", err)


Test patch:
diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go
index 249fadef8..7c4983c64 100644
--- a/pkg/github/issues_test.go
+++ b/pkg/github/issues_test.go
@@ -581,6 +581,7 @@ func Test_CreateIssue(t *testing.T) {
 	assert.Contains(t, tool.InputSchema.Properties, "assignees")
 	assert.Contains(t, tool.InputSchema.Properties, "labels")
 	assert.Contains(t, tool.InputSchema.Properties, "milestone")
+	assert.Contains(t, tool.InputSchema.Properties, "type")
 	assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "title"})
 
 	// Setup mock issue for success case
@@ -593,6 +594,7 @@ func Test_CreateIssue(t *testing.T) {
 		Assignees: []*github.User{{Login: github.Ptr("user1")}, {Login: github.Ptr("user2")}},
 		Labels:    []*github.Label{{Name: github.Ptr("bug")}, {Name: github.Ptr("help wanted")}},
 		Milestone: &github.Milestone{Number: github.Ptr(5)},
+		Type:      &github.IssueType{Name: github.Ptr("Bug")},
 	}
 
 	tests := []struct {
@@ -614,6 +616,7 @@ func Test_CreateIssue(t *testing.T) {
 						"labels":    []any{"bug", "help wanted"},
 						"assignees": []any{"user1", "user2"},
 						"milestone": float64(5),
+						"type":      "Bug",
 					}).andThen(
 						mockResponse(t, http.StatusCreated, mockIssue),
 					),
@@ -627,6 +630,7 @@ func Test_CreateIssue(t *testing.T) {
 				"assignees": []any{"user1", "user2"},
 				"labels":    []any{"bug", "help wanted"},
 				"milestone": float64(5),
+				"type":      "Bug",
 			},
 			expectError:   false,
 			expectedIssue: mockIssue,
@@ -722,6 +726,10 @@ func Test_CreateIssue(t *testing.T) {
 				assert.Equal(t, *tc.expectedIssue.Body, *returnedIssue.Body)
 			}
 
+			if tc.expectedIssue.Type != nil {
+				assert.Equal(t, *tc.expectedIssue.Type.Name, *returnedIssue.Type.Name)
+			}
+
 			// Check assignees if expected
 			if len(tc.expectedIssue.Assignees) > 0 {
 				assert.Equal(t, len(tc.expectedIssue.Assignees), len(returnedIssue.Assignees))
@@ -1066,6 +1074,7 @@ func Test_UpdateIssue(t *testing.T) {
 	assert.Contains(t, tool.InputSchema.Properties, "labels")
 	assert.Contains(t, tool.InputSchema.Properties, "assignees")
 	assert.Contains(t, tool.InputSchema.Properties, "milestone")
+	assert.Contains(t, tool.InputSchema.Properties, "type")
 	assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "issue_number"})
 
 	// Setup mock issue for success case
@@ -1078,6 +1087,7 @@ func Test_UpdateIssue(t *testing.T) {
 		Assignees: []*github.User{{Login: github.Ptr("assignee1")}, {Login: github.Ptr("assignee2")}},
 		Labels:    []*github.Label{{Name: github.Ptr("bug")}, {Name: github.Ptr("priority")}},
 		Milestone: &github.Milestone{Number: github.Ptr(5)},
+		Type:      &github.IssueType{Name: github.Ptr("Bug")},
 	}
 
 	tests := []struct {
@@ -1100,6 +1110,7 @@ func Test_UpdateIssue(t *testing.T) {
 						"labels":    []any{"bug", "priority"},
 						"assignees": []any{"assignee1", "assignee2"},
 						"milestone": float64(5),
+						"type":      "Bug",
 					}).andThen(
 						mockResponse(t, http.StatusOK, mockIssue),
 					),
@@ -1115,6 +1126,7 @@ func Test_UpdateIssue(t *testing.T) {
 				"labels":       []any{"bug", "priority"},
 				"assignees":    []any{"assignee1", "assignee2"},
 				"milestone":    float64(5),
+				"type":         "Bug",
 			},
 			expectError:   false,
 			expectedIssue: mockIssue,
@@ -1126,9 +1138,10 @@ func Test_UpdateIssue(t *testing.T) {
 					mock.PatchReposIssuesByOwnerByRepoByIssueNumber,
 					mockResponse(t, http.StatusOK, &github.Issue{
 						Number:  github.Ptr(123),
-						Title:   github.Ptr("Only Title Updated"),
+						Title:   github.Ptr("Updated Issue Title"),
 						HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"),
 						State:   github.Ptr("open"),
+						Type:    &github.IssueType{Name: github.Ptr("Feature")},
 					}),
 				),
 			),
@@ -1136,14 +1149,16 @@ func Test_UpdateIssue(t *testing.T) {
 				"owner":        "owner",
 				"repo":         "repo",
 				"issue_number": float64(123),
-				"title":        "Only Title Updated",
+				"title":        "Updated Issue Title",
+				"type":         "Feature",
 			},
 			expectError: false,
 			expectedIssue: &github.Issue{
 				Number:  github.Ptr(123),
-				Title:   github.Ptr("Only Title Updated"),
+				Title:   github.Ptr("Updated Issue Title"),
 				HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"),
 				State:   github.Ptr("open"),
+				Type:    &github.IssueType{Name: github.Ptr("Feature")},
 			},
 		},
 		{
@@ -1232,6 +1247,10 @@ func Test_UpdateIssue(t *testing.T) {
 				assert.Equal(t, *tc.expectedIssue.Body, *returnedIssue.Body)
 			}
 
+			if tc.expectedIssue.Type != nil {
+				assert.Equal(t, *tc.expectedIssue.Type.Name, *returnedIssue.Type.Name)
+			}
+
 			// Check assignees if expected
 			if len(tc.expectedIssue.Assignees) > 0 {
 				assert.Len(t, returnedIssue.Assignees, len(tc.expectedIssue.Assignees))