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_quality` | GitHub Code Quality related tools |
|
| `code_security` | Code security related tools, such as GitHub Code Scanning |
|
| `copilot` | Copilot related tools |
+|
| `copilot_automations` | Copilot Automation related tools |
|
| `copilot_issue_intents` | Opt-in Copilot issue assignment tools that carry intent metadata (rationale, confidence, suggestion) |
|
| `dependabot` | Dependabot tools |
|
| `discussions` | GitHub Discussions related tools |
@@ -776,6 +777,19 @@ For negotiated supported protocol versions `2026-07-28` or newer, the consolidat
+
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 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_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) |
| 
`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 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) |
+| 
`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_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 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) |
| 
`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),