Skip to content

Commit 311380b

Browse files
alondahariCopilot
andcommitted
Add confidence parameter to issue mutation MCP tools
Add an optional confidence integer parameter (0–100) to update_issue_type, update_issue_labels, and set_issue_fields MCP tools. The confidence score is passed through to the REST/GraphQL API on mutation calls. - Rename structs to WithIntent (labelWithIntent, issueTypeWithIntent) - Add confidence schema property (integer, min 0, max 100) with prompt guidance describing what different confidence levels represent - Update tool descriptions to encourage including confidence scores - Pass confidence in the API request body alongside rationale/suggest Closes github/plan-track-agentic-toolkit#219 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 33849e9 commit 311380b

7 files changed

Lines changed: 110 additions & 32 deletions

File tree

docs/feature-flags.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ runtime behavior (such as output formatting) won't appear here.
198198

199199
- **update_issue_type** - Update Issue Type
200200
- **Required OAuth Scopes**: `repo`
201+
- `confidence`: How confident you are in this choice (0–100). 90–100: very high — clear signal or explicit user request. 70–89: high — strong signals, likely correct. 50–69: moderate — reasonable inference but ambiguous. 30–49: low — best guess, user review recommended. 0–29: very low — speculative. (number, optional)
201202
- `is_suggestion`: If true, this issue type change is sent to the API as a suggestion (suggest:true) rather than an applied value. Whether the type is applied or recorded as a proposal is determined by the API. (boolean, optional)
202203
- `issue_number`: The issue number to update (number, required)
203204
- `issue_type`: The issue type to set (string, required)
@@ -240,7 +241,7 @@ runtime behavior (such as output formatting) won't appear here.
240241
- `owner`: Repository owner (username or organization) (string, required)
241242
- `pullNumber`: The pull request number (number, required)
242243
- `repo`: Repository name (string, required)
243-
- `reviewers`: GitHub usernames to request reviews from (string[], required)
244+
- `reviewers`: GitHub usernames or ORG/team-slug team reviewers to request reviews from (string[], required)
244245

245246
- **resolve_review_thread** - Resolve Review Thread
246247
- **Required OAuth Scopes**: `repo`

pkg/github/__toolsnaps__/request_pull_request_reviewers.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@
3737
"type": "object"
3838
},
3939
"name": "request_pull_request_reviewers"
40-
}
40+
}

pkg/github/__toolsnaps__/set_issue_fields.snap

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
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.",
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 score (0–100) reflecting how certain you are about the choice.",
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 (0–100). 90–100: very high — clear signal or explicit user request. 70–89: high — strong signals, likely correct. 50–69: moderate — reasonable inference but ambiguous. 30–49: low — best guess, user review recommended. 0–29: very low — speculative.",
16+
"maximum": 100,
17+
"minimum": 0,
18+
"type": "integer"
19+
},
1420
"date_value": {
1521
"description": "The value to set for a date field (ISO 8601 date string)",
1622
"type": "string"

pkg/github/__toolsnaps__/update_issue_labels.snap

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"openWorldHint": true,
55
"title": "Update Issue Labels"
66
},
7-
"description": "Update the labels of an existing issue. This replaces the current labels with the provided list.",
7+
"description": "Update the labels of an existing issue. This replaces the current labels with the provided list. When setting values, include a confidence score (0–100) reflecting how certain you are about the choice.",
88
"inputSchema": {
99
"properties": {
1010
"issue_number": {
@@ -22,6 +22,12 @@
2222
},
2323
{
2424
"properties": {
25+
"confidence": {
26+
"description": "How confident you are in this choice (0–100). 90–100: very high — clear signal or explicit user request. 70–89: high — strong signals, likely correct. 50–69: moderate — reasonable inference but ambiguous. 30–49: low — best guess, user review recommended. 0–29: very low — speculative.",
27+
"maximum": 100,
28+
"minimum": 0,
29+
"type": "integer"
30+
},
2531
"is_suggestion": {
2632
"description": "If true, this label is sent to the API as a suggestion (suggest:true) rather than an applied label. Whether the label is applied or recorded as a proposal is determined by the API.",
2733
"type": "boolean"

pkg/github/__toolsnaps__/update_issue_type.snap

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@
44
"openWorldHint": true,
55
"title": "Update Issue Type"
66
},
7-
"description": "Update the type of an existing issue (e.g. 'bug', 'feature').",
7+
"description": "Update the type of an existing issue (e.g. 'bug', 'feature'). When setting values, include a confidence score (0–100) reflecting how certain you are about the choice.",
88
"inputSchema": {
99
"properties": {
10+
"confidence": {
11+
"description": "How confident you are in this choice (0–100). 90–100: very high — clear signal or explicit user request. 70–89: high — strong signals, likely correct. 50–69: moderate — reasonable inference but ambiguous. 30–49: low — best guess, user review recommended. 0–29: very low — speculative.",
12+
"maximum": 100,
13+
"minimum": 0,
14+
"type": "integer"
15+
},
1016
"is_suggestion": {
1117
"description": "If true, this issue type change is sent to the API as a suggestion (suggest:true) rather than an applied value. Whether the type is applied or recorded as a proposal is determined by the API.",
1218
"type": "boolean"

pkg/github/__toolsnaps__/update_pull_request.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,4 @@
6161
"type": "object"
6262
},
6363
"name": "update_pull_request"
64-
}
64+
}

pkg/github/issues_granular.go

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -258,17 +258,18 @@ func GranularUpdateIssueAssignees(t translations.TranslationHelperFunc) inventor
258258
)
259259
}
260260

261-
// labelWithRationale represents the object form of a label entry, allowing a
262-
// rationale and/or suggest flag to be sent alongside the label name.
263-
type labelWithRationale struct {
264-
Name string `json:"name"`
265-
Rationale string `json:"rationale,omitempty"`
266-
Suggest bool `json:"suggest,omitempty"`
261+
// labelWithIntent represents the object form of a label entry, allowing a
262+
// rationale, confidence score, and/or suggest flag to be sent alongside the label name.
263+
type labelWithIntent struct {
264+
Name string `json:"name"`
265+
Rationale string `json:"rationale,omitempty"`
266+
Confidence *int `json:"confidence,omitempty"`
267+
Suggest bool `json:"suggest,omitempty"`
267268
}
268269

269270
// labelsUpdateRequest is a custom request body for updating an issue's labels
270271
// where individual labels may optionally include a rationale. Each element of
271-
// Labels is either a string (label name) or a labelWithRationale object.
272+
// Labels is either a string (label name) or a labelWithIntent object.
272273
type labelsUpdateRequest struct {
273274
Labels []any `json:"labels"`
274275
}
@@ -279,7 +280,7 @@ func GranularUpdateIssueLabels(t translations.TranslationHelperFunc) inventory.S
279280
ToolsetMetadataIssues,
280281
mcp.Tool{
281282
Name: "update_issue_labels",
282-
Description: t("TOOL_UPDATE_ISSUE_LABELS_DESCRIPTION", "Update the labels of an existing issue. This replaces the current labels with the provided list."),
283+
Description: t("TOOL_UPDATE_ISSUE_LABELS_DESCRIPTION", "Update the labels of an existing issue. This replaces the current labels with the provided list. When setting values, include a confidence score (0–100) reflecting how certain you are about the choice."),
283284
Annotations: &mcp.ToolAnnotations{
284285
Title: t("TOOL_UPDATE_ISSUE_LABELS_USER_TITLE", "Update Issue Labels"),
285286
ReadOnlyHint: false,
@@ -321,6 +322,12 @@ func GranularUpdateIssueLabels(t translations.TranslationHelperFunc) inventory.S
321322
"State the concrete signal (e.g. 'Reports a crash when saving' → bug).",
322323
MaxLength: jsonschema.Ptr(280),
323324
},
325+
"confidence": {
326+
Type: "integer",
327+
Description: "How confident you are in this choice (0–100). 90–100: very high — clear signal or explicit user request. 70–89: high — strong signals, likely correct. 50–69: moderate — reasonable inference but ambiguous. 30–49: low — best guess, user review recommended. 0–29: very low — speculative.",
328+
Minimum: jsonschema.Ptr(0.0),
329+
Maximum: jsonschema.Ptr(100.0),
330+
},
324331
"is_suggestion": {
325332
Type: "boolean",
326333
Description: "If true, this label is sent to the API as a suggestion (suggest:true) rather than an applied label. " +
@@ -387,18 +394,29 @@ func GranularUpdateIssueLabels(t translations.TranslationHelperFunc) inventory.S
387394
if len([]rune(rationale)) > 280 {
388395
return utils.NewToolResultError("label rationale must be 280 characters or less"), nil, nil
389396
}
397+
var confidence *int
398+
if _, exists := v["confidence"]; exists {
399+
c, err := OptionalIntParam(v, "confidence")
400+
if err != nil {
401+
return utils.NewToolResultError(err.Error()), nil, nil
402+
}
403+
if c < 0 || c > 100 {
404+
return utils.NewToolResultError("confidence must be between 0 and 100"), nil, nil
405+
}
406+
confidence = &c
407+
}
390408
isSuggestion, err := OptionalParam[bool](v, "is_suggestion")
391409
if err != nil {
392410
return utils.NewToolResultError(err.Error()), nil, nil
393411
}
394-
if rationale == "" && !isSuggestion {
412+
if rationale == "" && !isSuggestion && confidence == nil {
395413
payload = append(payload, name)
396414
} else {
397415
useObjectForm = true
398-
payload = append(payload, labelWithRationale{Name: name, Rationale: rationale, Suggest: isSuggestion})
416+
payload = append(payload, labelWithIntent{Name: name, Rationale: rationale, Confidence: confidence, Suggest: isSuggestion})
399417
}
400418
default:
401-
return utils.NewToolResultError("each label must be a string or an object with 'name' and optional 'rationale' and/or 'is_suggestion'"), nil, nil
419+
return utils.NewToolResultError("each label must be a string or an object with 'name' and optional 'rationale', 'confidence', and/or 'is_suggestion'"), nil, nil
402420
}
403421
}
404422

@@ -470,18 +488,19 @@ func GranularUpdateIssueMilestone(t translations.TranslationHelperFunc) inventor
470488
)
471489
}
472490

473-
// issueTypeWithRationale represents the object form of the issue type field,
474-
// allowing a rationale and/or suggest flag to be sent alongside the type name.
475-
type issueTypeWithRationale struct {
476-
Value string `json:"value"`
477-
Rationale string `json:"rationale,omitempty"`
478-
Suggest bool `json:"suggest,omitempty"`
491+
// issueTypeWithIntent represents the object form of the issue type field,
492+
// allowing a rationale, confidence score, and/or suggest flag to be sent alongside the type name.
493+
type issueTypeWithIntent struct {
494+
Value string `json:"value"`
495+
Rationale string `json:"rationale,omitempty"`
496+
Confidence *int `json:"confidence,omitempty"`
497+
Suggest bool `json:"suggest,omitempty"`
479498
}
480499

481500
// issueTypeUpdateRequest is a custom request body for updating an issue type
482-
// with an optional rationale, using the object form that the REST API accepts.
501+
// with optional intent metadata, using the object form that the REST API accepts.
483502
type issueTypeUpdateRequest struct {
484-
Type issueTypeWithRationale `json:"type"`
503+
Type issueTypeWithIntent `json:"type"`
485504
}
486505

487506
// GranularUpdateIssueType creates a tool to update an issue's type.
@@ -490,7 +509,7 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser
490509
ToolsetMetadataIssues,
491510
mcp.Tool{
492511
Name: "update_issue_type",
493-
Description: t("TOOL_UPDATE_ISSUE_TYPE_DESCRIPTION", "Update the type of an existing issue (e.g. 'bug', 'feature')."),
512+
Description: t("TOOL_UPDATE_ISSUE_TYPE_DESCRIPTION", "Update the type of an existing issue (e.g. 'bug', 'feature'). When setting values, include a confidence score (0–100) reflecting how certain you are about the choice."),
494513
Annotations: &mcp.ToolAnnotations{
495514
Title: t("TOOL_UPDATE_ISSUE_TYPE_USER_TITLE", "Update Issue Type"),
496515
ReadOnlyHint: false,
@@ -523,6 +542,12 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser
523542
"State the concrete signal (e.g. 'Reports a crash when saving' → bug, 'Asks for dark mode support' → feature).",
524543
MaxLength: jsonschema.Ptr(280),
525544
},
545+
"confidence": {
546+
Type: "integer",
547+
Description: "How confident you are in this choice (0–100). 90–100: very high — clear signal or explicit user request. 70–89: high — strong signals, likely correct. 50–69: moderate — reasonable inference but ambiguous. 30–49: low — best guess, user review recommended. 0–29: very low — speculative.",
548+
Minimum: jsonschema.Ptr(0.0),
549+
Maximum: jsonschema.Ptr(100.0),
550+
},
526551
"is_suggestion": {
527552
Type: "boolean",
528553
Description: "If true, this issue type change is sent to the API as a suggestion (suggest:true) rather than an applied value. " +
@@ -558,6 +583,17 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser
558583
if len([]rune(rationale)) > 280 {
559584
return utils.NewToolResultError("parameter rationale must be 280 characters or less"), nil, nil
560585
}
586+
var confidence *int
587+
if _, exists := args["confidence"]; exists {
588+
c, err := OptionalIntParam(args, "confidence")
589+
if err != nil {
590+
return utils.NewToolResultError(err.Error()), nil, nil
591+
}
592+
if c < 0 || c > 100 {
593+
return utils.NewToolResultError("confidence must be between 0 and 100"), nil, nil
594+
}
595+
confidence = &c
596+
}
561597
isSuggestion, err := OptionalParam[bool](args, "is_suggestion")
562598
if err != nil {
563599
return utils.NewToolResultError(err.Error()), nil, nil
@@ -569,12 +605,13 @@ func GranularUpdateIssueType(t translations.TranslationHelperFunc) inventory.Ser
569605
}
570606

571607
var body any
572-
if rationale != "" || isSuggestion {
608+
if rationale != "" || isSuggestion || confidence != nil {
573609
body = &issueTypeUpdateRequest{
574-
Type: issueTypeWithRationale{
575-
Value: issueType,
576-
Rationale: rationale,
577-
Suggest: isSuggestion,
610+
Type: issueTypeWithIntent{
611+
Value: issueType,
612+
Rationale: rationale,
613+
Confidence: confidence,
614+
Suggest: isSuggestion,
578615
},
579616
}
580617
} else {
@@ -887,6 +924,7 @@ type IssueFieldCreateOrUpdateInput struct {
887924
SingleSelectOptionID *githubv4.ID `json:"singleSelectOptionId,omitempty"`
888925
Delete *githubv4.Boolean `json:"delete,omitempty"`
889926
Rationale *githubv4.String `json:"rationale,omitempty"`
927+
Confidence *int `json:"confidence,omitempty"`
890928
Suggest *githubv4.Boolean `json:"suggest,omitempty"`
891929
}
892930

@@ -896,7 +934,7 @@ func GranularSetIssueFields(t translations.TranslationHelperFunc) inventory.Serv
896934
ToolsetMetadataIssues,
897935
mcp.Tool{
898936
Name: "set_issue_fields",
899-
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."),
937+
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 score (0–100) reflecting how certain you are about the choice."),
900938
Annotations: &mcp.ToolAnnotations{
901939
Title: t("TOOL_SET_ISSUE_FIELDS_USER_TITLE", "Set Issue Fields"),
902940
ReadOnlyHint: false,
@@ -956,6 +994,12 @@ func GranularSetIssueFields(t translations.TranslationHelperFunc) inventory.Serv
956994
"State the concrete signal (e.g. 'Reports a crash when saving' → high priority).",
957995
MaxLength: jsonschema.Ptr(280),
958996
},
997+
"confidence": {
998+
Type: "integer",
999+
Description: "How confident you are in this choice (0–100). 90–100: very high — clear signal or explicit user request. 70–89: high — strong signals, likely correct. 50–69: moderate — reasonable inference but ambiguous. 30–49: low — best guess, user review recommended. 0–29: very low — speculative.",
1000+
Minimum: jsonschema.Ptr(0.0),
1001+
Maximum: jsonschema.Ptr(100.0),
1002+
},
9591003
"is_suggestion": {
9601004
Type: "boolean",
9611005
Description: "If true, this field value is sent to the API as a suggestion (suggest:true) rather than an applied value. " +
@@ -1073,6 +1117,21 @@ func GranularSetIssueFields(t translations.TranslationHelperFunc) inventory.Serv
10731117
}
10741118
}
10751119

1120+
var confidence *int
1121+
if _, exists := fieldMap["confidence"]; exists {
1122+
c, err := OptionalIntParam(fieldMap, "confidence")
1123+
if err != nil {
1124+
return utils.NewToolResultError(err.Error()), nil, nil
1125+
}
1126+
if c < 0 || c > 100 {
1127+
return utils.NewToolResultError("confidence must be between 0 and 100"), nil, nil
1128+
}
1129+
confidence = &c
1130+
}
1131+
if confidence != nil {
1132+
input.Confidence = confidence
1133+
}
1134+
10761135
isSuggestion, err := OptionalParam[bool](fieldMap, "is_suggestion")
10771136
if err != nil {
10781137
return utils.NewToolResultError(err.Error()), nil, nil

0 commit comments

Comments
 (0)