diff --git a/README.md b/README.md index 432541f434..6632aec233 100644 --- a/README.md +++ b/README.md @@ -599,6 +599,7 @@ The following sets of tools are available: | code-square | `code_quality` | GitHub Code Quality related tools | | codescan | `code_security` | Code security related tools, such as GitHub Code Scanning | | copilot | `copilot` | Copilot related tools | +| workflow | `copilot_automations` | Copilot Automation related tools | | copilot | `copilot_issue_intents` | Opt-in Copilot issue assignment tools that carry intent metadata (rationale, confidence, suggestion) | | dependabot | `dependabot` | Dependabot tools | | comment-discussion | `discussions` | GitHub Discussions related tools | @@ -776,6 +777,19 @@ For negotiated supported protocol versions `2026-07-28` or newer, the consolidat
+workflow Copilot Automations + +- **list_automation_tags** - List automation tags + - **OAuth Challenge Scopes**: `repo` + - `owner`: Repository owner (string, required) + - `page`: Page number for pagination (min 1) (number, optional) + - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) + - `repo`: Repository name (string, required) + +
+ +
+ copilot Copilot Issue Intents - **assign_copilot_to_issue_with_intent** - Assign Copilot to issue with intent diff --git a/docs/remote-server.md b/docs/remote-server.md index be91009148..52dfd0a4fa 100644 --- a/docs/remote-server.md +++ b/docs/remote-server.md @@ -25,6 +25,7 @@ Below is a table of available toolsets for the remote GitHub MCP Server. Each to | code-square
`code_quality` | GitHub Code Quality related tools | https://api.githubcopilot.com/mcp/x/code_quality | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_quality&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_quality%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/code_quality/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_quality&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_quality%2Freadonly%22%7D) | | codescan
`code_security` | Code security related tools, such as GitHub Code Scanning | https://api.githubcopilot.com/mcp/x/code_security | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/code_security/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%2Freadonly%22%7D) | | copilot
`copilot` | Copilot related tools | https://api.githubcopilot.com/mcp/x/copilot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%2Freadonly%22%7D) | +| workflow
`copilot_automations` | Copilot Automation related tools | https://api.githubcopilot.com/mcp/x/copilot_automations | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_automations&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_automations%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot_automations/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_automations&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_automations%2Freadonly%22%7D) | | copilot
`copilot_issue_intents` | Opt-in Copilot issue assignment tools that carry intent metadata (rationale, confidence, suggestion) | https://api.githubcopilot.com/mcp/x/copilot_issue_intents | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_issue_intents&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_issue_intents%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot_issue_intents/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_issue_intents&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_issue_intents%2Freadonly%22%7D) | | dependabot
`dependabot` | Dependabot tools | https://api.githubcopilot.com/mcp/x/dependabot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/dependabot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%2Freadonly%22%7D) | | comment-discussion
`discussions` | GitHub Discussions related tools | https://api.githubcopilot.com/mcp/x/discussions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/discussions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%2Freadonly%22%7D) | diff --git a/pkg/github/__toolsnaps__/list_automation_tags.snap b/pkg/github/__toolsnaps__/list_automation_tags.snap new file mode 100644 index 0000000000..654999c3d9 --- /dev/null +++ b/pkg/github/__toolsnaps__/list_automation_tags.snap @@ -0,0 +1,39 @@ +{ + "annotations": { + "idempotentHint": false, + "readOnlyHint": true, + "title": "List automation tags" + }, + "description": "List caller-visible automation tags in a repository. Tags organize automations; they do not execute them. Use nextPage with the same perPage to continue. Pages are not a snapshot across concurrent changes.", + "inputSchema": { + "properties": { + "owner": { + "description": "Repository owner", + "minLength": 1, + "type": "string" + }, + "page": { + "description": "Page number for pagination (min 1)", + "minimum": 1, + "type": "number" + }, + "perPage": { + "description": "Results per page for pagination (min 1, max 100)", + "maximum": 100, + "minimum": 1, + "type": "number" + }, + "repo": { + "description": "Repository name", + "minLength": 1, + "type": "string" + } + }, + "required": [ + "owner", + "repo" + ], + "type": "object" + }, + "name": "list_automation_tags" +} \ No newline at end of file diff --git a/pkg/github/automation_tags.go b/pkg/github/automation_tags.go new file mode 100644 index 0000000000..c395a3f2de --- /dev/null +++ b/pkg/github/automation_tags.go @@ -0,0 +1,113 @@ +package github + +import ( + "context" + "fmt" + "net/http" + "net/url" + + ghErrors "github.com/github/github-mcp-server/v2/pkg/errors" + "github.com/github/github-mcp-server/v2/pkg/inventory" + "github.com/github/github-mcp-server/v2/pkg/sanitize" + "github.com/github/github-mcp-server/v2/pkg/scopes" + "github.com/github/github-mcp-server/v2/pkg/translations" + "github.com/github/github-mcp-server/v2/pkg/utils" + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +type AutomationTag struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Version string `json:"version"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type ListAutomationTagsResponse struct { + Tags []AutomationTag `json:"tags"` + TotalCount int `json:"total_count"` + NextPage int `json:"nextPage" jsonschema:"Next page from the REST Link header, or 0 when there is no next page"` +} + +func ListAutomationTags(t translations.TranslationHelperFunc) inventory.ServerTool { + return NewTool[RepositoryListInput, *ListAutomationTagsResponse]( + ToolsetMetadataCopilotAutomations, + mcp.Tool{ + Name: "list_automation_tags", + Description: t("TOOL_LIST_AUTOMATION_TAGS_DESCRIPTION", "List caller-visible automation tags in a repository. Tags organize automations; they do not execute them. Use nextPage with the same perPage to continue. Pages are not a snapshot across concurrent changes."), + Annotations: &mcp.ToolAnnotations{ + Title: t("TOOL_LIST_AUTOMATION_TAGS_USER_TITLE", "List automation tags"), + ReadOnlyHint: true, + }, + InputSchema: WithPagination(&jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "owner": {Type: "string", Description: "Repository owner", MinLength: new(1)}, + "repo": {Type: "string", Description: "Repository name", MinLength: new(1)}, + }, + Required: []string{"owner", "repo"}, + }), + }, + scopes.RequireAll(scopes.Repo), + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, input RepositoryListInput) (*mcp.CallToolResult, *ListAutomationTagsResponse, error) { + if input.Owner == "" { + return utils.NewToolResultError("missing required parameter: owner"), nil, nil + } + if input.Repo == "" { + return utils.NewToolResultError("missing required parameter: repo"), nil, nil + } + if input.Page != nil && *input.Page < 1 { + return utils.NewToolResultError("page must be at least 1"), nil, nil + } + if input.PerPage != nil && (*input.PerPage < 1 || *input.PerPage > 100) { + return utils.NewToolResultError("perPage must be between 1 and 100"), nil, nil + } + if input.Owner == "." || input.Owner == ".." { + return utils.NewToolResultError(fmt.Sprintf("owner must not be %q", input.Owner)), nil, nil + } + if input.Repo == "." || input.Repo == ".." { + return utils.NewToolResultError(fmt.Sprintf("repo must not be %q", input.Repo)), nil, nil + } + pagination := PaginationParams{Page: 1, PerPage: 30} + if input.Page != nil { + pagination.Page = *input.Page + } + if input.PerPage != nil { + pagination.PerPage = *input.PerPage + } + client, err := deps.GetClient(ctx) + if err != nil { + return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + apiURL := fmt.Sprintf("agents/repos/%s/%s/automation-tags?page=%d&per_page=%d", + url.PathEscape(input.Owner), url.PathEscape(input.Repo), pagination.Page, pagination.PerPage) + req, err := client.NewRequest(ctx, http.MethodGet, apiURL, nil) + if err != nil { + return utils.NewToolResultErrorFromErr("failed to create automation tags request", err), nil, nil + } + var output ListAutomationTagsResponse + resp, err := client.Do(req, &output) + if resp != nil { + defer func() { _ = resp.Body.Close() }() + } + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list automation tags", resp, err), nil, nil + } + if resp.StatusCode != http.StatusOK { + return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to list automation tags", resp, nil), nil, nil + } + for i := range output.Tags { + sanitizeAutomationTagNameAndDescription(&output.Tags[i]) + } + output.NextPage = resp.NextPage + return MarshalledTextResult(&output), &output, nil + }, + ) +} + +func sanitizeAutomationTagNameAndDescription(tag *AutomationTag) { + tag.Name = sanitize.PlainText(tag.Name) + tag.Description = sanitize.Content(tag.Description) +} diff --git a/pkg/github/automation_tags_test.go b/pkg/github/automation_tags_test.go new file mode 100644 index 0000000000..5f205cd089 --- /dev/null +++ b/pkg/github/automation_tags_test.go @@ -0,0 +1,230 @@ +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/github/github-mcp-server/v2/internal/toolsnaps" + "github.com/github/github-mcp-server/v2/pkg/inventory" + "github.com/github/github-mcp-server/v2/pkg/translations" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_ListAutomationTags(t *testing.T) { + toolDef := ListAutomationTags(translations.NullTranslationHelper) + require.NoError(t, toolsnaps.Test(toolDef.Tool.Name, toolDef.Tool)) + assert.True(t, toolDef.Tool.Annotations.ReadOnlyHint) + + t.Run("validates required parameters and pagination", func(t *testing.T) { + for _, tc := range []struct { + name string + args map[string]any + wantError string + }{ + {"missing owner", map[string]any{"repo": "repo"}, "owner"}, + {"empty owner", map[string]any{"owner": "", "repo": "repo"}, "owner"}, + {"missing repo", map[string]any{"owner": "owner"}, "repo"}, + {"empty repo", map[string]any{"owner": "owner", "repo": ""}, "repo"}, + {"zero page", map[string]any{"owner": "owner", "repo": "repo", "page": 0}, "page"}, + {"negative page", map[string]any{"owner": "owner", "repo": "repo", "page": -1}, "page"}, + {"zero page size", map[string]any{"owner": "owner", "repo": "repo", "perPage": 0}, "perPage"}, + {"negative page size", map[string]any{"owner": "owner", "repo": "repo", "perPage": -1}, "perPage"}, + {"page size limit", map[string]any{"owner": "owner", "repo": "repo", "perPage": 101}, "perPage"}, + } { + t.Run(tc.name, func(t *testing.T) { + calls := 0 + client := mustNewGHClient(t, MockHTTPClientWithHandler(func(w http.ResponseWriter, _ *http.Request) { + calls++ + _, err := fmt.Fprint(w, `{"tags":[],"total_count":0}`) + require.NoError(t, err) + })) + deps := BaseDeps{Client: client} + handler := toolDef.Handler(deps) + request := createMCPRequest(tc.args) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + assert.Zero(t, calls) + require.True(t, result.IsError) + assert.Contains(t, getErrorResult(t, result).Text, tc.wantError) + }) + } + }) + + t.Run("discovery", func(t *testing.T) { + for _, tc := range []struct { + name string + toolsets []string + scopes []string + want bool + }{ + {"default", []string{"default"}, []string{"repo"}, false}, + {"opt in read only", []string{"copilot_automations"}, []string{"repo"}, true}, + {"missing scope", []string{"copilot_automations"}, []string{}, false}, + } { + t.Run(tc.name, func(t *testing.T) { + inv, err := NewInventory(translations.NullTranslationHelper). + WithToolsets(tc.toolsets).WithReadOnly(true). + WithFilter(CreateToolScopeFilter(tc.scopes)).Build() + require.NoError(t, err) + require.Empty(t, inv.UnrecognizedToolsets()) + var found bool + for _, tool := range inv.AvailableTools(context.Background()) { + found = found || tool.Tool.Name == "list_automation_tags" + } + assert.Equal(t, tc.want, found) + }) + } + }) + + for _, protocol := range []string{inventory.ProtocolVersionMultiRoundTrip, "2025-11-25"} { + t.Run(protocol, func(t *testing.T) { + for _, tc := range []struct { + name string + args map[string]any + status int + body string + link string + query string + escapedPath string + want string + wantError string + }{ + { + name: "page and additive fields", + args: map[string]any{"owner": "owner", "repo": "repo", "page": 2, "perPage": 1}, + status: http.StatusOK, + body: `{"tags":[{"id":"tag-1","name":"Release","description":"","version":"opaque-version","created_at":"2026-10-01T00:00:00Z","updated_at":"2026-10-02T00:00:00Z","future_field":true}],"total_count":3,"future_field":true}`, + link: `; rel="next"`, + query: "page=2&per_page=1", + want: `{"tags":[{"id":"tag-1","name":"Release","description":"","version":"opaque-version","created_at":"2026-10-01T00:00:00Z","updated_at":"2026-10-02T00:00:00Z"}],"total_count":3,"nextPage":3}`, + }, + { + name: "empty visible catalog", + status: http.StatusOK, body: `{"tags":[],"total_count":0}`, + query: "page=1&per_page=30", want: `{"tags":[],"total_count":0,"nextPage":0}`, + }, + { + name: "last page", + args: map[string]any{"owner": "owner", "repo": "repo", "page": 3, "perPage": 1}, + status: http.StatusOK, body: `{"tags":[],"total_count":2}`, + query: "page=3&per_page=1", want: `{"tags":[],"total_count":2,"nextPage":0}`, + }, + { + name: "maximum page size", + args: map[string]any{"owner": "owner", "repo": "repo", "perPage": 100}, + status: http.StatusOK, body: `{"tags":[],"total_count":0}`, + query: "page=1&per_page=100", want: `{"tags":[],"total_count":0,"nextPage":0}`, + }, + { + name: "escaped path segments", + args: map[string]any{"owner": "own er/team", "repo": "re/po"}, + status: http.StatusOK, + body: `{"tags":[],"total_count":0}`, + escapedPath: "/agents/repos/own%20er%2Fteam/re%2Fpo/automation-tags", + want: `{"tags":[],"total_count":0,"nextPage":0}`, + }, + { + name: "sanitized name and description", + status: http.StatusOK, + body: `{"tags":[{"id":"tag-1","name":"Hello\u200bWorld","description":"Hello\u200bWorld","version":"v1","created_at":"","updated_at":""}],"total_count":1}`, + want: `{"tags":[{"id":"tag-1","name":"HelloWorld","description":"HelloWorld","version":"v1","created_at":"","updated_at":""}],"total_count":1,"nextPage":0}`, + }, + {name: "non-disclosure", status: http.StatusNotFound, body: `{"message":"Not Found"}`, wantError: "404"}, + {name: "empty denial", status: http.StatusNotFound, wantError: "404"}, + {name: "bad request", status: http.StatusBadRequest, body: `{"message":"Bad Request"}`, wantError: "400"}, + {name: "permission denied", status: http.StatusForbidden, body: `{"message":"Forbidden"}`, wantError: "403"}, + {name: "unauthenticated", status: http.StatusUnauthorized, body: `{"message":"Requires authentication"}`, wantError: "401"}, + {name: "temporarily unavailable", status: http.StatusServiceUnavailable, body: `{"message":"Unavailable"}`, wantError: "503"}, + {name: "server error", status: http.StatusInternalServerError, body: `{"message":"Internal Server Error"}`, wantError: "500"}, + {name: "validation", status: http.StatusUnprocessableEntity, body: `{"message":"Validation Failed"}`, wantError: "Validation Failed"}, + {name: "unexpected success status", status: http.StatusNoContent, wantError: "204"}, + {name: "missing owner", args: map[string]any{"repo": "repo"}, wantError: "owner"}, + {name: "empty repo", args: map[string]any{"owner": "owner", "repo": ""}, wantError: "repo"}, + {name: "dot owner", args: map[string]any{"owner": ".", "repo": "repo"}, wantError: `owner must not be "."`}, + {name: "dot dot owner", args: map[string]any{"owner": "..", "repo": "repo"}, wantError: `owner must not be ".."`}, + {name: "dot repo", args: map[string]any{"owner": "owner", "repo": "."}, wantError: `repo must not be "."`}, + {name: "dot dot repo", args: map[string]any{"owner": "owner", "repo": ".."}, wantError: `repo must not be ".."`}, + {name: "invalid page", args: map[string]any{"owner": "owner", "repo": "repo", "page": 0}, wantError: "page"}, + {name: "fractional page", args: map[string]any{"owner": "owner", "repo": "repo", "page": 1.5}, wantError: "page"}, + {name: "page size limit", args: map[string]any{"owner": "owner", "repo": "repo", "perPage": 101}, wantError: "perPage"}, + } { + t.Run(tc.name, func(t *testing.T) { + calls := 0 + client := mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "": func(w http.ResponseWriter, r *http.Request) { + calls++ + if tc.status == 0 { + _, err := fmt.Fprint(w, `{}`) + require.NoError(t, err) + return + } + assert.Equal(t, "api.github.com", r.URL.Host) + assert.Equal(t, http.MethodGet, r.Method) + escapedPath := tc.escapedPath + if escapedPath == "" { + escapedPath = "/agents/repos/owner/repo/automation-tags" + } + assert.Equal(t, escapedPath, r.URL.EscapedPath()) + query := tc.query + if query == "" { + query = "page=1&per_page=30" + } + assert.Equal(t, query, r.URL.RawQuery) + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Link", tc.link) + w.WriteHeader(tc.status) + _, err := fmt.Fprint(w, tc.body) + require.NoError(t, err) + }, + })) + deps := BaseDeps{Client: client} + inv, err := NewInventory(translations.NullTranslationHelper). + WithToolsets([]string{"copilot_automations"}).WithReadOnly(true).Build() + require.NoError(t, err) + server := mcp.NewServer(&mcp.Implementation{Name: "automation-tags", Version: "v1"}, nil) + server.AddReceivingMiddleware(InjectDepsMiddleware(deps)) + inv.RegisterTools(context.Background(), server, deps) + session := connectCommentVisibilityClient(t, server, protocol) + list, err := session.ListTools(context.Background(), nil) + require.NoError(t, err) + require.Len(t, list.Tools, 1) + assert.Equal(t, "list_automation_tags", list.Tools[0].Name) + args := tc.args + if args == nil { + args = map[string]any{"owner": "owner", "repo": "repo"} + } + result, err := session.CallTool(context.Background(), &mcp.CallToolParams{ + Name: "list_automation_tags", Arguments: args, + }) + require.NoError(t, err) + if tc.status == 0 { + assert.Zero(t, calls) + } else { + assert.Equal(t, 1, calls) + } + if tc.wantError != "" { + require.True(t, result.IsError) + assert.Contains(t, getErrorResult(t, result).Text, tc.wantError) + assert.Nil(t, result.StructuredContent) + return + } + require.False(t, result.IsError) + assert.JSONEq(t, tc.want, getTextResult(t, result).Text) + if protocol == inventory.ProtocolVersionMultiRoundTrip { + require.NotNil(t, list.Tools[0].OutputSchema) + encoded, err := json.Marshal(result.StructuredContent) + require.NoError(t, err) + assert.JSONEq(t, tc.want, string(encoded)) + } else { + assert.Nil(t, result.StructuredContent) + } + }) + } + }) + } +} diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 53503f68fe..e80de24627 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -148,6 +148,12 @@ var ( Icon: "copilot", } + ToolsetMetadataCopilotAutomations = inventory.ToolsetMetadata{ + ID: "copilot_automations", + Description: "Copilot Automation related tools", + Icon: "workflow", + } + // ToolsetMetadataCopilotIssueIntents is a non-default toolset that gates the // opt-in intent-aware Copilot issue assignment tool. Kept out of the default // configuration so its inputs (rationale, confidence, is_suggestion) do not @@ -293,6 +299,7 @@ func AllTools(t translations.TranslationHelperFunc, opts ...ToolOption) []invent // Copilot tools AssignCopilotToIssue(t), RequestCopilotReview(t), + ListAutomationTags(t), // Copilot issue intents (non-default, opt-in) AssignCopilotToIssueWithIntent(t),