Skip to content

Commit 3766bb1

Browse files
committed
Add Global Security Advisories Toolset
1 parent 96a705c commit 3766bb1

File tree

4 files changed

+504
-3
lines changed

4 files changed

+504
-3
lines changed

README.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Alternatively, to manually configure VS Code, choose the appropriate JSON block
3636
<tr><th align=left colspan=2>VS Code (version 1.101 or greater)</th></tr>
3737
<tr valign=top>
3838
<td>
39-
39+
4040
```json
4141
{
4242
"servers": {
@@ -130,7 +130,7 @@ To keep your GitHub PAT secure and reusable across different MCP hosts:
130130
```bash
131131
# CLI usage
132132
claude mcp update github -e GITHUB_PERSONAL_ACCESS_TOKEN=$GITHUB_PAT
133-
133+
134134
# In config files (where supported)
135135
"env": {
136136
"GITHUB_PERSONAL_ACCESS_TOKEN": "$GITHUB_PAT"
@@ -241,7 +241,7 @@ For other MCP host applications, please refer to our installation guides:
241241

242242
- **[GitHub Copilot in other IDEs](/docs/installation-guides/install-other-copilot-ides.md)** - Installation for JetBrains, Visual Studio, Eclipse, and Xcode with GitHub Copilot
243243
- **[Claude Code & Claude Desktop](docs/installation-guides/install-claude.md)** - Installation guide for Claude Code and Claude Desktop
244-
- **[Cursor](docs/installation-guides/install-cursor.md)** - Installation guide for Cursor IDE
244+
- **[Cursor](docs/installation-guides/install-cursor.md)** - Installation guide for Cursor IDE
245245
- **[Windsurf](docs/installation-guides/install-windsurf.md)** - Installation guide for Windsurf IDE
246246

247247
For a complete overview of all installation options, see our **[Installation Guides Index](docs/installation-guides/installation-guides.md)**.
@@ -928,6 +928,30 @@ The following sets of tools are available (all are on by default):
928928
- `sort`: Sort users by number of followers or repositories, or when the person joined GitHub. (string, optional)
929929

930930
</details>
931+
932+
933+
<summary>Security Advisories</summary>
934+
935+
<details>
936+
- **`list_global_security_advisories`** - List global security advisories
937+
- **Parameters**:
938+
- * `ghsaId`: Filter by GitHub Security Advisory ID (string, optional – format: `GHSA-xxxx-xxxx-xxxx`)
939+
- * `type`: Advisory type (string, optional – one of `reviewed`, `malware`, `unreviewed`)
940+
- * `cveId`: Filter by CVE ID (string, optional)
941+
- * `ecosystem`: Filter by package ecosystem (string, optional – one of `actions`, `composer`, `erlang`, `go`, `maven`, `npm`, `nuget`, `other`, `pip`, `pub`, `rubygems`, `rust`)
942+
- * `severity`: Filter by severity (string, optional – one of `unknown`, `low`, `medium`, `high`, `critical`)
943+
- * `cwes`: Filter by Common Weakness Enumeration IDs (array of strings, optional – e.g. `["79", "284", "22"]`)
944+
- * `isWithdrawn`: Whether to only return withdrawn advisories (boolean, optional)
945+
- * `affects`: Filter advisories by affected package or version (string, optional – e.g. `"package1,package2@1.0.0"`)
946+
- * `published`: Filter by publish date or date range (string, optional – ISO 8601 date or range)
947+
- * `updated`: Filter by update date or date range (string, optional – ISO 8601 date or range)
948+
- * `modified`: Filter by publish or update date or date range (string, optional – ISO 8601 date or range)
949+
950+
- **`get_global_security_advisory`** = Get a global security advisory
951+
- **Template**: `advisories/{ghsaId}`
952+
953+
</details>
954+
931955
<!-- END AUTOMATED TOOLS -->
932956

933957
### Additional Tools in Remote Github MCP Server

pkg/github/security_advisories.go

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
10+
"github.com/github/github-mcp-server/pkg/translations"
11+
"github.com/google/go-github/v74/github"
12+
"github.com/mark3labs/mcp-go/mcp"
13+
"github.com/mark3labs/mcp-go/server"
14+
)
15+
16+
func ListGlobalSecurityAdvisories(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
17+
return mcp.NewTool("list_global_security_advisories",
18+
mcp.WithDescription(t("TOOL_LIST_GLOBAL_SECURITY_ADVISORIES_DESCRIPTION", "List global security advisories from GitHub.")),
19+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
20+
Title: t("TOOL_LIST_GLOBAL_SECURITY_ADVISORIES_USER_TITLE", "List global security advisories"),
21+
ReadOnlyHint: ToBoolPtr(true),
22+
}),
23+
mcp.WithString("ghsaId",
24+
mcp.Description("Filter by GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx)."),
25+
),
26+
mcp.WithString("type",
27+
mcp.Required(),
28+
mcp.Description("Advisory type."),
29+
mcp.Enum("reviewed", "malware", "unreviewed"),
30+
mcp.DefaultString("reviewed"),
31+
),
32+
mcp.WithString("cveId",
33+
mcp.Description("Filter by CVE ID."),
34+
),
35+
mcp.WithString("ecosystem",
36+
mcp.Description("Filter by package ecosystem."),
37+
mcp.Enum("actions", "composer", "erlang", "go", "maven", "npm", "nuget", "other", "pip", "pub", "rubygems", "rust"),
38+
),
39+
mcp.WithString("severity",
40+
mcp.Description("Filter by severity."),
41+
mcp.Enum("unknown", "low", "medium", "high", "critical"),
42+
),
43+
mcp.WithArray("cwes",
44+
mcp.Description("Filter by Common Weakness Enumeration IDs (e.g. [\"79\", \"284\", \"22\"])."),
45+
mcp.Items(map[string]any{
46+
"type": "string",
47+
}),
48+
),
49+
mcp.WithBoolean("isWithdrawn",
50+
mcp.Description("Whether to only return withdrawn advisories."),
51+
),
52+
mcp.WithString("affects",
53+
mcp.Description("Filter advisories by affected package or version (e.g. \"package1,package2@1.0.0\")."),
54+
),
55+
mcp.WithString("published",
56+
mcp.Description("Filter by publish date or date range (ISO 8601 date or range)."),
57+
),
58+
mcp.WithString("updated",
59+
mcp.Description("Filter by update date or date range (ISO 8601 date or range)."),
60+
),
61+
mcp.WithString("modified",
62+
mcp.Description("Filter by publish or update date or date range (ISO 8601 date or range)."),
63+
),
64+
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
65+
client, err := getClient(ctx)
66+
if err != nil {
67+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
68+
}
69+
70+
ghsaID, err := OptionalParam[string](request, "ghsaId")
71+
if err != nil {
72+
return mcp.NewToolResultError(fmt.Sprintf("invalid ghsaId: %v", err)), nil
73+
}
74+
75+
typ, err := RequiredParam[string](request, "type")
76+
if err != nil {
77+
return mcp.NewToolResultError(fmt.Sprintf("invalid type: %v", err)), nil
78+
}
79+
80+
cveID, err := OptionalParam[string](request, "cveId")
81+
if err != nil {
82+
return mcp.NewToolResultError(fmt.Sprintf("invalid cveId: %v", err)), nil
83+
}
84+
85+
eco, err := OptionalParam[string](request, "ecosystem")
86+
if err != nil {
87+
return mcp.NewToolResultError(fmt.Sprintf("invalid ecosystem: %v", err)), nil
88+
}
89+
90+
sev, err := OptionalParam[string](request, "severity")
91+
if err != nil {
92+
return mcp.NewToolResultError(fmt.Sprintf("invalid severity: %v", err)), nil
93+
}
94+
95+
cwes, err := OptionalParam[[]string](request, "cwes")
96+
if err != nil {
97+
return mcp.NewToolResultError(fmt.Sprintf("invalid cwes: %v", err)), nil
98+
}
99+
100+
isWithdrawn, err := OptionalParam[bool](request, "isWithdrawn")
101+
if err != nil {
102+
return mcp.NewToolResultError(fmt.Sprintf("invalid isWithdrawn: %v", err)), nil
103+
}
104+
105+
affects, err := OptionalParam[string](request, "affects")
106+
if err != nil {
107+
return mcp.NewToolResultError(fmt.Sprintf("invalid affects: %v", err)), nil
108+
}
109+
110+
published, err := OptionalParam[string](request, "published")
111+
if err != nil {
112+
return mcp.NewToolResultError(fmt.Sprintf("invalid published: %v", err)), nil
113+
}
114+
115+
updated, err := OptionalParam[string](request, "updated")
116+
if err != nil {
117+
return mcp.NewToolResultError(fmt.Sprintf("invalid updated: %v", err)), nil
118+
}
119+
120+
modified, err := OptionalParam[string](request, "modified")
121+
if err != nil {
122+
return mcp.NewToolResultError(fmt.Sprintf("invalid modified: %v", err)), nil
123+
}
124+
125+
opts := &github.ListGlobalSecurityAdvisoriesOptions{}
126+
127+
if ghsaID != "" {
128+
opts.GHSAID = &ghsaID
129+
}
130+
if typ != "" {
131+
opts.Type = &typ
132+
}
133+
if cveID != "" {
134+
opts.CVEID = &cveID
135+
}
136+
if eco != "" {
137+
opts.Ecosystem = &eco
138+
}
139+
if sev != "" {
140+
opts.Severity = &sev
141+
}
142+
if cwes != nil && len(cwes) > 0 {
143+
opts.CWEs = cwes
144+
}
145+
146+
opts.IsWithdrawn = &isWithdrawn
147+
148+
if affects != "" {
149+
opts.Affects = &affects
150+
}
151+
if published != "" {
152+
opts.Published = &published
153+
}
154+
if updated != "" {
155+
opts.Updated = &updated
156+
}
157+
if modified != "" {
158+
opts.Modified = &modified
159+
}
160+
161+
advisories, resp, err := client.SecurityAdvisories.ListGlobalSecurityAdvisories(ctx, opts)
162+
if err != nil {
163+
return nil, fmt.Errorf("failed to list global security advisories: %w", err)
164+
}
165+
defer func() { _ = resp.Body.Close() }()
166+
167+
if resp.StatusCode != http.StatusOK {
168+
body, err := io.ReadAll(resp.Body)
169+
if err != nil {
170+
return nil, fmt.Errorf("failed to read response body: %w", err)
171+
}
172+
return mcp.NewToolResultError(fmt.Sprintf("failed to list advisories: %s", string(body))), nil
173+
}
174+
175+
r, err := json.Marshal(advisories)
176+
if err != nil {
177+
return nil, fmt.Errorf("failed to marshal advisories: %w", err)
178+
}
179+
180+
return mcp.NewToolResultText(string(r)), nil
181+
}
182+
}
183+
184+
func GetGlobalSecurityAdvisory(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
185+
return mcp.NewTool("get_global_security_advisory",
186+
mcp.WithDescription(t("TOOL_GET_GLOBAL_SECURITY_ADVISORY_DESCRIPTION", "Get a global security advisory")),
187+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
188+
Title: t("TOOL_GET_GLOBAL_SECURITY_ADVISORY_USER_TITLE", "Get a global security advisory"),
189+
ReadOnlyHint: ToBoolPtr(true),
190+
}),
191+
mcp.WithString("ghsaId",
192+
mcp.Description("GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx)."),
193+
mcp.Required(),
194+
),
195+
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
196+
client, err := getClient(ctx)
197+
if err != nil {
198+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
199+
}
200+
201+
ghsaID, err := RequiredParam[string](request, "ghsaId")
202+
if err != nil {
203+
return mcp.NewToolResultError(fmt.Sprintf("invalid ghsaId: %v", err)), nil
204+
}
205+
206+
advisory, resp, err := client.SecurityAdvisories.GetGlobalSecurityAdvisories(ctx, ghsaID)
207+
if err != nil {
208+
return nil, fmt.Errorf("failed to get advisory: %w", err)
209+
}
210+
defer func() { _ = resp.Body.Close() }()
211+
212+
if resp.StatusCode != http.StatusOK {
213+
body, err := io.ReadAll(resp.Body)
214+
if err != nil {
215+
return nil, fmt.Errorf("failed to read response body: %w", err)
216+
}
217+
return mcp.NewToolResultError(fmt.Sprintf("failed to get advisory: %s", string(body))), nil
218+
}
219+
220+
r, err := json.Marshal(advisory)
221+
if err != nil {
222+
return nil, fmt.Errorf("failed to marshal advisory: %w", err)
223+
}
224+
225+
return mcp.NewToolResultText(string(r)), nil
226+
}
227+
}

0 commit comments

Comments
 (0)