Skip to content

Commit dc53810

Browse files
Added get gist tool (#1105)
* Added get gist tool * adjust prompt * update readme (autogen) --------- Co-authored-by: tommaso-moro <[email protected]>
1 parent 0b65b1b commit dc53810

File tree

4 files changed

+160
-0
lines changed

4 files changed

+160
-0
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,9 @@ The following sets of tools are available:
611611
- `filename`: Filename for simple single-file gist creation (string, required)
612612
- `public`: Whether the gist is public (boolean, optional)
613613

614+
- **get_gist** - Get Gist Content
615+
- `gist_id`: The ID of the gist (string, required)
616+
614617
- **list_gists** - List Gists
615618
- `page`: Page number for pagination (min 1) (number, optional)
616619
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)

pkg/github/gists.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,53 @@ func ListGists(getClient GetClientFn, t translations.TranslationHelperFunc) (too
8989
}
9090
}
9191

92+
// GetGist creates a tool to get the content of a gist
93+
func GetGist(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
94+
return mcp.NewTool("get_gist",
95+
mcp.WithDescription(t("TOOL_GET_GIST_DESCRIPTION", "Get gist content of a particular gist, by gist ID")),
96+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
97+
Title: t("TOOL_GET_GIST", "Get Gist Content"),
98+
ReadOnlyHint: ToBoolPtr(true),
99+
}),
100+
mcp.WithString("gist_id",
101+
mcp.Required(),
102+
mcp.Description("The ID of the gist"),
103+
),
104+
),
105+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
106+
gistID, err := RequiredParam[string](request, "gist_id")
107+
if err != nil {
108+
return mcp.NewToolResultError(err.Error()), nil
109+
}
110+
111+
client, err := getClient(ctx)
112+
if err != nil {
113+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
114+
}
115+
116+
gist, resp, err := client.Gists.Get(ctx, gistID)
117+
if err != nil {
118+
return nil, fmt.Errorf("failed to get gist: %w", err)
119+
}
120+
defer func() { _ = resp.Body.Close() }()
121+
122+
if resp.StatusCode != http.StatusOK {
123+
body, err := io.ReadAll(resp.Body)
124+
if err != nil {
125+
return nil, fmt.Errorf("failed to read response body: %w", err)
126+
}
127+
return mcp.NewToolResultError(fmt.Sprintf("failed to get gist: %s", string(body))), nil
128+
}
129+
130+
r, err := json.Marshal(gist)
131+
if err != nil {
132+
return nil, fmt.Errorf("failed to marshal response: %w", err)
133+
}
134+
135+
return mcp.NewToolResultText(string(r)), nil
136+
}
137+
}
138+
92139
// CreateGist creates a tool to create a new gist
93140
func CreateGist(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
94141
return mcp.NewTool("create_gist",

pkg/github/gists_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,115 @@ func Test_ListGists(t *testing.T) {
192192
}
193193
}
194194

195+
func Test_GetGist(t *testing.T) {
196+
// Verify tool definition
197+
mockClient := github.NewClient(nil)
198+
tool, _ := GetGist(stubGetClientFn(mockClient), translations.NullTranslationHelper)
199+
200+
assert.Equal(t, "get_gist", tool.Name)
201+
assert.NotEmpty(t, tool.Description)
202+
assert.Contains(t, tool.InputSchema.Properties, "gist_id")
203+
204+
assert.Contains(t, tool.InputSchema.Required, "gist_id")
205+
206+
// Setup mock gist for success case
207+
mockGist := github.Gist{
208+
ID: github.Ptr("gist1"),
209+
Description: github.Ptr("First Gist"),
210+
HTMLURL: github.Ptr("https://gist.github.com/user/gist1"),
211+
Public: github.Ptr(true),
212+
CreatedAt: &github.Timestamp{Time: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)},
213+
Owner: &github.User{Login: github.Ptr("user")},
214+
Files: map[github.GistFilename]github.GistFile{
215+
github.GistFilename("file1.txt"): {
216+
Filename: github.Ptr("file1.txt"),
217+
Content: github.Ptr("content of file 1"),
218+
},
219+
},
220+
}
221+
222+
tests := []struct {
223+
name string
224+
mockedClient *http.Client
225+
requestArgs map[string]interface{}
226+
expectError bool
227+
expectedGists github.Gist
228+
expectedErrMsg string
229+
}{
230+
{
231+
name: "Successful fetching different gist",
232+
mockedClient: mock.NewMockedHTTPClient(
233+
mock.WithRequestMatchHandler(
234+
mock.GetGistsByGistId,
235+
mockResponse(t, http.StatusOK, mockGist),
236+
),
237+
),
238+
requestArgs: map[string]interface{}{
239+
"gist_id": "gist1",
240+
},
241+
expectError: false,
242+
expectedGists: mockGist,
243+
},
244+
{
245+
name: "gist_id parameter missing",
246+
mockedClient: mock.NewMockedHTTPClient(
247+
mock.WithRequestMatchHandler(
248+
mock.GetGistsByGistId,
249+
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
250+
w.WriteHeader(http.StatusUnprocessableEntity)
251+
_, _ = w.Write([]byte(`{"message": "Invalid Request"}`))
252+
}),
253+
),
254+
),
255+
requestArgs: map[string]interface{}{},
256+
expectError: true,
257+
expectedErrMsg: "missing required parameter: gist_id",
258+
},
259+
}
260+
261+
for _, tc := range tests {
262+
t.Run(tc.name, func(t *testing.T) {
263+
// Setup client with mock
264+
client := github.NewClient(tc.mockedClient)
265+
_, handler := GetGist(stubGetClientFn(client), translations.NullTranslationHelper)
266+
267+
// Create call request
268+
request := createMCPRequest(tc.requestArgs)
269+
270+
// Call handler
271+
result, err := handler(context.Background(), request)
272+
273+
// Verify results
274+
if tc.expectError {
275+
if err != nil {
276+
assert.Contains(t, err.Error(), tc.expectedErrMsg)
277+
} else {
278+
// For errors returned as part of the result, not as an error
279+
assert.NotNil(t, result)
280+
textContent := getTextResult(t, result)
281+
assert.Contains(t, textContent.Text, tc.expectedErrMsg)
282+
}
283+
return
284+
}
285+
286+
require.NoError(t, err)
287+
288+
// Parse the result and get the text content if no error
289+
textContent := getTextResult(t, result)
290+
291+
// Unmarshal and verify the result
292+
var returnedGists github.Gist
293+
err = json.Unmarshal([]byte(textContent.Text), &returnedGists)
294+
require.NoError(t, err)
295+
296+
assert.Equal(t, *tc.expectedGists.ID, *returnedGists.ID)
297+
assert.Equal(t, *tc.expectedGists.Description, *returnedGists.Description)
298+
assert.Equal(t, *tc.expectedGists.HTMLURL, *returnedGists.HTMLURL)
299+
assert.Equal(t, *tc.expectedGists.Public, *returnedGists.Public)
300+
})
301+
}
302+
}
303+
195304
func Test_CreateGist(t *testing.T) {
196305
// Verify tool definition
197306
mockClient := github.NewClient(nil)

pkg/github/tools.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
308308
gists := toolsets.NewToolset(ToolsetMetadataGists.ID, ToolsetMetadataGists.Description).
309309
AddReadTools(
310310
toolsets.NewServerTool(ListGists(getClient, t)),
311+
toolsets.NewServerTool(GetGist(getClient, t)),
311312
).
312313
AddWriteTools(
313314
toolsets.NewServerTool(CreateGist(getClient, t)),

0 commit comments

Comments
 (0)