From ea3f02b006216c8bc41dc333fb99cbc0b66252b3 Mon Sep 17 00:00:00 2001 From: Jurre Stender Date: Wed, 4 Jun 2025 18:00:51 +0000 Subject: [PATCH] Add Global Security Advisories Toolset --- README.md | 29 ++- docs/remote-server.md | 1 + pkg/github/security_advisories.go | 229 +++++++++++++++++++++++ pkg/github/security_advisories_test.go | 243 +++++++++++++++++++++++++ pkg/github/tools.go | 7 + 5 files changed, 506 insertions(+), 3 deletions(-) create mode 100644 pkg/github/security_advisories.go create mode 100644 pkg/github/security_advisories_test.go diff --git a/README.md b/README.md index e4543ecf5..631b8dba9 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Alternatively, to manually configure VS Code, choose the appropriate JSON block VS Code (version 1.101 or greater) - + ```json { "servers": { @@ -130,7 +130,7 @@ To keep your GitHub PAT secure and reusable across different MCP hosts: ```bash # CLI usage claude mcp update github -e GITHUB_PERSONAL_ACCESS_TOKEN=$GITHUB_PAT - + # In config files (where supported) "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "$GITHUB_PAT" @@ -241,7 +241,7 @@ For other MCP host applications, please refer to our installation guides: - **[GitHub Copilot in other IDEs](/docs/installation-guides/install-other-copilot-ides.md)** - Installation for JetBrains, Visual Studio, Eclipse, and Xcode with GitHub Copilot - **[Claude Code & Claude Desktop](docs/installation-guides/install-claude.md)** - Installation guide for Claude Code and Claude Desktop -- **[Cursor](docs/installation-guides/install-cursor.md)** - Installation guide for Cursor IDE +- **[Cursor](docs/installation-guides/install-cursor.md)** - Installation guide for Cursor IDE - **[Windsurf](docs/installation-guides/install-windsurf.md)** - Installation guide for Windsurf IDE For a complete overview of all installation options, see our **[Installation Guides Index](docs/installation-guides/installation-guides.md)**. @@ -295,6 +295,7 @@ The following sets of tools are available (all are on by default): | `pull_requests` | GitHub Pull Request related tools | | `repos` | GitHub Repository related tools | | `secret_protection` | Secret protection related tools, such as GitHub Secret Scanning | +| `security_advisories` | Security advisories related tools | | `users` | GitHub User related tools | @@ -918,6 +919,28 @@ The following sets of tools are available (all are on by default):
+Security Advisories + +- **get_global_security_advisory** - Get a global security advisory + - `ghsaId`: GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx). (string, required) + +- **list_global_security_advisories** - List global security advisories + - `affects`: Filter advisories by affected package or version (e.g. "package1,package2@1.0.0"). (string, optional) + - `cveId`: Filter by CVE ID. (string, optional) + - `cwes`: Filter by Common Weakness Enumeration IDs (e.g. ["79", "284", "22"]). (string[], optional) + - `ecosystem`: Filter by package ecosystem. (string, optional) + - `ghsaId`: Filter by GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx). (string, optional) + - `isWithdrawn`: Whether to only return withdrawn advisories. (boolean, optional) + - `modified`: Filter by publish or update date or date range (ISO 8601 date or range). (string, optional) + - `published`: Filter by publish date or date range (ISO 8601 date or range). (string, optional) + - `severity`: Filter by severity. (string, optional) + - `type`: Advisory type. (string, required) + - `updated`: Filter by update date or date range (ISO 8601 date or range). (string, optional) + +
+ +
+ Users - **search_users** - Search users diff --git a/docs/remote-server.md b/docs/remote-server.md index 5f57f4961..b6f7fa61d 100644 --- a/docs/remote-server.md +++ b/docs/remote-server.md @@ -32,6 +32,7 @@ Below is a table of available toolsets for the remote GitHub MCP Server. Each to | Pull Requests | GitHub Pull Request related tools | https://api.githubcopilot.com/mcp/x/pull_requests | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/pull_requests/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%2Freadonly%22%7D) | | Repositories | GitHub Repository related tools | https://api.githubcopilot.com/mcp/x/repos | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/repos/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%2Freadonly%22%7D) | | Secret Protection | Secret protection related tools, such as GitHub Secret Scanning | https://api.githubcopilot.com/mcp/x/secret_protection | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/secret_protection/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%2Freadonly%22%7D) | +| Security Advisories | Security advisories related tools | https://api.githubcopilot.com/mcp/x/security_advisories | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/security_advisories/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%2Freadonly%22%7D) | | Users | GitHub User related tools | https://api.githubcopilot.com/mcp/x/users | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/users/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%2Freadonly%22%7D) | diff --git a/pkg/github/security_advisories.go b/pkg/github/security_advisories.go new file mode 100644 index 000000000..ddfb12c13 --- /dev/null +++ b/pkg/github/security_advisories.go @@ -0,0 +1,229 @@ +package github + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/github/github-mcp-server/pkg/translations" + "github.com/google/go-github/v74/github" + "github.com/mark3labs/mcp-go/mcp" + "github.com/mark3labs/mcp-go/server" +) + +func ListGlobalSecurityAdvisories(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { + return mcp.NewTool("list_global_security_advisories", + mcp.WithDescription(t("TOOL_LIST_GLOBAL_SECURITY_ADVISORIES_DESCRIPTION", "List global security advisories from GitHub.")), + mcp.WithToolAnnotation(mcp.ToolAnnotation{ + Title: t("TOOL_LIST_GLOBAL_SECURITY_ADVISORIES_USER_TITLE", "List global security advisories"), + ReadOnlyHint: ToBoolPtr(true), + }), + mcp.WithString("ghsaId", + mcp.Description("Filter by GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx)."), + ), + mcp.WithString("type", + mcp.Required(), + mcp.Description("Advisory type."), + mcp.Enum("reviewed", "malware", "unreviewed"), + mcp.DefaultString("reviewed"), + ), + mcp.WithString("cveId", + mcp.Description("Filter by CVE ID."), + ), + mcp.WithString("ecosystem", + mcp.Description("Filter by package ecosystem."), + mcp.Enum("actions", "composer", "erlang", "go", "maven", "npm", "nuget", "other", "pip", "pub", "rubygems", "rust"), + ), + mcp.WithString("severity", + mcp.Description("Filter by severity."), + mcp.Enum("unknown", "low", "medium", "high", "critical"), + ), + mcp.WithArray("cwes", + mcp.Description("Filter by Common Weakness Enumeration IDs (e.g. [\"79\", \"284\", \"22\"])."), + mcp.Items(map[string]any{ + "type": "string", + }), + ), + mcp.WithBoolean("isWithdrawn", + mcp.Description("Whether to only return withdrawn advisories."), + ), + mcp.WithString("affects", + mcp.Description("Filter advisories by affected package or version (e.g. \"package1,package2@1.0.0\")."), + ), + mcp.WithString("published", + mcp.Description("Filter by publish date or date range (ISO 8601 date or range)."), + ), + mcp.WithString("updated", + mcp.Description("Filter by update date or date range (ISO 8601 date or range)."), + ), + mcp.WithString("modified", + mcp.Description("Filter by publish or update date or date range (ISO 8601 date or range)."), + ), + ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + client, err := getClient(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + + ghsaID, err := OptionalParam[string](request, "ghsaId") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid ghsaId: %v", err)), nil + } + + typ, err := RequiredParam[string](request, "type") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid type: %v", err)), nil + } + + cveID, err := OptionalParam[string](request, "cveId") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid cveId: %v", err)), nil + } + + eco, err := OptionalParam[string](request, "ecosystem") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid ecosystem: %v", err)), nil + } + + sev, err := OptionalParam[string](request, "severity") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid severity: %v", err)), nil + } + + cwes, err := OptionalParam[[]string](request, "cwes") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid cwes: %v", err)), nil + } + + isWithdrawn, err := OptionalParam[bool](request, "isWithdrawn") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid isWithdrawn: %v", err)), nil + } + + affects, err := OptionalParam[string](request, "affects") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid affects: %v", err)), nil + } + + published, err := OptionalParam[string](request, "published") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid published: %v", err)), nil + } + + updated, err := OptionalParam[string](request, "updated") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid updated: %v", err)), nil + } + + modified, err := OptionalParam[string](request, "modified") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid modified: %v", err)), nil + } + + opts := &github.ListGlobalSecurityAdvisoriesOptions{} + + if ghsaID != "" { + opts.GHSAID = &ghsaID + } + if typ != "" { + opts.Type = &typ + } + if cveID != "" { + opts.CVEID = &cveID + } + if eco != "" { + opts.Ecosystem = &eco + } + if sev != "" { + opts.Severity = &sev + } + if len(cwes) > 0 { + opts.CWEs = cwes + } + + if isWithdrawn { + opts.IsWithdrawn = &isWithdrawn + } + + if affects != "" { + opts.Affects = &affects + } + if published != "" { + opts.Published = &published + } + if updated != "" { + opts.Updated = &updated + } + if modified != "" { + opts.Modified = &modified + } + + advisories, resp, err := client.SecurityAdvisories.ListGlobalSecurityAdvisories(ctx, opts) + if err != nil { + return nil, fmt.Errorf("failed to list global security advisories: %w", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + return mcp.NewToolResultError(fmt.Sprintf("failed to list advisories: %s", string(body))), nil + } + + r, err := json.Marshal(advisories) + if err != nil { + return nil, fmt.Errorf("failed to marshal advisories: %w", err) + } + + return mcp.NewToolResultText(string(r)), nil + } +} + +func GetGlobalSecurityAdvisory(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { + return mcp.NewTool("get_global_security_advisory", + mcp.WithDescription(t("TOOL_GET_GLOBAL_SECURITY_ADVISORY_DESCRIPTION", "Get a global security advisory")), + mcp.WithToolAnnotation(mcp.ToolAnnotation{ + Title: t("TOOL_GET_GLOBAL_SECURITY_ADVISORY_USER_TITLE", "Get a global security advisory"), + ReadOnlyHint: ToBoolPtr(true), + }), + mcp.WithString("ghsaId", + mcp.Description("GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx)."), + mcp.Required(), + ), + ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + client, err := getClient(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + + ghsaID, err := RequiredParam[string](request, "ghsaId") + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("invalid ghsaId: %v", err)), nil + } + + advisory, resp, err := client.SecurityAdvisories.GetGlobalSecurityAdvisories(ctx, ghsaID) + if err != nil { + return nil, fmt.Errorf("failed to get advisory: %w", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + return mcp.NewToolResultError(fmt.Sprintf("failed to get advisory: %s", string(body))), nil + } + + r, err := json.Marshal(advisory) + if err != nil { + return nil, fmt.Errorf("failed to marshal advisory: %w", err) + } + + return mcp.NewToolResultText(string(r)), nil + } +} diff --git a/pkg/github/security_advisories_test.go b/pkg/github/security_advisories_test.go new file mode 100644 index 000000000..563fc8728 --- /dev/null +++ b/pkg/github/security_advisories_test.go @@ -0,0 +1,243 @@ +package github + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/github/github-mcp-server/pkg/translations" + "github.com/google/go-github/v74/github" + "github.com/migueleliasweb/go-github-mock/src/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_ListGlobalSecurityAdvisories(t *testing.T) { + mockClient := github.NewClient(nil) + tool, _ := ListGlobalSecurityAdvisories(stubGetClientFn(mockClient), translations.NullTranslationHelper) + + assert.Equal(t, "list_global_security_advisories", tool.Name) + assert.NotEmpty(t, tool.Description) + assert.Contains(t, tool.InputSchema.Properties, "ecosystem") + assert.Contains(t, tool.InputSchema.Properties, "severity") + assert.Contains(t, tool.InputSchema.Properties, "ghsaId") + assert.ElementsMatch(t, tool.InputSchema.Required, []string{"type"}) + + // Setup mock advisory for success case + mockAdvisory := &github.GlobalSecurityAdvisory{ + SecurityAdvisory: github.SecurityAdvisory{ + GHSAID: github.Ptr("GHSA-xxxx-xxxx-xxxx"), + Summary: github.Ptr("Test advisory"), + Description: github.Ptr("This is a test advisory."), + Severity: github.Ptr("high"), + }, + } + + tests := []struct { + name string + mockedClient *http.Client + requestArgs map[string]interface{} + expectError bool + expectedAdvisories []*github.GlobalSecurityAdvisory + expectedErrMsg string + }{ + { + name: "successful advisory fetch", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatch( + mock.GetAdvisories, + []*github.GlobalSecurityAdvisory{mockAdvisory}, + ), + ), + requestArgs: map[string]interface{}{ + "type": "reviewed", + "ecosystem": "npm", + "severity": "high", + }, + expectError: false, + expectedAdvisories: []*github.GlobalSecurityAdvisory{mockAdvisory}, + }, + { + name: "invalid severity value", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetAdvisories, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Bad Request"}`)) + }), + ), + ), + requestArgs: map[string]interface{}{ + "type": "reviewed", + "severity": "extreme", + }, + expectError: true, + expectedErrMsg: "failed to list global security advisories", + }, + { + name: "API error handling", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetAdvisories, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`{"message": "Internal Server Error"}`)) + }), + ), + ), + requestArgs: map[string]interface{}{"type": "reviewed"}, + expectError: true, + expectedErrMsg: "failed to list global security advisories", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Setup client with mock + client := github.NewClient(tc.mockedClient) + _, handler := ListGlobalSecurityAdvisories(stubGetClientFn(client), translations.NullTranslationHelper) + + // Create call request + request := createMCPRequest(tc.requestArgs) + + // Call handler + result, err := handler(context.Background(), request) + + // Verify results + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErrMsg) + return + } + + require.NoError(t, err) + + // Parse the result and get the text content if no error + textContent := getTextResult(t, result) + + // Unmarshal and verify the result + var returnedAdvisories []*github.GlobalSecurityAdvisory + err = json.Unmarshal([]byte(textContent.Text), &returnedAdvisories) + assert.NoError(t, err) + assert.Len(t, returnedAdvisories, len(tc.expectedAdvisories)) + for i, advisory := range returnedAdvisories { + assert.Equal(t, *tc.expectedAdvisories[i].GHSAID, *advisory.GHSAID) + assert.Equal(t, *tc.expectedAdvisories[i].Summary, *advisory.Summary) + assert.Equal(t, *tc.expectedAdvisories[i].Description, *advisory.Description) + assert.Equal(t, *tc.expectedAdvisories[i].Severity, *advisory.Severity) + } + }) + } +} + +func Test_GetGlobalSecurityAdvisory(t *testing.T) { + mockClient := github.NewClient(nil) + tool, _ := GetGlobalSecurityAdvisory(stubGetClientFn(mockClient), translations.NullTranslationHelper) + + assert.Equal(t, "get_global_security_advisory", tool.Name) + assert.NotEmpty(t, tool.Description) + assert.Contains(t, tool.InputSchema.Properties, "ghsaId") + assert.ElementsMatch(t, tool.InputSchema.Required, []string{"ghsaId"}) + + // Setup mock advisory for success case + mockAdvisory := &github.GlobalSecurityAdvisory{ + SecurityAdvisory: github.SecurityAdvisory{ + GHSAID: github.Ptr("GHSA-xxxx-xxxx-xxxx"), + Summary: github.Ptr("Test advisory"), + Description: github.Ptr("This is a test advisory."), + Severity: github.Ptr("high"), + }, + } + + tests := []struct { + name string + mockedClient *http.Client + requestArgs map[string]interface{} + expectError bool + expectedAdvisory *github.GlobalSecurityAdvisory + expectedErrMsg string + }{ + { + name: "successful advisory fetch", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatch( + mock.GetAdvisoriesByGhsaId, + mockAdvisory, + ), + ), + requestArgs: map[string]interface{}{ + "ghsaId": "GHSA-xxxx-xxxx-xxxx", + }, + expectError: false, + expectedAdvisory: mockAdvisory, + }, + { + name: "invalid ghsaId format", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetAdvisoriesByGhsaId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Bad Request"}`)) + }), + ), + ), + requestArgs: map[string]interface{}{ + "ghsaId": "invalid-ghsa-id", + }, + expectError: true, + expectedErrMsg: "failed to get advisory", + }, + { + name: "advisory not found", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetAdvisoriesByGhsaId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + ), + ), + requestArgs: map[string]interface{}{ + "ghsaId": "GHSA-xxxx-xxxx-xxxx", + }, + expectError: true, + expectedErrMsg: "failed to get advisory", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Setup client with mock + client := github.NewClient(tc.mockedClient) + _, handler := GetGlobalSecurityAdvisory(stubGetClientFn(client), translations.NullTranslationHelper) + + // Create call request + request := createMCPRequest(tc.requestArgs) + + // Call handler + result, err := handler(context.Background(), request) + + // Verify results + if tc.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErrMsg) + return + } + + require.NoError(t, err) + + // Parse the result and get the text content if no error + textContent := getTextResult(t, result) + + // Verify the result + assert.Contains(t, textContent.Text, *tc.expectedAdvisory.GHSAID) + assert.Contains(t, textContent.Text, *tc.expectedAdvisory.Summary) + assert.Contains(t, textContent.Text, *tc.expectedAdvisory.Description) + assert.Contains(t, textContent.Text, *tc.expectedAdvisory.Severity) + }) + } +} diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 3fb39ada7..d15dd913a 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -159,6 +159,12 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG toolsets.NewServerTool(DeleteWorkflowRunLogs(getClient, t)), ) + securityAdvisories := toolsets.NewToolset("security_advisories", "Security advisories related tools"). + AddReadTools( + toolsets.NewServerTool(ListGlobalSecurityAdvisories(getClient, t)), + toolsets.NewServerTool(GetGlobalSecurityAdvisory(getClient, t)), + ) + // Keep experiments alive so the system doesn't error out when it's always enabled experiments := toolsets.NewToolset("experiments", "Experimental features that are not considered stable yet") @@ -193,6 +199,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG tsg.AddToolset(experiments) tsg.AddToolset(discussions) tsg.AddToolset(gists) + tsg.AddToolset(securityAdvisories) return tsg }