Skip to content

Commit 36e6add

Browse files
omgitsadsSamMorrowDrums
authored andcommitted
Return concrete error types for API errors
1 parent 20afb0f commit 36e6add

File tree

9 files changed

+373
-68
lines changed

9 files changed

+373
-68
lines changed

pkg/errors/error.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package errors
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/google/go-github/v72/github"
7+
)
8+
9+
type GitHubAPIError struct {
10+
Message string `json:"message"`
11+
Response *github.Response `json:"-"`
12+
Err error `json:"-"`
13+
}
14+
15+
func NewGitHubAPIError(message string, resp *github.Response, err error) *GitHubAPIError {
16+
return &GitHubAPIError{
17+
Message: message,
18+
Response: resp,
19+
Err: err,
20+
}
21+
}
22+
23+
func (e *GitHubAPIError) Error() string {
24+
return fmt.Errorf("%s: %w", e.Message, e.Err).Error()
25+
}
26+
27+
type GitHubGraphQLError struct {
28+
Message string `json:"message"`
29+
Err error `json:"-"`
30+
}
31+
32+
func NewGitHubGraphQLError(message string, err error) *GitHubGraphQLError {
33+
return &GitHubGraphQLError{
34+
Message: message,
35+
Err: err,
36+
}
37+
}
38+
39+
func (e *GitHubGraphQLError) Error() string {
40+
return fmt.Errorf("%s: %w", e.Message, e.Err).Error()
41+
}

pkg/github/code_scanning.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"net/http"
99

10+
ghErrors "github.com/github/github-mcp-server/pkg/errors"
1011
"github.com/github/github-mcp-server/pkg/translations"
1112
"github.com/google/go-github/v72/github"
1213
"github.com/mark3labs/mcp-go/mcp"
@@ -54,7 +55,11 @@ func GetCodeScanningAlert(getClient GetClientFn, t translations.TranslationHelpe
5455

5556
alert, resp, err := client.CodeScanning.GetAlert(ctx, owner, repo, int64(alertNumber))
5657
if err != nil {
57-
return nil, fmt.Errorf("failed to get alert: %w", err)
58+
return nil, ghErrors.NewGitHubAPIError(
59+
"failed to get alert",
60+
resp,
61+
err,
62+
)
5863
}
5964
defer func() { _ = resp.Body.Close() }()
6065

@@ -138,7 +143,11 @@ func ListCodeScanningAlerts(getClient GetClientFn, t translations.TranslationHel
138143
}
139144
alerts, resp, err := client.CodeScanning.ListAlertsForRepo(ctx, owner, repo, &github.AlertListOptions{Ref: ref, State: state, Severity: severity, ToolName: toolName})
140145
if err != nil {
141-
return nil, fmt.Errorf("failed to list alerts: %w", err)
146+
return nil, ghErrors.NewGitHubAPIError(
147+
"failed to list alerts",
148+
resp,
149+
err,
150+
)
142151
}
143152
defer func() { _ = resp.Body.Close() }()
144153

pkg/github/context_tools.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package github
33
import (
44
"context"
55

6+
ghErrors "github.com/github/github-mcp-server/pkg/errors"
67
"github.com/github/github-mcp-server/pkg/translations"
78
"github.com/mark3labs/mcp-go/mcp"
89
"github.com/mark3labs/mcp-go/server"
@@ -28,9 +29,13 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Too
2829
return mcp.NewToolResultErrorFromErr("failed to get GitHub client", err), nil
2930
}
3031

31-
user, _, err := client.Users.Get(ctx, "")
32+
user, res, err := client.Users.Get(ctx, "")
3233
if err != nil {
33-
return mcp.NewToolResultErrorFromErr("failed to get user", err), nil
34+
return nil, ghErrors.NewGitHubAPIError(
35+
"failed to get user",
36+
res,
37+
err,
38+
)
3439
}
3540

3641
return MarshalledTextResult(user), nil

pkg/github/issues.go

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strings"
1010
"time"
1111

12+
ghErrors "github.com/github/github-mcp-server/pkg/errors"
1213
"github.com/github/github-mcp-server/pkg/translations"
1314
"github.com/go-viper/mapstructure/v2"
1415
"github.com/google/go-github/v72/github"
@@ -58,7 +59,11 @@ func GetIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (tool
5859
}
5960
issue, resp, err := client.Issues.Get(ctx, owner, repo, issueNumber)
6061
if err != nil {
61-
return nil, fmt.Errorf("failed to get issue: %w", err)
62+
return nil, ghErrors.NewGitHubAPIError(
63+
fmt.Sprintf("failed to get issue with number '%d'", issueNumber),
64+
resp,
65+
err,
66+
)
6267
}
6368
defer func() { _ = resp.Body.Close() }()
6469

@@ -132,7 +137,11 @@ func AddIssueComment(getClient GetClientFn, t translations.TranslationHelperFunc
132137
}
133138
createdComment, resp, err := client.Issues.CreateComment(ctx, owner, repo, issueNumber, comment)
134139
if err != nil {
135-
return nil, fmt.Errorf("failed to create comment: %w", err)
140+
return nil, ghErrors.NewGitHubAPIError(
141+
fmt.Sprintf("failed to create comment on issue '%d'", issueNumber),
142+
resp,
143+
err,
144+
)
136145
}
137146
defer func() { _ = resp.Body.Close() }()
138147

@@ -220,7 +229,11 @@ func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) (
220229
}
221230
result, resp, err := client.Search.Issues(ctx, query, opts)
222231
if err != nil {
223-
return nil, fmt.Errorf("failed to search issues: %w", err)
232+
return nil, ghErrors.NewGitHubAPIError(
233+
"failed to search issues",
234+
resp,
235+
err,
236+
)
224237
}
225238
defer func() { _ = resp.Body.Close() }()
226239

@@ -342,7 +355,11 @@ func CreateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t
342355
}
343356
issue, resp, err := client.Issues.Create(ctx, owner, repo, issueRequest)
344357
if err != nil {
345-
return nil, fmt.Errorf("failed to create issue: %w", err)
358+
return nil, ghErrors.NewGitHubAPIError(
359+
"failed to create issue",
360+
resp,
361+
err,
362+
)
346363
}
347364
defer func() { _ = resp.Body.Close() }()
348365

@@ -464,7 +481,11 @@ func ListIssues(getClient GetClientFn, t translations.TranslationHelperFunc) (to
464481
}
465482
issues, resp, err := client.Issues.ListByRepo(ctx, owner, repo, opts)
466483
if err != nil {
467-
return nil, fmt.Errorf("failed to list issues: %w", err)
484+
return nil, ghErrors.NewGitHubAPIError(
485+
"failed to list issues",
486+
resp,
487+
err,
488+
)
468489
}
469490
defer func() { _ = resp.Body.Close() }()
470491

@@ -610,7 +631,11 @@ func UpdateIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (t
610631
}
611632
updatedIssue, resp, err := client.Issues.Edit(ctx, owner, repo, issueNumber, issueRequest)
612633
if err != nil {
613-
return nil, fmt.Errorf("failed to update issue: %w", err)
634+
return nil, ghErrors.NewGitHubAPIError(
635+
"failed to update issue",
636+
resp,
637+
err,
638+
)
614639
}
615640
defer func() { _ = resp.Body.Close() }()
616641

@@ -693,7 +718,11 @@ func GetIssueComments(getClient GetClientFn, t translations.TranslationHelperFun
693718
}
694719
comments, resp, err := client.Issues.ListComments(ctx, owner, repo, issueNumber, opts)
695720
if err != nil {
696-
return nil, fmt.Errorf("failed to get issue comments: %w", err)
721+
return nil, ghErrors.NewGitHubAPIError(
722+
"failed to get issue comments",
723+
resp,
724+
err,
725+
)
697726
}
698727
defer func() { _ = resp.Body.Close() }()
699728

@@ -824,7 +853,10 @@ func AssignCopilotToIssue(getGQLClient GetGQLClientFn, t translations.Translatio
824853
var query suggestedActorsQuery
825854
err := client.Query(ctx, &query, variables)
826855
if err != nil {
827-
return nil, err
856+
return nil, ghErrors.NewGitHubGraphQLError(
857+
"failed to list suggested actors",
858+
err,
859+
)
828860
}
829861

830862
// Iterate all the returned nodes looking for the copilot bot, which is supposed to have the
@@ -870,7 +902,10 @@ func AssignCopilotToIssue(getGQLClient GetGQLClientFn, t translations.Translatio
870902
}
871903

872904
if err := client.Query(ctx, &getIssueQuery, variables); err != nil {
873-
return mcp.NewToolResultError(fmt.Sprintf("failed to get issue ID: %v", err)), nil
905+
return nil, ghErrors.NewGitHubGraphQLError(
906+
"failed to get issue ID",
907+
err,
908+
)
874909
}
875910

876911
// Finally, do the assignment. Just for reference, assigning copilot to an issue that it is already
@@ -896,7 +931,10 @@ func AssignCopilotToIssue(getGQLClient GetGQLClientFn, t translations.Translatio
896931
},
897932
nil,
898933
); err != nil {
899-
return nil, fmt.Errorf("failed to replace actors for assignable: %w", err)
934+
return nil, ghErrors.NewGitHubGraphQLError(
935+
"failed to replace actors for assignable",
936+
err,
937+
)
900938
}
901939

902940
return mcp.NewToolResultText("successfully assigned copilot to issue"), nil

pkg/github/notifications.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strconv"
1010
"time"
1111

12+
ghErrors "github.com/github/github-mcp-server/pkg/errors"
1213
"github.com/github/github-mcp-server/pkg/translations"
1314
"github.com/google/go-github/v72/github"
1415
"github.com/mark3labs/mcp-go/mcp"
@@ -118,7 +119,11 @@ func ListNotifications(getClient GetClientFn, t translations.TranslationHelperFu
118119
notifications, resp, err = client.Activity.ListNotifications(ctx, opts)
119120
}
120121
if err != nil {
121-
return nil, fmt.Errorf("failed to get notifications: %w", err)
122+
return nil, ghErrors.NewGitHubAPIError(
123+
"failed to list notifications",
124+
resp,
125+
err,
126+
)
122127
}
123128
defer func() { _ = resp.Body.Close() }()
124129

@@ -187,7 +192,11 @@ func DismissNotification(getclient GetClientFn, t translations.TranslationHelper
187192
}
188193

189194
if err != nil {
190-
return nil, fmt.Errorf("failed to mark notification as %s: %w", state, err)
195+
return nil, ghErrors.NewGitHubAPIError(
196+
fmt.Sprintf("failed to mark notification as %s", state),
197+
resp,
198+
err,
199+
)
191200
}
192201
defer func() { _ = resp.Body.Close() }()
193202

@@ -262,7 +271,11 @@ func MarkAllNotificationsRead(getClient GetClientFn, t translations.TranslationH
262271
resp, err = client.Activity.MarkNotificationsRead(ctx, markReadOptions)
263272
}
264273
if err != nil {
265-
return nil, fmt.Errorf("failed to mark all notifications as read: %w", err)
274+
return nil, ghErrors.NewGitHubAPIError(
275+
"failed to mark all notifications as read",
276+
resp,
277+
err,
278+
)
266279
}
267280
defer func() { _ = resp.Body.Close() }()
268281

@@ -304,7 +317,11 @@ func GetNotificationDetails(getClient GetClientFn, t translations.TranslationHel
304317

305318
thread, resp, err := client.Activity.GetThread(ctx, notificationID)
306319
if err != nil {
307-
return nil, fmt.Errorf("failed to get notification details: %w", err)
320+
return nil, ghErrors.NewGitHubAPIError(
321+
fmt.Sprintf("failed to get notification details for ID '%s'", notificationID),
322+
resp,
323+
err,
324+
)
308325
}
309326
defer func() { _ = resp.Body.Close() }()
310327

@@ -385,7 +402,11 @@ func ManageNotificationSubscription(getClient GetClientFn, t translations.Transl
385402
}
386403

387404
if apiErr != nil {
388-
return nil, fmt.Errorf("failed to %s notification subscription: %w", action, apiErr)
405+
return nil, ghErrors.NewGitHubAPIError(
406+
fmt.Sprintf("failed to %s notification subscription", action),
407+
resp,
408+
apiErr,
409+
)
389410
}
390411
defer func() { _ = resp.Body.Close() }()
391412

@@ -474,7 +495,11 @@ func ManageRepositoryNotificationSubscription(getClient GetClientFn, t translati
474495
}
475496

476497
if apiErr != nil {
477-
return nil, fmt.Errorf("failed to %s repository subscription: %w", action, apiErr)
498+
return nil, ghErrors.NewGitHubAPIError(
499+
fmt.Sprintf("failed to %s repository subscription", action),
500+
resp,
501+
apiErr,
502+
)
478503
}
479504
if resp != nil {
480505
defer func() { _ = resp.Body.Close() }()

0 commit comments

Comments
 (0)