Skip to content

Commit cf0fff7

Browse files
committed
Gate set_issue_fields confidence behind update_issue_confidence flag
The GitHub GraphQL API does not yet accept the per-field confidence input on setIssueFieldValue mutations. Hide it from the user-facing schema and drop it from the mutation payload unless the new update_issue_confidence feature flag is enabled so users do not try to use it before the API supports it.
1 parent 2f68b30 commit cf0fff7

4 files changed

Lines changed: 129 additions & 27 deletions

File tree

pkg/github/__toolsnaps__/set_issue_fields.snap

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,13 @@
44
"openWorldHint": true,
55
"title": "Set Issue Fields"
66
},
7-
"description": "Set issue field values for an issue. Fields are organization-level custom fields (text, number, date, or single select). Use this to create or update field values on an issue. When setting values, include a confidence level (low, medium, or high) reflecting how certain you are about the choice.",
7+
"description": "Set issue field values for an issue. Fields are organization-level custom fields (text, number, date, or single select). Use this to create or update field values on an issue.",
88
"inputSchema": {
99
"properties": {
1010
"fields": {
1111
"description": "Array of issue field values to set. Each element must have a 'field_id' (string, the GraphQL node ID of the field) and exactly one value field: 'text_value' for text fields, 'number_value' for number fields, 'date_value' (ISO 8601 date string) for date fields, or 'single_select_option_id' (the GraphQL node ID of the option) for single select fields. Set 'delete' to true to remove a field value.",
1212
"items": {
1313
"properties": {
14-
"confidence": {
15-
"description": "How confident you are in this choice. Use 'high' for clear signal or explicit user request, 'medium' for reasonable inference with some ambiguity, 'low' for best guess with limited signal.",
16-
"enum": [
17-
"low",
18-
"medium",
19-
"high"
20-
],
21-
"type": "string"
22-
},
2314
"date_value": {
2415
"description": "The value to set for a date field (ISO 8601 date string)",
2516
"type": "string"

pkg/github/feature_flags.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ const FeatureFlagIFCLabels = "ifc_labels"
1616
// and field_values enrichment in list_issues / search_issues output.
1717
const FeatureFlagIssueFields = "remote_mcp_issue_fields"
1818

19+
// FeatureFlagIssueConfidence is the feature flag name for exposing the
20+
// per-field `confidence` input on the set_issue_fields GraphQL mutation. The
21+
// GitHub GraphQL API does not yet accept the confidence input, so the schema
22+
// hides the parameter and the handler drops it from the mutation payload
23+
// unless this flag is enabled.
24+
const FeatureFlagIssueConfidence = "update_issue_confidence"
25+
1926
// AllowedFeatureFlags is the allowlist of feature flags that can be enabled
2027
// by users via --features CLI flag or X-MCP-Features HTTP header.
2128
// Only flags in this list are accepted; unknown flags are silently ignored.
@@ -25,6 +32,7 @@ var AllowedFeatureFlags = []string{
2532
FeatureFlagCSVOutput,
2633
FeatureFlagIFCLabels,
2734
FeatureFlagIssueFields,
35+
FeatureFlagIssueConfidence,
2836
FeatureFlagIssuesGranular,
2937
FeatureFlagPullRequestsGranular,
3038
}

pkg/github/granular_tools_test.go

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,7 +1668,12 @@ func TestGranularSetIssueFields(t *testing.T) {
16681668
}
16691669

16701670
gqlClient := githubv4.NewClient(githubv4mock.NewMockedHTTPClient(matchers...))
1671-
deps := BaseDeps{GQLClient: gqlClient}
1671+
deps := BaseDeps{
1672+
GQLClient: gqlClient,
1673+
featureChecker: func(_ context.Context, flag string) (bool, error) {
1674+
return flag == FeatureFlagIssueConfidence, nil
1675+
},
1676+
}
16721677
serverTool := GranularSetIssueFields(translations.NullTranslationHelper)
16731678
handler := serverTool.Handler(deps)
16741679

@@ -1690,7 +1695,11 @@ func TestGranularSetIssueFields(t *testing.T) {
16901695
})
16911696

16921697
t.Run("invalid confidence value returns error", func(t *testing.T) {
1693-
deps := BaseDeps{}
1698+
deps := BaseDeps{
1699+
featureChecker: func(_ context.Context, flag string) (bool, error) {
1700+
return flag == FeatureFlagIssueConfidence, nil
1701+
},
1702+
}
16941703
serverTool := GranularSetIssueFields(translations.NullTranslationHelper)
16951704
handler := serverTool.Handler(deps)
16961705

@@ -1712,6 +1721,99 @@ func TestGranularSetIssueFields(t *testing.T) {
17121721
assert.Contains(t, textContent.Text, "confidence must be one of: low, medium, high")
17131722
})
17141723

1724+
t.Run("confidence is dropped when feature flag is disabled", func(t *testing.T) {
1725+
matchers := []githubv4mock.Matcher{
1726+
githubv4mock.NewQueryMatcher(
1727+
struct {
1728+
Repository struct {
1729+
Issue struct {
1730+
ID githubv4.ID
1731+
} `graphql:"issue(number: $issueNumber)"`
1732+
} `graphql:"repository(owner: $owner, name: $repo)"`
1733+
}{},
1734+
map[string]any{
1735+
"owner": githubv4.String("owner"),
1736+
"repo": githubv4.String("repo"),
1737+
"issueNumber": githubv4.Int(5),
1738+
},
1739+
githubv4mock.DataResponse(map[string]any{
1740+
"repository": map[string]any{
1741+
"issue": map[string]any{"id": "ISSUE_123"},
1742+
},
1743+
}),
1744+
),
1745+
// Expect the mutation input WITHOUT Confidence, proving the handler
1746+
// dropped the user-supplied value because the feature flag is off.
1747+
githubv4mock.NewMutationMatcher(
1748+
struct {
1749+
SetIssueFieldValue struct {
1750+
Issue struct {
1751+
ID githubv4.ID
1752+
Number githubv4.Int
1753+
URL githubv4.String
1754+
}
1755+
IssueFieldValues []struct {
1756+
TextValue struct {
1757+
Value string
1758+
} `graphql:"... on IssueFieldTextValue"`
1759+
SingleSelectValue struct {
1760+
Name string
1761+
} `graphql:"... on IssueFieldSingleSelectValue"`
1762+
DateValue struct {
1763+
Value string
1764+
} `graphql:"... on IssueFieldDateValue"`
1765+
NumberValue struct {
1766+
Value float64
1767+
} `graphql:"... on IssueFieldNumberValue"`
1768+
}
1769+
} `graphql:"setIssueFieldValue(input: $input)"`
1770+
}{},
1771+
SetIssueFieldValueInput{
1772+
IssueID: githubv4.ID("ISSUE_123"),
1773+
IssueFields: []IssueFieldCreateOrUpdateInput{
1774+
{
1775+
FieldID: githubv4.ID("FIELD_1"),
1776+
TextValue: githubv4.NewString(githubv4.String("hello")),
1777+
},
1778+
},
1779+
},
1780+
nil,
1781+
githubv4mock.DataResponse(map[string]any{
1782+
"setIssueFieldValue": map[string]any{
1783+
"issue": map[string]any{
1784+
"id": "ISSUE_123",
1785+
"number": 5,
1786+
"url": "https://github.com/owner/repo/issues/5",
1787+
},
1788+
},
1789+
}),
1790+
),
1791+
}
1792+
1793+
gqlClient := githubv4.NewClient(githubv4mock.NewMockedHTTPClient(matchers...))
1794+
deps := BaseDeps{GQLClient: gqlClient}
1795+
serverTool := GranularSetIssueFields(translations.NullTranslationHelper)
1796+
handler := serverTool.Handler(deps)
1797+
1798+
request := createMCPRequest(map[string]any{
1799+
"owner": "owner",
1800+
"repo": "repo",
1801+
"issue_number": float64(5),
1802+
"fields": []any{
1803+
map[string]any{
1804+
"field_id": "FIELD_1",
1805+
"text_value": "hello",
1806+
// Confidence is supplied but should be silently dropped
1807+
// because FeatureFlagIssueConfidence is off.
1808+
"confidence": "high",
1809+
},
1810+
},
1811+
})
1812+
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
1813+
require.NoError(t, err)
1814+
assert.False(t, result.IsError, getTextResult(t, result).Text)
1815+
})
1816+
17151817
t.Run("successful set with suggest flag", func(t *testing.T) {
17161818
suggestTrue := githubv4.Boolean(true)
17171819
matchers := []githubv4mock.Matcher{

pkg/github/issues_granular.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -925,7 +925,7 @@ func GranularSetIssueFields(t translations.TranslationHelperFunc) inventory.Serv
925925
ToolsetMetadataIssues,
926926
mcp.Tool{
927927
Name: "set_issue_fields",
928-
Description: t("TOOL_SET_ISSUE_FIELDS_DESCRIPTION", "Set issue field values for an issue. Fields are organization-level custom fields (text, number, date, or single select). Use this to create or update field values on an issue. When setting values, include a confidence level (low, medium, or high) reflecting how certain you are about the choice."),
928+
Description: t("TOOL_SET_ISSUE_FIELDS_DESCRIPTION", "Set issue field values for an issue. Fields are organization-level custom fields (text, number, date, or single select). Use this to create or update field values on an issue."),
929929
Annotations: &mcp.ToolAnnotations{
930930
Title: t("TOOL_SET_ISSUE_FIELDS_USER_TITLE", "Set Issue Fields"),
931931
ReadOnlyHint: false,
@@ -985,11 +985,6 @@ func GranularSetIssueFields(t translations.TranslationHelperFunc) inventory.Serv
985985
"State the concrete signal (e.g. 'Reports a crash when saving' → high priority).",
986986
MaxLength: jsonschema.Ptr(280),
987987
},
988-
"confidence": {
989-
Type: "string",
990-
Description: "How confident you are in this choice. Use 'high' for clear signal or explicit user request, 'medium' for reasonable inference with some ambiguity, 'low' for best guess with limited signal.",
991-
Enum: []any{"low", "medium", "high"},
992-
},
993988
"is_suggestion": {
994989
Type: "boolean",
995990
Description: "If true, this field value is sent to the API as a suggestion (suggest:true) rather than an applied value. " +
@@ -1107,15 +1102,21 @@ func GranularSetIssueFields(t translations.TranslationHelperFunc) inventory.Serv
11071102
}
11081103
}
11091104

1110-
confidence, err := OptionalParam[string](fieldMap, "confidence")
1111-
if err != nil {
1112-
return utils.NewToolResultError(err.Error()), nil, nil
1113-
}
1114-
if confidence != "" && confidence != "low" && confidence != "medium" && confidence != "high" {
1115-
return utils.NewToolResultError("confidence must be one of: low, medium, high"), nil, nil
1116-
}
1117-
if confidence != "" {
1118-
input.Confidence = &confidence
1105+
// The `confidence` input is gated behind FeatureFlagIssueConfidence
1106+
// because the GitHub GraphQL API does not yet accept it. When the
1107+
// flag is off the schema hides the field and the handler drops
1108+
// any value supplied by older callers from the mutation payload.
1109+
if deps.IsFeatureEnabled(ctx, FeatureFlagIssueConfidence) {
1110+
confidence, err := OptionalParam[string](fieldMap, "confidence")
1111+
if err != nil {
1112+
return utils.NewToolResultError(err.Error()), nil, nil
1113+
}
1114+
if confidence != "" && confidence != "low" && confidence != "medium" && confidence != "high" {
1115+
return utils.NewToolResultError("confidence must be one of: low, medium, high"), nil, nil
1116+
}
1117+
if confidence != "" {
1118+
input.Confidence = &confidence
1119+
}
11191120
}
11201121

11211122
isSuggestion, err := OptionalParam[bool](fieldMap, "is_suggestion")

0 commit comments

Comments
 (0)