Skip to content

Commit 8589044

Browse files
committed
add dependabit get and list tools
1 parent f88456f commit 8589044

2 files changed

Lines changed: 176 additions & 0 deletions

File tree

pkg/github/dependabot.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
10+
ghErrors "github.com/github/github-mcp-server/pkg/errors"
11+
"github.com/github/github-mcp-server/pkg/translations"
12+
"github.com/google/go-github/v72/github"
13+
"github.com/mark3labs/mcp-go/mcp"
14+
"github.com/mark3labs/mcp-go/server"
15+
)
16+
17+
func GetDependabotAlert(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
18+
return mcp.NewTool(
19+
"get_dependabot_alert",
20+
mcp.WithDescription(t("TOOL_GET_DEPENDABOT_ALERT_DESCRIPTION", "Get details of a specific dependabot alert in a GitHub repository.")),
21+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
22+
Title: t("TOOL_GET_DEPENDABOT_ALERT_USER_TITLE", "Get dependabot alert"),
23+
ReadOnlyHint: ToBoolPtr(true),
24+
}),
25+
mcp.WithString("owner",
26+
mcp.Required(),
27+
mcp.Description("The owner of the repository."),
28+
),
29+
mcp.WithString("repo",
30+
mcp.Required(),
31+
mcp.Description("The name of the repository."),
32+
),
33+
mcp.WithNumber("alertNumber",
34+
mcp.Required(),
35+
mcp.Description("The number of the alert."),
36+
),
37+
),
38+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
39+
owner, err := RequiredParam[string](request, "owner")
40+
if err != nil {
41+
return mcp.NewToolResultError(err.Error()), nil
42+
}
43+
repo, err := RequiredParam[string](request, "repo")
44+
if err != nil {
45+
return mcp.NewToolResultError(err.Error()), nil
46+
}
47+
alertNumber, err := RequiredInt(request, "alertNumber")
48+
if err != nil {
49+
return mcp.NewToolResultError(err.Error()), nil
50+
}
51+
52+
client, err := getClient(ctx)
53+
if err != nil {
54+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
55+
}
56+
57+
alert, resp, err := client.Dependabot.GetRepoAlert(ctx, owner, repo, alertNumber)
58+
if err != nil {
59+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
60+
fmt.Sprintf("failed to get alert with number '%d'", alertNumber),
61+
resp,
62+
err,
63+
), nil
64+
}
65+
defer func() { _ = resp.Body.Close() }()
66+
67+
if resp.StatusCode != http.StatusOK {
68+
body, err := io.ReadAll(resp.Body)
69+
if err != nil {
70+
return nil, fmt.Errorf("failed to read response body: %w", err)
71+
}
72+
return mcp.NewToolResultError(fmt.Sprintf("failed to get alert: %s", string(body))), nil
73+
}
74+
75+
r, err := json.Marshal(alert)
76+
if err != nil {
77+
return nil, fmt.Errorf("failed to marshal alert: %w", err)
78+
}
79+
80+
return mcp.NewToolResultText(string(r)), nil
81+
}
82+
}
83+
84+
func ListDependabotAlerts(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
85+
return mcp.NewTool(
86+
"list_dependabot_alerts",
87+
mcp.WithDescription(t("TOOL_LIST_DEPENDABOT_ALERTS_DESCRIPTION", "List dependabot alerts in a GitHub repository.")),
88+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
89+
Title: t("TOOL_LIST_DEPENDABOT_ALERTS_USER_TITLE", "List dependabot alerts"),
90+
ReadOnlyHint: ToBoolPtr(true),
91+
}),
92+
mcp.WithString("owner",
93+
mcp.Required(),
94+
mcp.Description("The owner of the repository."),
95+
),
96+
mcp.WithString("repo",
97+
mcp.Required(),
98+
mcp.Description("The name of the repository."),
99+
),
100+
mcp.WithString("state",
101+
mcp.Description("Filter dependabot alerts by state. Defaults to open"),
102+
mcp.DefaultString("open"),
103+
mcp.Enum("open", "fixed", "dismissed", "auto_dismissed"),
104+
),
105+
mcp.WithString("severity",
106+
mcp.Description("Filter dependabot alerts by severity"),
107+
mcp.Enum("low", "medium", "high", "critical"),
108+
),
109+
),
110+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
111+
owner, err := RequiredParam[string](request, "owner")
112+
if err != nil {
113+
return mcp.NewToolResultError(err.Error()), nil
114+
}
115+
repo, err := RequiredParam[string](request, "repo")
116+
if err != nil {
117+
return mcp.NewToolResultError(err.Error()), nil
118+
}
119+
state, err := OptionalParam[string](request, "state")
120+
if err != nil {
121+
return mcp.NewToolResultError(err.Error()), nil
122+
}
123+
severity, err := OptionalParam[string](request, "severity")
124+
if err != nil {
125+
return mcp.NewToolResultError(err.Error()), nil
126+
}
127+
128+
client, err := getClient(ctx)
129+
if err != nil {
130+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
131+
}
132+
133+
alerts, resp, err := client.Dependabot.ListRepoAlerts(ctx, owner, repo, &github.ListAlertsOptions{
134+
State: ToStringPtr(state),
135+
Severity: ToStringPtr(severity),
136+
})
137+
if err != nil {
138+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
139+
fmt.Sprintf("failed to list alerts for repository '%s/%s'", owner, repo),
140+
resp,
141+
err,
142+
), nil
143+
}
144+
defer func() { _ = resp.Body.Close() }()
145+
146+
if resp.StatusCode != http.StatusOK {
147+
body, err := io.ReadAll(resp.Body)
148+
if err != nil {
149+
return nil, fmt.Errorf("failed to read response body: %w", err)
150+
}
151+
return mcp.NewToolResultError(fmt.Sprintf("failed to list alerts: %s", string(body))), nil
152+
}
153+
154+
r, err := json.Marshal(alerts)
155+
if err != nil {
156+
return nil, fmt.Errorf("failed to marshal alerts: %w", err)
157+
}
158+
159+
return mcp.NewToolResultText(string(r)), nil
160+
}
161+
}

pkg/github/tools.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
103103
toolsets.NewServerTool(GetSecretScanningAlert(getClient, t)),
104104
toolsets.NewServerTool(ListSecretScanningAlerts(getClient, t)),
105105
)
106+
dependabot := toolsets.NewToolset("dependabot", "Dependabot alerts tools").
107+
AddReadTools(
108+
toolsets.NewServerTool(GetDependabotAlert(getClient, t)),
109+
toolsets.NewServerTool(ListDependabotAlerts(getClient, t)),
110+
)
106111

107112
notifications := toolsets.NewToolset("notifications", "GitHub Notifications related tools").
108113
AddReadTools(
@@ -162,6 +167,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
162167
tsg.AddToolset(actions)
163168
tsg.AddToolset(codeSecurity)
164169
tsg.AddToolset(secretProtection)
170+
tsg.AddToolset(dependabot)
165171
tsg.AddToolset(notifications)
166172
tsg.AddToolset(experiments)
167173
tsg.AddToolset(discussions)
@@ -188,3 +194,12 @@ func InitDynamicToolset(s *server.MCPServer, tsg *toolsets.ToolsetGroup, t trans
188194
func ToBoolPtr(b bool) *bool {
189195
return &b
190196
}
197+
198+
// ToStringPtr converts a string to a *string pointer.
199+
// Returns nil if the string is empty.
200+
func ToStringPtr(s string) *string {
201+
if s == "" {
202+
return nil
203+
}
204+
return &s
205+
}

0 commit comments

Comments
 (0)