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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy