Skip to content

Commit ebbda93

Browse files
committed
Add initial Gist tools: ListGists, CreateGist
1 parent 614f226 commit ebbda93

File tree

3 files changed

+519
-0
lines changed

3 files changed

+519
-0
lines changed

pkg/github/gists.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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+
}

0 commit comments

Comments
 (0)