1
+ package github
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "net/http"
9
+
10
+ "github.com/github/github-mcp-server/pkg/translations"
11
+ "github.com/google/go-github/v69/github"
12
+ "github.com/mark3labs/mcp-go/mcp"
13
+ "github.com/mark3labs/mcp-go/server"
14
+ )
15
+
16
+ // ListGists creates a tool to list gists for a user
17
+ func ListGists (getClient GetClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
18
+ return mcp .NewTool ("list_gists" ,
19
+ mcp .WithDescription (t ("TOOL_LIST_GISTS_DESCRIPTION" , "List gists for a user" )),
20
+ mcp .WithString ("username" ,
21
+ mcp .Description ("GitHub username (omit for authenticated user's gists)" ),
22
+ ),
23
+ mcp .WithString ("since" ,
24
+ mcp .Description ("Only gists updated after this time (ISO 8601 timestamp)" ),
25
+ ),
26
+ WithPagination (),
27
+ ),
28
+ func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
29
+ username , err := OptionalParam [string ](request , "username" )
30
+ if err != nil {
31
+ return mcp .NewToolResultError (err .Error ()), nil
32
+ }
33
+
34
+ since , err := OptionalParam [string ](request , "since" )
35
+ if err != nil {
36
+ return mcp .NewToolResultError (err .Error ()), nil
37
+ }
38
+
39
+ pagination , err := OptionalPaginationParams (request )
40
+ if err != nil {
41
+ return mcp .NewToolResultError (err .Error ()), nil
42
+ }
43
+
44
+ opts := & github.GistListOptions {
45
+ ListOptions : github.ListOptions {
46
+ Page : pagination .page ,
47
+ PerPage : pagination .perPage ,
48
+ },
49
+ }
50
+
51
+ // Parse since timestamp if provided
52
+ if since != "" {
53
+ sinceTime , err := parseISOTimestamp (since )
54
+ if err != nil {
55
+ return mcp .NewToolResultError (fmt .Sprintf ("invalid since timestamp: %v" , err )), nil
56
+ }
57
+ opts .Since = sinceTime
58
+ }
59
+
60
+ client , err := getClient (ctx )
61
+ if err != nil {
62
+ return nil , fmt .Errorf ("failed to get GitHub client: %w" , err )
63
+ }
64
+
65
+ gists , resp , err := client .Gists .List (ctx , username , opts )
66
+ if err != nil {
67
+ return nil , fmt .Errorf ("failed to list gists: %w" , err )
68
+ }
69
+ defer func () { _ = resp .Body .Close () }()
70
+
71
+ if resp .StatusCode != http .StatusOK {
72
+ body , err := io .ReadAll (resp .Body )
73
+ if err != nil {
74
+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
75
+ }
76
+ return mcp .NewToolResultError (fmt .Sprintf ("failed to list gists: %s" , string (body ))), nil
77
+ }
78
+
79
+ r , err := json .Marshal (gists )
80
+ if err != nil {
81
+ return nil , fmt .Errorf ("failed to marshal response: %w" , err )
82
+ }
83
+
84
+ return mcp .NewToolResultText (string (r )), nil
85
+ }
86
+ }
87
+
88
+ // CreateGist creates a tool to create a new gist
89
+ func CreateGist (getClient GetClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
90
+ return mcp .NewTool ("create_gist" ,
91
+ mcp .WithDescription (t ("TOOL_CREATE_GIST_DESCRIPTION" , "Create a new gist" )),
92
+ mcp .WithString ("description" ,
93
+ mcp .Description ("Description of the gist" ),
94
+ ),
95
+ mcp .WithString ("filename" ,
96
+ mcp .Required (),
97
+ mcp .Description ("Filename for simple single-file gist creation" ),
98
+ ),
99
+ mcp .WithString ("content" ,
100
+ mcp .Required (),
101
+ mcp .Description ("Content for simple single-file gist creation" ),
102
+ ),
103
+ mcp .WithBoolean ("public" ,
104
+ mcp .Description ("Whether the gist is public" ),
105
+ mcp .DefaultBool (false ),
106
+ ),
107
+ ),
108
+ func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
109
+ description , err := OptionalParam [string ](request , "description" )
110
+ if err != nil {
111
+ return mcp .NewToolResultError (err .Error ()), nil
112
+ }
113
+
114
+ filename , err := requiredParam [string ](request , "filename" )
115
+ if err != nil {
116
+ return mcp .NewToolResultError (err .Error ()), nil
117
+ }
118
+
119
+ content , err := requiredParam [string ](request , "content" )
120
+ if err != nil {
121
+ return mcp .NewToolResultError (err .Error ()), nil
122
+ }
123
+
124
+ public , err := OptionalParam [bool ](request , "public" )
125
+ if err != nil {
126
+ return mcp .NewToolResultError (err .Error ()), nil
127
+ }
128
+
129
+ files := make (map [github.GistFilename ]github.GistFile )
130
+ files [github .GistFilename (filename )] = github.GistFile {
131
+ Filename : github .Ptr (filename ),
132
+ Content : github .Ptr (content ),
133
+ }
134
+
135
+ gist := & github.Gist {
136
+ Files : files ,
137
+ Public : github .Ptr (public ),
138
+ Description : github .Ptr (description ),
139
+ }
140
+
141
+ client , err := getClient (ctx )
142
+ if err != nil {
143
+ return nil , fmt .Errorf ("failed to get GitHub client: %w" , err )
144
+ }
145
+
146
+ createdGist , resp , err := client .Gists .Create (ctx , gist )
147
+ if err != nil {
148
+ return nil , fmt .Errorf ("failed to create gist: %w" , err )
149
+ }
150
+ defer func () { _ = resp .Body .Close () }()
151
+
152
+ if resp .StatusCode != http .StatusCreated {
153
+ body , err := io .ReadAll (resp .Body )
154
+ if err != nil {
155
+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
156
+ }
157
+ return mcp .NewToolResultError (fmt .Sprintf ("failed to create gist: %s" , string (body ))), nil
158
+ }
159
+
160
+ r , err := json .Marshal (createdGist )
161
+ if err != nil {
162
+ return nil , fmt .Errorf ("failed to marshal response: %w" , err )
163
+ }
164
+
165
+ return mcp .NewToolResultText (string (r )), nil
166
+ }
167
+ }
168
+
169
+ // UpdateGist creates a tool to edit an existing gist
170
+ func UpdateGist (getClient GetClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
171
+ return mcp .NewTool ("update_gist" ,
172
+ mcp .WithDescription (t ("TOOL_UPDATE_GIST_DESCRIPTION" , "Update an existing gist" )),
173
+ mcp .WithString ("gist_id" ,
174
+ mcp .Required (),
175
+ mcp .Description ("ID of the gist to update" ),
176
+ ),
177
+ mcp .WithString ("description" ,
178
+ mcp .Description ("Updated description of the gist" ),
179
+ ),
180
+ mcp .WithString ("filename" ,
181
+ mcp .Required (),
182
+ mcp .Description ("Filename to update or create" ),
183
+ ),
184
+ mcp .WithString ("content" ,
185
+ mcp .Required (),
186
+ mcp .Description ("Content for the file" ),
187
+ ),
188
+ ),
189
+ func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
190
+ gistID , err := requiredParam [string ](request , "gist_id" )
191
+ if err != nil {
192
+ return mcp .NewToolResultError (err .Error ()), nil
193
+ }
194
+
195
+ description , err := OptionalParam [string ](request , "description" )
196
+ if err != nil {
197
+ return mcp .NewToolResultError (err .Error ()), nil
198
+ }
199
+
200
+ filename , err := requiredParam [string ](request , "filename" )
201
+ if err != nil {
202
+ return mcp .NewToolResultError (err .Error ()), nil
203
+ }
204
+
205
+ content , err := requiredParam [string ](request , "content" )
206
+ if err != nil {
207
+ return mcp .NewToolResultError (err .Error ()), nil
208
+ }
209
+
210
+ files := make (map [github.GistFilename ]github.GistFile )
211
+ files [github .GistFilename (filename )] = github.GistFile {
212
+ Filename : github .Ptr (filename ),
213
+ Content : github .Ptr (content ),
214
+ }
215
+
216
+ gist := & github.Gist {
217
+ Files : files ,
218
+ Description : github .Ptr (description ),
219
+ }
220
+
221
+ client , err := getClient (ctx )
222
+ if err != nil {
223
+ return nil , fmt .Errorf ("failed to get GitHub client: %w" , err )
224
+ }
225
+
226
+ updatedGist , resp , err := client .Gists .Edit (ctx , gistID , gist )
227
+ if err != nil {
228
+ return nil , fmt .Errorf ("failed to update gist: %w" , err )
229
+ }
230
+ defer func () { _ = resp .Body .Close () }()
231
+
232
+ if resp .StatusCode != http .StatusOK {
233
+ body , err := io .ReadAll (resp .Body )
234
+ if err != nil {
235
+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
236
+ }
237
+ return mcp .NewToolResultError (fmt .Sprintf ("failed to update gist: %s" , string (body ))), nil
238
+ }
239
+
240
+ r , err := json .Marshal (updatedGist )
241
+ if err != nil {
242
+ return nil , fmt .Errorf ("failed to marshal response: %w" , err )
243
+ }
244
+
245
+ return mcp .NewToolResultText (string (r )), nil
246
+ }
247
+ }
0 commit comments