Skip to content

Commit d123b4f

Browse files
authored
GitHub: add gists (#66)
Signed-off-by: Edoardo Vacchi <evacchi@users.noreply.github.com>
1 parent 1888b7e commit d123b4f

File tree

4 files changed

+260
-3
lines changed

4 files changed

+260
-3
lines changed

go.work.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
12
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
23
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
4+
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
35
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
6+
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
47
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=

servlets/github/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ Branches:
2020
- `gh-create-branch` Create a new branch
2121
- `gh-create-pull-request` Create a PR from a branch
2222

23+
Gists
2324

25+
- `gh-create-gist` Create a gist
26+
- `gh-update-gist` Create a gist
27+
- `gh-get-gist` Create a gist
28+
- `gh-delete-gist` Create a gist
2429

2530
## Config
2631

servlets/github/gists.go

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/extism/go-pdk"
7+
)
8+
9+
var (
10+
CreateGistTool = ToolDescription{
11+
Name: "gh-create-gist",
12+
Description: "Create a GitHub Gist",
13+
InputSchema: schema{
14+
"type": "object",
15+
"properties": props{
16+
"description": prop("string", "Description of the gist"),
17+
"files": SchemaProperty{
18+
Type: "object",
19+
Description: "Files contained in the gist.",
20+
AdditionalProperties: &schema{
21+
"type": "object",
22+
"properties": schema{
23+
"content": schema{
24+
"type": "string",
25+
"description": "Content of the file",
26+
},
27+
},
28+
"required": []string{"content"},
29+
},
30+
},
31+
},
32+
"required": []string{"files"},
33+
},
34+
}
35+
GetGistTool = ToolDescription{
36+
Name: "gh-get-gist",
37+
Description: "Gets a specified gist.",
38+
InputSchema: schema{
39+
"type": "object",
40+
"properties": props{
41+
"gist_id": prop("string", "The unique identifier of the gist."),
42+
},
43+
"required": []string{"gist_id"},
44+
},
45+
}
46+
UpdateGistTool = ToolDescription{
47+
Name: "gh-update-gist",
48+
Description: "Lists pull requests in a specified repository. Supports different response formats via accept parameter.",
49+
InputSchema: schema{
50+
"type": "object",
51+
"properties": props{
52+
"gist_id": prop("string", "The unique identifier of the gist."),
53+
"description": prop("string", "Description of the gist"),
54+
"files": SchemaProperty{
55+
Type: "object",
56+
Description: "Files contained in the gist.",
57+
AdditionalProperties: &schema{
58+
"type": "object",
59+
"properties": schema{
60+
"content": schema{
61+
"type": "string",
62+
"description": "Content of the file",
63+
},
64+
},
65+
"required": []string{"content"},
66+
},
67+
},
68+
},
69+
"required": []string{"gist_id"},
70+
},
71+
}
72+
DeleteGistTool = ToolDescription{
73+
Name: "gh-delete-gist",
74+
Description: "Delete a specified gist.",
75+
InputSchema: schema{
76+
"type": "object",
77+
"properties": props{
78+
"gist_id": prop("string", "The unique identifier of the gist."),
79+
},
80+
"required": []string{"gist_id"},
81+
},
82+
}
83+
)
84+
85+
var GistTools = []ToolDescription{
86+
CreateGistTool,
87+
GetGistTool,
88+
UpdateGistTool,
89+
DeleteGistTool,
90+
}
91+
92+
func gistCreate(apiKey, description string, files map[string]any) CallToolResult {
93+
url := "https://api.github.com/gists"
94+
req := pdk.NewHTTPRequest(pdk.MethodPost, url)
95+
req.SetHeader("Authorization", fmt.Sprintf("token %s", apiKey))
96+
req.SetHeader("Content-Type", "application/json")
97+
req.SetHeader("Accept", "application/vnd.github+json")
98+
req.SetHeader("User-Agent", "github-mcpx-servlet")
99+
100+
data := map[string]any{
101+
"description": description,
102+
"files": files,
103+
}
104+
res, err := json.Marshal(data)
105+
if err != nil {
106+
return CallToolResult{
107+
IsError: some(true),
108+
Content: []Content{{
109+
Type: ContentTypeText,
110+
Text: some(fmt.Sprintf("Failed to marshal branch data: %s", err)),
111+
}},
112+
}
113+
}
114+
req.SetBody(res)
115+
resp := req.Send()
116+
if resp.Status() != 201 {
117+
return CallToolResult{
118+
IsError: some(true),
119+
Content: []Content{{
120+
Type: ContentTypeText,
121+
Text: some(fmt.Sprintf("Failed to create branch: %d %s", resp.Status(), string(resp.Body()))),
122+
}},
123+
}
124+
}
125+
126+
return CallToolResult{
127+
Content: []Content{{
128+
Type: ContentTypeText,
129+
Text: some(string(resp.Body())),
130+
}},
131+
}
132+
}
133+
134+
func gistUpdate(apiKey, gistId, description string, files map[string]any) CallToolResult {
135+
url := fmt.Sprintf("https://api.github.com/gists/%s", gistId)
136+
req := pdk.NewHTTPRequest(pdk.MethodPatch, url)
137+
req.SetHeader("Authorization", fmt.Sprintf("token %s", apiKey))
138+
req.SetHeader("Content-Type", "application/json")
139+
req.SetHeader("Accept", "application/vnd.github+json")
140+
req.SetHeader("User-Agent", "github-mcpx-servlet")
141+
142+
data := map[string]any{
143+
"description": description,
144+
"files": files,
145+
}
146+
res, err := json.Marshal(data)
147+
if err != nil {
148+
return CallToolResult{
149+
IsError: some(true),
150+
Content: []Content{{
151+
Type: ContentTypeText,
152+
Text: some(fmt.Sprintf("Failed to marshal branch data: %s", err)),
153+
}},
154+
}
155+
}
156+
req.SetBody(res)
157+
resp := req.Send()
158+
if resp.Status() != 201 {
159+
return CallToolResult{
160+
IsError: some(true),
161+
Content: []Content{{
162+
Type: ContentTypeText,
163+
Text: some(fmt.Sprintf("Failed to create branch: %d %s", resp.Status(), string(resp.Body()))),
164+
}},
165+
}
166+
}
167+
168+
return CallToolResult{
169+
Content: []Content{{
170+
Type: ContentTypeText,
171+
Text: some(string(resp.Body())),
172+
}},
173+
}
174+
}
175+
176+
func gistGet(apiKey, gistId string) CallToolResult {
177+
url := fmt.Sprintf("https://api.github.com/gists/%s", gistId)
178+
req := pdk.NewHTTPRequest(pdk.MethodGet, url)
179+
req.SetHeader("Authorization", fmt.Sprintf("token %s", apiKey))
180+
req.SetHeader("Content-Type", "application/json")
181+
req.SetHeader("Accept", "application/vnd.github+json")
182+
req.SetHeader("User-Agent", "github-mcpx-servlet")
183+
184+
resp := req.Send()
185+
if resp.Status() != 201 {
186+
return CallToolResult{
187+
IsError: some(true),
188+
Content: []Content{{
189+
Type: ContentTypeText,
190+
Text: some(fmt.Sprintf("Failed to create branch: %d %s", resp.Status(), string(resp.Body()))),
191+
}},
192+
}
193+
}
194+
195+
return CallToolResult{
196+
Content: []Content{{
197+
Type: ContentTypeText,
198+
Text: some(string(resp.Body())),
199+
}},
200+
}
201+
}
202+
203+
func gistDelete(apiKey, gistId string) CallToolResult {
204+
url := fmt.Sprintf("https://api.github.com/gists/%s", gistId)
205+
req := pdk.NewHTTPRequest(pdk.MethodDelete, url)
206+
req.SetHeader("Authorization", fmt.Sprintf("token %s", apiKey))
207+
req.SetHeader("Content-Type", "application/json")
208+
req.SetHeader("Accept", "application/vnd.github+json")
209+
req.SetHeader("User-Agent", "github-mcpx-servlet")
210+
211+
resp := req.Send()
212+
if resp.Status() != 201 {
213+
return CallToolResult{
214+
IsError: some(true),
215+
Content: []Content{{
216+
Type: ContentTypeText,
217+
Text: some(fmt.Sprintf("Failed to create branch: %d %s", resp.Status(), string(resp.Body()))),
218+
}},
219+
}
220+
}
221+
222+
return CallToolResult{
223+
Content: []Content{{
224+
Type: ContentTypeText,
225+
Text: some(string(resp.Body())),
226+
}},
227+
}
228+
}

servlets/github/main.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,25 @@ func Call(input CallToolRequest) (CallToolResult, error) {
116116
repo, _ := args["repo"].(string)
117117
return reposGetDetails(apiKey, owner, repo)
118118

119+
case CreateGistTool.Name:
120+
description, _ := args["description"].(string)
121+
files, _ := args["files"].(map[string]any)
122+
return gistCreate(apiKey, description, files), nil
123+
124+
case GetGistTool.Name:
125+
gistId, _ := args["gist_id"].(string)
126+
return gistGet(apiKey, gistId), nil
127+
128+
case UpdateGistTool.Name:
129+
gistId, _ := args["gist_id"].(string)
130+
description, _ := args["description"].(string)
131+
files, _ := args["files"].(map[string]any)
132+
return gistUpdate(apiKey, gistId, description, files), nil
133+
134+
case DeleteGistTool.Name:
135+
gistId, _ := args["gist_id"].(string)
136+
return gistDelete(apiKey, gistId), nil
137+
119138
default:
120139
return CallToolResult{
121140
IsError: some(true),
@@ -134,6 +153,7 @@ func Describe() (ListToolsResult, error) {
134153
FileTools,
135154
BranchTools,
136155
RepoTools,
156+
GistTools,
137157
}
138158

139159
tools := []ToolDescription{}
@@ -152,9 +172,10 @@ func some[T any](t T) *T {
152172
}
153173

154174
type SchemaProperty struct {
155-
Type string `json:"type"`
156-
Description string `json:"description,omitempty"`
157-
Items *schema `json:"items,omitempty"`
175+
Type string `json:"type"`
176+
Description string `json:"description,omitempty"`
177+
AdditionalProperties *schema `json:"additionalProperties,omitempty"`
178+
Items *schema `json:"items,omitempty"`
158179
}
159180

160181
func prop(tpe, description string) SchemaProperty {

0 commit comments

Comments
 (0)