From 754c64ca888540d40eae81723fc23c80c1de5d85 Mon Sep 17 00:00:00 2001 From: Iulia B Date: Tue, 12 May 2026 13:56:41 +0000 Subject: [PATCH 1/6] add support for fields in issue read --- pkg/github/issues_test.go | 84 +++++++++++++++++++++++++++++++++++++ pkg/github/minimal_types.go | 77 +++++++++++++++++++++++++--------- 2 files changed, 141 insertions(+), 20 deletions(-) diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index 6b4042bac5..30381d303d 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -392,6 +392,90 @@ func Test_IssueRead_IFC_InsidersMode(t *testing.T) { }) } +func Test_GetIssue_FieldValues(t *testing.T) { + // Verify that issue_field_values from the REST API are present in the returned object. + serverTool := IssueRead(translations.NullTranslationHelper) + + mockIssueWithFields := &github.Issue{ + Number: github.Ptr(99), + Title: github.Ptr("Issue with field values"), + Body: github.Ptr("body"), + State: github.Ptr("open"), + HTMLURL: github.Ptr("https://github.com/owner/repo/issues/99"), + User: &github.User{ + Login: github.Ptr("testuser"), + }, + IssueFieldValues: []*github.IssueFieldValue{ + { + IssueFieldID: 1001, + NodeID: "FV_node_1", + DataType: "single_select", + Value: "High", + SingleSelectOption: &github.IssueFieldValueSingleSelectOption{ + ID: 42, + Name: "High", + Color: "red", + }, + }, + { + IssueFieldID: 1002, + NodeID: "FV_node_2", + DataType: "text", + Value: "some text value", + }, + }, + } + + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssueWithFields), + }) + + cache := stubRepoAccessCache(nil, 15*time.Minute) + flags := stubFeatureFlags(map[string]bool{"lockdown-mode": false}) + deps := BaseDeps{ + Client: mustNewGHClient(t, mockedClient), + GQLClient: defaultGQLClient, + RepoAccessCache: cache, + Flags: flags, + } + handler := serverTool.Handler(deps) + + request := createMCPRequest(map[string]any{ + "method": "get", + "owner": "owner", + "repo": "repo", + "issue_number": float64(99), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + require.NotNil(t, result) + + textContent := getTextResult(t, result) + + var returnedIssue MinimalIssue + err = json.Unmarshal([]byte(textContent.Text), &returnedIssue) + require.NoError(t, err) + + require.Len(t, returnedIssue.IssueFieldValues, 2, "expected two issue field values") + + first := returnedIssue.IssueFieldValues[0] + assert.Equal(t, int64(1001), first.IssueFieldID) + assert.Equal(t, "FV_node_1", first.NodeID) + assert.Equal(t, "single_select", first.DataType) + assert.Equal(t, "High", first.Value) + require.NotNil(t, first.SingleSelectOption) + assert.Equal(t, int64(42), first.SingleSelectOption.ID) + assert.Equal(t, "High", first.SingleSelectOption.Name) + assert.Equal(t, "red", first.SingleSelectOption.Color) + + second := returnedIssue.IssueFieldValues[1] + assert.Equal(t, int64(1002), second.IssueFieldID) + assert.Equal(t, "FV_node_2", second.NodeID) + assert.Equal(t, "text", second.DataType) + assert.Equal(t, "some text value", second.Value) + assert.Nil(t, second.SingleSelectOption) +} + func Test_AddIssueComment(t *testing.T) { // Verify tool definition once serverTool := AddIssueComment(translations.NullTranslationHelper) diff --git a/pkg/github/minimal_types.go b/pkg/github/minimal_types.go index 65a18ade88..7ad718a211 100644 --- a/pkg/github/minimal_types.go +++ b/pkg/github/minimal_types.go @@ -201,28 +201,45 @@ type MinimalReactions struct { Eyes int `json:"eyes"` } +// MinimalIssueFieldValueSingleSelectOption is the trimmed output type for a single-select option of an issue field value. +type MinimalIssueFieldValueSingleSelectOption struct { + ID int64 `json:"id"` + Name string `json:"name"` + Color string `json:"color"` +} + +// MinimalIssueFieldValue is the trimmed output type for a custom field value attached to an issue. +type MinimalIssueFieldValue struct { + IssueFieldID int64 `json:"issue_field_id"` + NodeID string `json:"node_id"` + DataType string `json:"data_type"` + Value any `json:"value"` + SingleSelectOption *MinimalIssueFieldValueSingleSelectOption `json:"single_select_option,omitempty"` +} + // MinimalIssue is the trimmed output type for issue objects to reduce verbosity. type MinimalIssue struct { - Number int `json:"number"` - Title string `json:"title"` - Body string `json:"body,omitempty"` - State string `json:"state"` - StateReason string `json:"state_reason,omitempty"` - Draft bool `json:"draft,omitempty"` - Locked bool `json:"locked,omitempty"` - HTMLURL string `json:"html_url,omitempty"` - User *MinimalUser `json:"user,omitempty"` - AuthorAssociation string `json:"author_association,omitempty"` - Labels []string `json:"labels,omitempty"` - Assignees []string `json:"assignees,omitempty"` - Milestone string `json:"milestone,omitempty"` - Comments int `json:"comments,omitempty"` - Reactions *MinimalReactions `json:"reactions,omitempty"` - CreatedAt string `json:"created_at,omitempty"` - UpdatedAt string `json:"updated_at,omitempty"` - ClosedAt string `json:"closed_at,omitempty"` - ClosedBy string `json:"closed_by,omitempty"` - IssueType string `json:"issue_type,omitempty"` + Number int `json:"number"` + Title string `json:"title"` + Body string `json:"body,omitempty"` + State string `json:"state"` + StateReason string `json:"state_reason,omitempty"` + Draft bool `json:"draft,omitempty"` + Locked bool `json:"locked,omitempty"` + HTMLURL string `json:"html_url,omitempty"` + User *MinimalUser `json:"user,omitempty"` + AuthorAssociation string `json:"author_association,omitempty"` + Labels []string `json:"labels,omitempty"` + Assignees []string `json:"assignees,omitempty"` + Milestone string `json:"milestone,omitempty"` + Comments int `json:"comments,omitempty"` + Reactions *MinimalReactions `json:"reactions,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + ClosedAt string `json:"closed_at,omitempty"` + ClosedBy string `json:"closed_by,omitempty"` + IssueType string `json:"issue_type,omitempty"` + IssueFieldValues []MinimalIssueFieldValue `json:"issue_field_values,omitempty"` } // MinimalIssuesResponse is the trimmed output for a paginated list of issues. @@ -400,6 +417,26 @@ func convertToMinimalIssue(issue *github.Issue) MinimalIssue { m.IssueType = issueType.GetName() } + for _, fv := range issue.IssueFieldValues { + if fv == nil { + continue + } + mfv := MinimalIssueFieldValue{ + IssueFieldID: fv.IssueFieldID, + NodeID: fv.NodeID, + DataType: fv.DataType, + Value: fv.Value, + } + if opt := fv.SingleSelectOption; opt != nil { + mfv.SingleSelectOption = &MinimalIssueFieldValueSingleSelectOption{ + ID: opt.ID, + Name: opt.Name, + Color: opt.Color, + } + } + m.IssueFieldValues = append(m.IssueFieldValues, mfv) + } + if r := issue.Reactions; r != nil { m.Reactions = &MinimalReactions{ TotalCount: r.GetTotalCount(), From 562f8656233af47bdfac8bcf3620cb63ea9c98c8 Mon Sep 17 00:00:00 2001 From: Iulia B Date: Tue, 12 May 2026 13:56:58 +0000 Subject: [PATCH 2/6] add support for fields in issues write --- pkg/github/__toolsnaps__/issue_write.snap | 23 +++ pkg/github/issues.go | 203 +++++++++++++++++++++- pkg/github/issues_test.go | 148 +++++++++++++++- 3 files changed, 365 insertions(+), 9 deletions(-) diff --git a/pkg/github/__toolsnaps__/issue_write.snap b/pkg/github/__toolsnaps__/issue_write.snap index 24cff5df97..599c490f1b 100644 --- a/pkg/github/__toolsnaps__/issue_write.snap +++ b/pkg/github/__toolsnaps__/issue_write.snap @@ -29,6 +29,29 @@ "description": "Issue number that this issue is a duplicate of. Only used when state_reason is 'duplicate'.", "type": "number" }, + "issue_fields": { + "description": "Issue field values to set. Each item requires field_name and either value or field_option_name. field_option_name is for single-select fields and is resolved to the corresponding option ID automatically.", + "items": { + "properties": { + "field_name": { + "description": "Issue field name", + "type": "string" + }, + "field_option_name": { + "description": "Single-select option name to resolve and set for the field", + "type": "string" + }, + "value": { + "description": "Value for text/number/date/single-select fields. For single-select, you can use field_option_name instead." + } + }, + "required": [ + "field_name" + ], + "type": "object" + }, + "type": "array" + }, "issue_number": { "description": "Issue number to update", "type": "number" diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 52a024c298..505a0ad9e8 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -35,6 +35,36 @@ type CloseIssueInput struct { // Used to extend the functionality of the githubv4 library to support closing issues as duplicates. type IssueClosedStateReason string +// IssueWriteFieldInput is a user-friendly issue field input for issue_write. +// Field IDs and option IDs are resolved internally before calling the REST API. +type IssueWriteFieldInput struct { + FieldName string + Value any + FieldOptionName string +} + +type issueFieldMetadataOption struct { + DatabaseID githubv4.Int `graphql:"databaseId"` + Name githubv4.String +} + +type issueFieldMetadataNode struct { + DatabaseID githubv4.Int `graphql:"databaseId"` + Name githubv4.String + DataType githubv4.String + SingleSelectField struct { + Options []issueFieldMetadataOption `graphql:"options"` + } `graphql:"... on IssueFieldSingleSelect"` +} + +type issueFieldMetadataQuery struct { + Repository struct { + IssueFields struct { + Nodes []issueFieldMetadataNode + } `graphql:"issueFields(first: 100)"` + } `graphql:"repository(owner: $owner, name: $repo)"` +} + const ( IssueClosedStateReasonCompleted IssueClosedStateReason = "COMPLETED" IssueClosedStateReasonDuplicate IssueClosedStateReason = "DUPLICATE" @@ -103,6 +133,127 @@ func getCloseStateReason(stateReason string) IssueClosedStateReason { } } +func optionalIssueWriteFields(args map[string]any) ([]IssueWriteFieldInput, error) { + issueFieldsRaw, exists := args["issue_fields"] + if !exists { + return nil, nil + } + + var inputMaps []map[string]any + switch v := issueFieldsRaw.(type) { + case []any: + for _, item := range v { + itemMap, ok := item.(map[string]any) + if !ok { + return nil, fmt.Errorf("each issue_fields item must be an object") + } + inputMaps = append(inputMaps, itemMap) + } + case []map[string]any: + inputMaps = v + default: + return nil, fmt.Errorf("issue_fields must be an array") + } + + issueFields := make([]IssueWriteFieldInput, 0, len(inputMaps)) + for _, itemMap := range inputMaps { + fieldName, err := RequiredParam[string](itemMap, "field_name") + if err != nil || strings.TrimSpace(fieldName) == "" { + return nil, fmt.Errorf("field_name is required for each issue_fields item") + } + + fieldOptionName, err := OptionalParam[string](itemMap, "field_option_name") + if err != nil { + return nil, err + } + + value, hasValue := itemMap["value"] + if hasValue && value == nil { + return nil, fmt.Errorf("value cannot be null for field %q", fieldName) + } + + if hasValue && fieldOptionName != "" { + return nil, fmt.Errorf("issue field %q cannot specify both value and field_option_name", fieldName) + } + + if !hasValue && fieldOptionName == "" { + return nil, fmt.Errorf("issue field %q must specify either value or field_option_name", fieldName) + } + + issueFields = append(issueFields, IssueWriteFieldInput{ + FieldName: fieldName, + Value: value, + FieldOptionName: fieldOptionName, + }) + } + + return issueFields, nil +} + +func resolveIssueRequestFieldValues(ctx context.Context, gqlClient *githubv4.Client, owner, repo string, issueFields []IssueWriteFieldInput) ([]*github.IssueRequestFieldValue, error) { + if len(issueFields) == 0 { + return nil, nil + } + + query := issueFieldMetadataQuery{} + vars := map[string]any{ + "owner": githubv4.String(owner), + "repo": githubv4.String(repo), + } + if err := gqlClient.Query(ctx, &query, vars); err != nil { + return nil, fmt.Errorf("failed to query issue fields metadata: %w", err) + } + + fieldByName := make(map[string]issueFieldMetadataNode, len(query.Repository.IssueFields.Nodes)) + for _, field := range query.Repository.IssueFields.Nodes { + fieldByName[strings.ToLower(strings.TrimSpace(string(field.Name)))] = field + } + + resolved := make([]*github.IssueRequestFieldValue, 0, len(issueFields)) + for _, fieldInput := range issueFields { + field, ok := fieldByName[strings.ToLower(strings.TrimSpace(fieldInput.FieldName))] + if !ok { + return nil, fmt.Errorf("issue field %q was not found in %s/%s", fieldInput.FieldName, owner, repo) + } + + fieldID := int64(field.DatabaseID) + if fieldID == 0 { + return nil, fmt.Errorf("issue field %q is missing databaseId", fieldInput.FieldName) + } + + resolvedValue := fieldInput.Value + if fieldInput.FieldOptionName != "" { + if !strings.EqualFold(string(field.DataType), "single_select") { + return nil, fmt.Errorf("issue field %q is %q, so field_option_name cannot be used", fieldInput.FieldName, field.DataType) + } + + optionFound := false + for _, option := range field.SingleSelectField.Options { + if strings.EqualFold(strings.TrimSpace(string(option.Name)), strings.TrimSpace(fieldInput.FieldOptionName)) { + optionID := int64(option.DatabaseID) + if optionID == 0 { + return nil, fmt.Errorf("issue field option %q on field %q is missing databaseId", fieldInput.FieldOptionName, fieldInput.FieldName) + } + resolvedValue = optionID + optionFound = true + break + } + } + + if !optionFound { + return nil, fmt.Errorf("issue field option %q was not found for field %q", fieldInput.FieldOptionName, fieldInput.FieldName) + } + } + + resolved = append(resolved, &github.IssueRequestFieldValue{ + FieldID: fieldID, + Value: resolvedValue, + }) + } + + return resolved, nil +} + // IssueFragment represents a fragment of an issue node in the GraphQL API. type IssueFragment struct { Number githubv4.Int @@ -1171,6 +1322,27 @@ Options are: Type: "number", Description: "Issue number that this issue is a duplicate of. Only used when state_reason is 'duplicate'.", }, + "issue_fields": { + Type: "array", + Description: "Issue field values to set. Each item requires field_name and either value or field_option_name. field_option_name is for single-select fields and is resolved to the corresponding option ID automatically.", + Items: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "field_name": { + Type: "string", + Description: "Issue field name", + }, + "value": { + Description: "Value for text/number/date/single-select fields. For single-select, you can use field_option_name instead.", + }, + "field_option_name": { + Type: "string", + Description: "Single-select option name to resolve and set for the field", + }, + }, + Required: []string{"field_name"}, + }, + }, }, Required: []string{"method", "owner", "repo"}, }, @@ -1272,6 +1444,11 @@ Options are: return utils.NewToolResultError("duplicate_of can only be used when state_reason is 'duplicate'"), nil, nil } + issueFields, err := optionalIssueWriteFields(args) + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + client, err := deps.GetClient(ctx) if err != nil { return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil @@ -1282,16 +1459,21 @@ Options are: return utils.NewToolResultErrorFromErr("failed to get GraphQL client", err), nil, nil } + issueFieldValues, err := resolveIssueRequestFieldValues(ctx, gqlClient, owner, repo, issueFields) + if err != nil { + return utils.NewToolResultError(fmt.Sprintf("failed to resolve issue_fields: %v", err)), nil, nil + } + switch method { case "create": - result, err := CreateIssue(ctx, client, owner, repo, title, body, assignees, labels, milestoneNum, issueType) + result, err := CreateIssue(ctx, client, owner, repo, title, body, assignees, labels, milestoneNum, issueType, issueFieldValues) return result, nil, err case "update": issueNumber, err := RequiredInt(args, "issue_number") if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } - result, err := UpdateIssue(ctx, client, gqlClient, owner, repo, issueNumber, title, body, assignees, labels, milestoneNum, issueType, state, stateReason, duplicateOf) + result, err := UpdateIssue(ctx, client, gqlClient, owner, repo, issueNumber, title, body, assignees, labels, milestoneNum, issueType, issueFieldValues, state, stateReason, duplicateOf) return result, nil, err default: return utils.NewToolResultError("invalid method, must be either 'create' or 'update'"), nil, nil @@ -1301,17 +1483,18 @@ Options are: return st } -func CreateIssue(ctx context.Context, client *github.Client, owner string, repo string, title string, body string, assignees []string, labels []string, milestoneNum int, issueType string) (*mcp.CallToolResult, error) { +func CreateIssue(ctx context.Context, client *github.Client, owner string, repo string, title string, body string, assignees []string, labels []string, milestoneNum int, issueType string, issueFieldValues []*github.IssueRequestFieldValue) (*mcp.CallToolResult, error) { if title == "" { return utils.NewToolResultError("missing required parameter: title"), nil } // Create the issue request issueRequest := &github.IssueRequest{ - Title: github.Ptr(title), - Body: github.Ptr(body), - Assignees: &assignees, - Labels: &labels, + Title: github.Ptr(title), + Body: github.Ptr(body), + Assignees: &assignees, + Labels: &labels, + IssueFieldValues: issueFieldValues, } if milestoneNum != 0 { @@ -1354,7 +1537,7 @@ func CreateIssue(ctx context.Context, client *github.Client, owner string, repo return utils.NewToolResultText(string(r)), nil } -func UpdateIssue(ctx context.Context, client *github.Client, gqlClient *githubv4.Client, owner string, repo string, issueNumber int, title string, body string, assignees []string, labels []string, milestoneNum int, issueType string, state string, stateReason string, duplicateOf int) (*mcp.CallToolResult, error) { +func UpdateIssue(ctx context.Context, client *github.Client, gqlClient *githubv4.Client, owner string, repo string, issueNumber int, title string, body string, assignees []string, labels []string, milestoneNum int, issueType string, issueFieldValues []*github.IssueRequestFieldValue, state string, stateReason string, duplicateOf int) (*mcp.CallToolResult, error) { // Create the issue request with only provided fields issueRequest := &github.IssueRequest{} @@ -1383,6 +1566,10 @@ func UpdateIssue(ctx context.Context, client *github.Client, gqlClient *githubv4 issueRequest.Type = github.Ptr(issueType) } + if len(issueFieldValues) > 0 { + issueRequest.IssueFieldValues = issueFieldValues + } + updatedIssue, resp, err := client.Issues.Edit(ctx, owner, repo, issueNumber, issueRequest) if err != nil { return ghErrors.NewGitHubAPIErrorResponse(ctx, diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index 30381d303d..8d65cdb5d9 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -1076,6 +1076,7 @@ func Test_CreateIssue(t *testing.T) { assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "labels") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "milestone") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "type") + assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_fields") assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"method", "owner", "repo"}) // Setup mock issue for success case @@ -1094,6 +1095,7 @@ func Test_CreateIssue(t *testing.T) { tests := []struct { name string mockedClient *http.Client + mockedGQLClient *http.Client requestArgs map[string]any expectError bool expectedIssue *github.Issue @@ -1152,6 +1154,75 @@ func Test_CreateIssue(t *testing.T) { State: github.Ptr("open"), }, }, + { + name: "successful issue creation with issue fields reconciled by names", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesByOwnerByRepo: expectRequestBody(t, map[string]any{ + "title": "Issue with fields", + "body": "", + "labels": []any{}, + "assignees": []any{}, + "issue_field_values": []any{ + map[string]any{"field_id": float64(101), "value": float64(9001)}, + map[string]any{"field_id": float64(102), "value": "Acme"}, + }, + }).andThen( + mockResponse(t, http.StatusCreated, &github.Issue{ + Number: github.Ptr(125), + Title: github.Ptr("Issue with fields"), + HTMLURL: github.Ptr("https://github.com/owner/repo/issues/125"), + State: github.Ptr("open"), + }), + ), + }), + mockedGQLClient: githubv4mock.NewMockedHTTPClient( + githubv4mock.NewQueryMatcher( + issueFieldMetadataQuery{}, + map[string]any{ + "owner": githubv4.String("owner"), + "repo": githubv4.String("repo"), + }, + githubv4mock.DataResponse(map[string]any{ + "repository": map[string]any{ + "issueFields": map[string]any{ + "nodes": []map[string]any{ + { + "databaseId": 101, + "name": "Priority", + "dataType": "single_select", + "options": []map[string]any{ + {"databaseId": 9001, "name": "P1"}, + }, + }, + { + "databaseId": 102, + "name": "Customer", + "dataType": "text", + }, + }, + }, + }, + }), + ), + ), + requestArgs: map[string]any{ + "method": "create", + "owner": "owner", + "repo": "repo", + "title": "Issue with fields", + "issue_fields": []any{ + map[string]any{"field_name": "Priority", "field_option_name": "P1"}, + map[string]any{"field_name": "Customer", "value": "Acme"}, + }, + }, + expectError: false, + expectedIssue: &github.Issue{ + Number: github.Ptr(125), + Title: github.Ptr("Issue with fields"), + HTMLURL: github.Ptr("https://github.com/owner/repo/issues/125"), + State: github.Ptr("open"), + }, + }, { name: "issue creation fails", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ @@ -1169,13 +1240,32 @@ func Test_CreateIssue(t *testing.T) { expectError: false, expectedErrMsg: "missing required parameter: title", }, + { + name: "issue_fields rejects both value and field_option_name", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), + requestArgs: map[string]any{ + "method": "create", + "owner": "owner", + "repo": "repo", + "title": "Invalid fields", + "issue_fields": []any{ + map[string]any{"field_name": "Priority", "value": "P1", "field_option_name": "P1"}, + }, + }, + expectError: false, + expectedErrMsg: "cannot specify both value and field_option_name", + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := mustNewGHClient(t, tc.mockedClient) - gqlClient := githubv4.NewClient(nil) + gqlHTTPClient := tc.mockedGQLClient + if gqlHTTPClient == nil { + gqlHTTPClient = githubv4mock.NewMockedHTTPClient() + } + gqlClient := githubv4.NewClient(gqlHTTPClient) deps := BaseDeps{ Client: client, GQLClient: gqlClient, @@ -1865,6 +1955,7 @@ func Test_UpdateIssue(t *testing.T) { assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "state") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "state_reason") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "duplicate_of") + assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_fields") assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"method", "owner", "repo"}) // Mock issues for reuse across test cases @@ -1976,6 +2067,61 @@ func Test_UpdateIssue(t *testing.T) { expectError: false, expectedIssue: mockUpdatedIssue, }, + { + name: "partial update with issue fields reconciled by names", + mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, map[string]any{ + "issue_field_values": []any{ + map[string]any{"field_id": float64(101), "value": float64(9001)}, + map[string]any{"field_id": float64(102), "value": "Acme"}, + }, + "title": "Updated Title", + }).andThen( + mockResponse(t, http.StatusOK, mockUpdatedIssue), + ), + }), + mockedGQLClient: githubv4mock.NewMockedHTTPClient( + githubv4mock.NewQueryMatcher( + issueFieldMetadataQuery{}, + map[string]any{ + "owner": githubv4.String("owner"), + "repo": githubv4.String("repo"), + }, + githubv4mock.DataResponse(map[string]any{ + "repository": map[string]any{ + "issueFields": map[string]any{ + "nodes": []map[string]any{ + { + "databaseId": 101, + "name": "Priority", + "dataType": "single_select", + "options": []map[string]any{{"databaseId": 9001, "name": "P1"}}, + }, + { + "databaseId": 102, + "name": "Customer", + "dataType": "text", + }, + }, + }, + }, + }), + ), + ), + requestArgs: map[string]any{ + "method": "update", + "owner": "owner", + "repo": "repo", + "issue_number": float64(123), + "title": "Updated Title", + "issue_fields": []any{ + map[string]any{"field_name": "Priority", "field_option_name": "P1"}, + map[string]any{"field_name": "Customer", "value": "Acme"}, + }, + }, + expectError: false, + expectedIssue: mockUpdatedIssue, + }, { name: "issue not found when updating non-state fields only", mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ From ccd5b960074cbcab7f35fda7f6bf5ef8f4470ba2 Mon Sep 17 00:00:00 2001 From: Iulia B Date: Tue, 12 May 2026 14:32:05 +0000 Subject: [PATCH 3/6] add issues write support --- pkg/github/issues.go | 6 +++--- pkg/github/issues_test.go | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 505a0ad9e8..99c26267ec 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -49,9 +49,9 @@ type issueFieldMetadataOption struct { } type issueFieldMetadataNode struct { - DatabaseID githubv4.Int `graphql:"databaseId"` - Name githubv4.String - DataType githubv4.String + DatabaseID githubv4.Int `graphql:"databaseId"` + Name githubv4.String + DataType githubv4.String SingleSelectField struct { Options []issueFieldMetadataOption `graphql:"options"` } `graphql:"... on IssueFieldSingleSelect"` diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index 8d65cdb5d9..2a05354bfe 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -1093,13 +1093,13 @@ func Test_CreateIssue(t *testing.T) { } tests := []struct { - name string - mockedClient *http.Client + name string + mockedClient *http.Client mockedGQLClient *http.Client - requestArgs map[string]any - expectError bool - expectedIssue *github.Issue - expectedErrMsg string + requestArgs map[string]any + expectError bool + expectedIssue *github.Issue + expectedErrMsg string }{ { name: "successful issue creation with all fields", @@ -2095,7 +2095,7 @@ func Test_UpdateIssue(t *testing.T) { "databaseId": 101, "name": "Priority", "dataType": "single_select", - "options": []map[string]any{{"databaseId": 9001, "name": "P1"}}, + "options": []map[string]any{{"databaseId": 9001, "name": "P1"}}, }, { "databaseId": 102, From f6f64900068dd3154b6373d1c8dd74beebffdbbe Mon Sep 17 00:00:00 2001 From: Iulia B Date: Mon, 18 May 2026 13:29:12 +0000 Subject: [PATCH 4/6] docs --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1030f83ca0..12867c1197 100644 --- a/README.md +++ b/README.md @@ -859,6 +859,7 @@ The following sets of tools are available: - `assignees`: Usernames to assign to this issue (string[], optional) - `body`: Issue body content (string, optional) - `duplicate_of`: Issue number that this issue is a duplicate of. Only used when state_reason is 'duplicate'. (number, optional) + - `issue_fields`: Issue field values to set. Each item requires field_name and either value or field_option_name. field_option_name is for single-select fields and is resolved to the corresponding option ID automatically. (object[], optional) - `issue_number`: Issue number to update (number, optional) - `labels`: Labels to apply to this issue (string[], optional) - `method`: Write operation to perform on a single issue. From 4a70290af06332c7e24601db9da679854ba3b186 Mon Sep 17 00:00:00 2001 From: Iulia B Date: Thu, 21 May 2026 12:58:49 +0000 Subject: [PATCH 5/6] fix(tests): use MinimalFieldValue for GraphQL field value assertions --- .github/workflows/code-scanning.yml | 8 ++++---- .github/workflows/docker-publish.yml | 6 +++--- .github/workflows/docs-check.yml | 9 ++++++++- .github/workflows/go.yml | 10 +++++++++- .github/workflows/goreleaser.yml | 13 ++++++++++--- .github/workflows/license-check.yml | 9 ++++++++- .github/workflows/lint.yml | 7 ++++++- .github/workflows/mcp-diff.yml | 24 ++++++++++++++++++++++-- pkg/github/issues_test.go | 6 +++--- 9 files changed, 73 insertions(+), 19 deletions(-) diff --git a/.github/workflows/code-scanning.yml b/.github/workflows/code-scanning.yml index ecbe9f0dcb..e58a45e71e 100644 --- a/.github/workflows/code-scanning.yml +++ b/.github/workflows/code-scanning.yml @@ -78,9 +78,9 @@ jobs: go-version: ${{ fromJSON(steps.resolve-environment.outputs.environment).configuration.go.version }} cache: false - - name: Set up Node.js (for JavaScript CodeQL) - if: matrix.language == 'javascript' - uses: actions/setup-node@v6 + - name: Set up Node.js + if: matrix.language == 'go' || matrix.language == 'javascript' + uses: actions/setup-node@v4 with: node-version: "20" cache: "npm" @@ -88,7 +88,7 @@ jobs: - name: Build UI if: matrix.language == 'go' - uses: ./.github/actions/build-ui + run: script/build-ui - name: Autobuild uses: github/codeql-action/autobuild@v4 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index f56d4f31a2..638713c700 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -46,7 +46,7 @@ jobs: # https://github.com/sigstore/cosign-installer - name: Install cosign if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 #v4.1.2 + uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 #v4.1.0 with: cosign-release: "v2.2.4" @@ -60,7 +60,7 @@ jobs: # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -93,7 +93,7 @@ jobs: key: ${{ runner.os }}-go-build-cache-${{ hashFiles('**/go.sum') }} - name: Inject go-build-cache - uses: reproducible-containers/buildkit-cache-dance@5422eac04292c961a382e0f584ea0f03ad9da723 # v3.4.0 + uses: reproducible-containers/buildkit-cache-dance@1b8ab18fbda5ad3646e3fcc9ed9dd41ce2f297b4 # v3.3.2 with: cache-map: | { diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml index 309eddb38e..de62d6282c 100644 --- a/.github/workflows/docs-check.yml +++ b/.github/workflows/docs-check.yml @@ -16,8 +16,15 @@ jobs: - name: Checkout code uses: actions/checkout@v6 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: ui/package-lock.json + - name: Build UI - uses: ./.github/actions/build-ui + run: script/build-ui - name: Set up Go uses: actions/setup-go@v6 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 1fea50114a..f874b2b59d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -25,8 +25,16 @@ jobs: - name: Check out code uses: actions/checkout@v6 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: ui/package-lock.json + - name: Build UI - uses: ./.github/actions/build-ui + shell: bash + run: script/build-ui - name: Set up Go uses: actions/setup-go@v6 diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 1004fc2747..f8eddc076c 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -16,8 +16,15 @@ jobs: - name: Check out code uses: actions/checkout@v6 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: ui/package-lock.json + - name: Build UI - uses: ./.github/actions/build-ui + run: script/build-ui - name: Set up Go uses: actions/setup-go@v6 @@ -28,7 +35,7 @@ jobs: run: go mod download - name: Run GoReleaser - uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a with: distribution: goreleaser # GoReleaser version @@ -40,7 +47,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Generate signed build provenance attestations for workflow artifacts - uses: actions/attest-build-provenance@v4 + uses: actions/attest-build-provenance@v3 with: subject-path: | dist/*.tar.gz diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index 2f27353d83..9e352c3f69 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -32,8 +32,15 @@ jobs: GH_TOKEN: ${{ github.token }} run: gh pr checkout ${{ github.event.pull_request.number }} + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: ui/package-lock.json + - name: Build UI - uses: ./.github/actions/build-ui + run: script/build-ui - name: Set up Go uses: actions/setup-go@v6 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5b912cea0f..3676cb4103 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,8 +14,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: ui/package-lock.json - name: Build UI - uses: ./.github/actions/build-ui + run: script/build-ui - uses: actions/setup-go@v6 with: go-version: '1.25' diff --git a/.github/workflows/mcp-diff.yml b/.github/workflows/mcp-diff.yml index bb6341c096..3c6c0149a8 100644 --- a/.github/workflows/mcp-diff.yml +++ b/.github/workflows/mcp-diff.yml @@ -19,8 +19,13 @@ jobs: with: fetch-depth: 0 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Build UI - uses: ./.github/actions/build-ui + run: script/build-ui - name: Run MCP Server Diff uses: SamMorrowDrums/mcp-server-diff@v2.3.5 @@ -34,6 +39,8 @@ jobs: [ {"name": "default", "args": ""}, {"name": "read-only", "args": "--read-only"}, + {"name": "dynamic-toolsets", "args": "--dynamic-toolsets"}, + {"name": "read-only+dynamic", "args": "--read-only --dynamic-toolsets"}, {"name": "toolsets-repos", "args": "--toolsets=repos"}, {"name": "toolsets-issues", "args": "--toolsets=issues"}, {"name": "toolsets-context", "args": "--toolsets=context"}, @@ -43,7 +50,20 @@ jobs: {"name": "toolsets-all", "args": "--toolsets=all"}, {"name": "tools-get_me", "args": "--tools=get_me"}, {"name": "tools-get_me,list_issues", "args": "--tools=get_me,list_issues"}, - {"name": "toolsets-repos+read-only", "args": "--toolsets=repos --read-only"} + {"name": "toolsets-repos+read-only", "args": "--toolsets=repos --read-only"}, + {"name": "toolsets-all+dynamic", "args": "--toolsets=all --dynamic-toolsets"}, + {"name": "toolsets-repos+dynamic", "args": "--toolsets=repos --dynamic-toolsets"}, + {"name": "toolsets-repos,issues+dynamic", "args": "--toolsets=repos,issues --dynamic-toolsets"}, + { + "name": "dynamic-tool-calls", + "args": "--dynamic-toolsets", + "custom_messages": [ + {"id": 10, "name": "list_toolsets_before", "message": {"jsonrpc": "2.0", "id": 10, "method": "tools/call", "params": {"name": "list_available_toolsets", "arguments": {}}}}, + {"id": 11, "name": "get_toolset_tools", "message": {"jsonrpc": "2.0", "id": 11, "method": "tools/call", "params": {"name": "get_toolset_tools", "arguments": {"toolset": "repos"}}}}, + {"id": 12, "name": "enable_toolset", "message": {"jsonrpc": "2.0", "id": 12, "method": "tools/call", "params": {"name": "enable_toolset", "arguments": {"toolset": "repos"}}}}, + {"id": 13, "name": "list_toolsets_after", "message": {"jsonrpc": "2.0", "id": 13, "method": "tools/call", "params": {"name": "list_available_toolsets", "arguments": {}}}} + ] + } ] - name: Add interpretation note diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index d14dfcfc1f..3272f3772c 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -1186,7 +1186,7 @@ func Test_SearchIssues_FieldValuesEnrichment(t *testing.T) { require.Equal(t, 2, *response.Total) require.Len(t, response.Items, 2) assert.Equal(t, 42, *response.Items[0].Number) - assert.Equal(t, []MinimalIssueFieldValue{ + assert.Equal(t, []MinimalFieldValue{ {Field: "priority", Value: "P1"}, {Field: "estimate", Value: "2.5"}, }, response.Items[0].FieldValues) @@ -1976,9 +1976,9 @@ func Test_ListIssues(t *testing.T) { // (including float formatting); #789 has no field values. switch issue.Number { case 123: - assert.Equal(t, []MinimalIssueFieldValue{{Field: "priority", Value: "P1"}}, issue.FieldValues) + assert.Equal(t, []MinimalFieldValue{{Field: "priority", Value: "P1"}}, issue.FieldValues) case 456: - assert.Equal(t, []MinimalIssueFieldValue{ + assert.Equal(t, []MinimalFieldValue{ {Field: "due", Value: "2026-06-01"}, {Field: "estimate", Value: "2.5"}, {Field: "notes", Value: "needs triage"}, From e4467977736507321844cb51b95ef220c630e011 Mon Sep 17 00:00:00 2001 From: Iulia B Date: Thu, 21 May 2026 13:22:15 +0000 Subject: [PATCH 6/6] chore: sync .github/workflows from upstream/main --- .github/workflows/code-scanning.yml | 8 ++++---- .github/workflows/docker-publish.yml | 6 +++--- .github/workflows/docs-check.yml | 9 +-------- .github/workflows/go.yml | 10 +--------- .github/workflows/goreleaser.yml | 13 +++---------- .github/workflows/license-check.yml | 9 +-------- .github/workflows/lint.yml | 7 +------ .github/workflows/mcp-diff.yml | 24 ++---------------------- 8 files changed, 16 insertions(+), 70 deletions(-) diff --git a/.github/workflows/code-scanning.yml b/.github/workflows/code-scanning.yml index e58a45e71e..ecbe9f0dcb 100644 --- a/.github/workflows/code-scanning.yml +++ b/.github/workflows/code-scanning.yml @@ -78,9 +78,9 @@ jobs: go-version: ${{ fromJSON(steps.resolve-environment.outputs.environment).configuration.go.version }} cache: false - - name: Set up Node.js - if: matrix.language == 'go' || matrix.language == 'javascript' - uses: actions/setup-node@v4 + - name: Set up Node.js (for JavaScript CodeQL) + if: matrix.language == 'javascript' + uses: actions/setup-node@v6 with: node-version: "20" cache: "npm" @@ -88,7 +88,7 @@ jobs: - name: Build UI if: matrix.language == 'go' - run: script/build-ui + uses: ./.github/actions/build-ui - name: Autobuild uses: github/codeql-action/autobuild@v4 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 638713c700..f56d4f31a2 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -46,7 +46,7 @@ jobs: # https://github.com/sigstore/cosign-installer - name: Install cosign if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 #v4.1.0 + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 #v4.1.2 with: cosign-release: "v2.2.4" @@ -60,7 +60,7 @@ jobs: # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -93,7 +93,7 @@ jobs: key: ${{ runner.os }}-go-build-cache-${{ hashFiles('**/go.sum') }} - name: Inject go-build-cache - uses: reproducible-containers/buildkit-cache-dance@1b8ab18fbda5ad3646e3fcc9ed9dd41ce2f297b4 # v3.3.2 + uses: reproducible-containers/buildkit-cache-dance@5422eac04292c961a382e0f584ea0f03ad9da723 # v3.4.0 with: cache-map: | { diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml index de62d6282c..309eddb38e 100644 --- a/.github/workflows/docs-check.yml +++ b/.github/workflows/docs-check.yml @@ -16,15 +16,8 @@ jobs: - name: Checkout code uses: actions/checkout@v6 - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: ui/package-lock.json - - name: Build UI - run: script/build-ui + uses: ./.github/actions/build-ui - name: Set up Go uses: actions/setup-go@v6 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f874b2b59d..1fea50114a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -25,16 +25,8 @@ jobs: - name: Check out code uses: actions/checkout@v6 - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: ui/package-lock.json - - name: Build UI - shell: bash - run: script/build-ui + uses: ./.github/actions/build-ui - name: Set up Go uses: actions/setup-go@v6 diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index f8eddc076c..1004fc2747 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -16,15 +16,8 @@ jobs: - name: Check out code uses: actions/checkout@v6 - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: ui/package-lock.json - - name: Build UI - run: script/build-ui + uses: ./.github/actions/build-ui - name: Set up Go uses: actions/setup-go@v6 @@ -35,7 +28,7 @@ jobs: run: go mod download - name: Run GoReleaser - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a + uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 with: distribution: goreleaser # GoReleaser version @@ -47,7 +40,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Generate signed build provenance attestations for workflow artifacts - uses: actions/attest-build-provenance@v3 + uses: actions/attest-build-provenance@v4 with: subject-path: | dist/*.tar.gz diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index 9e352c3f69..2f27353d83 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -32,15 +32,8 @@ jobs: GH_TOKEN: ${{ github.token }} run: gh pr checkout ${{ github.event.pull_request.number }} - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: ui/package-lock.json - - name: Build UI - run: script/build-ui + uses: ./.github/actions/build-ui - name: Set up Go uses: actions/setup-go@v6 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3676cb4103..5b912cea0f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,13 +14,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: ui/package-lock.json - name: Build UI - run: script/build-ui + uses: ./.github/actions/build-ui - uses: actions/setup-go@v6 with: go-version: '1.25' diff --git a/.github/workflows/mcp-diff.yml b/.github/workflows/mcp-diff.yml index 3c6c0149a8..bb6341c096 100644 --- a/.github/workflows/mcp-diff.yml +++ b/.github/workflows/mcp-diff.yml @@ -19,13 +19,8 @@ jobs: with: fetch-depth: 0 - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - name: Build UI - run: script/build-ui + uses: ./.github/actions/build-ui - name: Run MCP Server Diff uses: SamMorrowDrums/mcp-server-diff@v2.3.5 @@ -39,8 +34,6 @@ jobs: [ {"name": "default", "args": ""}, {"name": "read-only", "args": "--read-only"}, - {"name": "dynamic-toolsets", "args": "--dynamic-toolsets"}, - {"name": "read-only+dynamic", "args": "--read-only --dynamic-toolsets"}, {"name": "toolsets-repos", "args": "--toolsets=repos"}, {"name": "toolsets-issues", "args": "--toolsets=issues"}, {"name": "toolsets-context", "args": "--toolsets=context"}, @@ -50,20 +43,7 @@ jobs: {"name": "toolsets-all", "args": "--toolsets=all"}, {"name": "tools-get_me", "args": "--tools=get_me"}, {"name": "tools-get_me,list_issues", "args": "--tools=get_me,list_issues"}, - {"name": "toolsets-repos+read-only", "args": "--toolsets=repos --read-only"}, - {"name": "toolsets-all+dynamic", "args": "--toolsets=all --dynamic-toolsets"}, - {"name": "toolsets-repos+dynamic", "args": "--toolsets=repos --dynamic-toolsets"}, - {"name": "toolsets-repos,issues+dynamic", "args": "--toolsets=repos,issues --dynamic-toolsets"}, - { - "name": "dynamic-tool-calls", - "args": "--dynamic-toolsets", - "custom_messages": [ - {"id": 10, "name": "list_toolsets_before", "message": {"jsonrpc": "2.0", "id": 10, "method": "tools/call", "params": {"name": "list_available_toolsets", "arguments": {}}}}, - {"id": 11, "name": "get_toolset_tools", "message": {"jsonrpc": "2.0", "id": 11, "method": "tools/call", "params": {"name": "get_toolset_tools", "arguments": {"toolset": "repos"}}}}, - {"id": 12, "name": "enable_toolset", "message": {"jsonrpc": "2.0", "id": 12, "method": "tools/call", "params": {"name": "enable_toolset", "arguments": {"toolset": "repos"}}}}, - {"id": 13, "name": "list_toolsets_after", "message": {"jsonrpc": "2.0", "id": 13, "method": "tools/call", "params": {"name": "list_available_toolsets", "arguments": {}}}} - ] - } + {"name": "toolsets-repos+read-only", "args": "--toolsets=repos --read-only"} ] - name: Add interpretation note