@@ -9,11 +9,15 @@ import (
9
9
"os"
10
10
"os/exec"
11
11
"slices"
12
+ "strings"
12
13
"sync"
13
14
"testing"
14
15
"time"
15
16
16
- "github.com/google/go-github/v69/github"
17
+ "github.com/github/github-mcp-server/internal/ghmcp"
18
+ "github.com/github/github-mcp-server/pkg/github"
19
+ "github.com/github/github-mcp-server/pkg/translations"
20
+ gogithub"github.com/google/go-github/v69/github"
17
21
mcpClient"github.com/mark3labs/mcp-go/client"
18
22
"github.com/mark3labs/mcp-go/mcp"
19
23
"github.com/stretchr/testify/require"
@@ -56,68 +60,91 @@ func ensureDockerImageBuilt(t *testing.T) {
56
60
require .NoError (t ,buildError ,"expected to build Docker image successfully" )
57
61
}
58
62
59
- //ClientOpts holds configuration options for the MCP client setup
60
- type ClientOpts struct {
61
- //Environment variables toset before starting theclient
62
- EnvVars map [ string ]string
63
+ //clientOpts holds configuration options for the MCP client setup
64
+ type clientOpts struct {
65
+ //Toolsets toenable in theMCP server
66
+ enabledToolsets [ ]string
63
67
}
64
68
65
- //ClientOption defines a function type for configuring ClientOpts
66
- type ClientOption func (* ClientOpts )
69
+ //clientOption defines a function type for configuring ClientOpts
70
+ type clientOption func (* clientOpts )
67
71
68
- // WithEnvVars returns an option that adds environment variables to the client options
69
- func WithEnvVars (envVars map [string ]string )ClientOption {
70
- return func (opts * ClientOpts ) {
71
- opts .EnvVars = envVars
72
+ // withToolsets returns an option that either sets an Env Var when executing in docker,
73
+ // or sets the toolsets in the MCP server when running in-process.
74
+ func withToolsets (toolsets []string )clientOption {
75
+ return func (opts * clientOpts ) {
76
+ opts .enabledToolsets = toolsets
72
77
}
73
78
}
74
79
75
- // setupMCPClient sets up the test environment and returns an initialized MCP client
76
- // It handles token retrieval, Docker image building, and applying the provided options
77
- func setupMCPClient (t * testing.T ,options ... ClientOption )* mcpClient.Client {
80
+ func setupMCPClient (t * testing.T ,options ... clientOption )* mcpClient.Client {
78
81
// Get token and ensure Docker image is built
79
82
token := getE2EToken (t )
80
- ensureDockerImageBuilt (t )
81
83
82
84
// Create and configure options
83
- opts := & ClientOpts {
84
- EnvVars :make (map [string ]string ),
85
- }
85
+ opts := & clientOpts {}
86
86
87
87
// Apply all options to configure the opts struct
88
88
for _ ,option := range options {
89
89
option (opts )
90
90
}
91
91
92
- // Prepare Docker arguments
93
- args := []string {
94
- "docker" ,
95
- "run" ,
96
- "-i" ,
97
- "--rm" ,
98
- "-e" ,
99
- "GITHUB_PERSONAL_ACCESS_TOKEN" ,// Personal access token is all required
100
- }
92
+ // By default, we run the tests including the Docker image, but with DEBUG
93
+ // enabled, we run the server in-process, allowing for easier debugging.
94
+ var client * mcpClient.Client
95
+ if os .Getenv ("GITHUB_MCP_SERVER_E2E_DEBUG" )== "" {
96
+ ensureDockerImageBuilt (t )
97
+
98
+ // Prepare Docker arguments
99
+ args := []string {
100
+ "docker" ,
101
+ "run" ,
102
+ "-i" ,
103
+ "--rm" ,
104
+ "-e" ,
105
+ "GITHUB_PERSONAL_ACCESS_TOKEN" ,// Personal access token is all required
106
+ }
101
107
102
- // Add all environment variables to the Docker arguments
103
- for key := range opts .EnvVars {
104
- args = append (args ,"-e" ,key )
105
- }
108
+ // Add toolsets environment variable to the Docker arguments
109
+ if len (opts .enabledToolsets )> 0 {
110
+ args = append (args ,"-e" ,"GITHUB_TOOLSETS" )
111
+ }
112
+
113
+ // Add the image name
114
+ args = append (args ,"github/e2e-github-mcp-server" )
106
115
107
- // Add the image name
108
- args = append (args ,"github/e2e-github-mcp-server" )
116
+ // Construct the env vars for the MCP Client to execute docker with
117
+ dockerEnvVars := []string {
118
+ fmt .Sprintf ("GITHUB_PERSONAL_ACCESS_TOKEN=%s" ,token ),
119
+ fmt .Sprintf ("GITHUB_TOOLSETS=%s" ,strings .Join (opts .enabledToolsets ,"," )),
120
+ }
109
121
110
- // Construct the env vars for the MCP Client to execute docker with
111
- dockerEnvVars := make ([]string ,0 ,len (opts .EnvVars )+ 1 )
112
- dockerEnvVars = append (dockerEnvVars ,fmt .Sprintf ("GITHUB_PERSONAL_ACCESS_TOKEN=%s" ,token ))
113
- for key ,value := range opts .EnvVars {
114
- dockerEnvVars = append (dockerEnvVars ,fmt .Sprintf ("%s=%s" ,key ,value ))
122
+ // Create the client
123
+ t .Log ("Starting Stdio MCP client..." )
124
+ var err error
125
+ client ,err = mcpClient .NewStdioMCPClient (args [0 ],dockerEnvVars ,args [1 :]... )
126
+ require .NoError (t ,err ,"expected to create client successfully" )
127
+ }else {
128
+ // We need this because the fully compiled server has a default for the viper config, which is
129
+ // not in scope for using the MCP server directly. This probably indicates that we should refactor
130
+ // so that there is a shared setup mechanism, but let's wait till we feel more friction.
131
+ enabledToolsets := opts .enabledToolsets
132
+ if enabledToolsets == nil {
133
+ enabledToolsets = github .DefaultTools
134
+ }
135
+
136
+ ghServer ,err := ghmcp .NewMCPServer (ghmcp.MCPServerConfig {
137
+ Token :token ,
138
+ EnabledToolsets :enabledToolsets ,
139
+ Translator :translations .NullTranslationHelper ,
140
+ })
141
+ require .NoError (t ,err ,"expected to construct MCP server successfully" )
142
+
143
+ t .Log ("Starting In Process MCP client..." )
144
+ client ,err = mcpClient .NewInProcessClient (ghServer )
145
+ require .NoError (t ,err ,"expected to create in-process client successfully" )
115
146
}
116
147
117
- // Create the client
118
- t .Log ("Starting Stdio MCP client..." )
119
- client ,err := mcpClient .NewStdioMCPClient (args [0 ],dockerEnvVars ,args [1 :]... )
120
- require .NoError (t ,err ,"expected to create client successfully" )
121
148
t .Cleanup (func () {
122
149
require .NoError (t ,client .Close (),"expected to close client successfully" )
123
150
})
@@ -169,7 +196,7 @@ func TestGetMe(t *testing.T) {
169
196
170
197
// Then the login in the response should match the login obtained via the same
171
198
// token using the GitHub API.
172
- ghClient := github .NewClient (nil ).WithAuthToken (getE2EToken (t ))
199
+ ghClient := gogithub .NewClient (nil ).WithAuthToken (getE2EToken (t ))
173
200
user ,_ ,err := ghClient .Users .Get (context .Background (),"" )
174
201
require .NoError (t ,err ,"expected to get user successfully" )
175
202
require .Equal (t ,trimmedContent .Login ,* user .Login ,"expected login to match" )
@@ -181,9 +208,7 @@ func TestToolsets(t *testing.T) {
181
208
182
209
mcpClient := setupMCPClient (
183
210
t ,
184
- WithEnvVars (map [string ]string {
185
- "GITHUB_TOOLSETS" :"repos,issues" ,
186
- }),
211
+ withToolsets ([]string {"repos" ,"issues" }),
187
212
)
188
213
189
214
ctx ,cancel := context .WithTimeout (context .Background (),5 * time .Second )
@@ -208,6 +233,8 @@ func TestToolsets(t *testing.T) {
208
233
}
209
234
210
235
func TestTags (t * testing.T ) {
236
+ t .Parallel ()
237
+
211
238
mcpClient := setupMCPClient (t )
212
239
213
240
ctx := context .Background ()
@@ -253,32 +280,32 @@ func TestTags(t *testing.T) {
253
280
// Cleanup the repository after the test
254
281
t .Cleanup (func () {
255
282
// MCP Server doesn't support deletions, but we can use the GitHub Client
256
- ghClient := github .NewClient (nil ).WithAuthToken (getE2EToken (t ))
283
+ ghClient := gogithub .NewClient (nil ).WithAuthToken (getE2EToken (t ))
257
284
t .Logf ("Deleting repository %s/%s..." ,currentOwner ,repoName )
258
285
_ ,err := ghClient .Repositories .Delete (context .Background (),currentOwner ,repoName )
259
286
require .NoError (t ,err ,"expected to delete repository successfully" )
260
287
})
261
288
262
289
// Then create a tag
263
290
// MCP Server doesn't support tag creation, but we can use the GitHub Client
264
- ghClient := github .NewClient (nil ).WithAuthToken (getE2EToken (t ))
291
+ ghClient := gogithub .NewClient (nil ).WithAuthToken (getE2EToken (t ))
265
292
t .Logf ("Creating tag %s/%s:%s..." ,currentOwner ,repoName ,"v0.0.1" )
266
293
ref ,_ ,err := ghClient .Git .GetRef (context .Background (),currentOwner ,repoName ,"refs/heads/main" )
267
294
require .NoError (t ,err ,"expected to get ref successfully" )
268
295
269
- tagObj ,_ ,err := ghClient .Git .CreateTag (context .Background (),currentOwner ,repoName ,& github .Tag {
270
- Tag :github .Ptr ("v0.0.1" ),
271
- Message :github .Ptr ("v0.0.1" ),
272
- Object :& github .GitObject {
296
+ tagObj ,_ ,err := ghClient .Git .CreateTag (context .Background (),currentOwner ,repoName ,& gogithub .Tag {
297
+ Tag :gogithub .Ptr ("v0.0.1" ),
298
+ Message :gogithub .Ptr ("v0.0.1" ),
299
+ Object :& gogithub .GitObject {
273
300
SHA :ref .Object .SHA ,
274
- Type :github .Ptr ("commit" ),
301
+ Type :gogithub .Ptr ("commit" ),
275
302
},
276
303
})
277
304
require .NoError (t ,err ,"expected to create tag object successfully" )
278
305
279
- _ ,_ ,err = ghClient .Git .CreateRef (context .Background (),currentOwner ,repoName ,& github .Reference {
280
- Ref :github .Ptr ("refs/tags/v0.0.1" ),
281
- Object :& github .GitObject {
306
+ _ ,_ ,err = ghClient .Git .CreateRef (context .Background (),currentOwner ,repoName ,& gogithub .Reference {
307
+ Ref :gogithub .Ptr ("refs/tags/v0.0.1" ),
308
+ Object :& gogithub .GitObject {
282
309
SHA :tagObj .SHA ,
283
310
},
284
311
})