Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3477535

Browse files
sridharavinashSamMorrowDrums
authored andcommitted
feat: add GitHub notifications tools for managing user notifications
1 parentb9a06d0 commit3477535

File tree

3 files changed

+356
-0
lines changed

3 files changed

+356
-0
lines changed

‎pkg/github/notifications.go

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"strconv"
10+
"time"
11+
12+
"github.com/github/github-mcp-server/pkg/translations"
13+
"github.com/google/go-github/v69/github"
14+
"github.com/mark3labs/mcp-go/mcp"
15+
"github.com/mark3labs/mcp-go/server"
16+
)
17+
18+
// getNotifications creates a tool to list notifications for the current user.
19+
funcGetNotifications(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
20+
returnmcp.NewTool("get_notifications",
21+
mcp.WithDescription(t("TOOL_GET_NOTIFICATIONS_DESCRIPTION","Get notifications for the authenticated GitHub user")),
22+
mcp.WithBoolean("all",
23+
mcp.Description("If true, show notifications marked as read. Default: false"),
24+
),
25+
mcp.WithBoolean("participating",
26+
mcp.Description("If true, only shows notifications in which the user is directly participating or mentioned. Default: false"),
27+
),
28+
mcp.WithString("since",
29+
mcp.Description("Only show notifications updated after the given time (ISO 8601 format)"),
30+
),
31+
mcp.WithString("before",
32+
mcp.Description("Only show notifications updated before the given time (ISO 8601 format)"),
33+
),
34+
mcp.WithNumber("per_page",
35+
mcp.Description("Results per page (max 100). Default: 30"),
36+
),
37+
mcp.WithNumber("page",
38+
mcp.Description("Page number of the results to fetch. Default: 1"),
39+
),
40+
),
41+
func(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
42+
client,err:=getClient(ctx)
43+
iferr!=nil {
44+
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
45+
}
46+
47+
// Extract optional parameters with defaults
48+
all,err:=OptionalParamWithDefault[bool](request,"all",false)
49+
iferr!=nil {
50+
returnmcp.NewToolResultError(err.Error()),nil
51+
}
52+
53+
participating,err:=OptionalParamWithDefault[bool](request,"participating",false)
54+
iferr!=nil {
55+
returnmcp.NewToolResultError(err.Error()),nil
56+
}
57+
58+
since,err:=OptionalParam[string](request,"since")
59+
iferr!=nil {
60+
returnmcp.NewToolResultError(err.Error()),nil
61+
}
62+
63+
before,err:=OptionalParam[string](request,"before")
64+
iferr!=nil {
65+
returnmcp.NewToolResultError(err.Error()),nil
66+
}
67+
68+
perPage,err:=OptionalIntParamWithDefault(request,"per_page",30)
69+
iferr!=nil {
70+
returnmcp.NewToolResultError(err.Error()),nil
71+
}
72+
73+
page,err:=OptionalIntParamWithDefault(request,"page",1)
74+
iferr!=nil {
75+
returnmcp.NewToolResultError(err.Error()),nil
76+
}
77+
78+
// Build options
79+
opts:=&github.NotificationListOptions{
80+
All:all,
81+
Participating:participating,
82+
ListOptions: github.ListOptions{
83+
Page:page,
84+
PerPage:perPage,
85+
},
86+
}
87+
88+
// Parse time parameters if provided
89+
ifsince!="" {
90+
sinceTime,err:=time.Parse(time.RFC3339,since)
91+
iferr!=nil {
92+
returnmcp.NewToolResultError(fmt.Sprintf("invalid since time format, should be RFC3339/ISO8601: %v",err)),nil
93+
}
94+
opts.Since=sinceTime
95+
}
96+
97+
ifbefore!="" {
98+
beforeTime,err:=time.Parse(time.RFC3339,before)
99+
iferr!=nil {
100+
returnmcp.NewToolResultError(fmt.Sprintf("invalid before time format, should be RFC3339/ISO8601: %v",err)),nil
101+
}
102+
opts.Before=beforeTime
103+
}
104+
105+
// Call GitHub API
106+
notifications,resp,err:=client.Activity.ListNotifications(ctx,opts)
107+
iferr!=nil {
108+
returnnil,fmt.Errorf("failed to get notifications: %w",err)
109+
}
110+
deferfunc() {_=resp.Body.Close() }()
111+
112+
ifresp.StatusCode!=http.StatusOK {
113+
body,err:=io.ReadAll(resp.Body)
114+
iferr!=nil {
115+
returnnil,fmt.Errorf("failed to read response body: %w",err)
116+
}
117+
returnmcp.NewToolResultError(fmt.Sprintf("failed to get notifications: %s",string(body))),nil
118+
}
119+
120+
// Marshal response to JSON
121+
r,err:=json.Marshal(notifications)
122+
iferr!=nil {
123+
returnnil,fmt.Errorf("failed to marshal response: %w",err)
124+
}
125+
126+
returnmcp.NewToolResultText(string(r)),nil
127+
}
128+
}
129+
130+
// markNotificationRead creates a tool to mark a notification as read.
131+
funcMarkNotificationRead(getclientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
132+
returnmcp.NewTool("mark_notification_read",
133+
mcp.WithDescription(t("TOOL_MARK_NOTIFICATION_READ_DESCRIPTION","Mark a notification as read")),
134+
mcp.WithString("threadID",
135+
mcp.Required(),
136+
mcp.Description("The ID of the notification thread"),
137+
),
138+
),
139+
func(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
140+
client,err:=getclient(ctx)
141+
iferr!=nil {
142+
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
143+
}
144+
145+
threadID,err:=requiredParam[string](request,"threadID")
146+
iferr!=nil {
147+
returnmcp.NewToolResultError(err.Error()),nil
148+
}
149+
150+
resp,err:=client.Activity.MarkThreadRead(ctx,threadID)
151+
iferr!=nil {
152+
returnnil,fmt.Errorf("failed to mark notification as read: %w",err)
153+
}
154+
deferfunc() {_=resp.Body.Close() }()
155+
156+
ifresp.StatusCode!=http.StatusResetContent&&resp.StatusCode!=http.StatusOK {
157+
body,err:=io.ReadAll(resp.Body)
158+
iferr!=nil {
159+
returnnil,fmt.Errorf("failed to read response body: %w",err)
160+
}
161+
returnmcp.NewToolResultError(fmt.Sprintf("failed to mark notification as read: %s",string(body))),nil
162+
}
163+
164+
returnmcp.NewToolResultText("Notification marked as read"),nil
165+
}
166+
}
167+
168+
// MarkAllNotificationsRead creates a tool to mark all notifications as read.
169+
funcMarkAllNotificationsRead(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
170+
returnmcp.NewTool("mark_all_notifications_read",
171+
mcp.WithDescription(t("TOOL_MARK_ALL_NOTIFICATIONS_READ_DESCRIPTION","Mark all notifications as read")),
172+
mcp.WithString("lastReadAt",
173+
mcp.Description("Describes the last point that notifications were checked (optional). Default: Now"),
174+
),
175+
),
176+
func(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
177+
client,err:=getClient(ctx)
178+
iferr!=nil {
179+
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
180+
}
181+
182+
lastReadAt,err:=OptionalParam(request,"lastReadAt")
183+
iferr!=nil {
184+
returnmcp.NewToolResultError(err.Error()),nil
185+
}
186+
187+
varmarkReadOptions github.Timestamp
188+
iflastReadAt!="" {
189+
lastReadTime,err:=time.Parse(time.RFC3339,lastReadAt)
190+
iferr!=nil {
191+
returnmcp.NewToolResultError(fmt.Sprintf("invalid lastReadAt time format, should be RFC3339/ISO8601: %v",err)),nil
192+
}
193+
markReadOptions= github.Timestamp{
194+
Time:lastReadTime,
195+
}
196+
}
197+
198+
resp,err:=client.Activity.MarkNotificationsRead(ctx,markReadOptions)
199+
iferr!=nil {
200+
returnnil,fmt.Errorf("failed to mark all notifications as read: %w",err)
201+
}
202+
deferfunc() {_=resp.Body.Close() }()
203+
204+
ifresp.StatusCode!=http.StatusResetContent&&resp.StatusCode!=http.StatusOK {
205+
body,err:=io.ReadAll(resp.Body)
206+
iferr!=nil {
207+
returnnil,fmt.Errorf("failed to read response body: %w",err)
208+
}
209+
returnmcp.NewToolResultError(fmt.Sprintf("failed to mark all notifications as read: %s",string(body))),nil
210+
}
211+
212+
returnmcp.NewToolResultText("All notifications marked as read"),nil
213+
}
214+
}
215+
216+
// GetNotificationThread creates a tool to get a specific notification thread.
217+
funcGetNotificationThread(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
218+
returnmcp.NewTool("get_notification_thread",
219+
mcp.WithDescription(t("TOOL_GET_NOTIFICATION_THREAD_DESCRIPTION","Get a specific notification thread")),
220+
mcp.WithString("threadID",
221+
mcp.Required(),
222+
mcp.Description("The ID of the notification thread"),
223+
),
224+
),
225+
func(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
226+
client,err:=getClient(ctx)
227+
iferr!=nil {
228+
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
229+
}
230+
231+
threadID,err:=requiredParam[string](request,"threadID")
232+
iferr!=nil {
233+
returnmcp.NewToolResultError(err.Error()),nil
234+
}
235+
236+
thread,resp,err:=client.Activity.GetThread(ctx,threadID)
237+
iferr!=nil {
238+
returnnil,fmt.Errorf("failed to get notification thread: %w",err)
239+
}
240+
deferfunc() {_=resp.Body.Close() }()
241+
242+
ifresp.StatusCode!=http.StatusOK {
243+
body,err:=io.ReadAll(resp.Body)
244+
iferr!=nil {
245+
returnnil,fmt.Errorf("failed to read response body: %w",err)
246+
}
247+
returnmcp.NewToolResultError(fmt.Sprintf("failed to get notification thread: %s",string(body))),nil
248+
}
249+
250+
r,err:=json.Marshal(thread)
251+
iferr!=nil {
252+
returnnil,fmt.Errorf("failed to marshal response: %w",err)
253+
}
254+
255+
returnmcp.NewToolResultText(string(r)),nil
256+
}
257+
}
258+
259+
// markNotificationDone creates a tool to mark a notification as done.
260+
funcMarkNotificationDone(getclientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
261+
returnmcp.NewTool("mark_notification_done",
262+
mcp.WithDescription(t("TOOL_MARK_NOTIFICATION_DONE_DESCRIPTION","Mark a notification as done")),
263+
mcp.WithString("threadID",
264+
mcp.Required(),
265+
mcp.Description("The ID of the notification thread"),
266+
),
267+
),
268+
func(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
269+
client,err:=getclient(ctx)
270+
iferr!=nil {
271+
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
272+
}
273+
274+
threadIDStr,err:=requiredParam[string](request,"threadID")
275+
iferr!=nil {
276+
returnmcp.NewToolResultError(err.Error()),nil
277+
}
278+
279+
threadID,err:=strconv.ParseInt(threadIDStr,10,64)
280+
iferr!=nil {
281+
returnmcp.NewToolResultError("Invalid threadID: must be a numeric value"),nil
282+
}
283+
284+
resp,err:=client.Activity.MarkThreadDone(ctx,threadID)
285+
iferr!=nil {
286+
returnnil,fmt.Errorf("failed to mark notification as done: %w",err)
287+
}
288+
deferfunc() {_=resp.Body.Close() }()
289+
290+
ifresp.StatusCode!=http.StatusResetContent&&resp.StatusCode!=http.StatusOK {
291+
body,err:=io.ReadAll(resp.Body)
292+
iferr!=nil {
293+
returnnil,fmt.Errorf("failed to read response body: %w",err)
294+
}
295+
returnmcp.NewToolResultError(fmt.Sprintf("failed to mark notification as done: %s",string(body))),nil
296+
}
297+
298+
returnmcp.NewToolResultText("Notification marked as done"),nil
299+
}
300+
}

‎pkg/github/server.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func NewServer(version string, opts ...server.ServerOption) *server.MCPServer {
2626
version,
2727
opts...,
2828
)
29+
2930
returns
3031
}
3132

@@ -143,6 +144,47 @@ func OptionalIntParamWithDefault(r mcp.CallToolRequest, p string, d int) (int, e
143144
returnv,nil
144145
}
145146

147+
// OptionalBoolParamWithDefault is a helper function that can be used to fetch a requested parameter from the request
148+
// similar to optionalParam, but it also takes a default value.
149+
funcOptionalBoolParamWithDefault(r mcp.CallToolRequest,pstring,dbool) (bool,error) {
150+
v,err:=OptionalParam[bool](r,p)
151+
iferr!=nil {
152+
returnfalse,err
153+
}
154+
if!v {
155+
returnd,nil
156+
}
157+
returnv,nil
158+
}
159+
160+
// OptionalStringParam is a helper function that can be used to fetch a requested parameter from the request.
161+
// It does the following checks:
162+
// 1. Checks if the parameter is present in the request, if not, it returns its zero-value
163+
// 2. If it is present, it checks if the parameter is of the expected type and returns it
164+
funcOptionalStringParam(r mcp.CallToolRequest,pstring) (string,error) {
165+
v,err:=OptionalParam[string](r,p)
166+
iferr!=nil {
167+
return"",err
168+
}
169+
ifv=="" {
170+
return"",nil
171+
}
172+
returnv,nil
173+
}
174+
175+
// OptionalStringParamWithDefault is a helper function that can be used to fetch a requested parameter from the request
176+
// similar to optionalParam, but it also takes a default value.
177+
funcOptionalStringParamWithDefault(r mcp.CallToolRequest,pstring,dstring) (string,error) {
178+
v,err:=OptionalParam[string](r,p)
179+
iferr!=nil {
180+
return"",err
181+
}
182+
ifv=="" {
183+
returnd,nil
184+
}
185+
returnv,nil
186+
}
187+
146188
// OptionalStringArrayParam is a helper function that can be used to fetch a requested parameter from the request.
147189
// It does the following checks:
148190
// 1. Checks if the parameter is present in the request, if not, it returns its zero-value

‎pkg/github/tools.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@ func InitToolsets(passedToolsets []string, readOnly bool, getClient GetClientFn,
9191
toolsets.NewServerTool(GetSecretScanningAlert(getClient,t)),
9292
toolsets.NewServerTool(ListSecretScanningAlerts(getClient,t)),
9393
)
94+
95+
notifications:=toolsets.NewToolset("notifications","GitHub Notifications related tools").
96+
AddReadTools(
97+
98+
toolsets.NewServerTool(MarkNotificationRead(getClient,t)),
99+
toolsets.NewServerTool(MarkAllNotificationsRead(getClient,t)),
100+
toolsets.NewServerTool(MarkNotificationDone(getClient,t)),
101+
).
102+
AddWriteTools(
103+
toolsets.NewServerTool(GetNotifications(getClient,t)),
104+
toolsets.NewServerTool(GetNotificationThread(getClient,t)),
105+
)
106+
94107
// Keep experiments alive so the system doesn't error out when it's always enabled
95108
experiments:=toolsets.NewToolset("experiments","Experimental features that are not considered stable yet")
96109

@@ -101,6 +114,7 @@ func InitToolsets(passedToolsets []string, readOnly bool, getClient GetClientFn,
101114
tsg.AddToolset(pullRequests)
102115
tsg.AddToolset(codeSecurity)
103116
tsg.AddToolset(secretProtection)
117+
tsg.AddToolset(notifications)
104118
tsg.AddToolset(experiments)
105119
// Enable the requested features
106120

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp