Skip to content
30 changes: 30 additions & 0 deletions pkg/github/__toolsnaps__/get_secret_scanning_alert.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"annotations": {
"readOnlyHint": true,
"title": "Get secret scanning alert"
},
"description": "Get details of a specific secret scanning alert in a GitHub repository.",
"inputSchema": {
"type": "object",
"required": [
"owner",
"repo",
"alertNumber"
],
"properties": {
"alertNumber": {
"type": "number",
"description": "The number of the alert."
},
"owner": {
"type": "string",
"description": "The owner of the repository."
},
"repo": {
"type": "string",
"description": "The name of the repository."
}
}
},
"name": "get_secret_scanning_alert"
}
49 changes: 49 additions & 0 deletions pkg/github/__toolsnaps__/list_secret_scanning_alerts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"annotations": {
"readOnlyHint": true,
"title": "List secret scanning alerts"
},
"description": "List secret scanning alerts in a GitHub repository.",
"inputSchema": {
"type": "object",
"required": [
"owner",
"repo"
],
"properties": {
"owner": {
"type": "string",
"description": "The owner of the repository."
},
"repo": {
"type": "string",
"description": "The name of the repository."
},
"resolution": {
"type": "string",
"description": "Filter by resolution",
"enum": [
"false_positive",
"wont_fix",
"revoked",
"pattern_edited",
"pattern_deleted",
"used_in_tests"
]
},
"secret_type": {
"type": "string",
"description": "A comma-separated list of secret types to return. All default secret patterns are returned. To return generic patterns, pass the token name(s) in the parameter."
},
"state": {
"type": "string",
"description": "Filter by state",
"enum": [
"open",
"resolved"
]
}
}
},
"name": "list_secret_scanning_alerts"
}
176 changes: 95 additions & 81 deletions pkg/github/secret_scanning.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build ignore

package github

import (
Expand All @@ -11,49 +9,56 @@ import (

ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/translations"
"github.com/github/github-mcp-server/pkg/utils"
"github.com/google/go-github/v79/github"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"github.com/google/jsonschema-go/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
)

func GetSecretScanningAlert(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
return mcp.NewTool(
"get_secret_scanning_alert",
mcp.WithDescription(t("TOOL_GET_SECRET_SCANNING_ALERT_DESCRIPTION", "Get details of a specific secret scanning alert in a GitHub repository.")),
mcp.WithToolAnnotation(mcp.ToolAnnotation{
func GetSecretScanningAlert(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
return mcp.Tool{
Name: "get_secret_scanning_alert",
Description: t("TOOL_GET_SECRET_SCANNING_ALERT_DESCRIPTION", "Get details of a specific secret scanning alert in a GitHub repository."),
Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_GET_SECRET_SCANNING_ALERT_USER_TITLE", "Get secret scanning alert"),
ReadOnlyHint: ToBoolPtr(true),
}),
mcp.WithString("owner",
mcp.Required(),
mcp.Description("The owner of the repository."),
),
mcp.WithString("repo",
mcp.Required(),
mcp.Description("The name of the repository."),
),
mcp.WithNumber("alertNumber",
mcp.Required(),
mcp.Description("The number of the alert."),
),
),
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
owner, err := RequiredParam[string](request, "owner")
ReadOnlyHint: true,
},
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"owner": {
Type: "string",
Description: "The owner of the repository.",
},
"repo": {
Type: "string",
Description: "The name of the repository.",
},
"alertNumber": {
Type: "number",
Description: "The number of the alert.",
},
},
Required: []string{"owner", "repo", "alertNumber"},
},
},
func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
owner, err := RequiredParam[string](args, "owner")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}
repo, err := RequiredParam[string](request, "repo")
repo, err := RequiredParam[string](args, "repo")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}
alertNumber, err := RequiredInt(request, "alertNumber")
alertNumber, err := RequiredInt(args, "alertNumber")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}

client, err := getClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
}

alert, resp, err := client.SecretScanning.GetAlert(ctx, owner, repo, int64(alertNumber))
Expand All @@ -62,104 +67,113 @@ func GetSecretScanningAlert(getClient GetClientFn, t translations.TranslationHel
fmt.Sprintf("failed to get alert with number '%d'", alertNumber),
resp,
err,
), nil
), nil, nil
}
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 nil, nil, fmt.Errorf("failed to read response body: %w", err)
}
return mcp.NewToolResultError(fmt.Sprintf("failed to get alert: %s", string(body))), nil
return utils.NewToolResultError(fmt.Sprintf("failed to get alert: %s", string(body))), nil, nil
}

r, err := json.Marshal(alert)
if err != nil {
return nil, fmt.Errorf("failed to marshal alert: %w", err)
return nil, nil, fmt.Errorf("failed to marshal alert: %w", err)
}

return mcp.NewToolResultText(string(r)), nil
return utils.NewToolResultText(string(r)), nil, nil
}
}

func ListSecretScanningAlerts(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
return mcp.NewTool(
"list_secret_scanning_alerts",
mcp.WithDescription(t("TOOL_LIST_SECRET_SCANNING_ALERTS_DESCRIPTION", "List secret scanning alerts in a GitHub repository.")),
mcp.WithToolAnnotation(mcp.ToolAnnotation{
func ListSecretScanningAlerts(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
return mcp.Tool{
Name: "list_secret_scanning_alerts",
Description: t("TOOL_LIST_SECRET_SCANNING_ALERTS_DESCRIPTION", "List secret scanning alerts in a GitHub repository."),
Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_LIST_SECRET_SCANNING_ALERTS_USER_TITLE", "List secret scanning alerts"),
ReadOnlyHint: ToBoolPtr(true),
}),
mcp.WithString("owner",
mcp.Required(),
mcp.Description("The owner of the repository."),
),
mcp.WithString("repo",
mcp.Required(),
mcp.Description("The name of the repository."),
),
mcp.WithString("state",
mcp.Description("Filter by state"),
mcp.Enum("open", "resolved"),
),
mcp.WithString("secret_type",
mcp.Description("A comma-separated list of secret types to return. All default secret patterns are returned. To return generic patterns, pass the token name(s) in the parameter."),
),
mcp.WithString("resolution",
mcp.Description("Filter by resolution"),
mcp.Enum("false_positive", "wont_fix", "revoked", "pattern_edited", "pattern_deleted", "used_in_tests"),
),
),
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
owner, err := RequiredParam[string](request, "owner")
ReadOnlyHint: true,
},
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"owner": {
Type: "string",
Description: "The owner of the repository.",
},
"repo": {
Type: "string",
Description: "The name of the repository.",
},
"state": {
Type: "string",
Description: "Filter by state",
Enum: []any{"open", "resolved"},
},
"secret_type": {
Type: "string",
Description: "A comma-separated list of secret types to return. All default secret patterns are returned. To return generic patterns, pass the token name(s) in the parameter.",
},
"resolution": {
Type: "string",
Description: "Filter by resolution",
Enum: []any{"false_positive", "wont_fix", "revoked", "pattern_edited", "pattern_deleted", "used_in_tests"},
},
},
Required: []string{"owner", "repo"},
},
},
func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
owner, err := RequiredParam[string](args, "owner")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}
repo, err := RequiredParam[string](request, "repo")
repo, err := RequiredParam[string](args, "repo")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}
state, err := OptionalParam[string](request, "state")
state, err := OptionalParam[string](args, "state")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}
secretType, err := OptionalParam[string](request, "secret_type")
secretType, err := OptionalParam[string](args, "secret_type")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}
resolution, err := OptionalParam[string](request, "resolution")
resolution, err := OptionalParam[string](args, "resolution")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
return utils.NewToolResultError(err.Error()), nil, nil
}

client, err := getClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
}
alerts, resp, err := client.SecretScanning.ListAlertsForRepo(ctx, owner, repo, &github.SecretScanningAlertListOptions{State: state, SecretType: secretType, Resolution: resolution})
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx,
fmt.Sprintf("failed to list alerts for repository '%s/%s'", owner, repo),
resp,
err,
), nil
), nil, nil
}
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 nil, nil, fmt.Errorf("failed to read response body: %w", err)
}
return mcp.NewToolResultError(fmt.Sprintf("failed to list alerts: %s", string(body))), nil
return utils.NewToolResultError(fmt.Sprintf("failed to list alerts: %s", string(body))), nil, nil
}

r, err := json.Marshal(alerts)
if err != nil {
return nil, fmt.Errorf("failed to marshal alerts: %w", err)
return nil, nil, fmt.Errorf("failed to marshal alerts: %w", err)
}

return mcp.NewToolResultText(string(r)), nil
return utils.NewToolResultText(string(r)), nil, nil
}
}
Loading
Loading