diff --git a/pkg/github/__toolsnaps__/get_dependabot_alert.snap b/pkg/github/__toolsnaps__/get_dependabot_alert.snap index 76b5ef126..a517809e2 100644 --- a/pkg/github/__toolsnaps__/get_dependabot_alert.snap +++ b/pkg/github/__toolsnaps__/get_dependabot_alert.snap @@ -1,30 +1,30 @@ { "annotations": { - "title": "Get dependabot alert", - "readOnlyHint": true + "readOnlyHint": true, + "title": "Get dependabot alert" }, "description": "Get details of a specific dependabot alert in a GitHub repository.", "inputSchema": { + "type": "object", + "required": [ + "owner", + "repo", + "alertNumber" + ], "properties": { "alertNumber": { - "description": "The number of the alert.", - "type": "number" + "type": "number", + "description": "The number of the alert." }, "owner": { - "description": "The owner of the repository.", - "type": "string" + "type": "string", + "description": "The owner of the repository." }, "repo": { - "description": "The name of the repository.", - "type": "string" + "type": "string", + "description": "The name of the repository." } - }, - "required": [ - "owner", - "repo", - "alertNumber" - ], - "type": "object" + } }, "name": "get_dependabot_alert" } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/list_dependabot_alerts.snap b/pkg/github/__toolsnaps__/list_dependabot_alerts.snap index 681d640b7..d96d3972c 100644 --- a/pkg/github/__toolsnaps__/list_dependabot_alerts.snap +++ b/pkg/github/__toolsnaps__/list_dependabot_alerts.snap @@ -1,46 +1,46 @@ { "annotations": { - "title": "List dependabot alerts", - "readOnlyHint": true + "readOnlyHint": true, + "title": "List dependabot alerts" }, "description": "List dependabot alerts in a GitHub repository.", "inputSchema": { + "type": "object", + "required": [ + "owner", + "repo" + ], "properties": { "owner": { - "description": "The owner of the repository.", - "type": "string" + "type": "string", + "description": "The owner of the repository." }, "repo": { - "description": "The name of the repository.", - "type": "string" + "type": "string", + "description": "The name of the repository." }, "severity": { + "type": "string", "description": "Filter dependabot alerts by severity", "enum": [ "low", "medium", "high", "critical" - ], - "type": "string" + ] }, "state": { - "default": "open", + "type": "string", "description": "Filter dependabot alerts by state. Defaults to open", + "default": "open", "enum": [ "open", "fixed", "dismissed", "auto_dismissed" - ], - "type": "string" + ] } - }, - "required": [ - "owner", - "repo" - ], - "type": "object" + } }, "name": "list_dependabot_alerts" } \ No newline at end of file diff --git a/pkg/github/dependabot.go b/pkg/github/dependabot.go index 3cf8d3a3f..7acda6d97 100644 --- a/pkg/github/dependabot.go +++ b/pkg/github/dependabot.go @@ -1,5 +1,3 @@ -//go:build ignore - package github import ( @@ -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/v77/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 GetDependabotAlert(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { - return mcp.NewTool( - "get_dependabot_alert", - mcp.WithDescription(t("TOOL_GET_DEPENDABOT_ALERT_DESCRIPTION", "Get details of a specific dependabot alert in a GitHub repository.")), - mcp.WithToolAnnotation(mcp.ToolAnnotation{ +func GetDependabotAlert(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler mcp.ToolHandlerFor[map[string]any, any]) { + return mcp.Tool{ + Name: "get_dependabot_alert", + Description: t("TOOL_GET_DEPENDABOT_ALERT_DESCRIPTION", "Get details of a specific dependabot alert in a GitHub repository."), + Annotations: &mcp.ToolAnnotations{ Title: t("TOOL_GET_DEPENDABOT_ALERT_USER_TITLE", "Get dependabot 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 utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, err } alert, resp, err := client.Dependabot.GetRepoAlert(ctx, owner, repo, alertNumber) @@ -62,74 +67,82 @@ func GetDependabotAlert(getClient GetClientFn, t translations.TranslationHelperF 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 utils.NewToolResultErrorFromErr("failed to read response body", err), nil, 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 utils.NewToolResultErrorFromErr("failed to marshal alert", err), nil, err } - return mcp.NewToolResultText(string(r)), nil + return utils.NewToolResultText(string(r)), nil, nil } } -func ListDependabotAlerts(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { - return mcp.NewTool( - "list_dependabot_alerts", - mcp.WithDescription(t("TOOL_LIST_DEPENDABOT_ALERTS_DESCRIPTION", "List dependabot alerts in a GitHub repository.")), - mcp.WithToolAnnotation(mcp.ToolAnnotation{ +func ListDependabotAlerts(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler mcp.ToolHandlerFor[map[string]any, any]) { + return mcp.Tool{ + Name: "list_dependabot_alerts", + Description: t("TOOL_LIST_DEPENDABOT_ALERTS_DESCRIPTION", "List dependabot alerts in a GitHub repository."), + Annotations: &mcp.ToolAnnotations{ Title: t("TOOL_LIST_DEPENDABOT_ALERTS_USER_TITLE", "List dependabot 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 dependabot alerts by state. Defaults to open"), - mcp.DefaultString("open"), - mcp.Enum("open", "fixed", "dismissed", "auto_dismissed"), - ), - mcp.WithString("severity", - mcp.Description("Filter dependabot alerts by severity"), - mcp.Enum("low", "medium", "high", "critical"), - ), - ), - 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 dependabot alerts by state. Defaults to open", + Enum: []any{"open", "fixed", "dismissed", "auto_dismissed"}, + Default: json.RawMessage(`"open"`), + }, + "severity": { + Type: "string", + Description: "Filter dependabot alerts by severity", + Enum: []any{"low", "medium", "high", "critical"}, + }, + }, + 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 } - severity, err := OptionalParam[string](request, "severity") + severity, err := OptionalParam[string](args, "severity") 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 utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, err } alerts, resp, err := client.Dependabot.ListRepoAlerts(ctx, owner, repo, &github.ListAlertsOptions{ @@ -141,23 +154,23 @@ func ListDependabotAlerts(getClient GetClientFn, t translations.TranslationHelpe 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 utils.NewToolResultErrorFromErr("failed to read response body", err), nil, 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 utils.NewToolResultErrorFromErr("failed to marshal alerts", err), nil, err } - return mcp.NewToolResultText(string(r)), nil + return utils.NewToolResultText(string(r)), nil, nil } } diff --git a/pkg/github/dependabot_test.go b/pkg/github/dependabot_test.go index 06cc5866f..78784df1e 100644 --- a/pkg/github/dependabot_test.go +++ b/pkg/github/dependabot_test.go @@ -1,5 +1,3 @@ -//go:build ignore - package github import ( @@ -25,10 +23,6 @@ func Test_GetDependabotAlert(t *testing.T) { // Validate tool schema assert.Equal(t, "get_dependabot_alert", tool.Name) assert.NotEmpty(t, tool.Description) - assert.Contains(t, tool.InputSchema.Properties, "owner") - assert.Contains(t, tool.InputSchema.Properties, "repo") - assert.Contains(t, tool.InputSchema.Properties, "alertNumber") - assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "alertNumber"}) // Setup mock alert for success case mockAlert := &github.DependabotAlert{ @@ -92,7 +86,7 @@ func Test_GetDependabotAlert(t *testing.T) { request := createMCPRequest(tc.requestArgs) // Call handler - result, err := handler(context.Background(), request) + result, _, err := handler(context.Background(), &request, tc.requestArgs) // Verify results if tc.expectError { @@ -128,11 +122,6 @@ func Test_ListDependabotAlerts(t *testing.T) { assert.Equal(t, "list_dependabot_alerts", tool.Name) assert.NotEmpty(t, tool.Description) - assert.Contains(t, tool.InputSchema.Properties, "owner") - assert.Contains(t, tool.InputSchema.Properties, "repo") - assert.Contains(t, tool.InputSchema.Properties, "state") - assert.Contains(t, tool.InputSchema.Properties, "severity") - assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo"}) // Setup mock alerts for success case criticalAlert := github.DependabotAlert{ @@ -244,7 +233,7 @@ func Test_ListDependabotAlerts(t *testing.T) { request := createMCPRequest(tc.requestArgs) - result, err := handler(context.Background(), request) + result, _, err := handler(context.Background(), &request, tc.requestArgs) if tc.expectError { require.NoError(t, err)