diff --git a/pkg/github/notifications.go b/pkg/github/notifications.go index 9e32c143..e040f6ef 100644 --- a/pkg/github/notifications.go +++ b/pkg/github/notifications.go @@ -15,7 +15,7 @@ import ( ) // getNotifications creates a tool to list notifications for the current user. -func getNotifications(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func GetNotifications(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_notifications", mcp.WithDescription(t("TOOL_GET_NOTIFICATIONS_DESCRIPTION", "Get notifications for the authenticated GitHub user")), mcp.WithBoolean("all", @@ -38,33 +38,38 @@ func getNotifications(client *github.Client, t translations.TranslationHelperFun ), ), 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) + } + // Extract optional parameters with defaults - all, err := optionalParamWithDefault[bool](request, "all", false) + all, err := OptionalBoolParamWithDefault(request, "all", false) if err != nil { return mcp.NewToolResultError(err.Error()), nil } - participating, err := optionalParamWithDefault[bool](request, "participating", false) + participating, err := OptionalBoolParamWithDefault(request, "participating", false) if err != nil { return mcp.NewToolResultError(err.Error()), nil } - since, err := optionalParam[string](request, "since") + since, err := OptionalStringParamWithDefault(request, "since", "") if err != nil { return mcp.NewToolResultError(err.Error()), nil } - before, err := optionalParam[string](request, "before") + before, err := OptionalStringParam(request, "before") if err != nil { return mcp.NewToolResultError(err.Error()), nil } - perPage, err := optionalIntParamWithDefault(request, "per_page", 30) + perPage, err := OptionalIntParamWithDefault(request, "per_page", 30) if err != nil { return mcp.NewToolResultError(err.Error()), nil } - page, err := optionalIntParamWithDefault(request, "page", 1) + page, err := OptionalIntParamWithDefault(request, "page", 1) if err != nil { return mcp.NewToolResultError(err.Error()), nil } @@ -122,7 +127,7 @@ func getNotifications(client *github.Client, t translations.TranslationHelperFun } // markNotificationRead creates a tool to mark a notification as read. -func markNotificationRead(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { +func MarkNotificationRead(getclient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("mark_notification_read", mcp.WithDescription(t("TOOL_MARK_NOTIFICATION_READ_DESCRIPTION", "Mark a notification as read")), mcp.WithString("threadID", @@ -131,6 +136,11 @@ func markNotificationRead(client *github.Client, t translations.TranslationHelpe ), ), 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) + } + threadID, err := requiredParam[string](request, "threadID") if err != nil { return mcp.NewToolResultError(err.Error()), nil @@ -154,8 +164,8 @@ func markNotificationRead(client *github.Client, t translations.TranslationHelpe } } -// markAllNotificationsRead creates a tool to mark all notifications as read. -func markAllNotificationsRead(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { +// MarkAllNotificationsRead creates a tool to mark all notifications as read. +func MarkAllNotificationsRead(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("mark_all_notifications_read", mcp.WithDescription(t("TOOL_MARK_ALL_NOTIFICATIONS_READ_DESCRIPTION", "Mark all notifications as read")), mcp.WithString("lastReadAt", @@ -163,7 +173,12 @@ func markAllNotificationsRead(client *github.Client, t translations.TranslationH ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { - lastReadAt, err := optionalParam[string](request, "lastReadAt") + client, err := getClient(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get GitHub client: %w", err) + } + + lastReadAt, err := OptionalStringParam(request, "lastReadAt") if err != nil { return mcp.NewToolResultError(err.Error()), nil } @@ -197,8 +212,8 @@ func markAllNotificationsRead(client *github.Client, t translations.TranslationH } } -// getNotificationThread creates a tool to get a specific notification thread. -func getNotificationThread(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { +// GetNotificationThread creates a tool to get a specific notification thread. +func GetNotificationThread(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { return mcp.NewTool("get_notification_thread", mcp.WithDescription(t("TOOL_GET_NOTIFICATION_THREAD_DESCRIPTION", "Get a specific notification thread")), mcp.WithString("threadID", @@ -207,6 +222,11 @@ func getNotificationThread(client *github.Client, t translations.TranslationHelp ), ), 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) + } + threadID, err := requiredParam[string](request, "threadID") if err != nil { return mcp.NewToolResultError(err.Error()), nil diff --git a/pkg/github/server.go b/pkg/github/server.go index 63772ee1..2a1b1fd0 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -91,12 +91,14 @@ func NewServer(getClient GetClientFn, version string, readOnly bool, t translati s.AddTool(GetCodeScanningAlert(getClient, t)) s.AddTool(ListCodeScanningAlerts(getClient, t)) - // Add GitHub tools - Notifications + // Add GitHub tools - Notifications + s.AddTool(GetNotifications(getClient, t)) + s.AddTool(GetNotificationThread(getClient, t)) if !readOnly { - s.AddTool(markNotificationRead(client, t)) - s.AddTool(markAllNotificationsRead(client, t)) + s.AddTool(MarkNotificationRead(getClient, t)) + s.AddTool(MarkAllNotificationsRead(getClient, t)) } - + return s } @@ -237,20 +239,6 @@ func OptionalIntParam(r mcp.CallToolRequest, p string) (int, error) { return int(v), nil } -// optionalParamWithDefault is a generic helper function that can be used to fetch a requested parameter from the request -// with a default value if the parameter is not provided or is zero value. -func optionalParamWithDefault[T comparable](r mcp.CallToolRequest, p string, d T) (T, error) { - var zero T - v, err := optionalParam[T](r, p) - if err != nil { - return zero, err - } - if v == zero { - return d, nil - } - return v, nil -} - // OptionalIntParamWithDefault is a helper function that can be used to fetch a requested parameter from the request // similar to optionalIntParam, but it also takes a default value. func OptionalIntParamWithDefault(r mcp.CallToolRequest, p string, d int) (int, error) { @@ -264,6 +252,47 @@ func OptionalIntParamWithDefault(r mcp.CallToolRequest, p string, d int) (int, e return v, nil } +// OptionalBoolParamWithDefault is a helper function that can be used to fetch a requested parameter from the request +// similar to optionalParam, but it also takes a default value. +func OptionalBoolParamWithDefault(r mcp.CallToolRequest, p string, d bool) (bool, error) { + v, err := OptionalParam[bool](r, p) + if err != nil { + return false, err + } + if v == false { + return d, nil + } + return v, nil +} + +// OptionalStringParam is a helper function that can be used to fetch a requested parameter from the request. +// It does the following checks: +// 1. Checks if the parameter is present in the request, if not, it returns its zero-value +// 2. If it is present, it checks if the parameter is of the expected type and returns it +func OptionalStringParam(r mcp.CallToolRequest, p string) (string, error) { + v, err := OptionalParam[string](r, p) + if err != nil { + return "", err + } + if v == "" { + return "", nil + } + return v, nil +} + +// OptionalStringParamWithDefault is a helper function that can be used to fetch a requested parameter from the request +// similar to optionalParam, but it also takes a default value. +func OptionalStringParamWithDefault(r mcp.CallToolRequest, p string, d string) (string, error) { + v, err := OptionalParam[string](r, p) + if err != nil { + return "", err + } + if v == "" { + return d, nil + } + return v, nil +} + // OptionalStringArrayParam is a helper function that can be used to fetch a requested parameter from the request. // It does the following checks: // 1. Checks if the parameter is present in the request, if not, it returns its zero-value