From 64030f70e475f6309a4788549a4dfeb37f1a8519 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 | 228 +++++++++++++++++++++++ pkg/github/security_advisories_test.go | 243 +++++++++++++++++++++++++ pkg/github/tools.go | 7 + 5 files changed, 505 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..5d1adaafc 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, optional) + - `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..ee9af3af9 --- /dev/null +++ b/pkg/github/security_advisories.go @@ -0,0 +1,228 @@ +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.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 := OptionalParam[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..76a63390a --- /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{}) + + // 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{}{}, + 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 } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy